From bab7d1e596ac5975dc73501a59033fa7c450d4fc Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 11 Apr 2017 12:17:28 -0700 Subject: [PATCH 001/134] 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 002/134] 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 003/134] 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 004/134] 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 005/134] 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 006/134] 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 a10b157affddd7bba5a294ffa63ef310a953b6a2 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 4 Apr 2017 17:26:00 -0700 Subject: [PATCH 007/134] First pass at having an explicit Hips IK target. Also, AnimManipulator nodes support setting position and rotation on a single joint. --- .../resources/avatar/avatar-animation.json | 23 ++- interface/src/avatar/MyAvatar.h | 3 + interface/src/avatar/SkeletonModel.cpp | 4 + .../animation/src/AnimInverseKinematics.cpp | 143 ++++++------------ libraries/animation/src/AnimManipulator.cpp | 95 ++++++++---- libraries/animation/src/AnimManipulator.h | 17 ++- libraries/animation/src/AnimNodeLoader.cpp | 33 ++-- libraries/animation/src/AnimOverlay.cpp | 8 + libraries/animation/src/AnimOverlay.h | 2 + libraries/animation/src/AnimVariant.h | 9 ++ libraries/animation/src/IKTarget.h | 3 +- libraries/animation/src/Rig.cpp | 11 +- libraries/animation/src/Rig.h | 2 + 13 files changed, 205 insertions(+), 148 deletions(-) diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 975f01855d..daa69b95f7 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -50,6 +50,12 @@ "type": "inverseKinematics", "data": { "targets": [ + { + "jointName": "Hips", + "positionVar": "hipsPosition", + "rotationVar": "hipsRotation", + "typeVar": "hipsType" + }, { "jointName": "RightHand", "positionVar": "rightHandPosition", @@ -91,20 +97,27 @@ "children": [] }, { - "id": "manipulatorOverlay", + "id": "hipsManipulatorOverlay", "type": "overlay", "data": { - "alpha": 1.0, - "boneSet": "spineOnly" + "alpha": 0.0, + "boneSet": "hipsOnly" }, "children": [ { - "id": "spineLean", + "id": "hipsManipulator", "type": "manipulator", "data": { "alpha": 0.0, + "alphaVar": "hipsManipulatorAlpha", "joints": [ - { "type": "absoluteRotation", "jointName": "Spine", "var": "lean" } + { + "jointName": "Hips", + "rotationType": "absolute", + "translationType": "absolute", + "rotationVar": "hipsManipulatorRotation", + "translationVar": "hipsManipulatorPosition" + } ] }, "children": [] diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 097d3a1059..b5bea23aa3 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -553,9 +553,12 @@ private: void setVisibleInSceneIfReady(Model* model, render::ScenePointer scene, bool visiblity); + // AJT: FIX ME... reorder this +public: // derive avatar body position and orientation from the current HMD Sensor location. // results are in HMD frame glm::mat4 deriveBodyFromHMDSensor() const; +private: virtual void updatePalms() override {} void lateUpdatePalms(); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 0c11fa456d..e26c339fb8 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -119,7 +119,11 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { headParams.rigHeadPosition = extractTranslation(rigHMDMat); headParams.rigHeadOrientation = extractRotation(rigHMDMat); headParams.worldHeadOrientation = extractRotation(worldHMDMat); + + headParams.hipsMatrix = worldToRig * myAvatar->getSensorToWorldMatrix() * myAvatar->deriveBodyFromHMDSensor(); + headParams.hipsEnabled = true; } else { + headParams.hipsEnabled = false; headParams.isInHMD = false; // We don't have a valid localHeadPosition. diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 2c9376d591..a27fd01b3c 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -87,6 +87,7 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: // build a list of valid targets from _targetVarVec and animVars _maxTargetIndex = -1; bool removeUnfoundJoints = false; + for (auto& targetVar : _targetVarVec) { if (targetVar.jointIndex == -1) { // this targetVar hasn't been validated yet... @@ -105,9 +106,8 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, underPoses); glm::quat rotation = animVars.lookupRigToGeometry(targetVar.rotationVar, defaultPose.rot()); glm::vec3 translation = animVars.lookupRigToGeometry(targetVar.positionVar, defaultPose.trans()); - if (target.getType() == IKTarget::Type::HipsRelativeRotationAndPosition) { - translation += _hipsOffset; - } + AnimPose absPose(glm::vec3(1.0f), rotation, translation); + target.setPose(rotation, translation); target.setIndex(targetVar.jointIndex); targets.push_back(target); @@ -441,26 +441,6 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars computeTargets(animVars, targets, underPoses); } - // debug render ik targets - if (context.getEnableDebugDrawIKTargets()) { - const vec4 WHITE(1.0f); - glm::mat4 rigToAvatarMat = createMatFromQuatAndPos(Quaternions::Y_180, glm::vec3()); - - for (auto& target : targets) { - glm::mat4 geomTargetMat = createMatFromQuatAndPos(target.getRotation(), target.getTranslation()); - glm::mat4 avatarTargetMat = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat; - - QString name = QString("ikTarget%1").arg(target.getIndex()); - DebugDraw::getInstance().addMyAvatarMarker(name, glmExtractRotation(avatarTargetMat), extractTranslation(avatarTargetMat), WHITE); - } - } else if (context.getEnableDebugDrawIKTargets() != _previousEnableDebugIKTargets) { - // remove markers if they were added last frame. - for (auto& target : targets) { - QString name = QString("ikTarget%1").arg(target.getIndex()); - DebugDraw::getInstance().removeMyAvatarMarker(name); - } - } - _previousEnableDebugIKTargets = context.getEnableDebugDrawIKTargets(); if (targets.empty()) { @@ -478,25 +458,52 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars { PROFILE_RANGE_EX(simulation_animation, "ik/shiftHips", 0xffff00ff, 0); - // shift hips according to the _hipsOffset from the previous frame - float offsetLength = glm::length(_hipsOffset); - const float MIN_HIPS_OFFSET_LENGTH = 0.03f; - if (offsetLength > MIN_HIPS_OFFSET_LENGTH && _hipsIndex >= 0) { - // but only if offset is long enough - float scaleFactor = ((offsetLength - MIN_HIPS_OFFSET_LENGTH) / offsetLength); - if (_hipsParentIndex == -1) { - // the hips are the root so _hipsOffset is in the correct frame - _relativePoses[_hipsIndex].trans() = underPoses[_hipsIndex].trans() + scaleFactor * _hipsOffset; - } else { - // the hips are NOT the root so we need to transform _hipsOffset into hips local-frame - glm::quat hipsFrameRotation = _relativePoses[_hipsParentIndex].rot(); - int index = _skeleton->getParentIndex(_hipsParentIndex); - while (index != -1) { - hipsFrameRotation *= _relativePoses[index].rot(); - index = _skeleton->getParentIndex(index); + // AJT: TODO only need abs poses below hips. + AnimPoseVec absolutePoses; + absolutePoses.resize(_relativePoses.size()); + computeAbsolutePoses(absolutePoses); + + for (auto& target: targets) { + if (target.getType() == IKTarget::Type::RotationAndPosition && target.getIndex() == _hipsIndex) { + AnimPose absPose = target.getPose(); + int parentIndex = _skeleton->getParentIndex(target.getIndex()); + if (parentIndex != -1) { + _relativePoses[_hipsIndex] = absolutePoses[parentIndex].inverse() * absPose; + } else { + _relativePoses[_hipsIndex] = absPose; } - _relativePoses[_hipsIndex].trans() = underPoses[_hipsIndex].trans() - + glm::inverse(glm::normalize(hipsFrameRotation)) * (scaleFactor * _hipsOffset); + } + } + + for (auto& target: targets) { + if (target.getType() == IKTarget::Type::HipsRelativeRotationAndPosition) { + AnimPose pose = target.getPose(); + pose.trans() = pose.trans() + (_relativePoses[_hipsIndex].trans() - underPoses[_hipsIndex].trans()); + target.setPose(pose.rot(), pose.trans()); + } + } + } + + { + PROFILE_RANGE_EX(simulation_animation, "ik/debugDraw", 0xffff00ff, 0); + + // debug render ik targets + if (context.getEnableDebugDrawIKTargets()) { + const vec4 WHITE(1.0f); + glm::mat4 rigToAvatarMat = createMatFromQuatAndPos(Quaternions::Y_180, glm::vec3()); + + for (auto& target : targets) { + glm::mat4 geomTargetMat = createMatFromQuatAndPos(target.getRotation(), target.getTranslation()); + glm::mat4 avatarTargetMat = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat; + + QString name = QString("ikTarget%1").arg(target.getIndex()); + DebugDraw::getInstance().addMyAvatarMarker(name, glmExtractRotation(avatarTargetMat), extractTranslation(avatarTargetMat), WHITE); + } + } else if (context.getEnableDebugDrawIKTargets() != _previousEnableDebugIKTargets) { + // remove markers if they were added last frame. + for (auto& target : targets) { + QString name = QString("ikTarget%1").arg(target.getIndex()); + DebugDraw::getInstance().removeMyAvatarMarker(name); } } } @@ -505,60 +512,6 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0); solveWithCyclicCoordinateDescent(targets); } - - { - PROFILE_RANGE_EX(simulation_animation, "ik/measureHipsOffset", 0xffff00ff, 0); - - // measure new _hipsOffset for next frame - // by looking for discrepancies between where a targeted endEffector is - // and where it wants to be (after IK solutions are done) - glm::vec3 newHipsOffset = Vectors::ZERO; - for (auto& target: targets) { - int targetIndex = target.getIndex(); - if (targetIndex == _headIndex && _headIndex != -1) { - // special handling for headTarget - if (target.getType() == IKTarget::Type::RotationOnly) { - // we want to shift the hips to bring the underPose closer - // to where the head happens to be (overpose) - glm::vec3 under = _skeleton->getAbsolutePose(_headIndex, underPoses).trans(); - glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans(); - const float HEAD_OFFSET_SLAVE_FACTOR = 0.65f; - newHipsOffset += HEAD_OFFSET_SLAVE_FACTOR * (actual - under); - } else if (target.getType() == IKTarget::Type::HmdHead) { - // we want to shift the hips to bring the head to its designated position - glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans(); - _hipsOffset += target.getTranslation() - actual; - // and ignore all other targets - newHipsOffset = _hipsOffset; - break; - } else if (target.getType() == IKTarget::Type::RotationAndPosition) { - glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans(); - glm::vec3 targetPosition = target.getTranslation(); - newHipsOffset += targetPosition - actualPosition; - - // Add downward pressure on the hips - newHipsOffset *= 0.95f; - newHipsOffset -= 1.0f; - } - } else if (target.getType() == IKTarget::Type::RotationAndPosition) { - glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans(); - glm::vec3 targetPosition = target.getTranslation(); - newHipsOffset += targetPosition - actualPosition; - } - } - - // smooth transitions by relaxing _hipsOffset toward the new value - const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.10f; - float tau = dt < HIPS_OFFSET_SLAVE_TIMESCALE ? dt / HIPS_OFFSET_SLAVE_TIMESCALE : 1.0f; - _hipsOffset += (newHipsOffset - _hipsOffset) * tau; - - // clamp the hips offset - float hipsOffsetLength = glm::length(_hipsOffset); - if (hipsOffsetLength > _maxHipsOffsetLength) { - _hipsOffset *= _maxHipsOffsetLength / hipsOffsetLength; - } - - } } } return _relativePoses; diff --git a/libraries/animation/src/AnimManipulator.cpp b/libraries/animation/src/AnimManipulator.cpp index 111501898a..070949ab3b 100644 --- a/libraries/animation/src/AnimManipulator.cpp +++ b/libraries/animation/src/AnimManipulator.cpp @@ -12,6 +12,16 @@ #include "AnimUtil.h" #include "AnimationLogging.h" +AnimManipulator::JointVar::JointVar(const QString& jointNameIn, Type rotationTypeIn, Type translationTypeIn, + const QString& rotationVarIn, const QString& translationVarIn) : + jointName(jointNameIn), + rotationType(rotationTypeIn), + translationType(translationTypeIn), + rotationVar(rotationVarIn), + translationVar(translationVarIn), + jointIndex(-1), + hasPerformedJointLookup(false) {} + AnimManipulator::AnimManipulator(const QString& id, float alpha) : AnimNode(AnimNode::Type::Manipulator, id), _alpha(alpha) { @@ -36,7 +46,10 @@ const AnimPoseVec& AnimManipulator::overlay(const AnimVariantMap& animVars, cons } for (auto& jointVar : _jointVars) { + if (!jointVar.hasPerformedJointLookup) { + + // map from joint name to joint index and cache the result. jointVar.jointIndex = _skeleton->nameToJointIndex(jointVar.jointName); if (jointVar.jointIndex < 0) { qCWarning(animation) << "AnimManipulator could not find jointName" << jointVar.jointName << "in skeleton"; @@ -100,34 +113,62 @@ AnimPose AnimManipulator::computeRelativePoseFromJointVar(const AnimVariantMap& AnimPose defaultAbsPose = _skeleton->getAbsolutePose(jointVar.jointIndex, underPoses); - if (jointVar.type == JointVar::Type::AbsoluteRotation || jointVar.type == JointVar::Type::AbsolutePosition) { + // compute relative translation + glm::vec3 relTrans; + switch (jointVar.translationType) { + case JointVar::Type::Absolute: { + glm::vec3 absTrans = animVars.lookupRigToGeometry(jointVar.translationVar, defaultAbsPose.trans()); - if (jointVar.type == JointVar::Type::AbsoluteRotation) { - defaultAbsPose.rot() = animVars.lookupRigToGeometry(jointVar.var, defaultAbsPose.rot()); - } else if (jointVar.type == JointVar::Type::AbsolutePosition) { - defaultAbsPose.trans() = animVars.lookupRigToGeometry(jointVar.var, defaultAbsPose.trans()); + // convert to from absolute to relative. + AnimPose parentAbsPose; + int parentIndex = _skeleton->getParentIndex(jointVar.jointIndex); + if (parentIndex >= 0) { + parentAbsPose = _skeleton->getAbsolutePose(parentIndex, underPoses); + } + + // convert from absolute to relative + relTrans = transformPoint(parentAbsPose.inverse(), absTrans); + break; } - - // because jointVar is absolute, we must use an absolute parent frame to convert into a relative pose. - AnimPose parentAbsPose = AnimPose::identity; - int parentIndex = _skeleton->getParentIndex(jointVar.jointIndex); - if (parentIndex >= 0) { - parentAbsPose = _skeleton->getAbsolutePose(parentIndex, underPoses); - } - - // convert from absolute to relative - return parentAbsPose.inverse() * defaultAbsPose; - - } else { - - // override the default rel pose - AnimPose relPose = defaultRelPose; - if (jointVar.type == JointVar::Type::RelativeRotation) { - relPose.rot() = animVars.lookupRigToGeometry(jointVar.var, defaultRelPose.rot()); - } else if (jointVar.type == JointVar::Type::RelativePosition) { - relPose.trans() = animVars.lookupRigToGeometry(jointVar.var, defaultRelPose.trans()); - } - - return relPose; + case JointVar::Type::Relative: + relTrans = animVars.lookupRigToGeometryVector(jointVar.translationVar, defaultRelPose.trans()); + break; + case JointVar::Type::UnderPose: + relTrans = underPoses[jointVar.jointIndex].trans(); + break; + case JointVar::Type::Default: + default: + relTrans = defaultRelPose.trans(); + break; } + + glm::quat relRot; + switch (jointVar.rotationType) { + case JointVar::Type::Absolute: { + glm::quat absRot = animVars.lookupRigToGeometry(jointVar.translationVar, defaultAbsPose.rot()); + + // convert to from absolute to relative. + AnimPose parentAbsPose; + int parentIndex = _skeleton->getParentIndex(jointVar.jointIndex); + if (parentIndex >= 0) { + parentAbsPose = _skeleton->getAbsolutePose(parentIndex, underPoses); + } + + // convert from absolute to relative + relRot = glm::inverse(parentAbsPose.rot()) * absRot; + break; + } + case JointVar::Type::Relative: + relRot = animVars.lookupRigToGeometry(jointVar.translationVar, defaultRelPose.rot()); + break; + case JointVar::Type::UnderPose: + relRot = underPoses[jointVar.jointIndex].rot(); + break; + case JointVar::Type::Default: + default: + relRot = defaultRelPose.rot(); + break; + } + + return AnimPose(glm::vec3(1), relRot, relTrans); } diff --git a/libraries/animation/src/AnimManipulator.h b/libraries/animation/src/AnimManipulator.h index 26f50a7dd9..1134f75da9 100644 --- a/libraries/animation/src/AnimManipulator.h +++ b/libraries/animation/src/AnimManipulator.h @@ -31,17 +31,20 @@ public: struct JointVar { enum class Type { - AbsoluteRotation = 0, - AbsolutePosition, - RelativeRotation, - RelativePosition, + Absolute, + Relative, + UnderPose, + Default, NumTypes }; - JointVar(const QString& varIn, const QString& jointNameIn, Type typeIn) : var(varIn), jointName(jointNameIn), type(typeIn), jointIndex(-1), hasPerformedJointLookup(false) {} - QString var = ""; + JointVar(const QString& jointNameIn, Type rotationType, Type translationType, const QString& rotationVarIn, const QString& translationVarIn); QString jointName = ""; - Type type = Type::AbsoluteRotation; + Type rotationType = Type::Absolute; + Type translationType = Type::Absolute; + QString rotationVar = ""; + QString translationVar = ""; + int jointIndex = -1; bool hasPerformedJointLookup = false; bool isRelative = false; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 876913fc58..bda4541f36 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -79,10 +79,10 @@ static AnimStateMachine::InterpType stringToInterpType(const QString& str) { static const char* animManipulatorJointVarTypeToString(AnimManipulator::JointVar::Type type) { switch (type) { - case AnimManipulator::JointVar::Type::AbsoluteRotation: return "absoluteRotation"; - case AnimManipulator::JointVar::Type::AbsolutePosition: return "absolutePosition"; - case AnimManipulator::JointVar::Type::RelativeRotation: return "relativeRotation"; - case AnimManipulator::JointVar::Type::RelativePosition: return "relativePosition"; + case AnimManipulator::JointVar::Type::Absolute: return "absolute"; + case AnimManipulator::JointVar::Type::Relative: return "relative"; + case AnimManipulator::JointVar::Type::UnderPose: return "underPose"; + case AnimManipulator::JointVar::Type::Default: return "default"; case AnimManipulator::JointVar::Type::NumTypes: return nullptr; }; return nullptr; @@ -339,7 +339,8 @@ static const char* boneSetStrings[AnimOverlay::NumBoneSets] = { "spineOnly", "empty", "leftHand", - "rightHand" + "rightHand", + "hipsOnly" }; static AnimOverlay::BoneSet stringToBoneSetEnum(const QString& str) { @@ -406,17 +407,25 @@ static AnimNode::Pointer loadManipulatorNode(const QJsonObject& jsonObj, const Q } auto jointObj = jointValue.toObject(); - READ_STRING(type, jointObj, id, jsonUrl, nullptr); READ_STRING(jointName, jointObj, id, jsonUrl, nullptr); - READ_STRING(var, jointObj, id, jsonUrl, nullptr); + READ_STRING(rotationType, jointObj, id, jsonUrl, nullptr); + READ_STRING(translationType, jointObj, id, jsonUrl, nullptr); + READ_STRING(rotationVar, jointObj, id, jsonUrl, nullptr); + READ_STRING(translationVar, jointObj, id, jsonUrl, nullptr); - AnimManipulator::JointVar::Type jointVarType = stringToAnimManipulatorJointVarType(type); - if (jointVarType == AnimManipulator::JointVar::Type::NumTypes) { - qCCritical(animation) << "AnimNodeLoader, bad type in \"joints\", id =" << id << ", url =" << jsonUrl.toDisplayString(); - return nullptr; + AnimManipulator::JointVar::Type jointVarRotationType = stringToAnimManipulatorJointVarType(rotationType); + if (jointVarRotationType == AnimManipulator::JointVar::Type::NumTypes) { + qCWarning(animation) << "AnimNodeLoader, bad rotationType in \"joints\", id =" << id << ", url =" << jsonUrl.toDisplayString(); + jointVarRotationType = AnimManipulator::JointVar::Type::Default; } - AnimManipulator::JointVar jointVar(var, jointName, jointVarType); + AnimManipulator::JointVar::Type jointVarTranslationType = stringToAnimManipulatorJointVarType(translationType); + if (jointVarTranslationType == AnimManipulator::JointVar::Type::NumTypes) { + qCWarning(animation) << "AnimNodeLoader, bad translationType in \"joints\", id =" << id << ", url =" << jsonUrl.toDisplayString(); + jointVarTranslationType = AnimManipulator::JointVar::Type::Default; + } + + AnimManipulator::JointVar jointVar(jointName, jointVarRotationType, jointVarTranslationType, rotationVar, translationVar); node->addJointVar(jointVar); }; diff --git a/libraries/animation/src/AnimOverlay.cpp b/libraries/animation/src/AnimOverlay.cpp index dbc635af66..e086413dde 100644 --- a/libraries/animation/src/AnimOverlay.cpp +++ b/libraries/animation/src/AnimOverlay.cpp @@ -34,6 +34,7 @@ void AnimOverlay::buildBoneSet(BoneSet boneSet) { case SpineOnlyBoneSet: buildSpineOnlyBoneSet(); break; case LeftHandBoneSet: buildLeftHandBoneSet(); break; case RightHandBoneSet: buildRightHandBoneSet(); break; + case HipsOnlyBoneSet: buildHipsOnlyBoneSet(); break; default: case EmptyBoneSet: buildEmptyBoneSet(); break; } @@ -188,6 +189,13 @@ void AnimOverlay::buildRightHandBoneSet() { }); } +void AnimOverlay::buildHipsOnlyBoneSet() { + assert(_skeleton); + buildEmptyBoneSet(); + int hipsJoint = _skeleton->nameToJointIndex("Hips"); + _boneSetVec[hipsJoint] = 1.0f; +} + // for AnimDebugDraw rendering const AnimPoseVec& AnimOverlay::getPosesInternal() const { return _poses; diff --git a/libraries/animation/src/AnimOverlay.h b/libraries/animation/src/AnimOverlay.h index 2f34c07309..ed9439feb7 100644 --- a/libraries/animation/src/AnimOverlay.h +++ b/libraries/animation/src/AnimOverlay.h @@ -37,6 +37,7 @@ public: EmptyBoneSet, LeftHandBoneSet, RightHandBoneSet, + HipsOnlyBoneSet, NumBoneSets }; @@ -75,6 +76,7 @@ public: void buildEmptyBoneSet(); void buildLeftHandBoneSet(); void buildRightHandBoneSet(); + void buildHipsOnlyBoneSet(); // no copies AnimOverlay(const AnimOverlay&) = delete; diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index 3466013ff6..d383b5abb8 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -165,6 +165,15 @@ public: } } + glm::vec3 lookupRigToGeometryVector(const QString& key, const glm::vec3& defaultValue) const { + if (key.isEmpty()) { + return defaultValue; + } else { + auto iter = _map.find(key); + return iter != _map.end() ? transformVectorFast(_rigToGeometryMat, iter->second.getVec3()) : defaultValue; + } + } + const glm::quat& lookupRaw(const QString& key, const glm::quat& defaultValue) const { if (key.isEmpty()) { return defaultValue; diff --git a/libraries/animation/src/IKTarget.h b/libraries/animation/src/IKTarget.h index 9ea34a6165..acb01d9861 100644 --- a/libraries/animation/src/IKTarget.h +++ b/libraries/animation/src/IKTarget.h @@ -21,13 +21,14 @@ public: RotationOnly, HmdHead, HipsRelativeRotationAndPosition, - Unknown, + Unknown }; IKTarget() {} const glm::vec3& getTranslation() const { return _pose.trans(); } const glm::quat& getRotation() const { return _pose.rot(); } + const AnimPose& getPose() const { return _pose; } int getIndex() const { return _index; } Type getType() const { return _type; } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index fb0867e2de..adc40d6e81 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1024,6 +1024,15 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { _animVars.set("isTalking", params.isTalking); _animVars.set("notIsTalking", !params.isTalking); + + // AJT: + 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); + } else { + _animVars.set("hipsType", (int)IKTarget::Type::Unknown); + } } void Rig::updateFromEyeParameters(const EyeParameters& params) { @@ -1094,7 +1103,7 @@ void Rig::updateNeckJoint(int index, const HeadParameters& params) { _animVars.set("headPosition", headPos); _animVars.set("headRotation", headRot); - _animVars.set("headType", (int)IKTarget::Type::HmdHead); + _animVars.set("headType", (int)IKTarget::Type::RotationAndPosition); _animVars.set("neckPosition", neckPos); _animVars.set("neckRotation", neckRot); _animVars.set("neckType", (int)IKTarget::Type::Unknown); // 'Unknown' disables the target diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 2cd20c2704..89f0d624f9 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -45,6 +45,8 @@ public: 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 + bool hipsEnabled = false; bool isInHMD = false; int neckJointIndex = -1; bool isTalking = false; From adaf7dda7c3b1105f8d93251691aa75a497a702c Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 7 Apr 2017 17:47:53 -0700 Subject: [PATCH 008/134] Check in viveMotionCapture test script. --- .../animation/src/AnimInverseKinematics.cpp | 4 + scripts/developer/tests/viveMotionCapture.js | 198 ++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 scripts/developer/tests/viveMotionCapture.js diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index a27fd01b3c..7fda318f5c 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -277,6 +277,9 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe const float MIN_AXIS_LENGTH = 1.0e-4f; RotationConstraint* constraint = getConstraint(pivotIndex); + + // AJT: disabled special case for the lower spine. + /* if (constraint && constraint->isLowerSpine() && tipIndex != _headIndex) { // for these types of targets we only allow twist at the lower-spine // (this prevents the hand targets from bending the spine too much and thereby driving the hips too far) @@ -292,6 +295,7 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe targetLine = Vectors::ZERO; } } + */ glm::vec3 axis = glm::cross(leverArm, targetLine); float axisLength = glm::length(axis); diff --git a/scripts/developer/tests/viveMotionCapture.js b/scripts/developer/tests/viveMotionCapture.js new file mode 100644 index 0000000000..0e6119a714 --- /dev/null +++ b/scripts/developer/tests/viveMotionCapture.js @@ -0,0 +1,198 @@ +/* global Xform */ +Script.include("/~/system/libraries/Xform.js"); + +var TRACKED_OBJECT_POSES = [ + "TrackedObject00", "TrackedObject01", "TrackedObject02", "TrackedObject03", + "TrackedObject04", "TrackedObject05", "TrackedObject06", "TrackedObject07", + "TrackedObject08", "TrackedObject09", "TrackedObject10", "TrackedObject11", + "TrackedObject12", "TrackedObject13", "TrackedObject14", "TrackedObject15" +]; + +var calibrated = false; +var rightTriggerPressed = false; +var leftTriggerPressed = false; + +var MAPPING_NAME = "com.highfidelity.viveMotionCapture"; + +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 leftFoot; +var rightFoot; +var hips; + +var Y_180 = {x: 0, y: 1, z: 0, w: 0}; + +function computeOffsetXform(pose, jointIndex) { + var poseXform = new Xform(pose.rotation, pose.translation); + var referenceXform = new Xform(MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame(jointIndex), + MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(jointIndex)); + return Xform.mul(poseXform.inv(), referenceXform); +} + +function calibrate() { + print("AJT: calibrating"); + var poses = []; + if (Controller.Hardware.Vive) { + TRACKED_OBJECT_POSES.forEach(function (key) { + var channel = Controller.Hardware.Vive[key]; + var pose = Controller.getPoseValue(channel); + if (pose.valid) { + poses.push({ + channel: channel, + pose: pose + }); + } + }); + } + + if (poses.length >= 2) { + // sort by y + poses.sort(function(a, b) { + var ay = a.pose.translation.y; + var by = b.pose.translation.y; + return ay - by; + }); + + if (poses[0].pose.translation.x > poses[1].pose.translation.x) { + rightFoot = poses[0]; + leftFoot = poses[1]; + } else { + rightFoot = poses[1]; + leftFoot = poses[0]; + } + + // compute offsets + rightFoot.offsetXform = computeOffsetXform(rightFoot.pose, MyAvatar.getJointIndex("RightFoot")); + leftFoot.offsetXform = computeOffsetXform(leftFoot.pose, MyAvatar.getJointIndex("LeftFoot")); + + print("AJT: rightFoot = " + JSON.stringify(rightFoot)); + print("AJT: leftFoot = " + JSON.stringify(leftFoot)); + + if (poses.length >= 3) { + hips = poses[2]; + hips.offsetXform = computeOffsetXform(hips.pose, MyAvatar.getJointIndex("Hips")); + + print("AJT: hips = " + JSON.stringify(hips)); + } + } else { + print("AJT: could not find two trackers, try again!"); + } +} + +var ikTypes = { + RotationAndPosition: 0, + RotationOnly: 1, + HmdHead: 2, + HipsRelativeRotationAndPosition: 3, + Off: 4 +}; + +var handlerId; + +function update(dt) { + if (rightTriggerPressed && leftTriggerPressed) { + if (!calibrated) { + calibrate(); + calibrated = true; + + if (handlerId) { + MyAvatar.removeAnimationStateHandler(handlerId); + } + + // hook up anim state callback + var propList = [ + "leftFootType", "leftFootPosition", "leftFootRotation", + "rightFootType", "rightFootPosition", "rightFootRotation", + "hipsType", "hipsPosition", "hipsRotation" + ]; + + handlerId = MyAvatar.addAnimationStateHandler(function (props) { + + var result = {}, pose, offsetXform, xform; + if (rightFoot) { + pose = Controller.getPoseValue(rightFoot.channel); + offsetXform = rightFoot.offsetXform; + + xform = Xform.mul(new Xform(pose.rotation, pose.translation), offsetXform); + result.rightFootType = ikTypes.RotationAndPosition; + result.rightFootPosition = Vec3.multiplyQbyV(Y_180, xform.pos); + result.rightFootRotation = Quat.multiply(Y_180, xform.rot); + + } else { + result.rightFootType = props.rightFootType; + result.rightFootPositon = props.rightFootPosition; + result.rightFootRotation = props.rightFootRotation; + } + + if (leftFoot) { + pose = Controller.getPoseValue(leftFoot.channel); + offsetXform = leftFoot.offsetXform; + xform = Xform.mul(new Xform(pose.rotation, pose.translation), offsetXform); + result.leftFootType = ikTypes.RotationAndPosition; + result.leftFootPosition = Vec3.multiplyQbyV(Y_180, xform.pos); + result.leftFootRotation = Quat.multiply(Y_180, xform.rot); + } else { + result.leftFootType = props.leftFootType; + result.leftFootPositon = props.leftFootPosition; + result.leftFootRotation = props.leftFootRotation; + } + + if (hips) { + pose = Controller.getPoseValue(hips.channel); + offsetXform = hips.offsetXform; + xform = Xform.mul(new Xform(pose.rotation, pose.translation), offsetXform); + result.hipsType = ikTypes.RotationAndPosition; + result.hipsPosition = Vec3.multiplyQbyV(Y_180, xform.pos); + result.hipsRotation = Quat.multiply(Y_180, xform.rot); + } else { + result.hipsType = props.hipsType; + result.hipsPositon = props.hipsPosition; + result.hipsRotation = props.hipsRotation; + } + + return result; + }, propList); + + } + } else { + calibrated = 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) { + var leftFootPose = Controller.getPoseValue(leftFoot.channel); + DebugDraw.addMyAvatarMarker("leftFootTracker", leftFootPose.rotation, leftFootPose.translation, BLUE); + } + + if (rightFoot) { + var rightFootPose = Controller.getPoseValue(rightFoot.channel); + DebugDraw.addMyAvatarMarker("rightFootTracker", rightFootPose.rotation, rightFootPose.translation, RED); + } + + if (hips) { + var hipsPose = Controller.getPoseValue(hips.channel); + DebugDraw.addMyAvatarMarker("hipsTracker", hipsPose.rotation, hipsPose.translation, GREEN); + } + } +} + +Script.update.connect(update); + +Script.scriptEnding.connect(function () { + Controller.disableMapping(MAPPING_NAME); + Script.update.disconnect(update); +}); +var TRIGGER_OFF_VALUE = 0.1; From 0ebaba7cf890aa7544365cff5057c87a87642e33 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 10 Apr 2017 13:36:30 -0700 Subject: [PATCH 009/134] Now supports sensorConfig with hips and chest sensors --- .../resources/avatar/avatar-animation.json | 8 +- libraries/animation/src/Rig.cpp | 3 + scripts/developer/tests/viveMotionCapture.js | 123 +++++++++++++----- scripts/developer/tests/viveTrackedObjects.js | 4 +- 4 files changed, 100 insertions(+), 38 deletions(-) diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index daa69b95f7..9efe3dd29b 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -81,10 +81,10 @@ "typeVar": "leftFootType" }, { - "jointName": "Neck", - "positionVar": "neckPosition", - "rotationVar": "neckRotation", - "typeVar": "neckType" + "jointName": "Spine2", + "positionVar": "spine2Position", + "rotationVar": "spine2Rotation", + "typeVar": "spine2Type" }, { "jointName": "Head", diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index adc40d6e81..c8717fd11e 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1033,6 +1033,9 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { } else { _animVars.set("hipsType", (int)IKTarget::Type::Unknown); } + + // by default this IK target is disabled. + _animVars.set("spine2Type", (int)IKTarget::Type::Unknown); } void Rig::updateFromEyeParameters(const EyeParameters& params) { diff --git a/scripts/developer/tests/viveMotionCapture.js b/scripts/developer/tests/viveMotionCapture.js index 0e6119a714..27c6809f5c 100644 --- a/scripts/developer/tests/viveMotionCapture.js +++ b/scripts/developer/tests/viveMotionCapture.js @@ -27,18 +27,59 @@ Controller.enableMapping(MAPPING_NAME); var leftFoot; var rightFoot; var hips; +var spine2; + +var FEET_ONLY = 0; +var FEET_AND_HIPS = 1; +var FEET_AND_CHEST = 2; +var FEET_HIPS_AND_CHEST = 3; + +var SENSOR_CONFIG_NAMES = [ + "FeetOnly", + "FeetAndHips", + "FeetAndChest", + "FeetHipsAndChest" +]; + +var ANIM_VARS = [ + "leftFootType", + "leftFootPosition", + "leftFootRotation", + "rightFootType", + "rightFootPosition", + "rightFootRotation", + "hipsType", + "hipsPosition", + "hipsRotation", + "spine2Type", + "spine2Position", + "spine2Rotation" +]; + +var sensorConfig = FEET_HIPS_AND_CHEST; 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}); function computeOffsetXform(pose, jointIndex) { var poseXform = new Xform(pose.rotation, pose.translation); + + // TODO: we can do better here... + // one idea, hang default pose skeleton from HMD head. var referenceXform = new Xform(MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame(jointIndex), MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(jointIndex)); + return Xform.mul(poseXform.inv(), referenceXform); } function calibrate() { - print("AJT: calibrating"); + print("AJT: calibrating..."); + + leftFoot = undefined; + rightFoot = undefined; + hips = undefined; + spine2 = undefined; + var poses = []; if (Controller.Hardware.Vive) { TRACKED_OBJECT_POSES.forEach(function (key) { @@ -76,12 +117,30 @@ function calibrate() { print("AJT: rightFoot = " + JSON.stringify(rightFoot)); print("AJT: leftFoot = " + JSON.stringify(leftFoot)); - if (poses.length >= 3) { + if (sensorConfig === FEET_ONLY) { + // we're done! + } else if (sensorConfig === FEET_AND_HIPS && poses.length >= 3) { hips = poses[2]; - hips.offsetXform = computeOffsetXform(hips.pose, MyAvatar.getJointIndex("Hips")); + } else if (sensorConfig === FEET_AND_CHEST && poses.length >= 3) { + spine2 = poses[2]; + } else if (sensorConfig === FEET_HIPS_AND_CHEST && poses.length >= 4) { + hips = poses[2]; + spine2 = poses[3]; + } else { + // TODO: better error messages + print("AJT: could not calibrate for sensor config " + SENSOR_CONFIG_NAMES[sensorConfig] + ", please try again!"); + } + if (hips) { + hips.offsetXform = computeOffsetXform(hips.pose, MyAvatar.getJointIndex("Hips")); print("AJT: hips = " + JSON.stringify(hips)); } + + if (spine2) { + spine2.offsetXform = computeOffsetXform(spine2.pose, MyAvatar.getJointIndex("Spine2")); + print("AJT: spine2 = " + JSON.stringify(spine2)); + } + } else { print("AJT: could not find two trackers, try again!"); } @@ -97,6 +156,12 @@ var ikTypes = { var handlerId; +function computeIKTargetXform(jointInfo) { + var pose = Controller.getPoseValue(jointInfo.channel); + var offsetXform = jointInfo.offsetXform; + return Xform.mul(Y_180_XFORM, Xform.mul(new Xform(pose.rotation, pose.translation), offsetXform)); +} + function update(dt) { if (rightTriggerPressed && leftTriggerPressed) { if (!calibrated) { @@ -107,59 +172,53 @@ function update(dt) { MyAvatar.removeAnimationStateHandler(handlerId); } - // hook up anim state callback - var propList = [ - "leftFootType", "leftFootPosition", "leftFootRotation", - "rightFootType", "rightFootPosition", "rightFootRotation", - "hipsType", "hipsPosition", "hipsRotation" - ]; - handlerId = MyAvatar.addAnimationStateHandler(function (props) { - var result = {}, pose, offsetXform, xform; + var result = {}, xform; if (rightFoot) { - pose = Controller.getPoseValue(rightFoot.channel); - offsetXform = rightFoot.offsetXform; - - xform = Xform.mul(new Xform(pose.rotation, pose.translation), offsetXform); + xform = computeIKTargetXform(rightFoot); result.rightFootType = ikTypes.RotationAndPosition; - result.rightFootPosition = Vec3.multiplyQbyV(Y_180, xform.pos); - result.rightFootRotation = Quat.multiply(Y_180, xform.rot); - + result.rightFootPosition = xform.pos; + result.rightFootRotation = xform.rot; } else { result.rightFootType = props.rightFootType; - result.rightFootPositon = props.rightFootPosition; + result.rightFootPosition = props.rightFootPosition; result.rightFootRotation = props.rightFootRotation; } if (leftFoot) { - pose = Controller.getPoseValue(leftFoot.channel); - offsetXform = leftFoot.offsetXform; - xform = Xform.mul(new Xform(pose.rotation, pose.translation), offsetXform); + xform = computeIKTargetXform(leftFoot); result.leftFootType = ikTypes.RotationAndPosition; - result.leftFootPosition = Vec3.multiplyQbyV(Y_180, xform.pos); - result.leftFootRotation = Quat.multiply(Y_180, xform.rot); + result.leftFootPosition = xform.pos; + result.leftFootRotation = xform.rot; } else { result.leftFootType = props.leftFootType; - result.leftFootPositon = props.leftFootPosition; + result.leftFootPosition = props.leftFootPosition; result.leftFootRotation = props.leftFootRotation; } if (hips) { - pose = Controller.getPoseValue(hips.channel); - offsetXform = hips.offsetXform; - xform = Xform.mul(new Xform(pose.rotation, pose.translation), offsetXform); + xform = computeIKTargetXform(hips); result.hipsType = ikTypes.RotationAndPosition; - result.hipsPosition = Vec3.multiplyQbyV(Y_180, xform.pos); - result.hipsRotation = Quat.multiply(Y_180, xform.rot); + result.hipsPosition = xform.pos; + result.hipsRotation = xform.rot; } else { result.hipsType = props.hipsType; - result.hipsPositon = props.hipsPosition; + result.hipsPosition = props.hipsPosition; result.hipsRotation = props.hipsRotation; } + if (spine2) { + xform = computeIKTargetXform(spine2); + result.spine2Type = ikTypes.RotationAndPosition; + result.spine2Position = xform.pos; + result.spine2Rotation = xform.rot; + } else { + result.spine2Type = ikTypes.Off; + } + return result; - }, propList); + }, ANIM_VARS); } } else { diff --git a/scripts/developer/tests/viveTrackedObjects.js b/scripts/developer/tests/viveTrackedObjects.js index 78911538e4..4155afb82b 100644 --- a/scripts/developer/tests/viveTrackedObjects.js +++ b/scripts/developer/tests/viveTrackedObjects.js @@ -18,14 +18,14 @@ function shutdown() { }); } -var WHITE = {x: 1, y: 1, z: 1, w: 1}; +var BLUE = {x: 0, y: 0, z: 1, w: 1}; function update(dt) { if (Controller.Hardware.Vive) { TRACKED_OBJECT_POSES.forEach(function (key) { var pose = Controller.getPoseValue(Controller.Hardware.Vive[key]); if (pose.valid) { - DebugDraw.addMyAvatarMarker(key, pose.rotation, pose.translation, WHITE); + DebugDraw.addMyAvatarMarker(key, pose.rotation, pose.translation, BLUE); } else { DebugDraw.removeMyAvatarMarker(key); } From d3b4a2c08d968da1ce0b2ef6a54926e350b10b5f Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 10 Apr 2017 15:08:03 -0700 Subject: [PATCH 010/134] better "auto" configuration --- libraries/script-engine/src/Quat.cpp | 7 ++ libraries/script-engine/src/Quat.h | 2 + scripts/developer/tests/viveMotionCapture.js | 86 ++++++++++++++++---- 3 files changed, 77 insertions(+), 18 deletions(-) diff --git a/libraries/script-engine/src/Quat.cpp b/libraries/script-engine/src/Quat.cpp index 6d49ed27c1..05002dcf5d 100644 --- a/libraries/script-engine/src/Quat.cpp +++ b/libraries/script-engine/src/Quat.cpp @@ -122,3 +122,10 @@ bool Quat::equal(const glm::quat& q1, const glm::quat& q2) { return q1 == q2; } +glm::quat Quat::cancelOutRollAndPitch(const glm::quat& q) { + return ::cancelOutRollAndPitch(q); +} + +glm::quat Quat::cancelOutRoll(const glm::quat& q) { + return ::cancelOutRoll(q); +} diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h index 8a88767a41..ee3ab9aa7c 100644 --- a/libraries/script-engine/src/Quat.h +++ b/libraries/script-engine/src/Quat.h @@ -60,6 +60,8 @@ public slots: float dot(const glm::quat& q1, const glm::quat& q2); void print(const QString& label, const glm::quat& q); bool equal(const glm::quat& q1, const glm::quat& q2); + glm::quat cancelOutRollAndPitch(const glm::quat& q); + glm::quat cancelOutRoll(const glm::quat& q); }; #endif // hifi_Quat_h diff --git a/scripts/developer/tests/viveMotionCapture.js b/scripts/developer/tests/viveMotionCapture.js index 27c6809f5c..6cb0f92b9b 100644 --- a/scripts/developer/tests/viveMotionCapture.js +++ b/scripts/developer/tests/viveMotionCapture.js @@ -33,12 +33,14 @@ var FEET_ONLY = 0; var FEET_AND_HIPS = 1; var FEET_AND_CHEST = 2; var FEET_HIPS_AND_CHEST = 3; +var AUTO = 4; var SENSOR_CONFIG_NAMES = [ "FeetOnly", "FeetAndHips", "FeetAndChest", - "FeetHipsAndChest" + "FeetHipsAndChest", + "Auto" ]; var ANIM_VARS = [ @@ -56,30 +58,47 @@ var ANIM_VARS = [ "spine2Rotation" ]; -var sensorConfig = FEET_HIPS_AND_CHEST; +var sensorConfig = AUTO; 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}); -function computeOffsetXform(pose, jointIndex) { +function computeOffsetXform(defaultToReferenceXform, pose, jointIndex) { var poseXform = new Xform(pose.rotation, pose.translation); - // TODO: we can do better here... - // one idea, hang default pose skeleton from HMD head. - var referenceXform = new Xform(MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame(jointIndex), - MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(jointIndex)); + var defaultJointXform = new Xform(MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame(jointIndex), + MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(jointIndex)); - return Xform.mul(poseXform.inv(), referenceXform); + var referenceJointXform = Xform.mul(defaultToReferenceXform, defaultJointXform); + + return Xform.mul(poseXform.inv(), referenceJointXform); +} + +function computeDefaultToReferenceXform() { + var headIndex = MyAvatar.getJointIndex("Head"); + if (headIndex >= 0) { + var defaultHeadXform = new Xform(MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame(headIndex), + MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(headIndex)); + var currentHeadXform = new Xform(Quat.cancelOutRollAndPitch(MyAvatar.getAbsoluteJointRotationInObjectFrame(headIndex)), + MyAvatar.getAbsoluteJointTranslationInObjectFrame(headIndex)); + + var defaultToReferenceXform = Xform.mul(currentHeadXform, defaultHeadXform.inv()); + + return defaultToReferenceXform; + } else { + return new Xform.ident(); + } } function calibrate() { - print("AJT: calibrating..."); leftFoot = undefined; rightFoot = undefined; hips = undefined; spine2 = undefined; + var defaultToReferenceXform = computeDefaultToReferenceXform(); + var poses = []; if (Controller.Hardware.Vive) { TRACKED_OBJECT_POSES.forEach(function (key) { @@ -94,6 +113,23 @@ function calibrate() { }); } + print("AJT: calibrating, num tracked poses = " + poses.length + ", sensorConfig = " + SENSOR_CONFIG_NAMES[sensorConfig]); + + var config = sensorConfig; + + if (config === AUTO) { + if (poses.length === 2) { + config = FEET_ONLY; + } else if (poses.length === 3) { + config = FEET_AND_HIPS; + } else if (poses.length >= 4) { + config = FEET_HIPS_AND_CHEST; + } else { + print("AJT: auto config failed: poses.length = " + poses.length); + config = FEET_ONLY; + } + } + if (poses.length >= 2) { // sort by y poses.sort(function(a, b) { @@ -111,33 +147,33 @@ function calibrate() { } // compute offsets - rightFoot.offsetXform = computeOffsetXform(rightFoot.pose, MyAvatar.getJointIndex("RightFoot")); - leftFoot.offsetXform = computeOffsetXform(leftFoot.pose, MyAvatar.getJointIndex("LeftFoot")); + rightFoot.offsetXform = computeOffsetXform(defaultToReferenceXform, rightFoot.pose, MyAvatar.getJointIndex("RightFoot")); + leftFoot.offsetXform = computeOffsetXform(defaultToReferenceXform, leftFoot.pose, MyAvatar.getJointIndex("LeftFoot")); print("AJT: rightFoot = " + JSON.stringify(rightFoot)); print("AJT: leftFoot = " + JSON.stringify(leftFoot)); - if (sensorConfig === FEET_ONLY) { + if (config === FEET_ONLY) { // we're done! - } else if (sensorConfig === FEET_AND_HIPS && poses.length >= 3) { + } else if (config === FEET_AND_HIPS && poses.length >= 3) { hips = poses[2]; - } else if (sensorConfig === FEET_AND_CHEST && poses.length >= 3) { + } else if (config === FEET_AND_CHEST && poses.length >= 3) { spine2 = poses[2]; - } else if (sensorConfig === FEET_HIPS_AND_CHEST && poses.length >= 4) { + } else if (config === FEET_HIPS_AND_CHEST && poses.length >= 4) { hips = poses[2]; spine2 = poses[3]; } else { // TODO: better error messages - print("AJT: could not calibrate for sensor config " + SENSOR_CONFIG_NAMES[sensorConfig] + ", please try again!"); + print("AJT: could not calibrate for sensor config " + SENSOR_CONFIG_NAMES[config] + ", please try again!"); } if (hips) { - hips.offsetXform = computeOffsetXform(hips.pose, MyAvatar.getJointIndex("Hips")); + hips.offsetXform = computeOffsetXform(defaultToReferenceXform, hips.pose, MyAvatar.getJointIndex("Hips")); print("AJT: hips = " + JSON.stringify(hips)); } if (spine2) { - spine2.offsetXform = computeOffsetXform(spine2.pose, MyAvatar.getJointIndex("Spine2")); + spine2.offsetXform = computeOffsetXform(defaultToReferenceXform, spine2.pose, MyAvatar.getJointIndex("Spine2")); print("AJT: spine2 = " + JSON.stringify(spine2)); } @@ -246,6 +282,20 @@ function update(dt) { DebugDraw.addMyAvatarMarker("hipsTracker", hipsPose.rotation, hipsPose.translation, GREEN); } } + + var drawReferencePose = false; + if (drawReferencePose) { + var GREEN = {x: 0, y: 1, z: 0, w: 1}; + var defaultToReferenceXform = computeDefaultToReferenceXform(); + var leftFootIndex = MyAvatar.getJointIndex("LeftFoot"); + if (leftFootIndex > 0) { + var defaultLeftFootXform = new Xform(MyAvatar.getAbsoluteDefaultJointRotationInObjectFrame(leftFootIndex), + MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(leftFootIndex)); + var referenceLeftFootXform = Xform.mul(defaultToReferenceXform, defaultLeftFootXform); + DebugDraw.addMyAvatarMarker("leftFootTracker", referenceLeftFootXform.rot, referenceLeftFootXform.pos, GREEN); + } + } + } Script.update.connect(update); From d464020577bbb3336e9153f9a21b55af65af03b8 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 10 Apr 2017 16:03:34 -0700 Subject: [PATCH 011/134] Adjust min angle of knee constraint to prevent leg locks --- libraries/animation/src/AnimInverseKinematics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 7fda318f5c..d6c976ce21 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -829,7 +829,7 @@ void AnimInverseKinematics::initConstraints() { // we determine the max/min angles by rotating the swing limit lines from parent- to child-frame // then measure the angles to swing the yAxis into alignment - const float MIN_KNEE_ANGLE = 0.0f; + const float MIN_KNEE_ANGLE = 0.097f; // ~5 deg const float MAX_KNEE_ANGLE = 7.0f * PI / 8.0f; glm::quat invReferenceRotation = glm::inverse(referenceRotation); glm::vec3 minSwingAxis = invReferenceRotation * glm::angleAxis(MIN_KNEE_ANGLE, hingeAxis) * Vectors::UNIT_Y; From b49760cee2a729cc177c7c3cc7a126489868ab98 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 10 Apr 2017 16:52:46 -0700 Subject: [PATCH 012/134] Remove hand IK target collision with body capsule --- libraries/animation/src/Rig.cpp | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index c8717fd11e..dd7ccb6b27 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1174,15 +1174,7 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f const glm::vec3 bodyCapsuleEnd = bodyCapsuleCenter + glm::vec3(0, params.bodyCapsuleHalfHeight, 0); if (params.isLeftEnabled) { - - // prevent the hand IK targets from intersecting the body capsule - glm::vec3 handPosition = params.leftPosition; - glm::vec3 displacement; - if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) { - handPosition -= displacement; - } - - _animVars.set("leftHandPosition", handPosition); + _animVars.set("leftHandPosition", params.leftPosition); _animVars.set("leftHandRotation", params.leftOrientation); _animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); } else { @@ -1192,15 +1184,7 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f } if (params.isRightEnabled) { - - // prevent the hand IK targets from intersecting the body capsule - glm::vec3 handPosition = params.rightPosition; - glm::vec3 displacement; - if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) { - handPosition -= displacement; - } - - _animVars.set("rightHandPosition", handPosition); + _animVars.set("rightHandPosition", params.rightPosition); _animVars.set("rightHandRotation", params.rightOrientation); _animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); } else { From 22e79504bb71a380e8f4692550e8aa25340e16ec Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 14 Apr 2017 16:57:30 -0700 Subject: [PATCH 013/134] Elliptical swing targets for the spine, Bug fix for debug draw --- .../animation/src/AnimInverseKinematics.cpp | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index d6c976ce21..0e7a41e74c 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -149,7 +149,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector MAX_ERROR_TOLERANCE && numLoops < MAX_IK_LOOPS) { ++numLoops; @@ -365,7 +365,9 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe } // store the relative rotation change in the accumulator - _accumulators[pivotIndex].add(newRot, target.getWeight()); + // AJT: Hack give head more weight. + float weight = (target.getIndex() == _headIndex) ? 2.0f : 1.0f; + _accumulators[pivotIndex].add(newRot, weight); // this joint has been changed so we check to see if it has the lowest index if (pivotIndex < lowestMovedIndex) { @@ -445,8 +447,6 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars computeTargets(animVars, targets, underPoses); } - _previousEnableDebugIKTargets = context.getEnableDebugDrawIKTargets(); - if (targets.empty()) { // no IK targets but still need to enforce constraints std::map::iterator constraintItr = _constraints.begin(); @@ -510,6 +510,8 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars DebugDraw::getInstance().removeMyAvatarMarker(name); } } + + _previousEnableDebugIKTargets = context.getEnableDebugDrawIKTargets(); } { @@ -551,6 +553,22 @@ void AnimInverseKinematics::clearConstraints() { _constraints.clear(); } +// set up swing limits around a swingTwistConstraint in an ellipse, where lateralSwingTheta is the swing limit for lateral swings (side to side) +// anteriorSwingTheta is swing limit for forward and backward swings. (where x-axis of reference rotation is sideways and -z-axis is forward) +static void setEllipticalSwingLimits(SwingTwistConstraint* stConstraint, float lateralSwingTheta, float anteriorSwingTheta) { + assert(stConstraint); + const int NUM_SUBDIVISIONS = 8; + std::vector minDots; + minDots.reserve(NUM_SUBDIVISIONS); + float dTheta = (2.0f * PI) / NUM_SUBDIVISIONS; + float theta = 0.0f; + for (int i = 0; i < NUM_SUBDIVISIONS; i++) { + minDots.push_back(cosf(glm::length(glm::vec2(anteriorSwingTheta * cosf(theta), lateralSwingTheta * sinf(theta))))); + theta += dTheta; + } + stConstraint->setSwingLimits(minDots); +} + void AnimInverseKinematics::initConstraints() { if (!_skeleton) { return; @@ -740,41 +758,40 @@ void AnimInverseKinematics::initConstraints() { } else if (baseName.startsWith("Spine", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot()); - const float MAX_SPINE_TWIST = PI / 12.0f; + const float MAX_SPINE_TWIST = PI / 20.0f; stConstraint->setTwistLimits(-MAX_SPINE_TWIST, MAX_SPINE_TWIST); + /* std::vector minDots; const float MAX_SPINE_SWING = PI / 10.0f; minDots.push_back(cosf(MAX_SPINE_SWING)); stConstraint->setSwingLimits(minDots); + */ + + // AJT: limit lateral swings more then forward-backward swings + setEllipticalSwingLimits(stConstraint, PI / 30.0f, PI / 20.0f); + if (0 == baseName.compare("Spine1", Qt::CaseSensitive) || 0 == baseName.compare("Spine", Qt::CaseSensitive)) { stConstraint->setLowerSpine(true); } constraint = static_cast(stConstraint); - } else if (baseName.startsWith("Hips2", Qt::CaseSensitive)) { - SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); - stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot()); - const float MAX_SPINE_TWIST = PI / 8.0f; - stConstraint->setTwistLimits(-MAX_SPINE_TWIST, MAX_SPINE_TWIST); - std::vector minDots; - const float MAX_SPINE_SWING = PI / 14.0f; - minDots.push_back(cosf(MAX_SPINE_SWING)); - stConstraint->setSwingLimits(minDots); - - constraint = static_cast(stConstraint); } else if (0 == baseName.compare("Neck", Qt::CaseSensitive)) { SwingTwistConstraint* stConstraint = new SwingTwistConstraint(); stConstraint->setReferenceRotation(_defaultRelativePoses[i].rot()); - const float MAX_NECK_TWIST = PI / 9.0f; + const float MAX_NECK_TWIST = PI / 10.0f; stConstraint->setTwistLimits(-MAX_NECK_TWIST, MAX_NECK_TWIST); + /* std::vector minDots; const float MAX_NECK_SWING = PI / 8.0f; minDots.push_back(cosf(MAX_NECK_SWING)); stConstraint->setSwingLimits(minDots); + */ + + setEllipticalSwingLimits(stConstraint, PI / 10.0f, PI / 8.0f); constraint = static_cast(stConstraint); } else if (0 == baseName.compare("Head", Qt::CaseSensitive)) { From c5f1455872b5b7b20e61db98a0b3aef97af29f38 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 19 Apr 2017 12:47:28 -0700 Subject: [PATCH 014/134] go horizontal --- interface/resources/qml/hifi/Feed.qml | 78 +++++++++++++++++++ .../qml/hifi/tablet/TabletAddressDialog.qml | 72 +++++------------ 2 files changed, 96 insertions(+), 54 deletions(-) create mode 100644 interface/resources/qml/hifi/Feed.qml diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml new file mode 100644 index 0000000000..1adeec8bf2 --- /dev/null +++ b/interface/resources/qml/hifi/Feed.qml @@ -0,0 +1,78 @@ +// +// Feed.qml +// qml/hifi +// +// Displays a particular type of feed +// +// Created by Howard Stearns on 4/18/2017 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Hifi 1.0 +import QtQuick 2.5 +import QtGraphicalEffects 1.0 +import "toolbars" +import "../styles-uit" + +Rectangle { + id: root; + + property int cardWidth: 212; + property int cardHeight: 152; + property int stackedCardShadowHeight: 10; + property alias suggestions: feed; + + HifiConstants { id: hifi } + ListModel { id: feed; } + + RalewayLight { + id: label; + text: "Places"; + color: hifi.colors.blueHighlight; + size: 38; + width: root.width; + anchors { + top: parent.top; + left: parent.left; + margins: 10; + } + } + + ListView { + id: scroll; + clip: true; + model: feed; + orientation: ListView.Horizontal; + + spacing: 14; + height: cardHeight + stackedCardShadowHeight; + anchors { + top: label.bottom; + left: parent.left; + right: parent.right; + leftMargin: 10; + } + + delegate: Card { + width: cardWidth; + height: cardHeight; + goFunction: goCard; + userName: model.username; + placeName: model.place_name; + hifiUrl: model.place_name + model.path; + thumbnail: model.thumbnail_url; + imageUrl: model.image_url; + action: model.action; + timestamp: model.created_at; + onlineUsers: model.online_users; + storyId: model.metaverseId; + drillDownToPlace: model.drillDownToPlace; + shadowHeight: stackedCardShadowHeight; + hoverThunk: function () { scroll.currentIndex = index; } + unhoverThunk: function () { scroll.currentIndex = -1; } + } + } +} diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index bed1f82ac2..b97eaeafdb 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -31,8 +31,8 @@ StackView { height: parent !== null ? parent.height : undefined property var eventBridge; property var allStories: []; - property int cardWidth: 460; - property int cardHeight: 320; + property int cardWidth: 212; + property int cardHeight: 152; property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/"; property var tablet: null; @@ -275,58 +275,21 @@ StackView { } Rectangle { - id: bgMain - color: hifiStyleConstants.colors.white - anchors.bottom: parent.keyboardEnabled ? keyboard.top : parent.bottom - anchors.bottomMargin: 0 - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.top: topBar.bottom - anchors.topMargin: 0 - - ListModel { id: suggestions } - - ListView { - id: scroll - - property int stackedCardShadowHeight: 0; - clip: true - spacing: 14 - anchors { - bottom: parent.bottom - top: parent.top - left: parent.left - right: parent.right - leftMargin: 10 - } - - model: suggestions - orientation: ListView.Vertical - - delegate: Card { - width: cardWidth; - height: cardHeight; - goFunction: goCard; - userName: model.username; - placeName: model.place_name; - hifiUrl: model.place_name + model.path; - thumbnail: model.thumbnail_url; - imageUrl: model.image_url; - action: model.action; - timestamp: model.created_at; - onlineUsers: model.online_users; - storyId: model.metaverseId; - drillDownToPlace: model.drillDownToPlace; - shadowHeight: scroll.stackedCardShadowHeight; - hoverThunk: function () { scroll.currentIndex = index; } - unhoverThunk: function () { scroll.currentIndex = -1; } - } - - highlightMoveDuration: -1; - highlightMoveVelocity: -1; - highlight: Rectangle { color: "transparent"; border.width: 4; border.color: hifiStyleConstants.colors.blueHighlight; z: 1; } + id: bgMain; + color: hifiStyleConstants.colors.white; + anchors { + bottom: parent.keyboardEnabled ? keyboard.top : parent.bottom; + bottomMargin: 0; + right: parent.right; + rightMargin: 0; + left: parent.left; + leftMargin: 0; + top: topBar.bottom; + topMargin: 0; + } + Feed { + id: feed; + width: bgMain.width; } } @@ -363,6 +326,7 @@ StackView { } } + property alias suggestions: feed.suggestions; function getRequest(url, cb) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects. // TODO: make available to other .qml. From 3ce238b8a0a8a21ac93740fc4d652ec9575079d7 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 19 Apr 2017 13:45:58 -0700 Subject: [PATCH 015/134] make feed self contained --- interface/resources/qml/hifi/Feed.qml | 122 ++++++++++++++++- .../qml/hifi/tablet/TabletAddressDialog.qml | 123 +----------------- 2 files changed, 122 insertions(+), 123 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 1adeec8bf2..2a7156b31e 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -23,11 +23,125 @@ Rectangle { property int cardWidth: 212; property int cardHeight: 152; property int stackedCardShadowHeight: 10; - property alias suggestions: feed; + property string metaverseServerUrl: ''; + property string filter: ''; + onFilterChanged: filterChoicesByText(); + property alias suggestions: feed; // fixme. don't need to expose HifiConstants { id: hifi } ListModel { id: feed; } - + + function resolveUrl(url) { + return (url.indexOf('/') === 0) ? (metaverseServerUrl + url) : url; + } + function makeModelData(data) { // create a new obj from data + // ListModel elements will only ever have those properties that are defined by the first obj that is added. + // So here we make sure that we have all the properties we need, regardless of whether it is a place data or user story. + var name = data.place_name, + tags = data.tags || [data.action, data.username], + description = data.description || "", + thumbnail_url = data.thumbnail_url || ""; + return { + place_name: name, + username: data.username || "", + path: data.path || "", + created_at: data.created_at || "", + action: data.action || "", + thumbnail_url: resolveUrl(thumbnail_url), + image_url: resolveUrl(data.details.image_url), + + metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity. + + tags: tags, + description: description, + online_users: data.details.concurrency || 0, + drillDownToPlace: false, + + searchText: [name].concat(tags, description || []).join(' ').toUpperCase() + } + } + property var allStories: []; + property var placeMap: ({}); // Used for making stacks. FIXME: generalize to not just by place. + property int requestId: 0; + function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model + var options = [ + 'now=' + new Date().toISOString(), + 'include_actions=' + selectedTab.includeActions, + 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), + 'require_online=true', + 'protocol=' + encodeURIComponent(AddressManager.protocolVersion()), + 'page=' + pageNumber + ]; + var url = metaverseBase + 'user_stories?' + options.join('&'); + var thisRequestId = ++requestId; + getRequest(url, function (error, data) { + if ((thisRequestId !== requestId) || handleError(url, error, data, cb)) { + return; // abandon stale requests + } + var stories = data.user_stories.map(function (story) { // explicit single-argument function + return makeModelData(story, url); + }); + allStories = allStories.concat(stories); + stories.forEach(makeFilteredPlaceProcessor()); + if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now + return getUserStoryPage(pageNumber + 1, cb); + } + cb(); + }); + } + function fillDestinations() { // Public + allStories = []; + suggestions.clear(); + placeMap = {}; + getUserStoryPage(1, function (error) { + console.log('user stories query', error || 'ok', allStories.length); + }); + } + function addToSuggestions(place) { // fixme: move to makeFilteredPlaceProcessor + var collapse = allTab.selected && (place.action !== 'concurrency'); + if (collapse) { + var existing = placeMap[place.place_name]; + if (existing) { + existing.drillDownToPlace = true; + return; + } + } + suggestions.append(place); + if (collapse) { + placeMap[place.place_name] = suggestions.get(suggestions.count - 1); + } else if (place.action === 'concurrency') { + suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories). + } + } + function suggestable(place) { // fixme add to makeFilteredPlaceProcessor + if (place.action === 'snapshot') { + return true; + } + return (place.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. + } + function makeFilteredPlaceProcessor() { // answer a function(placeData) that adds it to suggestions if it matches + var words = filter.toUpperCase().split(/\s+/).filter(identity), + data = allStories; + function matches(place) { + if (!words.length) { + return suggestable(place); + } + return words.every(function (word) { + return place.searchText.indexOf(word) >= 0; + }); + } + return function (place) { + if (matches(place)) { + addToSuggestions(place); + } + }; + } + function filterChoicesByText() { + suggestions.clear(); + placeMap = {}; + allStories.forEach(makeFilteredPlaceProcessor()); + } + RalewayLight { id: label; text: "Places"; @@ -40,7 +154,6 @@ Rectangle { margins: 10; } } - ListView { id: scroll; clip: true; @@ -55,11 +168,10 @@ Rectangle { right: parent.right; leftMargin: 10; } - delegate: Card { width: cardWidth; height: cardHeight; - goFunction: goCard; + goFunction: goCard; // fixme global userName: model.username; placeName: model.place_name; hifiUrl: model.place_name + model.path; diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index b97eaeafdb..57e24c2421 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -30,7 +30,6 @@ StackView { width: parent !== null ? parent.width : undefined height: parent !== null ? parent.height : undefined property var eventBridge; - property var allStories: []; property int cardWidth: 212; property int cardHeight: 152; property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/"; @@ -39,9 +38,8 @@ StackView { Component { id: tabletWebView; TabletWebView {} } Component.onCompleted: { - fillDestinations(); updateLocationText(false); - fillDestinations(); + feed.fillDestinations(); addressLine.focus = !HMD.active; root.parentChanged.connect(center); center(); @@ -189,7 +187,7 @@ StackView { } font.pixelSize: hifi.fonts.pixelSize * 0.75 onTextChanged: { - filterChoicesByText(); + console.log('fixme onTextChanged "' + addressLine.text + "'."); updateLocationText(text.length > 0); } onAccepted: { @@ -290,6 +288,8 @@ StackView { Feed { id: feed; width: bgMain.width; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + filter: addressLine.text; } } @@ -326,7 +326,6 @@ StackView { } } - property alias suggestions: feed.suggestions; function getRequest(url, cb) { // cb(error, responseOfCorrectContentType) of url. General for 'get' text/html/json, but without redirects. // TODO: make available to other .qml. @@ -372,122 +371,10 @@ StackView { return true; } - - function resolveUrl(url) { - return (url.indexOf('/') === 0) ? (addressBarDialog.metaverseServerUrl + url) : url; - } - - function makeModelData(data) { // create a new obj from data - // ListModel elements will only ever have those properties that are defined by the first obj that is added. - // So here we make sure that we have all the properties we need, regardless of whether it is a place data or user story. - var name = data.place_name, - tags = data.tags || [data.action, data.username], - description = data.description || "", - thumbnail_url = data.thumbnail_url || ""; - return { - place_name: name, - username: data.username || "", - path: data.path || "", - created_at: data.created_at || "", - action: data.action || "", - thumbnail_url: resolveUrl(thumbnail_url), - image_url: resolveUrl(data.details.image_url), - - metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity. - - tags: tags, - description: description, - online_users: data.details.concurrency || 0, - drillDownToPlace: false, - - searchText: [name].concat(tags, description || []).join(' ').toUpperCase() - } - } - function suggestable(place) { - if (place.action === 'snapshot') { - return true; - } - return (place.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. - } property var selectedTab: allTab; function tabSelect(textButton) { selectedTab = textButton; - fillDestinations(); - } - property var placeMap: ({}); - function addToSuggestions(place) { - var collapse = allTab.selected && (place.action !== 'concurrency'); - if (collapse) { - var existing = placeMap[place.place_name]; - if (existing) { - existing.drillDownToPlace = true; - return; - } - } - suggestions.append(place); - if (collapse) { - placeMap[place.place_name] = suggestions.get(suggestions.count - 1); - } else if (place.action === 'concurrency') { - suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories). - } - } - property int requestId: 0; - function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model - var options = [ - 'now=' + new Date().toISOString(), - 'include_actions=' + selectedTab.includeActions, - 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), - 'require_online=true', - 'protocol=' + encodeURIComponent(AddressManager.protocolVersion()), - 'page=' + pageNumber - ]; - var url = metaverseBase + 'user_stories?' + options.join('&'); - var thisRequestId = ++requestId; - getRequest(url, function (error, data) { - if ((thisRequestId !== requestId) || handleError(url, error, data, cb)) { - return; - } - var stories = data.user_stories.map(function (story) { // explicit single-argument function - return makeModelData(story, url); - }); - allStories = allStories.concat(stories); - stories.forEach(makeFilteredPlaceProcessor()); - if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now - return getUserStoryPage(pageNumber + 1, cb); - } - cb(); - }); - } - function makeFilteredPlaceProcessor() { // answer a function(placeData) that adds it to suggestions if it matches - var words = addressLine.text.toUpperCase().split(/\s+/).filter(identity), - data = allStories; - function matches(place) { - if (!words.length) { - return suggestable(place); - } - return words.every(function (word) { - return place.searchText.indexOf(word) >= 0; - }); - } - return function (place) { - if (matches(place)) { - addToSuggestions(place); - } - }; - } - function filterChoicesByText() { - suggestions.clear(); - placeMap = {}; - allStories.forEach(makeFilteredPlaceProcessor()); - } - - function fillDestinations() { - allStories = []; - suggestions.clear(); - placeMap = {}; - getUserStoryPage(1, function (error) { - console.log('user stories query', error || 'ok', allStories.length); - }); + feed.fillDestinations(); } function updateLocationText(enteringAddress) { From 2512255dd05efd186d686e72685e3d53580f77e9 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 19 Apr 2017 16:33:02 -0700 Subject: [PATCH 016/134] reusable feeds --- interface/resources/qml/hifi/Feed.qml | 68 ++++++++----------- .../qml/hifi/tablet/TabletAddressDialog.qml | 21 +++++- 2 files changed, 46 insertions(+), 43 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 2a7156b31e..a1bd38c281 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -17,19 +17,22 @@ import QtGraphicalEffects 1.0 import "toolbars" import "../styles-uit" -Rectangle { +Column { id: root; property int cardWidth: 212; property int cardHeight: 152; property int stackedCardShadowHeight: 10; property string metaverseServerUrl: ''; + property string actions: 'snapshot'; + onActionsChanged: fillDestinations(); + Component.onCompleted: fillDestinations(); + property string labelText: actions; property string filter: ''; onFilterChanged: filterChoicesByText(); - property alias suggestions: feed; // fixme. don't need to expose HifiConstants { id: hifi } - ListModel { id: feed; } + ListModel { id: suggestions; } function resolveUrl(url) { return (url.indexOf('/') === 0) ? (metaverseServerUrl + url) : url; @@ -66,7 +69,7 @@ Rectangle { function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model var options = [ 'now=' + new Date().toISOString(), - 'include_actions=' + selectedTab.includeActions, + 'include_actions=' + actions, 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), 'require_online=true', 'protocol=' + encodeURIComponent(AddressManager.protocolVersion()), @@ -78,11 +81,7 @@ Rectangle { if ((thisRequestId !== requestId) || handleError(url, error, data, cb)) { return; // abandon stale requests } - var stories = data.user_stories.map(function (story) { // explicit single-argument function - return makeModelData(story, url); - }); - allStories = allStories.concat(stories); - stories.forEach(makeFilteredPlaceProcessor()); + allStories = allStories.concat(data.user_stories.map(makeModelData)); if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now return getUserStoryPage(pageNumber + 1, cb); } @@ -94,11 +93,12 @@ Rectangle { suggestions.clear(); placeMap = {}; getUserStoryPage(1, function (error) { - console.log('user stories query', error || 'ok', allStories.length); + allStories.forEach(makeFilteredStoryProcessor()); + console.log('user stories query', actions, error || 'ok', allStories.length, 'filtered to', suggestions.count); }); } - function addToSuggestions(place) { // fixme: move to makeFilteredPlaceProcessor - var collapse = allTab.selected && (place.action !== 'concurrency'); + function addToSuggestions(place) { // fixme: move to makeFilteredStoryProcessor + var collapse = (actions === 'snapshot,concurrency') && (place.action !== 'concurrency'); // fixme generalize? if (collapse) { var existing = placeMap[place.place_name]; if (existing) { @@ -113,61 +113,49 @@ Rectangle { suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories). } } - function suggestable(place) { // fixme add to makeFilteredPlaceProcessor - if (place.action === 'snapshot') { + function suggestable(story) { // fixme add to makeFilteredStoryProcessor + if (story.action === 'snapshot') { return true; } - return (place.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 makeFilteredPlaceProcessor() { // answer a function(placeData) that adds it to suggestions if it matches - var words = filter.toUpperCase().split(/\s+/).filter(identity), - data = allStories; - function matches(place) { + function makeFilteredStoryProcessor() { // answer a function(storyData) that adds it to suggestions if it matches + var words = filter.toUpperCase().split(/\s+/).filter(identity); + function matches(story) { if (!words.length) { - return suggestable(place); + return suggestable(story); } return words.every(function (word) { - return place.searchText.indexOf(word) >= 0; + return story.searchText.indexOf(word) >= 0; }); } - return function (place) { - if (matches(place)) { - addToSuggestions(place); + return function (story) { + if (matches(story)) { + addToSuggestions(story); } }; } function filterChoicesByText() { suggestions.clear(); placeMap = {}; - allStories.forEach(makeFilteredPlaceProcessor()); + allStories.forEach(makeFilteredStoryProcessor()); } RalewayLight { id: label; - text: "Places"; + text: labelText; color: hifi.colors.blueHighlight; - size: 38; - width: root.width; - anchors { - top: parent.top; - left: parent.left; - margins: 10; - } + size: 28; } ListView { id: scroll; clip: true; - model: feed; + model: suggestions; orientation: ListView.Horizontal; spacing: 14; + width: parent.width; height: cardHeight + stackedCardShadowHeight; - anchors { - top: label.bottom; - left: parent.left; - right: parent.right; - leftMargin: 10; - } delegate: Card { width: cardWidth; height: cardHeight; diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 57e24c2421..e79855ec51 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -39,7 +39,6 @@ StackView { Component { id: tabletWebView; TabletWebView {} } Component.onCompleted: { updateLocationText(false); - feed.fillDestinations(); addressLine.focus = !HMD.active; root.parentChanged.connect(center); center(); @@ -286,11 +285,28 @@ StackView { topMargin: 0; } Feed { - id: feed; + id: happeningNow; width: bgMain.width; metaverseServerUrl: addressBarDialog.metaverseServerUrl; + actions: selectedTab.includeActions; filter: addressLine.text; } + Feed { + id: places; + width: bgMain.width; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + actions: 'concurrency'; + filter: addressLine.text; + anchors.top: happeningNow.bottom; + } + Feed { + id: snapshots; + width: bgMain.width; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + actions: 'snapshot'; + filter: addressLine.text; + anchors.top: places.bottom; + } } Timer { @@ -374,7 +390,6 @@ StackView { property var selectedTab: allTab; function tabSelect(textButton) { selectedTab = textButton; - feed.fillDestinations(); } function updateLocationText(enteringAddress) { From b8e9fb67b8e991a25b9a064ad0b86006f4eb639d Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Wed, 19 Apr 2017 16:34:10 -0700 Subject: [PATCH 017/134] old style card fonts, margins, etc. --- interface/resources/qml/hifi/Card.qml | 120 ++++++++++---------------- 1 file changed, 46 insertions(+), 74 deletions(-) diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index b72901fbdf..c6e833826a 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -80,7 +80,7 @@ Rectangle { id: lobby; visible: !hasGif || (animation.status !== Image.Ready); width: parent.width - (isConcurrency ? 0 : (2 * smallMargin)); - height: parent.height - (isConcurrency ? 0 : smallMargin); + height: parent.height - messageHeight - (isConcurrency ? 0 : smallMargin); source: thumbnail || defaultThumbnail; fillMode: Image.PreserveAspectCrop; anchors { @@ -129,7 +129,7 @@ Rectangle { property int dropSamples: 9; property int dropSpread: 0; DropShadow { - visible: true; + visible: showPlace && desktop.gradientsSupported; source: place; anchors.fill: place; horizontalOffset: dropHorizontalOffset; @@ -139,12 +139,12 @@ Rectangle { color: hifi.colors.black; spread: dropSpread; } - RalewayLight { + RalewaySemiBold { id: place; visible: showPlace; text: placeName; color: hifi.colors.white; - size: 38; + size: textSize; elide: Text.ElideRight; // requires constrained width anchors { top: parent.top; @@ -153,57 +153,44 @@ Rectangle { margins: textPadding; } } - Rectangle { - id: rectRow - z: 1 - width: message.width + (users.visible ? users.width + bottomRow.spacing : 0) - + (icon.visible ? icon.width + bottomRow.spacing: 0) + bottomRow.spacing; - height: messageHeight + 1; - radius: 25 - - anchors { - bottom: parent.bottom - left: parent.left - leftMargin: textPadding - bottomMargin: textPadding + Row { + FiraSansRegular { + id: users; + visible: isConcurrency; + text: onlineUsers; + size: textSize; + color: messageColor; + anchors.verticalCenter: message.verticalCenter; } - - Row { - id: bottomRow - FiraSansRegular { - id: users; - visible: isConcurrency; - text: onlineUsers; - size: textSize; - color: messageColor; - anchors.verticalCenter: message.verticalCenter; - } - Image { - id: icon; - source: "../../images/snap-icon.svg" - width: 40; - height: 40; - visible: action === 'snapshot'; - } - RalewayRegular { - id: message; - text: isConcurrency ? ((onlineUsers === 1) ? "person" : "people") : (drillDownToPlace ? "snapshots" : ("by " + userName)); - size: textSizeSmall; - color: messageColor; - elide: Text.ElideRight; // requires a width to be specified` - anchors { - bottom: parent.bottom; - bottomMargin: parent.spacing; - } - } - spacing: textPadding; - height: messageHeight; + Image { + id: icon; + source: "../../images/snap-icon.svg" + width: 40; + height: 40; + visible: action === 'snapshot'; + } + RalewayRegular { + id: message; + text: isConcurrency ? ((onlineUsers === 1) ? "person" : "people") : (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) + - (actionIcon.width + (2 * smallMargin)); anchors { bottom: parent.bottom; - left: parent.left; - leftMargin: 4 + bottomMargin: parent.spacing; } } + spacing: textPadding; + height: messageHeight; + anchors { + bottom: parent.bottom; + left: parent.left; + leftMargin: textPadding; + } } // These two can be supplied to provide hover behavior. // For example, AddressBarDialog provides functions that set the current list view item @@ -218,37 +205,22 @@ Rectangle { onEntered: hoverThunk(); onExited: unhoverThunk(); } - Rectangle { - id: rectIcon - z: 1 - width: 32 - height: 32 - radius: 15 + StateImage { + id: actionIcon; + imageURL: "../../images/info-icon-2-state.svg"; + size: 32; + buttonState: messageArea.containsMouse ? 1 : 0; anchors { bottom: parent.bottom; right: parent.right; - bottomMargin: textPadding; - rightMargin: textPadding; - } - - StateImage { - id: actionIcon; - imageURL: "../../images/info-icon-2-state.svg"; - size: 32; - buttonState: messageArea.containsMouse ? 1 : 0; - anchors { - bottom: parent.bottom; - right: parent.right; - //margins: smallMargin; - } + margins: smallMargin; } } - MouseArea { id: messageArea; - width: rectIcon.width; - height: rectIcon.height; - anchors.fill: rectIcon + width: parent.width; + height: messageHeight; + anchors.top: lobby.bottom; acceptedButtons: Qt.LeftButton; onClicked: goFunction(drillDownToPlace ? ("/places/" + placeName) : ("/user_stories/" + storyId)); hoverEnabled: true; From bc77eeeabdee888d815face0961c057b599dc43c Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 20 Apr 2017 13:43:55 -0700 Subject: [PATCH 018/134] scroll, margins --- interface/resources/qml/hifi/Feed.qml | 5 +- .../qml/hifi/tablet/TabletAddressDialog.qml | 134 +++++++----------- 2 files changed, 55 insertions(+), 84 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index a1bd38c281..9f2dfcd553 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -144,7 +144,7 @@ Column { RalewayLight { id: label; text: labelText; - color: hifi.colors.blueHighlight; + color: hifi.colors.white; size: 28; } ListView { @@ -152,6 +152,9 @@ Column { clip: true; model: suggestions; orientation: ListView.Horizontal; + highlightMoveDuration: -1; + highlightMoveVelocity: -1; + highlight: Rectangle { color: "transparent"; border.width: 4; border.color: hifiStyleConstants.colors.blueHighlight; z: 1; } spacing: 14; width: parent.width; diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index e79855ec51..1d07625e34 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -221,91 +221,64 @@ StackView { } } } - Rectangle { - id: topBar - height: 37 - color: hifiStyleConstants.colors.white - - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.topMargin: 0 - anchors.top: addressBar.bottom - - Row { - id: thing - spacing: 5 * hifi.layout.spacing - - anchors { - top: parent.top; - left: parent.left - leftMargin: 25 - } - - TabletTextButton { - id: allTab; - text: "ALL"; - property string includeActions: 'snapshot,concurrency'; - selected: allTab === selectedTab; - action: tabSelect; - } - - TabletTextButton { - id: placeTab; - text: "PLACES"; - property string includeActions: 'concurrency'; - selected: placeTab === selectedTab; - action: tabSelect; - - } - - TabletTextButton { - id: snapTab; - text: "SNAP"; - property string includeActions: 'snapshot'; - selected: snapTab === selectedTab; - action: tabSelect; - } - } - - } Rectangle { id: bgMain; - color: hifiStyleConstants.colors.white; + color: hifiStyleConstants.colors.faintGray50; anchors { + top: addressBar.bottom; bottom: parent.keyboardEnabled ? keyboard.top : parent.bottom; - bottomMargin: 0; - right: parent.right; - rightMargin: 0; left: parent.left; - leftMargin: 0; - top: topBar.bottom; - topMargin: 0; + right: parent.right; } - Feed { - id: happeningNow; - width: bgMain.width; - metaverseServerUrl: addressBarDialog.metaverseServerUrl; - actions: selectedTab.includeActions; - filter: addressLine.text; - } - Feed { - id: places; - width: bgMain.width; - metaverseServerUrl: addressBarDialog.metaverseServerUrl; - actions: 'concurrency'; - filter: addressLine.text; - anchors.top: happeningNow.bottom; - } - Feed { - id: snapshots; - width: bgMain.width; - metaverseServerUrl: addressBarDialog.metaverseServerUrl; - actions: 'snapshot'; - filter: addressLine.text; - anchors.top: places.bottom; + ScrollView { + anchors.fill: bgMain; + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff; + verticalScrollBarPolicy: Qt.ScrollBarAlwaysOn; //Qt.ScrollBarAsNeeded; + Rectangle { // Column margins require QtQuick 2.7, which we don't use yet. + id: column; + property real pad: 10; + width: bgMain.width - column.pad; + height: stack.height; + color: "transparent"; + anchors { + left: bgMain.left; + leftMargin: column.pad; + topMargin: column.pad; + } + Column { + id: stack; + width: column.width; + spacing: column.pad; + Feed { + id: happeningNow; + width: parent.width; + property real cardScale: 1.5; + cardWidth: places.cardWidth * happeningNow.cardScale; + cardHeight: places.cardHeight * happeningNow.cardScale; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + labelText: 'Happening Now'; + actions: 'concurrency,snapshot'; //selectedTab.includeActions; + filter: addressLine.text; + } + Feed { + id: places; + width: parent.width; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + labelText: 'Places'; + actions: 'concurrency'; + filter: addressLine.text; + } + Feed { + id: snapshots; + width: parent.width; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + labelText: 'Recent Activity'; + actions: 'snapshot'; + filter: addressLine.text; + } + } + } } } @@ -387,11 +360,6 @@ StackView { return true; } - property var selectedTab: allTab; - function tabSelect(textButton) { - selectedTab = textButton; - } - function updateLocationText(enteringAddress) { if (enteringAddress) { notice.text = "Go To a place, @user, path, or network address:"; From ce7145a3884ad1633fb73c64c201eb96357e526e Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 20 Apr 2017 14:40:24 -0700 Subject: [PATCH 019/134] Announcement cards, debug data. --- interface/resources/qml/hifi/Card.qml | 5 +- interface/resources/qml/hifi/Feed.qml | 55 +++++++++++-------- .../qml/hifi/tablet/TabletAddressDialog.qml | 6 +- 3 files changed, 37 insertions(+), 29 deletions(-) diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index c6e833826a..1a3ae43692 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -35,6 +35,7 @@ Rectangle { property string timePhrase: pastTime(timestamp); property int onlineUsers: 0; property bool isConcurrency: action === 'concurrency'; + property bool isAnnouncement: action === 'announcement'; property bool isStacked: !isConcurrency && drillDownToPlace; property int textPadding: 10; @@ -156,7 +157,7 @@ Rectangle { Row { FiraSansRegular { id: users; - visible: isConcurrency; + visible: isConcurrency || isAnnouncement; text: onlineUsers; size: textSize; color: messageColor; @@ -171,7 +172,7 @@ Rectangle { } RalewayRegular { id: message; - text: isConcurrency ? ((onlineUsers === 1) ? "person" : "people") : (drillDownToPlace ? "snapshots" : ("by " + userName)); + 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` diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 9f2dfcd553..4f73d5bd16 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -19,6 +19,7 @@ import "../styles-uit" Column { id: root; + visible: false; property int cardWidth: 212; property int cardHeight: 152; @@ -44,6 +45,11 @@ Column { tags = data.tags || [data.action, data.username], description = data.description || "", thumbnail_url = data.thumbnail_url || ""; + if (actions === 'concurrency,snapshot') { + // A temporary hack for simulating announcements. We won't use this in production, but if requested, we'll use this data like announcements. + data.details.connections = 4; + data.action = 'announcement'; + } return { place_name: name, username: data.username || "", @@ -57,14 +63,14 @@ Column { tags: tags, description: description, - online_users: data.details.concurrency || 0, + online_users: data.details.connections || data.details.concurrency || 0, drillDownToPlace: false, searchText: [name].concat(tags, description || []).join(' ').toUpperCase() } } property var allStories: []; - property var placeMap: ({}); // Used for making stacks. FIXME: generalize to not just by place. + property var placeMap: ({}); // Used for making stacks. property int requestId: 0; function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model var options = [ @@ -95,32 +101,17 @@ Column { getUserStoryPage(1, function (error) { allStories.forEach(makeFilteredStoryProcessor()); console.log('user stories query', actions, error || 'ok', allStories.length, 'filtered to', suggestions.count); + root.visible = !!suggestions.count; // fixme reset on filtering, too! }); } - function addToSuggestions(place) { // fixme: move to makeFilteredStoryProcessor - var collapse = (actions === 'snapshot,concurrency') && (place.action !== 'concurrency'); // fixme generalize? - if (collapse) { - var existing = placeMap[place.place_name]; - if (existing) { - existing.drillDownToPlace = true; - return; - } - } - suggestions.append(place); - if (collapse) { - placeMap[place.place_name] = suggestions.get(suggestions.count - 1); - } else if (place.action === 'concurrency') { - suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories). - } - } - function suggestable(story) { // fixme add to makeFilteredStoryProcessor - if (story.action === 'snapshot') { - return true; - } - return (story.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. - } function makeFilteredStoryProcessor() { // answer a function(storyData) that adds it to suggestions if it matches var words = filter.toUpperCase().split(/\s+/).filter(identity); + function suggestable(story) { // fixme add to makeFilteredStoryProcessor + if (story.action === 'snapshot') { + return true; + } + return (story.place_name !== AddressManager.placename); // Not our entry, but do show other entry points to current domain. + } function matches(story) { if (!words.length) { return suggestable(story); @@ -129,6 +120,22 @@ Column { return story.searchText.indexOf(word) >= 0; }); } + function addToSuggestions(place) { + var collapse = ((actions === 'concurrency,snapshot') && (place.action !== 'concurrency')) || (place.action === 'announcement'); + if (collapse) { + var existing = placeMap[place.place_name]; + if (existing) { + existing.drillDownToPlace = true; + return; + } + } + suggestions.append(place); + if (collapse) { + placeMap[place.place_name] = suggestions.get(suggestions.count - 1); + } else if (place.action === 'concurrency') { + suggestions.get(suggestions.count - 1).drillDownToPlace = true; // Don't change raw place object (in allStories). + } + } return function (story) { if (matches(story)) { addToSuggestions(story); diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 1d07625e34..5be8e158f4 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -186,7 +186,6 @@ StackView { } font.pixelSize: hifi.fonts.pixelSize * 0.75 onTextChanged: { - console.log('fixme onTextChanged "' + addressLine.text + "'."); updateLocationText(text.length > 0); } onAccepted: { @@ -234,7 +233,7 @@ StackView { ScrollView { anchors.fill: bgMain; horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff; - verticalScrollBarPolicy: Qt.ScrollBarAlwaysOn; //Qt.ScrollBarAsNeeded; + verticalScrollBarPolicy: Qt.ScrollBarAsNeeded; Rectangle { // Column margins require QtQuick 2.7, which we don't use yet. id: column; property real pad: 10; @@ -258,7 +257,8 @@ StackView { cardHeight: places.cardHeight * happeningNow.cardScale; metaverseServerUrl: addressBarDialog.metaverseServerUrl; labelText: 'Happening Now'; - actions: 'concurrency,snapshot'; //selectedTab.includeActions; + actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. + //actions: 'announcement'; filter: addressLine.text; } Feed { From 4ea882cad13fbc52e74cab5c3600655290a4e185 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 20 Apr 2017 14:56:34 -0700 Subject: [PATCH 020/134] faster initial response --- interface/resources/qml/hifi/Feed.qml | 18 +++++++++++++++--- .../qml/hifi/tablet/TabletAddressDialog.qml | 4 ++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 4f73d5bd16..c02cd1dd36 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -72,7 +72,8 @@ Column { property var allStories: []; property var placeMap: ({}); // Used for making stacks. property int requestId: 0; - function getUserStoryPage(pageNumber, cb) { // cb(error) after all pages of domain data have been added to model + 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(), 'include_actions=' + actions, @@ -89,19 +90,29 @@ Column { } allStories = allStories.concat(data.user_stories.map(makeModelData)); if ((data.current_page < data.total_pages) && (data.current_page <= 10)) { // just 10 pages = 100 stories for now + if ((pageNumber === 1) && cb1) { + cb1(); + } return getUserStoryPage(pageNumber + 1, cb); } cb(); }); } function fillDestinations() { // Public + var filter = makeFilteredStoryProcessor(), counter = 0; allStories = []; suggestions.clear(); placeMap = {}; getUserStoryPage(1, function (error) { - allStories.forEach(makeFilteredStoryProcessor()); + allStories.slice(counter).forEach(filter); console.log('user stories query', actions, error || 'ok', allStories.length, 'filtered to', suggestions.count); - root.visible = !!suggestions.count; // fixme reset on filtering, too! + 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. + allStories.forEach(function (story) { + counter++; + filter(story); + root.visible = !!suggestions.count; + }); }); } function makeFilteredStoryProcessor() { // answer a function(storyData) that adds it to suggestions if it matches @@ -146,6 +157,7 @@ Column { suggestions.clear(); placeMap = {}; allStories.forEach(makeFilteredStoryProcessor()); + root.visible = !!suggestions.count; } RalewayLight { diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 5be8e158f4..02881f14c5 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -257,8 +257,8 @@ StackView { cardHeight: places.cardHeight * happeningNow.cardScale; metaverseServerUrl: addressBarDialog.metaverseServerUrl; labelText: 'Happening Now'; - actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. - //actions: 'announcement'; + //actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. + actions: 'announcement'; filter: addressLine.text; } Feed { From ce7f3197f679eafc367134419b5ecb54107ad66b Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 21 Apr 2017 14:27:38 -0700 Subject: [PATCH 021/134] watch for announcements --- .../scripting/WindowScriptingInterface.cpp | 4 + .../src/scripting/WindowScriptingInterface.h | 2 + scripts/system/notifications.js | 5 +- scripts/system/tablet-goto.js | 109 +++++++++++++++++- 4 files changed, 116 insertions(+), 4 deletions(-) diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 39c2f2e402..6256bd456d 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -243,6 +243,10 @@ void WindowScriptingInterface::makeConnection(bool success, const QString& userN } } +void WindowScriptingInterface::displayAnnouncement(const QString& message) { + emit announcement(message); +} + bool WindowScriptingInterface::isPhysicsEnabled() { return qApp->isPhysicsEnabled(); } diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index d4ff278fea..99ccf4eed0 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -57,6 +57,7 @@ public slots: void copyToClipboard(const QString& text); void takeSnapshot(bool notify = true, bool includeAnimated = false, float aspectRatio = 0.0f); void makeConnection(bool success, const QString& userNameOrError); + void displayAnnouncement(const QString& message); void shareSnapshot(const QString& path, const QUrl& href = QUrl("")); bool isPhysicsEnabled(); @@ -78,6 +79,7 @@ signals: void connectionAdded(const QString& connectionName); void connectionError(const QString& errorString); + void announcement(const QString& message); void messageBoxClosed(int id, int button); diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index 006ef3f90f..c08cb44c0c 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -188,10 +188,10 @@ function fadeOut(noticeOut, buttonOut, arraysOut) { pauseTimer = Script.setInterval(function () { r -= 1; - rFade = r / 10.0; + rFade = Math.max(0.0, r / 10.0); Overlays.editOverlay(noticeOut, { alpha: rFade }); Overlays.editOverlay(buttonOut, { alpha: rFade }); - if (r < 0) { + if (r <= 0) { dismiss(noticeOut, buttonOut, arraysOut); arrays.splice(arraysOut, 1); ready = true; @@ -660,6 +660,7 @@ Window.stillSnapshotTaken.connect(onSnapshotTaken); Window.processingGifStarted.connect(processingGif); Window.connectionAdded.connect(connectionAdded); Window.connectionError.connect(connectionError); +Window.announcement.connect(onNotify); Window.notifyEditError = onEditError; Window.notify = onNotify; Tablet.tabletNotification.connect(tabletNotification); diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 84f7357b1a..3bb8c746c7 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -33,6 +33,7 @@ onGotoScreen = true; shouldActivateButton = true; button.editProperties({isActive: shouldActivateButton}); + messagesWaiting(false); } else { shouldActivateButton = false; onGotoScreen = false; @@ -41,17 +42,121 @@ } var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var NORMAL_ICON = "icons/tablet-icons/goto-i.svg"; + var NORMAL_ACTIVE = "icons/tablet-icons/goto-a.svg"; + var WAITING_ICON = "icons/tablet-icons/help-i.svg"; // To be changed when we get the artwork. + var WAITING_ACTIVE = "icons/tablet-icons/help-a.svg"; var button = tablet.addButton({ - icon: "icons/tablet-icons/goto-i.svg", - activeIcon: "icons/tablet-icons/goto-a.svg", + icon: NORMAL_ICON, + activeIcon: NORMAL_ACTIVE, text: buttonName, sortOrder: 8 }); + function messagesWaiting(isWaiting) { + button.editProperties({ + icon: isWaiting ? WAITING_ICON : NORMAL_ICON, + activeIcon: isWaiting ? WAITING_ACTIVE : NORMAL_ACTIVE + }); + } button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); + var METAVERSE_BASE = location.metaverseServerUrl; + 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. + httpRequest.onreadystatechange = function () { + var READY_STATE_DONE = 4; + var HTTP_OK = 200; + if (httpRequest.readyState >= READY_STATE_DONE) { + var error = (httpRequest.status !== HTTP_OK) && httpRequest.status.toString() + ':' + httpRequest.statusText, + response = !error && httpRequest.responseText, + contentType = !error && httpRequest.getResponseHeader('content-type'); + if (!error && contentType.indexOf('application/json') === 0) { // ignoring charset, etc. + try { + response = JSON.parse(response); + } catch (e) { + error = e; + } + } + callback(error, response); + } + }; + if (typeof options === 'string') { + options = {uri: options}; + } + if (options.url) { + options.uri = options.url; + } + if (!options.method) { + options.method = 'GET'; + } + if (options.body && (options.method === 'GET')) { // add query parameters + var params = [], appender = (-1 === options.uri.search('?')) ? '?' : '&'; + for (key in options.body) { + params.push(key + '=' + options.body[key]); + } + options.uri += appender + params.join('&'); + delete options.body; + } + if (options.json) { + options.headers = options.headers || {}; + options.headers["Content-type"] = "application/json"; + options.body = JSON.stringify(options.body); + } + for (key in options.headers || {}) { + httpRequest.setRequestHeader(key, options.headers[key]); + } + httpRequest.open(options.method, options.uri, true); + httpRequest.send(options.body); + } + + var stories = {}; + var DEBUG = false; + function pollForAnnouncements() { + var actions = DEBUG ? 'snapshot' : 'announcement'; + var count = DEBUG ? 10 : 100; + var options = [ + 'now=' + new Date().toISOString(), + 'include_actions=' + actions, + 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), + 'require_online=true', + 'protocol=' + encodeURIComponent(location.protocolVersion()), + 'per_page=' + count + ]; + var url = location.metaverseServerUrl + '/api/v1/user_stories?' + options.join('&'); + request({ + uri: url + }, function (error, data) { + if (error || (data.status !== 'success')) { + print("Error: unable to get", url, error || response.status); + return; + } + var didNotify = false; + data.user_stories.forEach(function (story) { + if (stories[story.id]) { // already seen + return; + } + stories[story.id] = story; + var message = story.username + " says something is happending in " + story.place_name + ". Open GOTO to join them."; + Window.displayAnnouncement(message); + didNotify = true; + }); + if (didNotify) { + messagesWaiting(true); + if (HMD.isHandControllerAvailable()) { + var STRENGTH = 1.0, DURATION_MS = 60, HAND = 2; // both hands + Controller.triggerHapticPulse(STRENGTH, DURATION_MS, HAND); + } + } + }); + } + var ANNOUNCEMENTS_POLL_TIME_MS = (DEBUG ? 10 : 60) * 1000; + var pollTimer = Script.setInterval(pollForAnnouncements, ANNOUNCEMENTS_POLL_TIME_MS); + Script.scriptEnding.connect(function () { + Script.clearInterval(pollTimer); button.clicked.disconnect(onClicked); tablet.removeButton(button); tablet.screenChanged.disconnect(onScreenChanged); From fac40f51d9fc8536d552c43106db4ca41bb3449b Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 21 Apr 2017 16:00:23 -0700 Subject: [PATCH 022/134] message waiting art --- .../resources/icons/tablet-icons/goto-msg.svg | 18 ++++++++++++++++++ scripts/system/tablet-goto.js | 7 +++---- 2 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 interface/resources/icons/tablet-icons/goto-msg.svg diff --git a/interface/resources/icons/tablet-icons/goto-msg.svg b/interface/resources/icons/tablet-icons/goto-msg.svg new file mode 100644 index 0000000000..9b576ab1bf --- /dev/null +++ b/interface/resources/icons/tablet-icons/goto-msg.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 3bb8c746c7..bf4f79a346 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -44,8 +44,7 @@ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var NORMAL_ICON = "icons/tablet-icons/goto-i.svg"; var NORMAL_ACTIVE = "icons/tablet-icons/goto-a.svg"; - var WAITING_ICON = "icons/tablet-icons/help-i.svg"; // To be changed when we get the artwork. - var WAITING_ACTIVE = "icons/tablet-icons/help-a.svg"; + var WAITING_ICON = "icons/tablet-icons/goto-msg.svg"; var button = tablet.addButton({ icon: NORMAL_ICON, activeIcon: NORMAL_ACTIVE, @@ -54,8 +53,8 @@ }); function messagesWaiting(isWaiting) { button.editProperties({ - icon: isWaiting ? WAITING_ICON : NORMAL_ICON, - activeIcon: isWaiting ? WAITING_ACTIVE : NORMAL_ACTIVE + icon: isWaiting ? WAITING_ICON : NORMAL_ICON + // No need for a different activeIcon, because we issue messagesWaiting(false) when the button goes active anyway. }); } From 47e51493e87cc3d36906bdf67e2d8079133c602e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Apr 2017 09:56:10 -0700 Subject: [PATCH 023/134] dynamicallyAdjustLimits on the underPoses not the relaxed poses. --- libraries/animation/src/AnimInverseKinematics.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 0e7a41e74c..a457adddd5 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -426,13 +426,13 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars _relativePoses[i].trans() = underPoses[i].trans(); } - if (!_relativePoses.empty()) { + if (!underPoses.empty()) { // Sometimes the underpose itself can violate the constraints. Rather than // clamp the animation we dynamically expand each constraint to accomodate it. std::map::iterator constraintItr = _constraints.begin(); while (constraintItr != _constraints.end()) { int index = constraintItr->first; - constraintItr->second->dynamicallyAdjustLimits(_relativePoses[index].rot()); + constraintItr->second->dynamicallyAdjustLimits(underPoses[index].rot()); ++constraintItr; } } From dc3803a2250d0c896ddce146729be50687af831e Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Apr 2017 13:59:02 -0700 Subject: [PATCH 024/134] Re-enable IK _hipsOffset computation when no hips IK target is present. --- interface/src/avatar/SkeletonModel.cpp | 6 +- .../animation/src/AnimInverseKinematics.cpp | 140 ++++++++++++++---- .../animation/src/AnimInverseKinematics.h | 2 + libraries/animation/src/Rig.cpp | 32 +++- 4 files changed, 144 insertions(+), 36 deletions(-) diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index e26c339fb8..3cf866fb6b 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -120,8 +120,10 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { headParams.rigHeadOrientation = extractRotation(rigHMDMat); headParams.worldHeadOrientation = extractRotation(worldHMDMat); - headParams.hipsMatrix = worldToRig * myAvatar->getSensorToWorldMatrix() * myAvatar->deriveBodyFromHMDSensor(); - headParams.hipsEnabled = true; + // TODO: if hips target sensor is valid. + // Copy it into headParams.hipsMatrix, and set headParams.hipsEnabled to true. + + headParams.hipsEnabled = false; } else { headParams.hipsEnabled = false; headParams.isInHMD = false; diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index a457adddd5..a9898fb4d2 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -86,6 +86,7 @@ void AnimInverseKinematics::setTargetVars( void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::vector& targets, const AnimPoseVec& underPoses) { // build a list of valid targets from _targetVarVec and animVars _maxTargetIndex = -1; + _hipsTargetIndex = -1; bool removeUnfoundJoints = false; for (auto& targetVar : _targetVarVec) { @@ -114,6 +115,11 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: if (targetVar.jointIndex > _maxTargetIndex) { _maxTargetIndex = targetVar.jointIndex; } + + // record the index of the hips ik target. + if (target.getIndex() == _hipsIndex) { + _hipsTargetIndex = (int)targets.size() - 1; + } } } } @@ -242,6 +248,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe // the tip's parent-relative as we proceed up the chain glm::quat tipParentOrientation = absolutePoses[pivotIndex].rot(); + // AJT: REMOVE + /* if (targetType == IKTarget::Type::HmdHead) { // rotate tip directly to target orientation tipOrientation = target.getRotation(); @@ -259,6 +267,7 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe // store the relative rotation change in the accumulator _accumulators[tipIndex].add(tipRelativeRotation, target.getWeight()); } + */ // cache tip absolute position glm::vec3 tipPosition = absolutePoses[tipIndex].trans(); @@ -278,9 +287,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe const float MIN_AXIS_LENGTH = 1.0e-4f; RotationConstraint* constraint = getConstraint(pivotIndex); - // AJT: disabled special case for the lower spine. - /* - if (constraint && constraint->isLowerSpine() && tipIndex != _headIndex) { + // only allow swing on lowerSpine if there is a hips IK target. + if (_hipsTargetIndex < 0 && constraint && constraint->isLowerSpine() && tipIndex != _headIndex) { // for these types of targets we only allow twist at the lower-spine // (this prevents the hand targets from bending the spine too much and thereby driving the hips too far) glm::vec3 twistAxis = absolutePoses[pivotIndex].trans() - absolutePoses[pivotsParentIndex].trans(); @@ -295,7 +303,6 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe targetLine = Vectors::ZERO; } } - */ glm::vec3 axis = glm::cross(leverArm, targetLine); float axisLength = glm::length(axis); @@ -365,9 +372,7 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe } // store the relative rotation change in the accumulator - // AJT: Hack give head more weight. - float weight = (target.getIndex() == _headIndex) ? 2.0f : 1.0f; - _accumulators[pivotIndex].add(newRot, weight); + _accumulators[pivotIndex].add(newRot, target.getWeight()); // this joint has been changed so we check to see if it has the lowest index if (pivotIndex < lowestMovedIndex) { @@ -448,41 +453,46 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars } if (targets.empty()) { - // no IK targets but still need to enforce constraints - std::map::iterator constraintItr = _constraints.begin(); - while (constraintItr != _constraints.end()) { - int index = constraintItr->first; - glm::quat rotation = _relativePoses[index].rot(); - constraintItr->second->apply(rotation); - _relativePoses[index].rot() = rotation; - ++constraintItr; - } + _relativePoses = underPoses; } else { { PROFILE_RANGE_EX(simulation_animation, "ik/shiftHips", 0xffff00ff, 0); - // AJT: TODO only need abs poses below hips. - AnimPoseVec absolutePoses; - absolutePoses.resize(_relativePoses.size()); - computeAbsolutePoses(absolutePoses); - - for (auto& target: targets) { - if (target.getType() == IKTarget::Type::RotationAndPosition && target.getIndex() == _hipsIndex) { - AnimPose absPose = target.getPose(); - int parentIndex = _skeleton->getParentIndex(target.getIndex()); - if (parentIndex != -1) { - _relativePoses[_hipsIndex] = absolutePoses[parentIndex].inverse() * absPose; + if (_hipsTargetIndex >= 0 && _hipsTargetIndex < targets.size()) { + // slam the hips to match the _hipsTarget + AnimPose absPose = targets[_hipsTargetIndex].getPose(); + int parentIndex = _skeleton->getParentIndex(targets[_hipsTargetIndex].getIndex()); + if (parentIndex != -1) { + _relativePoses[_hipsIndex] = _skeleton->getAbsolutePose(parentIndex, _relativePoses).inverse() * absPose; + } else { + _relativePoses[_hipsIndex] = absPose; + } + } else { + // if there is no hips target, shift hips according to the _hipsOffset from the previous frame + float offsetLength = glm::length(_hipsOffset); + const float MIN_HIPS_OFFSET_LENGTH = 0.03f; + if (offsetLength > MIN_HIPS_OFFSET_LENGTH && _hipsIndex >= 0) { + float scaleFactor = ((offsetLength - MIN_HIPS_OFFSET_LENGTH) / offsetLength); + glm::vec3 hipsOffset = scaleFactor * _hipsOffset; + if (_hipsParentIndex == -1) { + _relativePoses[_hipsIndex].trans() = underPoses[_hipsIndex].trans() + hipsOffset; } else { - _relativePoses[_hipsIndex] = absPose; + auto absHipsPose = _skeleton->getAbsolutePose(_hipsIndex, underPoses); + absHipsPose.trans() += hipsOffset; + _relativePoses[_hipsIndex] = _skeleton->getAbsolutePose(_hipsParentIndex, _relativePoses).inverse() * absHipsPose; } } } + // update all HipsRelative targets to account for the hips shift/ik target. + auto shiftedHipsAbsPose = _skeleton->getAbsolutePose(_hipsIndex, _relativePoses); + auto underHipsAbsPose = _skeleton->getAbsolutePose(_hipsIndex, underPoses); + auto absHipsOffset = shiftedHipsAbsPose.trans() - underHipsAbsPose.trans(); for (auto& target: targets) { if (target.getType() == IKTarget::Type::HipsRelativeRotationAndPosition) { - AnimPose pose = target.getPose(); - pose.trans() = pose.trans() + (_relativePoses[_hipsIndex].trans() - underPoses[_hipsIndex].trans()); + auto pose = target.getPose(); + pose.trans() = pose.trans() + absHipsOffset; target.setPose(pose.rot(), pose.trans()); } } @@ -518,11 +528,81 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0); solveWithCyclicCoordinateDescent(targets); } + + if (_hipsTargetIndex < 0) { + PROFILE_RANGE_EX(simulation_animation, "ik/measureHipsOffset", 0xffff00ff, 0); + computeHipsOffset(targets, underPoses, dt); + } else { + _hipsOffset = Vectors::ZERO; + } } } return _relativePoses; } +void AnimInverseKinematics::computeHipsOffset(const std::vector& targets, const AnimPoseVec& underPoses, float dt) { + // measure new _hipsOffset for next frame + // by looking for discrepancies between where a targeted endEffector is + // and where it wants to be (after IK solutions are done) + + // OUTOFBODY_HACK:use weighted average between HMD and other targets + float HMD_WEIGHT = 10.0f; + float OTHER_WEIGHT = 1.0f; + float totalWeight = 0.0f; + + glm::vec3 additionalHipsOffset = Vectors::ZERO; + for (auto& target: targets) { + int targetIndex = target.getIndex(); + if (targetIndex == _headIndex && _headIndex != -1) { + // special handling for headTarget + if (target.getType() == IKTarget::Type::RotationOnly) { + // we want to shift the hips to bring the underPose closer + // to where the head happens to be (overpose) + glm::vec3 under = _skeleton->getAbsolutePose(_headIndex, underPoses).trans(); + glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans(); + const float HEAD_OFFSET_SLAVE_FACTOR = 0.65f; + additionalHipsOffset += (OTHER_WEIGHT * HEAD_OFFSET_SLAVE_FACTOR) * (under - actual); + totalWeight += OTHER_WEIGHT; + } else if (target.getType() == IKTarget::Type::HmdHead) { + glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans(); + glm::vec3 thisOffset = target.getTranslation() - actual; + glm::vec3 futureHipsOffset = _hipsOffset + thisOffset; + if (glm::length(glm::vec2(futureHipsOffset.x, futureHipsOffset.z)) < _maxHipsOffsetLength) { + // it is imperative to shift the hips and bring the head to its designated position + // so we slam newHipsOffset here and ignore all other targets + additionalHipsOffset = futureHipsOffset - _hipsOffset; + totalWeight = 0.0f; + break; + } else { + additionalHipsOffset += HMD_WEIGHT * (target.getTranslation() - actual); + totalWeight += HMD_WEIGHT; + } + } + } else if (target.getType() == IKTarget::Type::RotationAndPosition) { + glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans(); + glm::vec3 targetPosition = target.getTranslation(); + additionalHipsOffset += OTHER_WEIGHT * (targetPosition - actualPosition); + totalWeight += OTHER_WEIGHT; + } + } + if (totalWeight > 1.0f) { + additionalHipsOffset /= totalWeight; + } + + // smooth transitions by relaxing _hipsOffset toward the new value + const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.10f; + float tau = dt < HIPS_OFFSET_SLAVE_TIMESCALE ? dt / HIPS_OFFSET_SLAVE_TIMESCALE : 1.0f; + _hipsOffset += additionalHipsOffset * tau; + + // clamp the horizontal component of the hips offset + float hipsOffsetLength2D = glm::length(glm::vec2(_hipsOffset.x, _hipsOffset.z)); + if (hipsOffsetLength2D > _maxHipsOffsetLength) { + _hipsOffset.x *= _maxHipsOffsetLength / hipsOffsetLength2D; + _hipsOffset.z *= _maxHipsOffsetLength / hipsOffsetLength2D; + } + +} + void AnimInverseKinematics::setMaxHipsOffsetLength(float maxLength) { // manually adjust scale here const float METERS_TO_CENTIMETERS = 100.0f; diff --git a/libraries/animation/src/AnimInverseKinematics.h b/libraries/animation/src/AnimInverseKinematics.h index 366e5f765e..c91b7aa9c4 100644 --- a/libraries/animation/src/AnimInverseKinematics.h +++ b/libraries/animation/src/AnimInverseKinematics.h @@ -55,6 +55,7 @@ protected: RotationConstraint* getConstraint(int index); void clearConstraints(); void initConstraints(); + void computeHipsOffset(const std::vector& targets, const AnimPoseVec& underPoses, float dt); // no copies AnimInverseKinematics(const AnimInverseKinematics&) = delete; @@ -91,6 +92,7 @@ protected: int _headIndex { -1 }; int _hipsIndex { -1 }; int _hipsParentIndex { -1 }; + int _hipsTargetIndex { -1 }; // _maxTargetIndex is tracked to help optimize the recalculation of absolute poses // during the the cyclic coordinate descent algorithm diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index dd7ccb6b27..116758b1ba 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1025,7 +1025,6 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) { _animVars.set("isTalking", params.isTalking); _animVars.set("notIsTalking", !params.isTalking); - // AJT: if (params.hipsEnabled) { _animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition); _animVars.set("hipsPosition", extractTranslation(params.hipsMatrix)); @@ -1106,7 +1105,7 @@ void Rig::updateNeckJoint(int index, const HeadParameters& params) { _animVars.set("headPosition", headPos); _animVars.set("headRotation", headRot); - _animVars.set("headType", (int)IKTarget::Type::RotationAndPosition); + _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 @@ -1173,8 +1172,22 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f const glm::vec3 bodyCapsuleStart = bodyCapsuleCenter - glm::vec3(0, params.bodyCapsuleHalfHeight, 0); const glm::vec3 bodyCapsuleEnd = bodyCapsuleCenter + glm::vec3(0, params.bodyCapsuleHalfHeight, 0); + // TODO: add isHipsEnabled + bool bodySensorTrackingEnabled = params.isLeftFootEnabled || params.isRightFootEnabled; + if (params.isLeftEnabled) { - _animVars.set("leftHandPosition", params.leftPosition); + + glm::vec3 handPosition = params.leftPosition; + + if (!bodySensorTrackingEnabled) { + // prevent the hand IK targets from intersecting the body capsule + glm::vec3 displacement; + if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) { + handPosition -= displacement; + } + } + + _animVars.set("leftHandPosition", handPosition); _animVars.set("leftHandRotation", params.leftOrientation); _animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); } else { @@ -1184,7 +1197,18 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f } if (params.isRightEnabled) { - _animVars.set("rightHandPosition", params.rightPosition); + + glm::vec3 handPosition = params.rightPosition; + + if (!bodySensorTrackingEnabled) { + // prevent the hand IK targets from intersecting the body capsule + glm::vec3 displacement; + if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) { + handPosition -= displacement; + } + } + + _animVars.set("rightHandPosition", handPosition); _animVars.set("rightHandRotation", params.rightOrientation); _animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); } else { From 1cd0f032421b6fdef13e2a11a12fc6d7a9d63b54 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Apr 2017 14:19:06 -0700 Subject: [PATCH 025/134] Restore master version of computeHipsOffset() and special case for HeadHMD target type --- .../animation/src/AnimInverseKinematics.cpp | 60 +++++++------------ 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index a9898fb4d2..ce199704a0 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -107,7 +107,6 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, underPoses); glm::quat rotation = animVars.lookupRigToGeometry(targetVar.rotationVar, defaultPose.rot()); glm::vec3 translation = animVars.lookupRigToGeometry(targetVar.positionVar, defaultPose.trans()); - AnimPose absPose(glm::vec3(1.0f), rotation, translation); target.setPose(rotation, translation); target.setIndex(targetVar.jointIndex); @@ -155,7 +154,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector MAX_ERROR_TOLERANCE && numLoops < MAX_IK_LOOPS) { ++numLoops; @@ -248,8 +247,6 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe // the tip's parent-relative as we proceed up the chain glm::quat tipParentOrientation = absolutePoses[pivotIndex].rot(); - // AJT: REMOVE - /* if (targetType == IKTarget::Type::HmdHead) { // rotate tip directly to target orientation tipOrientation = target.getRotation(); @@ -267,7 +264,6 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe // store the relative rotation change in the accumulator _accumulators[tipIndex].add(tipRelativeRotation, target.getWeight()); } - */ // cache tip absolute position glm::vec3 tipPosition = absolutePoses[tipIndex].trans(); @@ -544,13 +540,7 @@ void AnimInverseKinematics::computeHipsOffset(const std::vector& targe // measure new _hipsOffset for next frame // by looking for discrepancies between where a targeted endEffector is // and where it wants to be (after IK solutions are done) - - // OUTOFBODY_HACK:use weighted average between HMD and other targets - float HMD_WEIGHT = 10.0f; - float OTHER_WEIGHT = 1.0f; - float totalWeight = 0.0f; - - glm::vec3 additionalHipsOffset = Vectors::ZERO; + glm::vec3 newHipsOffset = Vectors::ZERO; for (auto& target: targets) { int targetIndex = target.getIndex(); if (targetIndex == _headIndex && _headIndex != -1) { @@ -561,46 +551,40 @@ void AnimInverseKinematics::computeHipsOffset(const std::vector& targe glm::vec3 under = _skeleton->getAbsolutePose(_headIndex, underPoses).trans(); glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans(); const float HEAD_OFFSET_SLAVE_FACTOR = 0.65f; - additionalHipsOffset += (OTHER_WEIGHT * HEAD_OFFSET_SLAVE_FACTOR) * (under - actual); - totalWeight += OTHER_WEIGHT; + newHipsOffset += HEAD_OFFSET_SLAVE_FACTOR * (actual - under); } else if (target.getType() == IKTarget::Type::HmdHead) { + // we want to shift the hips to bring the head to its designated position glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans(); - glm::vec3 thisOffset = target.getTranslation() - actual; - glm::vec3 futureHipsOffset = _hipsOffset + thisOffset; - if (glm::length(glm::vec2(futureHipsOffset.x, futureHipsOffset.z)) < _maxHipsOffsetLength) { - // it is imperative to shift the hips and bring the head to its designated position - // so we slam newHipsOffset here and ignore all other targets - additionalHipsOffset = futureHipsOffset - _hipsOffset; - totalWeight = 0.0f; - break; - } else { - additionalHipsOffset += HMD_WEIGHT * (target.getTranslation() - actual); - totalWeight += HMD_WEIGHT; - } + _hipsOffset += target.getTranslation() - actual; + // and ignore all other targets + newHipsOffset = _hipsOffset; + break; + } else if (target.getType() == IKTarget::Type::RotationAndPosition) { + glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans(); + glm::vec3 targetPosition = target.getTranslation(); + newHipsOffset += targetPosition - actualPosition; + + // Add downward pressure on the hips + newHipsOffset *= 0.95f; + newHipsOffset -= 1.0f; } } else if (target.getType() == IKTarget::Type::RotationAndPosition) { glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans(); glm::vec3 targetPosition = target.getTranslation(); - additionalHipsOffset += OTHER_WEIGHT * (targetPosition - actualPosition); - totalWeight += OTHER_WEIGHT; + newHipsOffset += targetPosition - actualPosition; } } - if (totalWeight > 1.0f) { - additionalHipsOffset /= totalWeight; - } // smooth transitions by relaxing _hipsOffset toward the new value const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.10f; float tau = dt < HIPS_OFFSET_SLAVE_TIMESCALE ? dt / HIPS_OFFSET_SLAVE_TIMESCALE : 1.0f; - _hipsOffset += additionalHipsOffset * tau; + _hipsOffset += (newHipsOffset - _hipsOffset) * tau; - // clamp the horizontal component of the hips offset - float hipsOffsetLength2D = glm::length(glm::vec2(_hipsOffset.x, _hipsOffset.z)); - if (hipsOffsetLength2D > _maxHipsOffsetLength) { - _hipsOffset.x *= _maxHipsOffsetLength / hipsOffsetLength2D; - _hipsOffset.z *= _maxHipsOffsetLength / hipsOffsetLength2D; + // clamp the hips offset + float hipsOffsetLength = glm::length(_hipsOffset); + if (hipsOffsetLength > _maxHipsOffsetLength) { + _hipsOffset *= _maxHipsOffsetLength / hipsOffsetLength; } - } void AnimInverseKinematics::setMaxHipsOffsetLength(float maxLength) { From deca26e9eba3d405458742a2394bc9a7a1d5cbf0 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Apr 2017 14:33:14 -0700 Subject: [PATCH 026/134] Fix for HMDHead tip constraint --- libraries/animation/src/AnimInverseKinematics.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index ce199704a0..9f6a9ed56f 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -248,17 +248,18 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe glm::quat tipParentOrientation = absolutePoses[pivotIndex].rot(); if (targetType == IKTarget::Type::HmdHead) { + // rotate tip directly to target orientation tipOrientation = target.getRotation(); - glm::quat tipRelativeRotation = glm::normalize(tipOrientation * glm::inverse(tipParentOrientation)); + glm::quat tipRelativeRotation = glm::inverse(tipParentOrientation) * tipOrientation; - // enforce tip's constraint + // then enforce tip's constraint RotationConstraint* constraint = getConstraint(tipIndex); if (constraint) { bool constrained = constraint->apply(tipRelativeRotation); if (constrained) { - tipOrientation = glm::normalize(tipRelativeRotation * tipParentOrientation); - tipRelativeRotation = glm::normalize(tipOrientation * glm::inverse(tipParentOrientation)); + tipOrientation = tipParentOrientation * tipRelativeRotation; + tipRelativeRotation = tipRelativeRotation; } } // store the relative rotation change in the accumulator From 8adbe34c2736e20e2dc77c335af27b5f3fdf97a7 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Apr 2017 14:36:56 -0700 Subject: [PATCH 027/134] added comment --- libraries/animation/src/AnimInverseKinematics.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 9f6a9ed56f..05a5d3bac6 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -247,6 +247,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe // the tip's parent-relative as we proceed up the chain glm::quat tipParentOrientation = absolutePoses[pivotIndex].rot(); + // NOTE: if this code is removed, the head will remain rigid, causing the spine/hips to thrust forward backward + // as the head is nodded. if (targetType == IKTarget::Type::HmdHead) { // rotate tip directly to target orientation From 8b16a7c7e19ba4496a3136db154286244b8ddee6 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Apr 2017 16:40:24 -0700 Subject: [PATCH 028/134] Added hipsIkTest.js test script --- scripts/developer/tests/hipsIkTest.js | 118 ++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 scripts/developer/tests/hipsIkTest.js diff --git a/scripts/developer/tests/hipsIkTest.js b/scripts/developer/tests/hipsIkTest.js new file mode 100644 index 0000000000..340d1ae7a0 --- /dev/null +++ b/scripts/developer/tests/hipsIkTest.js @@ -0,0 +1,118 @@ +// +// hipsIKTest.js +// +// Created by Anthony Thibault on 4/24/17 +// Copyright 2017 High Fidelity, Inc. +// +// Test procedural manipulation of the Avatar hips IK target. +// 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 calibrated = 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 ANIM_VARS = [ + "headType", + "hipsType", + "hipsPosition", + "hipsRotation" +]; + +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 Xform.mul(Y_180_XFORM, currentXform); +} + +function calibrate() { + hips = computeCurrentXform(MyAvatar.getJointIndex("Hips")); +} + +var ikTypes = { + RotationAndPosition: 0, + RotationOnly: 1, + HmdHead: 2, + HipsRelativeRotationAndPosition: 3, + Off: 4 +}; + +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 handlerId; + +function update(dt) { + if (rightTriggerPressed && leftTriggerPressed) { + if (!calibrated) { + calibrate(); + calibrated = true; + + if (handlerId) { + MyAvatar.removeAnimationStateHandler(handlerId); + handlerId = undefined; + } else { + + var n = Y_AXIS; + var t = 0; + handlerId = MyAvatar.addAnimationStateHandler(function (props) { + + t += (1 / 60) * 4; + var result = {}, xform; + if (hips) { + xform = hips; + result.hipsType = ikTypes.RotationAndPosition; + result.hipsPosition = Vec3.sum(xform.pos, circleOffset(0.1, t, n)); + result.hipsRotation = xform.rot; + result.headType = ikTypes.RotationAndPosition; + } else { + result.headType = ikTypes.HmdHead; + result.hipsType = props.hipsType; + result.hipsPosition = props.hipsPosition; + result.hipsRotation = props.hipsRotation; + } + + return result; + }, ANIM_VARS); + } + } + } else { + calibrated = false; + } +} + +Script.update.connect(update); + +Script.scriptEnding.connect(function () { + Controller.disableMapping(MAPPING_NAME); + Script.update.disconnect(update); +}); + From 937f308ba86bcc63e07f0bf30a62089553cd4a6a Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Apr 2017 17:04:16 -0700 Subject: [PATCH 029/134] code cleanup --- interface/src/avatar/MyAvatar.h | 9 ++++----- .../animation/src/AnimInverseKinematics.cpp | 16 +--------------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index b5bea23aa3..0146bd11a4 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -465,6 +465,10 @@ public: void removeHoldAction(AvatarActionHold* holdAction); // thread-safe void updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose& postUpdatePose); + // derive avatar body position and orientation from the current HMD Sensor location. + // results are in HMD frame + glm::mat4 deriveBodyFromHMDSensor() const; + public slots: void increaseSize(); void decreaseSize(); @@ -553,11 +557,6 @@ private: void setVisibleInSceneIfReady(Model* model, render::ScenePointer scene, bool visiblity); - // AJT: FIX ME... reorder this -public: - // derive avatar body position and orientation from the current HMD Sensor location. - // results are in HMD frame - glm::mat4 deriveBodyFromHMDSensor() const; private: virtual void updatePalms() override {} diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 05a5d3bac6..83419afe65 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -828,14 +828,7 @@ void AnimInverseKinematics::initConstraints() { const float MAX_SPINE_TWIST = PI / 20.0f; stConstraint->setTwistLimits(-MAX_SPINE_TWIST, MAX_SPINE_TWIST); - /* - std::vector minDots; - const float MAX_SPINE_SWING = PI / 10.0f; - minDots.push_back(cosf(MAX_SPINE_SWING)); - stConstraint->setSwingLimits(minDots); - */ - - // AJT: limit lateral swings more then forward-backward swings + // limit lateral swings more then forward-backward swings setEllipticalSwingLimits(stConstraint, PI / 30.0f, PI / 20.0f); if (0 == baseName.compare("Spine1", Qt::CaseSensitive) @@ -851,13 +844,6 @@ void AnimInverseKinematics::initConstraints() { const float MAX_NECK_TWIST = PI / 10.0f; stConstraint->setTwistLimits(-MAX_NECK_TWIST, MAX_NECK_TWIST); - /* - std::vector minDots; - const float MAX_NECK_SWING = PI / 8.0f; - minDots.push_back(cosf(MAX_NECK_SWING)); - stConstraint->setSwingLimits(minDots); - */ - setEllipticalSwingLimits(stConstraint, PI / 10.0f, PI / 8.0f); constraint = static_cast(stConstraint); From 4336e22f5a56752c2c2d954cb280f2abf6ee76ae Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 24 Apr 2017 17:06:02 -0700 Subject: [PATCH 030/134] clang warning fix --- libraries/animation/src/AnimInverseKinematics.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index 83419afe65..b36abebb1b 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -458,7 +458,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars { PROFILE_RANGE_EX(simulation_animation, "ik/shiftHips", 0xffff00ff, 0); - if (_hipsTargetIndex >= 0 && _hipsTargetIndex < targets.size()) { + if (_hipsTargetIndex >= 0 && _hipsTargetIndex < (int)targets.size()) { // slam the hips to match the _hipsTarget AnimPose absPose = targets[_hipsTargetIndex].getPose(); int parentIndex = _skeleton->getParentIndex(targets[_hipsTargetIndex].getIndex()); From 0cd2863df452a39b309a298b14c68322b7b73ad7 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 18 Apr 2017 14:42:32 -0700 Subject: [PATCH 031/134] 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 032/134] 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 ee9b953b1c8cc3bce5c9cea72e33aa8c46a90c4f Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Tue, 25 Apr 2017 12:12:26 -0700 Subject: [PATCH 033/134] typo --- scripts/system/tablet-goto.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index bf4f79a346..8235b69c90 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -138,7 +138,7 @@ return; } stories[story.id] = story; - var message = story.username + " says something is happending in " + story.place_name + ". Open GOTO to join them."; + var message = story.username + " says something is happening in " + story.place_name + ". Open GOTO to join them."; Window.displayAnnouncement(message); didNotify = true; }); From dc19f3772643eff3516e19435136c043bf9fb27c Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 25 Apr 2017 15:04:47 -0700 Subject: [PATCH 034/134] 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 ce8b71ff9464669ad324ee814ee8c93f447da02b Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 25 Apr 2017 15:05:52 -0700 Subject: [PATCH 035/134] 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 1931a8d1f6982b9264b650e7166905249799f709 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 25 Apr 2017 15:05:52 -0700 Subject: [PATCH 036/134] 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 037/134] 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 038/134] 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 0f8ee7051cf0ebd22bc036af2732dc0c017ac17e Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Wed, 26 Apr 2017 08:20:12 -0700 Subject: [PATCH 039/134] lint --- scripts/system/notifications.js | 1083 ++++++++++++++++--------------- scripts/system/tablet-goto.js | 56 +- 2 files changed, 571 insertions(+), 568 deletions(-) diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index c08cb44c0c..6429d6e0c6 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -1,5 +1,6 @@ "use strict"; - +/*jslint vars:true, plusplus:true, forin:true*/ +/*global Script, Settings, Window, Controller, Overlays, SoundArray, LODManager, MyAvatar, Tablet, Camera, HMD, Menu, Quat, Vec3*/ // // notifications.js // Version 0.801 @@ -56,614 +57,614 @@ // } // } -/* global Script, Controller, Overlays, SoundArray, Quat, Vec3, MyAvatar, Menu, HMD, AudioDevice, LODManager, Settings, Camera */ -(function() { // BEGIN LOCAL_SCOPE +(function () { // BEGIN LOCAL_SCOPE -Script.include("./libraries/soundArray.js"); + Script.include("./libraries/soundArray.js"); -var width = 340.0; //width of notification overlay -var windowDimensions = Controller.getViewportDimensions(); // get the size of the interface window -var overlayLocationX = (windowDimensions.x - (width + 20.0)); // positions window 20px from the right of the interface window -var buttonLocationX = overlayLocationX + (width - 28.0); -var locationY = 20.0; // position down from top of interface window -var topMargin = 13.0; -var leftMargin = 10.0; -var textColor = { red: 228, green: 228, blue: 228}; // text color -var backColor = { red: 2, green: 2, blue: 2}; // background color was 38,38,38 -var backgroundAlpha = 0; -var fontSize = 12.0; -var PERSIST_TIME_2D = 10.0; // Time in seconds before notification fades -var PERSIST_TIME_3D = 15.0; -var persistTime = PERSIST_TIME_2D; -var frame = 0; -var ourWidth = Window.innerWidth; -var ourHeight = Window.innerHeight; -var ctrlIsPressed = false; -var ready = true; -var MENU_NAME = 'Tools > Notifications'; -var PLAY_NOTIFICATION_SOUNDS_MENU_ITEM = "Play Notification Sounds"; -var NOTIFICATION_MENU_ITEM_POST = " Notifications"; -var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds"; -var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_"; -var lodTextID = false; + var width = 340.0; //width of notification overlay + var windowDimensions = Controller.getViewportDimensions(); // get the size of the interface window + var overlayLocationX = (windowDimensions.x - (width + 20.0)); // positions window 20px from the right of the interface window + var buttonLocationX = overlayLocationX + (width - 28.0); + var locationY = 20.0; // position down from top of interface window + var topMargin = 13.0; + var leftMargin = 10.0; + var textColor = { red: 228, green: 228, blue: 228}; // text color + var backColor = { red: 2, green: 2, blue: 2}; // background color was 38,38,38 + var backgroundAlpha = 0; + var fontSize = 12.0; + var PERSIST_TIME_2D = 10.0; // Time in seconds before notification fades + var PERSIST_TIME_3D = 15.0; + var persistTime = PERSIST_TIME_2D; + var frame = 0; + var ctrlIsPressed = false; + var ready = true; + var MENU_NAME = 'Tools > Notifications'; + var PLAY_NOTIFICATION_SOUNDS_MENU_ITEM = "Play Notification Sounds"; + var NOTIFICATION_MENU_ITEM_POST = " Notifications"; + var PLAY_NOTIFICATION_SOUNDS_SETTING = "play_notification_sounds"; + var PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE = "play_notification_sounds_type_"; + var lodTextID = false; -var NotificationType = { - UNKNOWN: 0, - SNAPSHOT: 1, - LOD_WARNING: 2, - CONNECTION_REFUSED: 3, - EDIT_ERROR: 4, - TABLET: 5, - CONNECTION: 6, - properties: [ - { text: "Snapshot" }, - { text: "Level of Detail" }, - { text: "Connection Refused" }, - { text: "Edit error" }, - { text: "Tablet" }, - { text: "Connection" } - ], - getTypeFromMenuItem: function(menuItemName) { - if (menuItemName.substr(menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length) !== NOTIFICATION_MENU_ITEM_POST) { - return NotificationType.UNKNOWN; - } - var preMenuItemName = menuItemName.substr(0, menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length); - for (var type in this.properties) { - if (this.properties[type].text === preMenuItemName) { - return parseInt(type) + 1; + var NotificationType = { + UNKNOWN: 0, + SNAPSHOT: 1, + LOD_WARNING: 2, + CONNECTION_REFUSED: 3, + EDIT_ERROR: 4, + TABLET: 5, + CONNECTION: 6, + properties: [ + { text: "Snapshot" }, + { text: "Level of Detail" }, + { text: "Connection Refused" }, + { text: "Edit error" }, + { text: "Tablet" }, + { text: "Connection" } + ], + getTypeFromMenuItem: function (menuItemName) { + var type; + if (menuItemName.substr(menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length) !== NOTIFICATION_MENU_ITEM_POST) { + return NotificationType.UNKNOWN; } + var preMenuItemName = menuItemName.substr(0, menuItemName.length - NOTIFICATION_MENU_ITEM_POST.length); + for (type in this.properties) { + if (this.properties[type].text === preMenuItemName) { + return parseInt(type, 10) + 1; + } + } + return NotificationType.UNKNOWN; + }, + getMenuString: function (type) { + return this.properties[type - 1].text + NOTIFICATION_MENU_ITEM_POST; } - return NotificationType.UNKNOWN; - }, - getMenuString: function(type) { - return this.properties[type - 1].text + NOTIFICATION_MENU_ITEM_POST; - } -}; - -var randomSounds = new SoundArray({ localOnly: true }, true); -var numberOfSounds = 2; -for (var i = 1; i <= numberOfSounds; i++) { - randomSounds.addSound(Script.resolvePath("assets/sounds/notification-general"+ i + ".raw")); -} - -var notifications = []; -var buttons = []; -var times = []; -var heights = []; -var myAlpha = []; -var arrays = []; -var isOnHMD = false, - NOTIFICATIONS_3D_DIRECTION = 0.0, // Degrees from avatar orientation. - NOTIFICATIONS_3D_DISTANCE = 0.6, // Horizontal distance from avatar position. - NOTIFICATIONS_3D_ELEVATION = -0.8, // Height of top middle of top notification relative to avatar eyes. - NOTIFICATIONS_3D_YAW = 0.0, // Degrees relative to notifications direction. - NOTIFICATIONS_3D_PITCH = -60.0, // Degrees from vertical. - NOTIFICATION_3D_SCALE = 0.002, // Multiplier that converts 2D overlay dimensions to 3D overlay dimensions. - NOTIFICATION_3D_BUTTON_WIDTH = 40 * NOTIFICATION_3D_SCALE, // Need a little more room for button in 3D. - overlay3DDetails = []; - -// push data from above to the 2 dimensional array -function createArrays(notice, button, createTime, height, myAlpha) { - arrays.push([notice, button, createTime, height, myAlpha]); -} - -// This handles the final dismissal of a notification after fading -function dismiss(firstNoteOut, firstButOut, firstOut) { - if (firstNoteOut == lodTextID) { - lodTextID = false; - } - - Overlays.deleteOverlay(firstNoteOut); - Overlays.deleteOverlay(firstButOut); - notifications.splice(firstOut, 1); - buttons.splice(firstOut, 1); - times.splice(firstOut, 1); - heights.splice(firstOut, 1); - myAlpha.splice(firstOut, 1); - overlay3DDetails.splice(firstOut, 1); -} - -function fadeIn(noticeIn, buttonIn) { - var q = 0, - qFade, - pauseTimer = null; - - pauseTimer = Script.setInterval(function () { - q += 1; - qFade = q / 10.0; - Overlays.editOverlay(noticeIn, { alpha: qFade }); - Overlays.editOverlay(buttonIn, { alpha: qFade }); - if (q >= 9.0) { - Script.clearInterval(pauseTimer); - } - }, 10); -} - -// this fades the notification ready for dismissal, and removes it from the arrays -function fadeOut(noticeOut, buttonOut, arraysOut) { - var r = 9.0, - rFade, - pauseTimer = null; - - pauseTimer = Script.setInterval(function () { - r -= 1; - rFade = Math.max(0.0, r / 10.0); - Overlays.editOverlay(noticeOut, { alpha: rFade }); - Overlays.editOverlay(buttonOut, { alpha: rFade }); - if (r <= 0) { - dismiss(noticeOut, buttonOut, arraysOut); - arrays.splice(arraysOut, 1); - ready = true; - Script.clearInterval(pauseTimer); - } - }, 20); -} - -function calculate3DOverlayPositions(noticeWidth, noticeHeight, y) { - // Calculates overlay positions and orientations in avatar coordinates. - var noticeY, - originOffset, - notificationOrientation, - notificationPosition, - buttonPosition; - - // Notification plane positions - noticeY = -y * NOTIFICATION_3D_SCALE - noticeHeight / 2; - notificationPosition = { x: 0, y: noticeY, z: 0 }; - buttonPosition = { x: (noticeWidth - NOTIFICATION_3D_BUTTON_WIDTH) / 2, y: noticeY, z: 0.001 }; - - // Rotate plane - notificationOrientation = Quat.fromPitchYawRollDegrees(NOTIFICATIONS_3D_PITCH, - NOTIFICATIONS_3D_DIRECTION + NOTIFICATIONS_3D_YAW, 0); - notificationPosition = Vec3.multiplyQbyV(notificationOrientation, notificationPosition); - buttonPosition = Vec3.multiplyQbyV(notificationOrientation, buttonPosition); - - // Translate plane - originOffset = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, NOTIFICATIONS_3D_DIRECTION, 0), - { x: 0, y: 0, z: -NOTIFICATIONS_3D_DISTANCE }); - originOffset.y += NOTIFICATIONS_3D_ELEVATION; - notificationPosition = Vec3.sum(originOffset, notificationPosition); - buttonPosition = Vec3.sum(originOffset, buttonPosition); - - return { - notificationOrientation: notificationOrientation, - notificationPosition: notificationPosition, - buttonPosition: buttonPosition }; -} -// Pushes data to each array and sets up data for 2nd dimension array -// to handle auxiliary data not carried by the overlay class -// specifically notification "heights", "times" of creation, and . -function notify(notice, button, height, imageProperties, image) { - var notificationText, - noticeWidth, - noticeHeight, - positions, - last; + var randomSounds = new SoundArray({ localOnly: true }, true); + var numberOfSounds = 2; + var soundIndex; + for (soundIndex = 1; soundIndex <= numberOfSounds; soundIndex++) { + randomSounds.addSound(Script.resolvePath("assets/sounds/notification-general" + soundIndex + ".raw")); + } - if (isOnHMD) { - // Calculate 3D values from 2D overlay properties. + var notifications = []; + var buttons = []; + var times = []; + var heights = []; + var myAlpha = []; + var arrays = []; + var isOnHMD = false, + NOTIFICATIONS_3D_DIRECTION = 0.0, // Degrees from avatar orientation. + NOTIFICATIONS_3D_DISTANCE = 0.6, // Horizontal distance from avatar position. + NOTIFICATIONS_3D_ELEVATION = -0.8, // Height of top middle of top notification relative to avatar eyes. + NOTIFICATIONS_3D_YAW = 0.0, // Degrees relative to notifications direction. + NOTIFICATIONS_3D_PITCH = -60.0, // Degrees from vertical. + NOTIFICATION_3D_SCALE = 0.002, // Multiplier that converts 2D overlay dimensions to 3D overlay dimensions. + NOTIFICATION_3D_BUTTON_WIDTH = 40 * NOTIFICATION_3D_SCALE, // Need a little more room for button in 3D. + overlay3DDetails = []; - noticeWidth = notice.width * NOTIFICATION_3D_SCALE + NOTIFICATION_3D_BUTTON_WIDTH; - noticeHeight = notice.height * NOTIFICATION_3D_SCALE; + // push data from above to the 2 dimensional array + function createArrays(notice, button, createTime, height, myAlpha) { + arrays.push([notice, button, createTime, height, myAlpha]); + } - notice.size = { x: noticeWidth, y: noticeHeight }; - - positions = calculate3DOverlayPositions(noticeWidth, noticeHeight, notice.y); - - notice.parentID = MyAvatar.sessionUUID; - notice.parentJointIndex = -2; - - if (!image) { - notice.topMargin = 0.75 * notice.topMargin * NOTIFICATION_3D_SCALE; - notice.leftMargin = 2 * notice.leftMargin * NOTIFICATION_3D_SCALE; - notice.bottomMargin = 0; - notice.rightMargin = 0; - notice.lineHeight = 10.0 * (fontSize / 12.0) * NOTIFICATION_3D_SCALE; - notice.isFacingAvatar = false; - - notificationText = Overlays.addOverlay("text3d", notice); - notifications.push(notificationText); - } else { - notifications.push(Overlays.addOverlay("image3d", notice)); + // This handles the final dismissal of a notification after fading + function dismiss(firstNoteOut, firstButOut, firstOut) { + if (firstNoteOut === lodTextID) { + lodTextID = false; } - button.url = button.imageURL; - button.scale = button.width * NOTIFICATION_3D_SCALE; - button.isFacingAvatar = false; - button.parentID = MyAvatar.sessionUUID; - button.parentJointIndex = -2; + Overlays.deleteOverlay(firstNoteOut); + Overlays.deleteOverlay(firstButOut); + notifications.splice(firstOut, 1); + buttons.splice(firstOut, 1); + times.splice(firstOut, 1); + heights.splice(firstOut, 1); + myAlpha.splice(firstOut, 1); + overlay3DDetails.splice(firstOut, 1); + } - buttons.push((Overlays.addOverlay("image3d", button))); - overlay3DDetails.push({ - notificationOrientation: positions.notificationOrientation, - notificationPosition: positions.notificationPosition, - buttonPosition: positions.buttonPosition, - width: noticeWidth, - height: noticeHeight - }); + function fadeIn(noticeIn, buttonIn) { + var q = 0, + qFade, + pauseTimer = null; + pauseTimer = Script.setInterval(function () { + q += 1; + qFade = q / 10.0; + Overlays.editOverlay(noticeIn, { alpha: qFade }); + Overlays.editOverlay(buttonIn, { alpha: qFade }); + if (q >= 9.0) { + Script.clearInterval(pauseTimer); + } + }, 10); + } - var defaultEyePosition, - avatarOrientation, - notificationPosition, + // this fades the notification ready for dismissal, and removes it from the arrays + function fadeOut(noticeOut, buttonOut, arraysOut) { + var r = 9.0, + rFade, + pauseTimer = null; + + pauseTimer = Script.setInterval(function () { + r -= 1; + rFade = Math.max(0.0, r / 10.0); + Overlays.editOverlay(noticeOut, { alpha: rFade }); + Overlays.editOverlay(buttonOut, { alpha: rFade }); + if (r <= 0) { + dismiss(noticeOut, buttonOut, arraysOut); + arrays.splice(arraysOut, 1); + ready = true; + Script.clearInterval(pauseTimer); + } + }, 20); + } + + function calculate3DOverlayPositions(noticeWidth, noticeHeight, y) { + // Calculates overlay positions and orientations in avatar coordinates. + var noticeY, + originOffset, notificationOrientation, + notificationPosition, buttonPosition; - if (isOnHMD && notifications.length > 0) { - // Update 3D overlays to maintain positions relative to avatar - defaultEyePosition = MyAvatar.getDefaultEyePosition(); - avatarOrientation = MyAvatar.orientation; + // Notification plane positions + noticeY = -y * NOTIFICATION_3D_SCALE - noticeHeight / 2; + notificationPosition = { x: 0, y: noticeY, z: 0 }; + buttonPosition = { x: (noticeWidth - NOTIFICATION_3D_BUTTON_WIDTH) / 2, y: noticeY, z: 0.001 }; - for (i = 0; i < notifications.length; i += 1) { - notificationPosition = Vec3.sum(defaultEyePosition, - Vec3.multiplyQbyV(avatarOrientation, - overlay3DDetails[i].notificationPosition)); - notificationOrientation = Quat.multiply(avatarOrientation, - overlay3DDetails[i].notificationOrientation); - buttonPosition = Vec3.sum(defaultEyePosition, - Vec3.multiplyQbyV(avatarOrientation, - overlay3DDetails[i].buttonPosition)); - Overlays.editOverlay(notifications[i], { position: notificationPosition, - rotation: notificationOrientation }); - Overlays.editOverlay(buttons[i], { position: buttonPosition, rotation: notificationOrientation }); - } - } + // Rotate plane + notificationOrientation = Quat.fromPitchYawRollDegrees(NOTIFICATIONS_3D_PITCH, + NOTIFICATIONS_3D_DIRECTION + NOTIFICATIONS_3D_YAW, 0); + notificationPosition = Vec3.multiplyQbyV(notificationOrientation, notificationPosition); + buttonPosition = Vec3.multiplyQbyV(notificationOrientation, buttonPosition); - } else { - if (!image) { - notificationText = Overlays.addOverlay("text", notice); - notifications.push((notificationText)); - } else { - notifications.push(Overlays.addOverlay("image", notice)); - } - buttons.push(Overlays.addOverlay("image", button)); + // Translate plane + originOffset = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, NOTIFICATIONS_3D_DIRECTION, 0), + { x: 0, y: 0, z: -NOTIFICATIONS_3D_DISTANCE }); + originOffset.y += NOTIFICATIONS_3D_ELEVATION; + notificationPosition = Vec3.sum(originOffset, notificationPosition); + buttonPosition = Vec3.sum(originOffset, buttonPosition); + + return { + notificationOrientation: notificationOrientation, + notificationPosition: notificationPosition, + buttonPosition: buttonPosition + }; } - height = height + 1.0; - heights.push(height); - times.push(new Date().getTime() / 1000); - last = notifications.length - 1; - myAlpha.push(notifications[last].alpha); - createArrays(notifications[last], buttons[last], times[last], heights[last], myAlpha[last]); - fadeIn(notifications[last], buttons[last]); + // Pushes data to each array and sets up data for 2nd dimension array + // to handle auxiliary data not carried by the overlay class + // specifically notification "heights", "times" of creation, and . + function notify(notice, button, height, imageProperties, image) { + var notificationText, + noticeWidth, + noticeHeight, + positions, + last; - if (imageProperties && !image) { - var imageHeight = notice.width / imageProperties.aspectRatio; - notice = { - x: notice.x, - y: notice.y + height, - width: notice.width, - height: imageHeight, - subImage: { x: 0, y: 0 }, + if (isOnHMD) { + // Calculate 3D values from 2D overlay properties. + + noticeWidth = notice.width * NOTIFICATION_3D_SCALE + NOTIFICATION_3D_BUTTON_WIDTH; + noticeHeight = notice.height * NOTIFICATION_3D_SCALE; + + notice.size = { x: noticeWidth, y: noticeHeight }; + + positions = calculate3DOverlayPositions(noticeWidth, noticeHeight, notice.y); + + notice.parentID = MyAvatar.sessionUUID; + notice.parentJointIndex = -2; + + if (!image) { + notice.topMargin = 0.75 * notice.topMargin * NOTIFICATION_3D_SCALE; + notice.leftMargin = 2 * notice.leftMargin * NOTIFICATION_3D_SCALE; + notice.bottomMargin = 0; + notice.rightMargin = 0; + notice.lineHeight = 10.0 * (fontSize / 12.0) * NOTIFICATION_3D_SCALE; + notice.isFacingAvatar = false; + + notificationText = Overlays.addOverlay("text3d", notice); + notifications.push(notificationText); + } else { + notifications.push(Overlays.addOverlay("image3d", notice)); + } + + button.url = button.imageURL; + button.scale = button.width * NOTIFICATION_3D_SCALE; + button.isFacingAvatar = false; + button.parentID = MyAvatar.sessionUUID; + button.parentJointIndex = -2; + + buttons.push((Overlays.addOverlay("image3d", button))); + overlay3DDetails.push({ + notificationOrientation: positions.notificationOrientation, + notificationPosition: positions.notificationPosition, + buttonPosition: positions.buttonPosition, + width: noticeWidth, + height: noticeHeight + }); + + + var defaultEyePosition, + avatarOrientation, + notificationPosition, + notificationOrientation, + buttonPosition, + notificationIndex; + + if (isOnHMD && notifications.length > 0) { + // Update 3D overlays to maintain positions relative to avatar + defaultEyePosition = MyAvatar.getDefaultEyePosition(); + avatarOrientation = MyAvatar.orientation; + + for (notificationIndex = 0; notificationIndex < notifications.length; notificationIndex += 1) { + notificationPosition = Vec3.sum(defaultEyePosition, + Vec3.multiplyQbyV(avatarOrientation, + overlay3DDetails[notificationIndex].notificationPosition)); + notificationOrientation = Quat.multiply(avatarOrientation, + overlay3DDetails[notificationIndex].notificationOrientation); + buttonPosition = Vec3.sum(defaultEyePosition, + Vec3.multiplyQbyV(avatarOrientation, + overlay3DDetails[notificationIndex].buttonPosition)); + Overlays.editOverlay(notifications[notificationIndex], { position: notificationPosition, + rotation: notificationOrientation }); + Overlays.editOverlay(buttons[notificationIndex], { position: buttonPosition, rotation: notificationOrientation }); + } + } + + } else { + if (!image) { + notificationText = Overlays.addOverlay("text", notice); + notifications.push(notificationText); + } else { + notifications.push(Overlays.addOverlay("image", notice)); + } + buttons.push(Overlays.addOverlay("image", button)); + } + + height = height + 1.0; + heights.push(height); + times.push(new Date().getTime() / 1000); + last = notifications.length - 1; + myAlpha.push(notifications[last].alpha); + createArrays(notifications[last], buttons[last], times[last], heights[last], myAlpha[last]); + fadeIn(notifications[last], buttons[last]); + + if (imageProperties && !image) { + var imageHeight = notice.width / imageProperties.aspectRatio; + notice = { + x: notice.x, + y: notice.y + height, + width: notice.width, + height: imageHeight, + subImage: { x: 0, y: 0 }, + color: { red: 255, green: 255, blue: 255}, + visible: true, + imageURL: imageProperties.path, + alpha: backgroundAlpha + }; + notify(notice, button, imageHeight, imageProperties, true); + } + + return notificationText; + } + + var CLOSE_NOTIFICATION_ICON = Script.resolvePath("assets/images/close-small-light.svg"); + + // This function creates and sizes the overlays + function createNotification(text, notificationType, imageProperties) { + var count = (text.match(/\n/g) || []).length, + breakPoint = 43.0, // length when new line is added + extraLine = 0, + breaks = 0, + height = 40.0, + stack = 0, + level, + noticeProperties, + bLevel, + buttonProperties, + i; + + if (text.length >= breakPoint) { + breaks = count; + } + extraLine = breaks * 16.0; + for (i = 0; i < heights.length; i += 1) { + stack = stack + heights[i]; + } + + level = (stack + 20.0); + height = height + extraLine; + + noticeProperties = { + x: overlayLocationX, + y: level, + width: width, + height: height, + color: textColor, + backgroundColor: backColor, + alpha: backgroundAlpha, + topMargin: topMargin, + leftMargin: leftMargin, + font: {size: fontSize}, + text: text + }; + + bLevel = level + 12.0; + buttonProperties = { + x: buttonLocationX, + y: bLevel, + width: 10.0, + height: 10.0, + subImage: { x: 0, y: 0, width: 10, height: 10 }, + imageURL: CLOSE_NOTIFICATION_ICON, color: { red: 255, green: 255, blue: 255}, visible: true, - imageURL: imageProperties.path, alpha: backgroundAlpha }; - notify(notice, button, imageHeight, imageProperties, true); + + if (Menu.isOptionChecked(PLAY_NOTIFICATION_SOUNDS_MENU_ITEM) && + Menu.isOptionChecked(NotificationType.getMenuString(notificationType))) { + randomSounds.playRandom(); + } + + return notify(noticeProperties, buttonProperties, height, imageProperties); } - return notificationText; -} - -var CLOSE_NOTIFICATION_ICON = Script.resolvePath("assets/images/close-small-light.svg"); - -// This function creates and sizes the overlays -function createNotification(text, notificationType, imageProperties) { - var count = (text.match(/\n/g) || []).length, - breakPoint = 43.0, // length when new line is added - extraLine = 0, - breaks = 0, - height = 40.0, - stack = 0, - level, - noticeProperties, - bLevel, - buttonProperties, - i; - - if (text.length >= breakPoint) { - breaks = count; - } - extraLine = breaks * 16.0; - for (i = 0; i < heights.length; i += 1) { - stack = stack + heights[i]; + function deleteNotification(index) { + var notificationTextID = notifications[index]; + if (notificationTextID === lodTextID) { + lodTextID = false; + } + Overlays.deleteOverlay(notificationTextID); + Overlays.deleteOverlay(buttons[index]); + notifications.splice(index, 1); + buttons.splice(index, 1); + times.splice(index, 1); + heights.splice(index, 1); + myAlpha.splice(index, 1); + overlay3DDetails.splice(index, 1); + arrays.splice(index, 1); } - level = (stack + 20.0); - height = height + extraLine; - noticeProperties = { - x: overlayLocationX, - y: level, - width: width, - height: height, - color: textColor, - backgroundColor: backColor, - alpha: backgroundAlpha, - topMargin: topMargin, - leftMargin: leftMargin, - font: {size: fontSize}, - text: text - }; - - bLevel = level + 12.0; - buttonProperties = { - x: buttonLocationX, - y: bLevel, - width: 10.0, - height: 10.0, - subImage: { x: 0, y: 0, width: 10, height: 10 }, - imageURL: CLOSE_NOTIFICATION_ICON, - color: { red: 255, green: 255, blue: 255}, - visible: true, - alpha: backgroundAlpha - }; - - if (Menu.isOptionChecked(PLAY_NOTIFICATION_SOUNDS_MENU_ITEM) && - Menu.isOptionChecked(NotificationType.getMenuString(notificationType))) { - randomSounds.playRandom(); - } - - return notify(noticeProperties, buttonProperties, height, imageProperties); -} - -function deleteNotification(index) { - var notificationTextID = notifications[index]; - if (notificationTextID == lodTextID) { - lodTextID = false; - } - Overlays.deleteOverlay(notificationTextID); - Overlays.deleteOverlay(buttons[index]); - notifications.splice(index, 1); - buttons.splice(index, 1); - times.splice(index, 1); - heights.splice(index, 1); - myAlpha.splice(index, 1); - overlay3DDetails.splice(index, 1); - arrays.splice(index, 1); -} - - -// Trims extra whitespace and breaks into lines of length no more than MAX_LENGTH, breaking at spaces. Trims extra whitespace. -var MAX_LENGTH = 42; -function wordWrap(string) { - var finishedLines = [], currentLine = ''; - string.split(/\s/).forEach(function (word) { - var tail = currentLine ? ' ' + word : word; - if ((currentLine.length + tail.length) <= MAX_LENGTH) { - currentLine += tail; - } else { + // Trims extra whitespace and breaks into lines of length no more than MAX_LENGTH, breaking at spaces. Trims extra whitespace. + var MAX_LENGTH = 42; + function wordWrap(string) { + var finishedLines = [], currentLine = ''; + string.split(/\s/).forEach(function (word) { + var tail = currentLine ? ' ' + word : word; + if ((currentLine.length + tail.length) <= MAX_LENGTH) { + currentLine += tail; + } else { + finishedLines.push(currentLine); + currentLine = word; + } + }); + if (currentLine) { finishedLines.push(currentLine); - currentLine = word; } - }); - if (currentLine) { - finishedLines.push(currentLine); - } - return finishedLines.join('\n'); -} - -function update() { - var nextOverlay, - noticeOut, - buttonOut, - arraysOut, - positions, - i, - j, - k; - - if (isOnHMD !== HMD.active) { - while (arrays.length > 0) { - deleteNotification(0); - } - isOnHMD = !isOnHMD; - persistTime = isOnHMD ? PERSIST_TIME_3D : PERSIST_TIME_2D; - return; + return finishedLines.join('\n'); } - frame += 1; - if ((frame % 60.0) === 0) { // only update once a second - locationY = 20.0; - for (i = 0; i < arrays.length; i += 1) { //repositions overlays as others fade - nextOverlay = Overlays.getOverlayAtPoint({ x: overlayLocationX, y: locationY }); - Overlays.editOverlay(notifications[i], { x: overlayLocationX, y: locationY }); - Overlays.editOverlay(buttons[i], { x: buttonLocationX, y: locationY + 12.0 }); - if (isOnHMD) { - positions = calculate3DOverlayPositions(overlay3DDetails[i].width, - overlay3DDetails[i].height, locationY); - overlay3DDetails[i].notificationOrientation = positions.notificationOrientation; - overlay3DDetails[i].notificationPosition = positions.notificationPosition; - overlay3DDetails[i].buttonPosition = positions.buttonPosition; + function update() { + var noticeOut, + buttonOut, + arraysOut, + positions, + i, + j, + k; + + if (isOnHMD !== HMD.active) { + while (arrays.length > 0) { + deleteNotification(0); } - locationY = locationY + arrays[i][3]; + isOnHMD = !isOnHMD; + persistTime = isOnHMD ? PERSIST_TIME_3D : PERSIST_TIME_2D; + return; } - } - // This checks the age of the notification and prepares to fade it after 9.0 seconds (var persistTime - 1) - for (i = 0; i < arrays.length; i += 1) { - if (ready) { - j = arrays[i][2]; - k = j + persistTime; - if (k < (new Date().getTime() / 1000)) { - ready = false; - noticeOut = arrays[i][0]; - buttonOut = arrays[i][1]; - arraysOut = i; - fadeOut(noticeOut, buttonOut, arraysOut); + frame += 1; + if ((frame % 60.0) === 0) { // only update once a second + locationY = 20.0; + for (i = 0; i < arrays.length; i += 1) { //repositions overlays as others fade + Overlays.editOverlay(notifications[i], { x: overlayLocationX, y: locationY }); + Overlays.editOverlay(buttons[i], { x: buttonLocationX, y: locationY + 12.0 }); + if (isOnHMD) { + positions = calculate3DOverlayPositions(overlay3DDetails[i].width, + overlay3DDetails[i].height, locationY); + overlay3DDetails[i].notificationOrientation = positions.notificationOrientation; + overlay3DDetails[i].notificationPosition = positions.notificationPosition; + overlay3DDetails[i].buttonPosition = positions.buttonPosition; + } + locationY = locationY + arrays[i][3]; + } + } + + // This checks the age of the notification and prepares to fade it after 9.0 seconds (var persistTime - 1) + for (i = 0; i < arrays.length; i += 1) { + if (ready) { + j = arrays[i][2]; + k = j + persistTime; + if (k < (new Date().getTime() / 1000)) { + ready = false; + noticeOut = arrays[i][0]; + buttonOut = arrays[i][1]; + arraysOut = i; + fadeOut(noticeOut, buttonOut, arraysOut); + } } } } -} -var STARTUP_TIMEOUT = 500, // ms - startingUp = true, - startupTimer = null; + var STARTUP_TIMEOUT = 500, // ms + startingUp = true, + startupTimer = null; -function finishStartup() { - startingUp = false; - Script.clearTimeout(startupTimer); -} + function finishStartup() { + startingUp = false; + Script.clearTimeout(startupTimer); + } -function isStartingUp() { - // Is starting up until get no checks that it is starting up for STARTUP_TIMEOUT - if (startingUp) { - if (startupTimer) { - Script.clearTimeout(startupTimer); + function isStartingUp() { + // Is starting up until get no checks that it is starting up for STARTUP_TIMEOUT + if (startingUp) { + if (startupTimer) { + Script.clearTimeout(startupTimer); + } + startupTimer = Script.setTimeout(finishStartup, STARTUP_TIMEOUT); } - startupTimer = Script.setTimeout(finishStartup, STARTUP_TIMEOUT); - } - return startingUp; -} - -function onDomainConnectionRefused(reason) { - createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED); -} - -function onEditError(msg) { - createNotification(wordWrap(msg), NotificationType.EDIT_ERROR); -} - -function onNotify(msg) { - createNotification(wordWrap(msg), NotificationType.UNKNOWN); // Needs a generic notification system for user feedback, thus using this -} - -function onSnapshotTaken(pathStillSnapshot, notify) { - if (notify) { - var imageProperties = { - path: "file:///" + pathStillSnapshot, - aspectRatio: Window.innerWidth / Window.innerHeight - }; - createNotification(wordWrap("Snapshot saved to " + pathStillSnapshot), NotificationType.SNAPSHOT, imageProperties); - } -} - -function tabletNotification() { - createNotification("Tablet needs your attention", NotificationType.TABLET); -} - -function processingGif() { - createNotification("Processing GIF snapshot...", NotificationType.SNAPSHOT); -} - -function connectionAdded(connectionName) { - createNotification(connectionName, NotificationType.CONNECTION); -} - -function connectionError(error) { - createNotification(wordWrap("Error trying to make connection: " + error), NotificationType.CONNECTION); -} - -// handles mouse clicks on buttons -function mousePressEvent(event) { - var pickRay, - clickedOverlay, - i; - - if (isOnHMD) { - pickRay = Camera.computePickRay(event.x, event.y); - clickedOverlay = Overlays.findRayIntersection(pickRay).overlayID; - } else { - clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + return startingUp; } - for (i = 0; i < buttons.length; i += 1) { - if (clickedOverlay === buttons[i]) { - deleteNotification(i); + function onDomainConnectionRefused(reason) { + createNotification("Connection refused: " + reason, NotificationType.CONNECTION_REFUSED); + } + + function onEditError(msg) { + createNotification(wordWrap(msg), NotificationType.EDIT_ERROR); + } + + function onNotify(msg) { + createNotification(wordWrap(msg), NotificationType.UNKNOWN); // Needs a generic notification system for user feedback, thus using this + } + + function onSnapshotTaken(pathStillSnapshot, notify) { + if (notify) { + var imageProperties = { + path: "file:///" + pathStillSnapshot, + aspectRatio: Window.innerWidth / Window.innerHeight + }; + createNotification(wordWrap("Snapshot saved to " + pathStillSnapshot), NotificationType.SNAPSHOT, imageProperties); } } -} -// Control key remains active only while key is held down -function keyReleaseEvent(key) { - if (key.key === 16777249) { - ctrlIsPressed = false; + function tabletNotification() { + createNotification("Tablet needs your attention", NotificationType.TABLET); } -} -// Triggers notification on specific key driven events -function keyPressEvent(key) { - if (key.key === 16777249) { - ctrlIsPressed = true; + function processingGif() { + createNotification("Processing GIF snapshot...", NotificationType.SNAPSHOT); } -} -function setup() { - Menu.addMenu(MENU_NAME); - var checked = Settings.getValue(PLAY_NOTIFICATION_SOUNDS_SETTING); - checked = checked === '' ? true : checked; - Menu.addMenuItem({ - menuName: MENU_NAME, - menuItemName: PLAY_NOTIFICATION_SOUNDS_MENU_ITEM, - isCheckable: true, - isChecked: Settings.getValue(PLAY_NOTIFICATION_SOUNDS_SETTING) - }); - Menu.addSeparator(MENU_NAME, "Play sounds for:"); - for (var type in NotificationType.properties) { - checked = Settings.getValue(PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE + (parseInt(type) + 1)); + function connectionAdded(connectionName) { + createNotification(connectionName, NotificationType.CONNECTION); + } + + function connectionError(error) { + createNotification(wordWrap("Error trying to make connection: " + error), NotificationType.CONNECTION); + } + + // handles mouse clicks on buttons + function mousePressEvent(event) { + var pickRay, + clickedOverlay, + i; + + if (isOnHMD) { + pickRay = Camera.computePickRay(event.x, event.y); + clickedOverlay = Overlays.findRayIntersection(pickRay).overlayID; + } else { + clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + } + + for (i = 0; i < buttons.length; i += 1) { + if (clickedOverlay === buttons[i]) { + deleteNotification(i); + } + } + } + + // Control key remains active only while key is held down + function keyReleaseEvent(key) { + if (key.key === 16777249) { + ctrlIsPressed = false; + } + } + + // Triggers notification on specific key driven events + function keyPressEvent(key) { + if (key.key === 16777249) { + ctrlIsPressed = true; + } + } + + function setup() { + var type; + Menu.addMenu(MENU_NAME); + var checked = Settings.getValue(PLAY_NOTIFICATION_SOUNDS_SETTING); checked = checked === '' ? true : checked; Menu.addMenuItem({ menuName: MENU_NAME, - menuItemName: NotificationType.properties[type].text + NOTIFICATION_MENU_ITEM_POST, + menuItemName: PLAY_NOTIFICATION_SOUNDS_MENU_ITEM, isCheckable: true, - isChecked: checked + isChecked: Settings.getValue(PLAY_NOTIFICATION_SOUNDS_SETTING) }); + Menu.addSeparator(MENU_NAME, "Play sounds for:"); + for (type in NotificationType.properties) { + checked = Settings.getValue(PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE + (parseInt(type, 10) + 1)); + checked = checked === '' ? true : checked; + Menu.addMenuItem({ + menuName: MENU_NAME, + menuItemName: NotificationType.properties[type].text + NOTIFICATION_MENU_ITEM_POST, + isCheckable: true, + isChecked: checked + }); + } } -} -// When our script shuts down, we should clean up all of our overlays -function scriptEnding() { - for (var i = 0; i < notifications.length; i++) { - Overlays.deleteOverlay(notifications[i]); - Overlays.deleteOverlay(buttons[i]); + // When our script shuts down, we should clean up all of our overlays + function scriptEnding() { + var notificationIndex; + for (notificationIndex = 0; notificationIndex < notifications.length; notificationIndex++) { + Overlays.deleteOverlay(notifications[notificationIndex]); + Overlays.deleteOverlay(buttons[notificationIndex]); + } + Menu.removeMenu(MENU_NAME); } - Menu.removeMenu(MENU_NAME); -} -function menuItemEvent(menuItem) { - if (menuItem === PLAY_NOTIFICATION_SOUNDS_MENU_ITEM) { - Settings.setValue(PLAY_NOTIFICATION_SOUNDS_SETTING, Menu.isOptionChecked(PLAY_NOTIFICATION_SOUNDS_MENU_ITEM)); - return; + function menuItemEvent(menuItem) { + if (menuItem === PLAY_NOTIFICATION_SOUNDS_MENU_ITEM) { + Settings.setValue(PLAY_NOTIFICATION_SOUNDS_SETTING, Menu.isOptionChecked(PLAY_NOTIFICATION_SOUNDS_MENU_ITEM)); + return; + } + var notificationType = NotificationType.getTypeFromMenuItem(menuItem); + if (notificationType !== notificationType.UNKNOWN) { + Settings.setValue(PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE + notificationType, Menu.isOptionChecked(menuItem)); + } } - var notificationType = NotificationType.getTypeFromMenuItem(menuItem); - if (notificationType !== notificationType.UNKNOWN) { - Settings.setValue(PLAY_NOTIFICATION_SOUNDS_TYPE_SETTING_PRE + notificationType, Menu.isOptionChecked(menuItem)); - } -} -LODManager.LODDecreased.connect(function() { - var warningText = "\n" + - "Due to the complexity of the content, the \n" + - "level of detail has been decreased. " + - "You can now see: \n" + - LODManager.getLODFeedbackText(); + LODManager.LODDecreased.connect(function () { + var warningText = "\n" + + "Due to the complexity of the content, the \n" + + "level of detail has been decreased. " + + "You can now see: \n" + + LODManager.getLODFeedbackText(); - if (lodTextID === false) { - lodTextID = createNotification(warningText, NotificationType.LOD_WARNING); - } else { - Overlays.editOverlay(lodTextID, { text: warningText }); - } -}); + if (lodTextID === false) { + lodTextID = createNotification(warningText, NotificationType.LOD_WARNING); + } else { + Overlays.editOverlay(lodTextID, { text: warningText }); + } + }); -Controller.keyPressEvent.connect(keyPressEvent); -Controller.mousePressEvent.connect(mousePressEvent); -Controller.keyReleaseEvent.connect(keyReleaseEvent); -Script.update.connect(update); -Script.scriptEnding.connect(scriptEnding); -Menu.menuItemEvent.connect(menuItemEvent); -Window.domainConnectionRefused.connect(onDomainConnectionRefused); -Window.stillSnapshotTaken.connect(onSnapshotTaken); -Window.processingGifStarted.connect(processingGif); -Window.connectionAdded.connect(connectionAdded); -Window.connectionError.connect(connectionError); -Window.announcement.connect(onNotify); -Window.notifyEditError = onEditError; -Window.notify = onNotify; -Tablet.tabletNotification.connect(tabletNotification); -setup(); + Controller.keyPressEvent.connect(keyPressEvent); + Controller.mousePressEvent.connect(mousePressEvent); + Controller.keyReleaseEvent.connect(keyReleaseEvent); + Script.update.connect(update); + Script.scriptEnding.connect(scriptEnding); + Menu.menuItemEvent.connect(menuItemEvent); + Window.domainConnectionRefused.connect(onDomainConnectionRefused); + Window.stillSnapshotTaken.connect(onSnapshotTaken); + Window.processingGifStarted.connect(processingGif); + Window.connectionAdded.connect(connectionAdded); + Window.connectionError.connect(connectionError); + Window.announcement.connect(onNotify); + Window.notifyEditError = onEditError; + Window.notify = onNotify; + Tablet.tabletNotification.connect(tabletNotification); + setup(); }()); // END LOCAL_SCOPE diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 8235b69c90..8ba19d18a8 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -1,4 +1,6 @@ "use strict"; +/*jslint vars:true, plusplus:true, forin:true*/ +/*global Window, Script, Tablet, HMD, Controller, Account, XMLHttpRequest, location, print*/ // // goto.js @@ -11,35 +13,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -(function() { // BEGIN LOCAL_SCOPE +(function () { // BEGIN LOCAL_SCOPE var gotoQmlSource = "TabletAddressDialog.qml"; var buttonName = "GOTO"; var onGotoScreen = false; var shouldActivateButton = false; - - 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) { - if (url === gotoQmlSource) { - onGotoScreen = true; - shouldActivateButton = true; - button.editProperties({isActive: shouldActivateButton}); - messagesWaiting(false); - } else { - shouldActivateButton = false; - onGotoScreen = false; - button.editProperties({isActive: shouldActivateButton}); - } - } + function ignore() { } var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var NORMAL_ICON = "icons/tablet-icons/goto-i.svg"; @@ -58,10 +37,33 @@ }); } + 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); - var METAVERSE_BASE = location.metaverseServerUrl; 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. @@ -129,7 +131,7 @@ uri: url }, function (error, data) { if (error || (data.status !== 'success')) { - print("Error: unable to get", url, error || response.status); + print("Error: unable to get", url, error || data.status); return; } var didNotify = false; From c82bd67a1491fbf2adcbc25d1596c8c603bb43dc Mon Sep 17 00:00:00 2001 From: anshuman64 Date: Wed, 26 Apr 2017 09:07:40 -0700 Subject: [PATCH 040/134] Update BUILD_WIN.md --- BUILD_WIN.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/BUILD_WIN.md b/BUILD_WIN.md index e37bf27503..9df19e5525 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -12,9 +12,11 @@ Download and install the CMake 3.8.0-rc2 "win64-x64 Installer" from the [CMake W ###Step 3. Installing Qt -Download and install the [Qt 5.6.1 Installer](https://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-windows-x86-msvc2013_64-5.6.1-1.exe). Please note that the download file is large (850MB) and may take some time. +Download and install the [Qt Online Installer for Windows](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea#section-2). + +Select the following components on the installer: + -Make sure to select all components when going through the installer. ###Step 4. Setting Qt Environment Variable From 2d4ac0fc436b05a7452e847d2f387fe1f0a6dbb6 Mon Sep 17 00:00:00 2001 From: samcake Date: Wed, 26 Apr 2017 11:04:48 -0700 Subject: [PATCH 041/134] Bringing a fix to the ImageReader::read crash to stable --- .../model-networking/src/model-networking/TextureCache.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 98b03eba1e..b8d3a51534 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -559,9 +559,12 @@ void ImageReader::read() { // Load the image into a gpu::Texture auto networkTexture = resource.staticCast(); texture.reset(networkTexture->getTextureLoader()(image, url)); - texture->setSource(url); if (texture) { + texture->setSource(url); texture->setFallbackTexture(networkTexture->getFallbackTexture()); + } else { + qCDebug(modelnetworking) << _url << "loading stopped; texture wasn't created"; + return; } auto textureCache = DependencyManager::get(); From ef43ff4b99565a9fb4d485b5ece1d47536a3331f Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 26 Apr 2017 11:55:24 -0700 Subject: [PATCH 042/134] 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 ed6211fcb7bd4b0dc954abae13959593a146a89d Mon Sep 17 00:00:00 2001 From: Anshuman Dewangan Date: Wed, 26 Apr 2017 13:26:14 -0700 Subject: [PATCH 043/134] First commit --- BUILD_WIN.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 9df19e5525..c1ec381b52 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -12,11 +12,9 @@ Download and install the CMake 3.8.0-rc2 "win64-x64 Installer" from the [CMake W ###Step 3. Installing Qt -Download and install the [Qt Online Installer for Windows](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea#section-2). - -Select the following components on the installer: - +Download and install the [Qt 5.6.2 for Windows 64-bit (VS 2013)](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea#section-2). +Make sure to select all components when going through the installer. ###Step 4. Setting Qt Environment Variable From c13f63a4f4c57b3c1c6f2cb7e4e9b0dc952608db Mon Sep 17 00:00:00 2001 From: Anshuman Dewangan Date: Wed, 26 Apr 2017 14:47:03 -0700 Subject: [PATCH 044/134] Changed references from Qt 5.6.1 to 5.6.2 --- BUILD.md | 8 ++++---- BUILD_OSX.md | 10 +++------- BUILD_WIN.md | 6 +++--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/BUILD.md b/BUILD.md index 547b79cb08..fc2359b057 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,7 +1,7 @@ ###Dependencies * [cmake](https://cmake.org/download/) ~> 3.3.2 -* [Qt](https://www.qt.io/download-open-source) ~> 5.6.1 +* [Qt](https://www.qt.io/download-open-source) ~> 5.6.2 * [OpenSSL](https://www.openssl.org/community/binaries.html) * IMPORTANT: Use the latest available version of OpenSSL to avoid security vulnerabilities. * [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional) @@ -46,8 +46,8 @@ This can either be entered directly into your shell session before you build or The path it needs to be set to will depend on where and how Qt5 was installed. e.g. - export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.1/clang_64/lib/cmake/ - export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.1-1/lib/cmake + export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.2/clang_64/lib/cmake/ + export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake ####Generating build files @@ -64,7 +64,7 @@ Any variables that need to be set for CMake to find dependencies can be set as E For example, to pass the QT_CMAKE_PREFIX_PATH variable during build file generation: - cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.1/lib/cmake + cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.2/lib/cmake ####Finding Dependencies diff --git a/BUILD_OSX.md b/BUILD_OSX.md index 980263cbbc..fceddf0198 100644 --- a/BUILD_OSX.md +++ b/BUILD_OSX.md @@ -16,16 +16,12 @@ For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR: Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change. ###Qt -You can use the online installer or the offline installer. +Download and install the [Qt 5.6.2 for macOS](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea#section-2). -* [Download the online installer](https://www.qt.io/download-open-source/#section-2) - * When it asks you to select components, select the following: - * Qt > Qt 5.6 - -* [Download the offline installer](https://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-mac-x64-clang-5.6.1-1.dmg) +Keep the default components checked when going through the installer. Once Qt is installed, you need to manually configure the following: -* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt5.6.1/5.6/clang_64/lib/cmake/` directory. +* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt5.6.2/5.6/clang_64/lib/cmake/` directory. ###Xcode If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles. diff --git a/BUILD_WIN.md b/BUILD_WIN.md index c1ec381b52..0da05e4101 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -14,13 +14,13 @@ Download and install the CMake 3.8.0-rc2 "win64-x64 Installer" from the [CMake W Download and install the [Qt 5.6.2 for Windows 64-bit (VS 2013)](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea#section-2). -Make sure to select all components when going through the installer. +Keep the default components checked when going through the installer. ###Step 4. Setting Qt Environment Variable Go to "Control Panel > System > Advanced System Settings > Environment Variables > New..." (or search “Environment Variables” in Start Search). * Set "Variable name": QT_CMAKE_PREFIX_PATH -* Set "Variable value": `C:\Qt\Qt5.6.1\5.6\msvc2013_64\lib\cmake` +* Set "Variable value": `C:\Qt\Qt5.6.2\5.6\msvc2013_64\lib\cmake` ###Step 5. Installing OpenSSL @@ -77,5 +77,5 @@ If not, add the directory where nmake is located to the PATH environment variabl ####Qt is throwing an error -Make sure you have the correct version (5.6.1-1) installed and 'QT_CMAKE_PREFIX_PATH' environment variable is set correctly. +Make sure you have the correct version (5.6.2) installed and 'QT_CMAKE_PREFIX_PATH' environment variable is set correctly. From 54e4eb5eae364c994a9c92c8b441c90738484a6e Mon Sep 17 00:00:00 2001 From: Anshuman Dewangan Date: Wed, 26 Apr 2017 15:17:34 -0700 Subject: [PATCH 045/134] Updated downloads to direct links --- BUILD_OSX.md | 2 +- BUILD_WIN.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/BUILD_OSX.md b/BUILD_OSX.md index fceddf0198..afd3fa040c 100644 --- a/BUILD_OSX.md +++ b/BUILD_OSX.md @@ -16,7 +16,7 @@ For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR: Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change. ###Qt -Download and install the [Qt 5.6.2 for macOS](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea#section-2). +Download and install the [Qt 5.6.2 for macOS](http://download.qt.io/official_releases/qt/5.6/5.6.2/qt-opensource-mac-x64-clang-5.6.2.dmg). Keep the default components checked when going through the installer. diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 0da05e4101..54e84bb95c 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -8,11 +8,11 @@ Note: Newer versions of Visual Studio are not yet compatible. ###Step 2. Installing CMake -Download and install the CMake 3.8.0-rc2 "win64-x64 Installer" from the [CMake Website](https://cmake.org/download/). Make sure "Add CMake to system PATH for all users" is checked when going through the installer. +Download and install the [CMake 3.8.0 win64-x64 Installer](https://cmake.org/files/v3.8/cmake-3.8.0-win64-x64.msi). Make sure "Add CMake to system PATH for all users" is checked when going through the installer. ###Step 3. Installing Qt -Download and install the [Qt 5.6.2 for Windows 64-bit (VS 2013)](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea#section-2). +Download and install the [Qt 5.6.2 for Windows 64-bit (VS 2013)](http://download.qt.io/official_releases/qt/5.6/5.6.2/qt-opensource-windows-x86-msvc2013_64-5.6.2.exe). Keep the default components checked when going through the installer. @@ -24,7 +24,7 @@ Go to "Control Panel > System > Advanced System Settings > Environment Variables ###Step 5. Installing OpenSSL -Download and install the "Win64 OpenSSL v1.0.2k" Installer from [this website](https://slproweb.com/products/Win32OpenSSL.html). +Download and install the [Win64 OpenSSL v1.0.2k Installer](https://slproweb.com/download/Win64OpenSSL-1_0_2k.exe). ###Step 6. Running CMake to Generate Build Files From f795027ab70ae1141a21c7279d96bb9e376256a8 Mon Sep 17 00:00:00 2001 From: Triplelexx Date: Thu, 27 Apr 2017 01:09:04 +0100 Subject: [PATCH 046/134] change menu item locations AudioNoiseReduction moved under Audio. Avatar Collision under Avatar. --- interface/src/Menu.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index c99178d8cc..8754951317 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -156,6 +156,8 @@ Menu::Menu() { // Audio > Show Level Meter addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::AudioTools, 0, false); + addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::AudioNoiseReduction, 0, true, + audioIO.data(), SLOT(toggleAudioNoiseReduction())); // Avatar menu ---------------------------------- MenuWrapper* avatarMenu = addMenu("Avatar"); @@ -196,6 +198,9 @@ Menu::Menu() { 0, // QML Qt::Key_Apostrophe, qApp, SLOT(resetSensors())); + addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::EnableCharacterController, 0, true, + avatar.get(), SLOT(updateMotionBehaviorFromMenu())); + // Avatar > AvatarBookmarks related menus -- Note: the AvatarBookmarks class adds its own submenus here. auto avatarBookmarks = DependencyManager::get(); avatarBookmarks->setupMenus(this, avatarMenu); @@ -532,10 +537,6 @@ Menu::Menu() { avatar.get(), SLOT(updateMotionBehaviorFromMenu()), UNSPECIFIED_POSITION, "Developer"); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableCharacterController, 0, true, - avatar.get(), SLOT(updateMotionBehaviorFromMenu()), - UNSPECIFIED_POSITION, "Developer"); - // Developer > Hands >>> MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands"); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false, @@ -622,8 +623,6 @@ Menu::Menu() { QString("../../hifi/tablet/TabletAudioPreferences.qml"), "AudioPreferencesDialog"); }); - addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction, 0, true, - audioIO.data(), SLOT(toggleAudioNoiseReduction())); addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoServerAudio, 0, false, audioIO.data(), SLOT(toggleServerEcho())); addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoLocalAudio, 0, false, From f9c6cb27f4466905042c5b4c2b44a6d1a62405ef Mon Sep 17 00:00:00 2001 From: Triplelexx Date: Thu, 27 Apr 2017 03:16:04 +0100 Subject: [PATCH 047/134] change menu item for avatar collisions to "Collide with world" --- interface/src/Menu.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Menu.h b/interface/src/Menu.h index b6f70f5339..dd87a0cee3 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -96,7 +96,7 @@ namespace MenuOption { const QString DontRenderEntitiesAsScene = "Don't Render Entities as Scene"; const QString EchoLocalAudio = "Echo Local Audio"; const QString EchoServerAudio = "Echo Server Audio"; - const QString EnableCharacterController = "Enable avatar collisions"; + const QString EnableCharacterController = "Collide with world"; const QString EnableInverseKinematics = "Enable Inverse Kinematics"; const QString EntityScriptServerLog = "Entity Script Server Log"; const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation"; From a154879001208205daa81406d8a36905333f08fe Mon Sep 17 00:00:00 2001 From: Triplelexx Date: Thu, 27 Apr 2017 03:25:24 +0100 Subject: [PATCH 048/134] change menu item text Audio Noise Reduction to Noise Reduction --- interface/src/Menu.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Menu.h b/interface/src/Menu.h index dd87a0cee3..eeffcac7ca 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -36,7 +36,7 @@ namespace MenuOption { const QString AssetMigration = "ATP Asset Migration"; const QString AssetServer = "Asset Browser"; const QString Attachments = "Attachments..."; - const QString AudioNoiseReduction = "Audio Noise Reduction"; + const QString AudioNoiseReduction = "Noise Reduction"; const QString AudioScope = "Show Scope"; const QString AudioScopeFiftyFrames = "Fifty"; const QString AudioScopeFiveFrames = "Five"; From 0d53b823261b0a9077d63fef23e3d12962f61de2 Mon Sep 17 00:00:00 2001 From: Vladyslav Stelmakhovskyi Date: Thu, 27 Apr 2017 09:30:00 +0200 Subject: [PATCH 049/134] Renamed EDIT to CREATE --- scripts/system/edit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 6fabeb2ec6..6fae4df64e 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -343,7 +343,7 @@ var toolBar = (function () { activeButton = tablet.addButton({ icon: "icons/tablet-icons/edit-i.svg", activeIcon: "icons/tablet-icons/edit-a.svg", - text: "EDIT", + text: "CREATE", sortOrder: 10 }); tablet.screenChanged.connect(function (type, url) { From 1916eafc41b658e535c4bc2a8dff7e4a06019aa7 Mon Sep 17 00:00:00 2001 From: Vladyslav Stelmakhovskyi Date: Thu, 27 Apr 2017 16:02:47 +0200 Subject: [PATCH 050/134] Trigger rebuild --- scripts/system/edit.js | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 6fae4df64e..a6d2d165f7 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -2094,3 +2094,4 @@ entityListTool.webView.webEventReceived.connect(function (data) { }); }()); // END LOCAL_SCOPE + From 521db82ed0ddc237e9e6a6608b31a6c5877175be Mon Sep 17 00:00:00 2001 From: Anshuman Dewangan Date: Thu, 27 Apr 2017 10:56:56 -0700 Subject: [PATCH 051/134] Use generic dir syntax --- BUILD_WIN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 54e84bb95c..841cfba3b3 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -20,7 +20,7 @@ Keep the default components checked when going through the installer. Go to "Control Panel > System > Advanced System Settings > Environment Variables > New..." (or search “Environment Variables” in Start Search). * Set "Variable name": QT_CMAKE_PREFIX_PATH -* Set "Variable value": `C:\Qt\Qt5.6.2\5.6\msvc2013_64\lib\cmake` +* Set "Variable value": `%QT_DIR%\5.6\msvc2013_64\lib\cmake` ###Step 5. Installing OpenSSL From 63468cc9e1483d2c92ea1547fb6b763d3d0819de Mon Sep 17 00:00:00 2001 From: David Kelly Date: Thu, 27 Apr 2017 12:20:56 -0700 Subject: [PATCH 052/134] Thread safety for sending location --- interface/src/DiscoverabilityManager.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp index 98bfa9c0c7..36f6d8633e 100644 --- a/interface/src/DiscoverabilityManager.cpp +++ b/interface/src/DiscoverabilityManager.cpp @@ -23,6 +23,8 @@ #include "DiscoverabilityManager.h" #include "Menu.h" +#include + const Discoverability::Mode DEFAULT_DISCOVERABILITY_MODE = Discoverability::Friends; DiscoverabilityManager::DiscoverabilityManager() : @@ -37,6 +39,13 @@ const QString API_USER_HEARTBEAT_PATH = "/api/v1/user/heartbeat"; const QString SESSION_ID_KEY = "session_id"; void DiscoverabilityManager::updateLocation() { + // since we store the last location and compare it to + // the current one in this function, we need to do this in + // the object's main thread (or use a mutex) + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "updateLocation"); + return; + } auto accountManager = DependencyManager::get(); auto addressManager = DependencyManager::get(); auto& domainHandler = DependencyManager::get()->getDomainHandler(); @@ -143,7 +152,7 @@ void DiscoverabilityManager::removeLocation() { void DiscoverabilityManager::setDiscoverabilityMode(Discoverability::Mode discoverabilityMode) { if (static_cast(_mode.get()) != discoverabilityMode) { - + // update the setting to the new value _mode.set(static_cast(discoverabilityMode)); updateLocation(); // update right away From 56196c03a355beb7a32ea006de1b6ebbca789c06 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 27 Apr 2017 12:56:32 -0700 Subject: [PATCH 053/134] pr review --- interface/resources/qml/hifi/Card.qml | 2 +- interface/resources/qml/hifi/Feed.qml | 5 +++-- .../resources/qml/hifi/tablet/TabletAddressDialog.qml | 9 ++++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 1a3ae43692..59fa66af0b 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -130,7 +130,7 @@ Rectangle { property int dropSamples: 9; property int dropSpread: 0; DropShadow { - visible: showPlace && desktop.gradientsSupported; + visible: showPlace; // Do we have to check for whatever the modern equivalent is for desktop.gradientsSupported? source: place; anchors.fill: place; horizontalOffset: dropHorizontalOffset; diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index c02cd1dd36..d95518b891 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -31,6 +31,7 @@ Column { property string labelText: actions; property string filter: ''; onFilterChanged: filterChoicesByText(); + property var goFunction: null; HifiConstants { id: hifi } ListModel { id: suggestions; } @@ -57,7 +58,7 @@ Column { created_at: data.created_at || "", action: data.action || "", thumbnail_url: resolveUrl(thumbnail_url), - image_url: resolveUrl(data.details.image_url), + image_url: resolveUrl(data.details && data.details.image_url), metaverseId: (data.id || "").toString(), // Some are strings from server while others are numbers. Model objects require uniformity. @@ -181,7 +182,7 @@ Column { delegate: Card { width: cardWidth; height: cardHeight; - goFunction: goCard; // fixme global + goFunction: root.goFunction; userName: model.username; placeName: model.place_name; hifiUrl: model.place_name + model.path; diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index ef120b9c4f..6695ac08f6 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -242,7 +242,7 @@ StackView { height: stack.height; color: "transparent"; anchors { - left: bgMain.left; + left: parent.left; leftMargin: column.pad; topMargin: column.pad; } @@ -258,9 +258,10 @@ StackView { cardHeight: places.cardHeight * happeningNow.cardScale; metaverseServerUrl: addressBarDialog.metaverseServerUrl; labelText: 'Happening Now'; - //actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. - actions: 'announcement'; + actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. + //actions: 'announcement'; filter: addressLine.text; + goFunction: goCard; } Feed { id: places; @@ -269,6 +270,7 @@ StackView { labelText: 'Places'; actions: 'concurrency'; filter: addressLine.text; + goFunction: goCard; } Feed { id: snapshots; @@ -277,6 +279,7 @@ StackView { labelText: 'Recent Activity'; actions: 'snapshot'; filter: addressLine.text; + goFunction: goCard; } } } From e4f259e6303dd64da07b47631fd0a6a3ab8237ec Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 27 Apr 2017 13:01:33 -0700 Subject: [PATCH 054/134] back to non-fake data --- interface/resources/qml/hifi/tablet/TabletAddressDialog.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 6695ac08f6..5578b94168 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -258,8 +258,8 @@ StackView { cardHeight: places.cardHeight * happeningNow.cardScale; metaverseServerUrl: addressBarDialog.metaverseServerUrl; labelText: 'Happening Now'; - actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. - //actions: 'announcement'; + //actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. + actions: 'announcement'; filter: addressLine.text; goFunction: goCard; } From 0860352caacf4c7b60ddb491673516954c363f75 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 18 Apr 2017 14:44:24 -0700 Subject: [PATCH 055/134] Checkpoint before switching to crash duty --- scripts/system/html/SnapshotReview.html | 36 ++---- scripts/system/html/css/SnapshotReview.css | 140 +++++++++++++-------- scripts/system/html/css/hifi-style.css | 90 +++++++++++++ scripts/system/html/js/SnapshotReview.js | 6 + scripts/system/snapshot.js | 5 + 5 files changed, 200 insertions(+), 77 deletions(-) create mode 100644 scripts/system/html/css/hifi-style.css diff --git a/scripts/system/html/SnapshotReview.html b/scripts/system/html/SnapshotReview.html index 145cfb16a9..2a68217976 100644 --- a/scripts/system/html/SnapshotReview.html +++ b/scripts/system/html/SnapshotReview.html @@ -1,7 +1,7 @@ Share - + @@ -10,6 +10,9 @@
+ + +

@@ -17,29 +20,16 @@
-
-
-
- - - - -
-
-
- -
+
+
+
+ +
+ + +
-
-
- - - - - - - - +
diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 34b690a021..058fe141dd 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -9,40 +9,66 @@ */ body { - padding-top: 0; - padding-bottom: 14px; + padding: 0; } +/* +// START styling of top bar and its contents +*/ .snapsection { - padding-top: 14px; - text-align: center; + padding-top: 12px; } .snapsection.title { padding-top: 0; text-align: left; + height: 24px; + clear: both; } .title label { - font-size: 18px; position: relative; - top: 12px; + top: 10px; + font-size: 18px; + float: left; } +#snapshotSettings { + position: relative; + top: 4px; + float: right; +} +#settingsLabel { + position: relative; + float: right; + top: 12px; + font-family: Raleway-SemiBold; + font-size: 14px; +} +.hifi-glyph { + font-size: 30px; +} +input[type=button].naked { + color: #afafaf; + background: none; +} +input[type=button].naked:hover { + color: #ffffff; +} +input[type=button].naked:active { + color: #afafaf; +} +/* +// END styling of top bar and its contents +*/ + +/* +// START styling of snapshot pane and its contents +*/ #snapshot-pane { - width: 100%; - height: 100%; - position: absolute; - top: 0; - left: 0; - box-sizing: border-box; - padding-top: 56px; - padding-bottom: 175px; } #snapshot-images { - height: 100%; - width: 100%; position: relative; } @@ -77,23 +103,59 @@ body { #snapshot-images img.multiple { padding-left: 28px; } +/* +// END styling of snapshot pane and its contents +*/ +/* +// START styling of snapshot controls (bottom panel) and its contents +*/ #snapshot-controls { width: 100%; position: absolute; left: 0; bottom: 14px; + margin-bottom: 14px; + overflow: hidden; +} +#snap-settings { + float: left; + margin-left: 10px; +} +#snap-settings label { + margin-bottom: 50px; +} +#snap-settings form input { + margin-bottom: 50px; } +#snap-button { + width: 65px; + height: 65px; + border-radius: 50%; + background: #EA4C5F; + border: 3px solid white; + margin-left: auto; + margin-right: auto; +} +#snap-button:hover { + background: #C62147; +} +#snap-button:active { + background: #EA4C5F; +} +/* +// END styling of snapshot controls (bottom panel) and its contents +*/ + +/* +// START misc styling +*/ .prompt { font-family: Raleway-SemiBold; font-size: 14px; } -div.button { - padding-top: 21px; -} - .compound-button { position: relative; height: auto; @@ -103,7 +165,7 @@ div.button { padding-left: 40px; } -.compound-button .glyph { +.compound-button { display: inline-block; position: absolute; left: 12px; @@ -114,36 +176,6 @@ div.button { background-repeat: no-repeat; background-size: 23px 23px; } - -.setting { - display: inline-table; - height: 28px; -} - -.setting label { - display: table-cell; - vertical-align: middle; - font-family: Raleway-SemiBold; - font-size: 14px; -} - -.setting + .setting { - margin-left: 18px; -} - -input[type=button].naked { - font-size: 40px; - line-height: 40px; - width: 30px; - padding: 0; - margin: 0 0 -6px 0; - position: relative; - top: -6px; - left: -8px; - background: none; -} - -input[type=button].naked:hover { - color: #00b4ef; - background: none; -} +/* +// END misc styling +*/ diff --git a/scripts/system/html/css/hifi-style.css b/scripts/system/html/css/hifi-style.css new file mode 100644 index 0000000000..52d4c72b23 --- /dev/null +++ b/scripts/system/html/css/hifi-style.css @@ -0,0 +1,90 @@ +/* +// hifi-style.css +// +// Created by Zach Fox on 2017-04-18 +// 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 +*/ + +@font-face { + font-family: Raleway-Regular; + src: url(../../../../resources/fonts/Raleway-Regular.ttf), /* Windows production */ + url(../../../../fonts/Raleway-Regular.ttf), /* OSX production */ + url(../../../../interface/resources/fonts/Raleway-Regular.ttf); /* Development, running script in /HiFi/examples */ +} + +@font-face { + font-family: Raleway-Light; + src: url(../../../../resources/fonts/Raleway-Light.ttf), + url(../../../../fonts/Raleway-Light.ttf), + url(../../../../interface/resources/fonts/Raleway-Light.ttf); +} + +@font-face { + font-family: Raleway-Bold; + src: url(../../../../resources/fonts/Raleway-Bold.ttf), + url(../../../../fonts/Raleway-Bold.ttf), + url(../../../../interface/resources/fonts/Raleway-Bold.ttf); +} + +@font-face { + font-family: Raleway-SemiBold; + src: url(../../../../resources/fonts/Raleway-SemiBold.ttf), + url(../../../../fonts/Raleway-SemiBold.ttf), + url(../../../../interface/resources/fonts/Raleway-SemiBold.ttf); +} + +@font-face { + font-family: FiraSans-SemiBold; + src: url(../../../../resources/fonts/FiraSans-SemiBold.ttf), + url(../../../../fonts/FiraSans-SemiBold.ttf), + url(../../../../interface/resources/fonts/FiraSans-SemiBold.ttf); +} + +@font-face { + font-family: AnonymousPro-Regular; + src: url(../../../../resources/fonts/AnonymousPro-Regular.ttf), + url(../../../../fonts/AnonymousPro-Regular.ttf), + url(../../../../interface/resources/fonts/AnonymousPro-Regular.ttf); +} + +@font-face { + font-family: HiFi-Glyphs; + src: url(../../../../resources/fonts/hifi-glyphs.ttf), + url(../../../../fonts/hifi-glyphs.ttf), + url(../../../../interface/resources/fonts/hifi-glyphs.ttf); +} + +body { + color: #afafaf; + background-color: #404040; + font-family: Raleway-Regular; + font-size: 15px; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + overflow-x: hidden; + overflow-y: auto; +} + +hr { + border: none; + background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAjSURBVBhXY1RVVf3PgARYjIyMoEwIYHRwcEBRwQSloYCBAQCwjgPMiI7W2QAAAABJRU5ErkJggg==) repeat-x top left; + width: 100%; + margin: 21px -21px 0 -21px; + padding: 14px 21px 0 21px; +} + +.hifi-glyph { + font-family: HiFi-Glyphs; + border: none; + //margin: -10px; + padding: 0; +} diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index f140c54e09..7fec372c94 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -124,3 +124,9 @@ function snapshotSettings() { action: "openSettings" })); } +function takeSnapshot() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "takeSnapshot" + })); +} diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 6039bc09c1..47abf41021 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -75,6 +75,11 @@ function onMessage(message) { tablet.loadQMLOnTop("TabletGeneralPreferences.qml"); } break; + case 'takeSnapshot': + // In settings, first store the paths to the last snapshot + // + onClicked(); + break; case 'setOpenFeedFalse': Settings.setValue('openFeedAfterShare', false); break; From 7acdc866440db2c95744de2210d256796d6aa582 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 19 Apr 2017 13:55:47 -0700 Subject: [PATCH 056/134] Progress --- scripts/system/html/SnapshotReview.html | 2 +- scripts/system/html/css/SnapshotReview.css | 33 +------ scripts/system/html/js/SnapshotReview.js | 102 +++++++++++---------- scripts/system/snapshot.js | 7 ++ 4 files changed, 67 insertions(+), 77 deletions(-) diff --git a/scripts/system/html/SnapshotReview.html b/scripts/system/html/SnapshotReview.html index 2a68217976..1dff573014 100644 --- a/scripts/system/html/SnapshotReview.html +++ b/scripts/system/html/SnapshotReview.html @@ -23,7 +23,7 @@

- +
diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 058fe141dd..3985253a03 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -10,6 +10,7 @@ body { padding: 0; + margin: 0; } /* @@ -17,6 +18,7 @@ body { */ .snapsection { padding-top: 12px; + margin: 8px; } .snapsection.title { @@ -66,43 +68,18 @@ input[type=button].naked:active { // START styling of snapshot pane and its contents */ #snapshot-pane { + height: 510px; } #snapshot-images { position: relative; } -#snapshot-images > div { - position: relative; - text-align: center; -} - #snapshot-images img { max-width: 100%; max-height: 100%; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); vertical-align: middle; } - -#snapshot-images div.property { - margin-top: 0; - position: absolute; - top: 50%; - left: 7px; - transform: translate(0%, -50%); -} - -#snapshot-images img { - box-sizing: border-box; - padding: 0 7px 0 7px; -} - -#snapshot-images img.multiple { - padding-left: 28px; -} /* // END styling of snapshot pane and its contents */ @@ -123,10 +100,10 @@ input[type=button].naked:active { margin-left: 10px; } #snap-settings label { - margin-bottom: 50px; + height: 50px; } #snap-settings form input { - margin-bottom: 50px; + margin-bottom: 10px; } #snap-button { diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 7fec372c94..4b95507309 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -10,43 +10,44 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var paths = [], idCounter = 0, imageCount; +var paths = [], idCounter = 0, imageCount = 1; function addImage(data) { if (!data.localPath) { return; } - var div = document.createElement("DIV"), - input = document.createElement("INPUT"), - label = document.createElement("LABEL"), - img = document.createElement("IMG"), - div2 = document.createElement("DIV"), - id = "p" + idCounter++; - img.id = id + "img"; - function toggle() { data.share = input.checked; } + var div = document.createElement("DIV"); + var id = "p" + idCounter++; + var img = document.createElement("IMG"); + img.id = "img" + id; div.style.height = "" + Math.floor(100 / imageCount) + "%"; if (imageCount > 1) { img.setAttribute("class", "multiple"); } img.src = data.localPath; div.appendChild(img); - if (imageCount > 1) { // I'd rather use css, but the included stylesheet is quite particular. - // Our stylesheet(?) requires input.id to match label.for. Otherwise input doesn't display the check state. - label.setAttribute('for', id); // cannot do label.for = - input.id = id; - input.type = "checkbox"; - input.checked = false; - data.share = input.checked; - input.addEventListener('change', toggle); - div2.setAttribute("class", "property checkbox"); - div2.appendChild(input); - div2.appendChild(label); - div.appendChild(div2); - } else { - data.share = true; - } document.getElementById("snapshot-images").appendChild(div); paths.push(data); } +function handleCaptureSetting(setting) { + var stillAndGif = document.getElementById('stillAndGif'); + var stillOnly = document.getElementById('stillOnly'); + stillAndGif.checked = setting; + stillOnly.checked = !setting; + + stillAndGif.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "captureSettings", + action: true + })); + } + stillOnly.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "captureSettings", + action: false + })); + } + +} function handleShareButtons(messageOptions) { var openFeed = document.getElementById('openFeed'); openFeed.checked = messageOptions.openFeedAfterShare; @@ -66,36 +67,41 @@ function handleShareButtons(messageOptions) { window.onload = function () { // Something like the following will allow testing in a browser. //addImage({localPath: 'c:/Users/howar/OneDrive/Pictures/hifi-snap-by--on-2016-07-27_12-58-43.jpg'}); - //addImage({ localPath: 'http://lorempixel.com/1512/1680' }); + addImage({ localPath: 'http://lorempixel.com/1512/1680' }); openEventBridge(function () { // Set up a handler for receiving the data, and tell the .js we are ready to receive it. EventBridge.scriptEventReceived.connect(function (message) { + message = JSON.parse(message); - if (message.type !== "snapshot") { - return; - } - // The last element of the message contents list contains a bunch of options, - // including whether or not we can share stuff - // The other elements of the list contain image paths. - var messageOptions = message.action.pop(); - handleShareButtons(messageOptions); + switch (message.type) { + case 'snapshot': + // The last element of the message contents list contains a bunch of options, + // including whether or not we can share stuff + // The other elements of the list contain image paths. + var messageOptions = message.action.pop(); + handleShareButtons(messageOptions); - if (messageOptions.containsGif) { - if (messageOptions.processingGif) { - imageCount = message.action.length + 1; // "+1" for the GIF that'll finish processing soon - message.action.unshift({ localPath: messageOptions.loadingGifPath }); - message.action.forEach(addImage); - document.getElementById('p0').disabled = true; - } else { - var gifPath = message.action[0].localPath; - document.getElementById('p0').disabled = false; - document.getElementById('p0img').src = gifPath; - paths[0].localPath = gifPath; - } - } else { - imageCount = message.action.length; - message.action.forEach(addImage); + if (messageOptions.containsGif) { + if (messageOptions.processingGif) { + imageCount = message.action.length + 1; // "+1" for the GIF that'll finish processing soon + message.action.unshift({ localPath: messageOptions.loadingGifPath }); + message.action.forEach(addImage); + } else { + var gifPath = message.action[0].localPath; + document.getElementById('imgp0').src = gifPath; + paths[0].localPath = gifPath; + } + } else { + imageCount = message.action.length; + message.action.forEach(addImage); + } + break; + case 'snapshotSettings': + handleCaptureSetting(message.action); + break; + default: + return; } }); EventBridge.emitWebEvent(JSON.stringify({ diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 47abf41021..266f90c704 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -61,6 +61,10 @@ function onMessage(message) { var needsLogin = false; switch (message.action) { case 'ready': // Send it. + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshotSettings", + action: Settings.getValue("alsoTakeAnimatedSnapshot", true) + })); tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", action: readyData @@ -75,6 +79,9 @@ function onMessage(message) { tablet.loadQMLOnTop("TabletGeneralPreferences.qml"); } break; + case 'captureSettings': + Settings.setValue("alsoTakeAnimatedSnapshot", message.action); + break; case 'takeSnapshot': // In settings, first store the paths to the last snapshot // From a4f428a6e666239d5e6728ab637b2ad4bbcf9855 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 19 Apr 2017 15:27:10 -0700 Subject: [PATCH 057/134] Lots of progress --- scripts/system/html/js/SnapshotReview.js | 41 ++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 4b95507309..10d5b0eb92 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -19,15 +19,47 @@ function addImage(data) { var id = "p" + idCounter++; var img = document.createElement("IMG"); img.id = "img" + id; + div.style.width = "100%"; div.style.height = "" + Math.floor(100 / imageCount) + "%"; + div.style.display = "flex"; + div.style.justifyContent = "center"; + div.style.alignItems = "center"; + div.style.marginBottom = "5px"; + div.style.position = "relative"; if (imageCount > 1) { img.setAttribute("class", "multiple"); } img.src = data.localPath; div.appendChild(img); + div.appendChild(createShareOverlayDiv()); document.getElementById("snapshot-images").appendChild(div); paths.push(data); } +function createShareOverlayDiv() { + var div = document.createElement("DIV"); + div.style.position = "absolute"; + div.style.display = "flex"; + div.style.alignItems = "flex-end"; + div.style.top = "0px"; + div.style.left = "0px"; + div.style.width = "100%"; + div.style.height = "100%"; + + var shareBar = document.createElement("div"); + shareBar.style.backgroundColor = "black"; + shareBar.style.opacity = "0.5"; + shareBar.style.width = "100%"; + shareBar.style.height = "50px"; + div.appendChild(shareBar); + + var shareOverlay = document.createElement("div"); + shareOverlay.style.display = "none"; + shareOverlay.style.backgroundColor = "black"; + shareOverlay.style.opacity = "0.5"; + div.appendChild(shareOverlay); + + return div; +} function handleCaptureSetting(setting) { var stillAndGif = document.getElementById('stillAndGif'); var stillOnly = document.getElementById('stillOnly'); @@ -65,9 +97,14 @@ function handleShareButtons(messageOptions) { } } window.onload = function () { - // Something like the following will allow testing in a browser. - //addImage({localPath: 'c:/Users/howar/OneDrive/Pictures/hifi-snap-by--on-2016-07-27_12-58-43.jpg'}); + // TESTING FUNCTIONS START + // Uncomment and modify the lines below to test SnapshotReview in a browser. + imageCount = 2; addImage({ localPath: 'http://lorempixel.com/1512/1680' }); + addImage({ localPath: 'http://lorempixel.com/553/255' }); + //addImage({localPath: 'c:/Users/howar/OneDrive/Pictures/hifi-snap-by--on-2016-07-27_12-58-43.jpg'}); + // TESTING FUNCTIONS END + openEventBridge(function () { // Set up a handler for receiving the data, and tell the .js we are ready to receive it. EventBridge.scriptEventReceived.connect(function (message) { From 0b4cd41d7558c13b67ebadfb346c81f4a82752ad Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 19 Apr 2017 16:44:16 -0700 Subject: [PATCH 058/134] More and more progress! --- scripts/system/html/css/SnapshotReview.css | 38 ++++++++++++++++++++++ scripts/system/html/js/SnapshotReview.js | 33 ++++++++++++++----- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 3985253a03..636edd9756 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -80,6 +80,44 @@ input[type=button].naked:active { max-height: 100%; vertical-align: middle; } + +.gifLabel { + font-family: Raleway-SemiBold; + font-size: 18px; + color: white; + float: left; + text-shadow: 2px 2px 3px #000000; + margin-left: 20px; +} +.shareButtonDiv { + display: flex; + align-items: center; + font-family: Raleway-SemiBold; + font-size: 14px; + color: white; + float: right; + text-shadow: 2px 2px 3px #000000; + width: 100px; + height: 100%; + margin-right: 10px; +} +.shareButtonLabel { + vertical-align: middle; +} +.shareButton { + background-color: white; + width: 40px; + height: 40px; + border-radius: 50%; + border-width: 0; + margin-left: 5px; +} +.shareButton:hover { + background-color: #afafaf; +} +.shareButton:active { + background-color: white; +} /* // END styling of snapshot pane and its contents */ diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 10d5b0eb92..b0c53be502 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -18,7 +18,7 @@ function addImage(data) { var div = document.createElement("DIV"); var id = "p" + idCounter++; var img = document.createElement("IMG"); - img.id = "img" + id; + img.id = id + "img"; div.style.width = "100%"; div.style.height = "" + Math.floor(100 / imageCount) + "%"; div.style.display = "flex"; @@ -31,11 +31,16 @@ function addImage(data) { } img.src = data.localPath; div.appendChild(img); - div.appendChild(createShareOverlayDiv()); document.getElementById("snapshot-images").appendChild(div); + div.appendChild(createShareOverlayDiv(id, img.src.split('.').pop().toLowerCase() === "gif")); + img.onload = function () { + var shareBar = document.getElementById(id + "shareBar"); + shareBar.style.width = img.clientWidth; + shareBar.style.display = "inline"; + } paths.push(data); } -function createShareOverlayDiv() { +function createShareOverlayDiv(parentID, isGif) { var div = document.createElement("DIV"); div.style.position = "absolute"; div.style.display = "flex"; @@ -46,14 +51,24 @@ function createShareOverlayDiv() { div.style.height = "100%"; var shareBar = document.createElement("div"); - shareBar.style.backgroundColor = "black"; - shareBar.style.opacity = "0.5"; + shareBar.id = parentID + "shareBar" + shareBar.style.display = "none"; shareBar.style.width = "100%"; - shareBar.style.height = "50px"; + shareBar.style.height = "60px"; + shareBar.style.lineHeight = "60px"; + shareBar.style.clear = "both"; + shareBar.style.marginLeft = "auto"; + shareBar.style.marginRight = "auto"; + shareBar.innerHTML = isGif ? 'GIF' : ""; + var shareButtonID = parentID + "shareButton"; + shareBar.innerHTML += '
' + + '' + + '' + + '
' div.appendChild(shareBar); var shareOverlay = document.createElement("div"); - shareOverlay.style.display = "none"; + shareOverlay.style.visibilty = "hidden"; shareOverlay.style.backgroundColor = "black"; shareOverlay.style.opacity = "0.5"; div.appendChild(shareOverlay); @@ -100,7 +115,7 @@ window.onload = function () { // TESTING FUNCTIONS START // Uncomment and modify the lines below to test SnapshotReview in a browser. imageCount = 2; - addImage({ localPath: 'http://lorempixel.com/1512/1680' }); + addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/2017-01-27 50 Avatars!/!!!.gif' }); addImage({ localPath: 'http://lorempixel.com/553/255' }); //addImage({localPath: 'c:/Users/howar/OneDrive/Pictures/hifi-snap-by--on-2016-07-27_12-58-43.jpg'}); // TESTING FUNCTIONS END @@ -126,7 +141,7 @@ window.onload = function () { message.action.forEach(addImage); } else { var gifPath = message.action[0].localPath; - document.getElementById('imgp0').src = gifPath; + document.getElementById('p0img').src = gifPath; paths[0].localPath = gifPath; } } else { From 4270086a2250bc89cfbe9b65987a2bb1a1109251 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 19 Apr 2017 17:47:12 -0700 Subject: [PATCH 059/134] Support 1 and 2 images --- scripts/system/html/css/SnapshotReview.css | 13 ++++-- scripts/system/html/js/SnapshotReview.js | 52 +++++++++++++++------- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 636edd9756..9cbc129ab6 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -17,6 +17,8 @@ body { // START styling of top bar and its contents */ .snapsection { + padding-left: 8px; + padding-right: 8px; padding-top: 12px; margin: 8px; } @@ -68,17 +70,20 @@ input[type=button].naked:active { // START styling of snapshot pane and its contents */ #snapshot-pane { + width: 100%; height: 510px; + display: flex; + justify-content: center; + align-items: center; } #snapshot-images { - position: relative; + width: 100%; } #snapshot-images img { max-width: 100%; max-height: 100%; - vertical-align: middle; } .gifLabel { @@ -126,11 +131,13 @@ input[type=button].naked:active { // START styling of snapshot controls (bottom panel) and its contents */ #snapshot-controls { + padding-left: 8px; + padding-right: 8px; width: 100%; position: absolute; left: 0; bottom: 14px; - margin-bottom: 14px; + margin-bottom: 4px; overflow: hidden; } #snap-settings { diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index b0c53be502..3ebff3f687 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -18,13 +18,13 @@ function addImage(data) { var div = document.createElement("DIV"); var id = "p" + idCounter++; var img = document.createElement("IMG"); + div.id = id; img.id = id + "img"; div.style.width = "100%"; div.style.height = "" + Math.floor(100 / imageCount) + "%"; div.style.display = "flex"; div.style.justifyContent = "center"; div.style.alignItems = "center"; - div.style.marginBottom = "5px"; div.style.position = "relative"; if (imageCount > 1) { img.setAttribute("class", "multiple"); @@ -32,23 +32,26 @@ function addImage(data) { img.src = data.localPath; div.appendChild(img); document.getElementById("snapshot-images").appendChild(div); - div.appendChild(createShareOverlayDiv(id, img.src.split('.').pop().toLowerCase() === "gif")); + div.appendChild(createShareOverlay(id, img.src.split('.').pop().toLowerCase() === "gif")); img.onload = function () { var shareBar = document.getElementById(id + "shareBar"); shareBar.style.width = img.clientWidth; shareBar.style.display = "inline"; + + document.getElementById(id).style.height = img.clientHeight; } paths.push(data); } -function createShareOverlayDiv(parentID, isGif) { - var div = document.createElement("DIV"); - div.style.position = "absolute"; - div.style.display = "flex"; - div.style.alignItems = "flex-end"; - div.style.top = "0px"; - div.style.left = "0px"; - div.style.width = "100%"; - div.style.height = "100%"; +function createShareOverlay(parentID, isGif) { + var shareOverlayContainer = document.createElement("DIV"); + shareOverlayContainer.id = parentID + "shareOverlayContainer"; + shareOverlayContainer.style.position = "absolute"; + shareOverlayContainer.style.top = "0px"; + shareOverlayContainer.style.left = "0px"; + shareOverlayContainer.style.display = "flex"; + shareOverlayContainer.style.alignItems = "flex-end"; + shareOverlayContainer.style.width = "100%"; + shareOverlayContainer.style.height = "100%"; var shareBar = document.createElement("div"); shareBar.id = parentID + "shareBar" @@ -63,18 +66,35 @@ function createShareOverlayDiv(parentID, isGif) { var shareButtonID = parentID + "shareButton"; shareBar.innerHTML += '
' + '' + - '' + + '' + '
' - div.appendChild(shareBar); + shareOverlayContainer.appendChild(shareBar); var shareOverlay = document.createElement("div"); - shareOverlay.style.visibilty = "hidden"; + shareOverlay.id = parentID + "shareOverlay"; + shareOverlay.style.display = "none"; shareOverlay.style.backgroundColor = "black"; shareOverlay.style.opacity = "0.5"; - div.appendChild(shareOverlay); + shareOverlay.style.width = "100%"; + shareOverlay.style.height = "100%"; + shareOverlayContainer.appendChild(shareOverlay); - return div; + return shareOverlayContainer; } +function selectImageToShare(selectedID) { + selectedID = selectedID.id; // Why is this necessary? + var shareOverlayContainer = document.getElementById(selectedID + "shareOverlayContainer"); + var shareBar = document.getElementById(selectedID + "shareBar"); + var shareOverlay = document.getElementById(selectedID + "shareOverlay"); + + shareOverlayContainer.style.outline = "4px solid #00b4ef"; + shareOverlayContainer.style.outlineOffset = "-4px"; + + shareBar.style.display = "none"; + + shareOverlay.style.display = "inline"; +} + function handleCaptureSetting(setting) { var stillAndGif = document.getElementById('stillAndGif'); var stillOnly = document.getElementById('stillOnly'); From 2880b22f9fa28ecfadf5e47d39d60faede33af81 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 20 Apr 2017 14:00:28 -0700 Subject: [PATCH 060/134] External share buttons! --- scripts/system/html/SnapshotReview.html | 19 +++++++- scripts/system/html/css/SnapshotReview.css | 33 +++++++++++++ scripts/system/html/js/SnapshotReview.js | 54 ++++++++++++++++++++-- 3 files changed, 101 insertions(+), 5 deletions(-) diff --git a/scripts/system/html/SnapshotReview.html b/scripts/system/html/SnapshotReview.html index 1dff573014..3d8387890a 100644 --- a/scripts/system/html/SnapshotReview.html +++ b/scripts/system/html/SnapshotReview.html @@ -5,12 +5,29 @@ +
-
diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 9cbc129ab6..a0602b2575 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -123,6 +123,39 @@ input[type=button].naked:active { .shareButton:active { background-color: white; } + +/* +// START styling of share overlay +*/ +.shareOverlayDiv { + text-align: center; +} +.shareControls { + text-align: left; + display: flex; + justify-content: center; + flex-direction: row; + align-items: flex-start; + height: 50px; +} +.shareOverlayLabel { + line-height: 75px; +} +.hifiShareControls { + text-align: left; + width: 40%; + margin-left: 10%; +} +.externalShareControls { + text-align: left; + width: 40%; + margin-right: 10%; +} +.cancelShare { +} +/* +// END styling of share overlay +*/ /* // END styling of snapshot pane and its contents */ diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 3ebff3f687..21affd772b 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -70,14 +70,44 @@ function createShareOverlay(parentID, isGif) { '
' shareOverlayContainer.appendChild(shareBar); + var shareOverlayBackground = document.createElement("div"); + shareOverlayBackground.id = parentID + "shareOverlayBackground"; + shareOverlayBackground.style.display = "none"; + shareOverlayBackground.style.position = "absolute"; + shareOverlayBackground.style.zIndex = "1"; + shareOverlayBackground.style.top = "0px"; + shareOverlayBackground.style.left = "0px"; + shareOverlayBackground.style.backgroundColor = "black"; + shareOverlayBackground.style.opacity = "0.5"; + shareOverlayBackground.style.width = "100%"; + shareOverlayBackground.style.height = "100%"; + shareOverlayContainer.appendChild(shareOverlayBackground); + var shareOverlay = document.createElement("div"); shareOverlay.id = parentID + "shareOverlay"; + shareOverlay.className = "shareOverlayDiv"; shareOverlay.style.display = "none"; - shareOverlay.style.backgroundColor = "black"; - shareOverlay.style.opacity = "0.5"; shareOverlay.style.width = "100%"; shareOverlay.style.height = "100%"; + shareOverlay.style.zIndex = "2"; + var shareWithEveryoneButtonID = parentID + "shareWithEveryoneButton"; + var inviteConnectionsCheckboxID = parentID + "inviteConnectionsCheckbox"; + shareOverlay.innerHTML = '' + + '
' + + '
' + + '
' + + '
' + + '' + + '
' + + '' + + '
' + + '
' + + '' + + '' + + '
' + + '
'; shareOverlayContainer.appendChild(shareOverlay); + twttr.widgets.load(shareOverlay); return shareOverlayContainer; } @@ -85,15 +115,31 @@ function selectImageToShare(selectedID) { selectedID = selectedID.id; // Why is this necessary? var shareOverlayContainer = document.getElementById(selectedID + "shareOverlayContainer"); var shareBar = document.getElementById(selectedID + "shareBar"); + var shareOverlayBackground = document.getElementById(selectedID + "shareOverlayBackground"); var shareOverlay = document.getElementById(selectedID + "shareOverlay"); - shareOverlayContainer.style.outline = "4px solid #00b4ef"; - shareOverlayContainer.style.outlineOffset = "-4px"; + shareOverlay.style.outline = "4px solid #00b4ef"; + shareOverlay.style.outlineOffset = "-4px"; shareBar.style.display = "none"; + shareOverlayBackground.style.display = "inline"; shareOverlay.style.display = "inline"; } +function cancelSharing(selectedID) { + selectedID = selectedID.id; // Why is this necessary? + var shareOverlayContainer = document.getElementById(selectedID + "shareOverlayContainer"); + var shareBar = document.getElementById(selectedID + "shareBar"); + var shareOverlayBackground = document.getElementById(selectedID + "shareOverlayBackground"); + var shareOverlay = document.getElementById(selectedID + "shareOverlay"); + + shareOverlay.style.outline = "none"; + + shareBar.style.display = "inline"; + + shareOverlayBackground.style.display = "none"; + shareOverlay.style.display = "none"; +} function handleCaptureSetting(setting) { var stillAndGif = document.getElementById('stillAndGif'); From 16b4af8a9d8f581af5869f6d383511094d4a4be1 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 20 Apr 2017 16:25:21 -0700 Subject: [PATCH 061/134] Rearchitecture --- interface/src/Application.cpp | 2 +- scripts/system/html/SnapshotReview.html | 4 +- scripts/system/html/css/SnapshotReview.css | 25 ++---- scripts/system/html/css/hifi-style.css | 8 +- scripts/system/html/js/SnapshotReview.js | 63 ++++++------- scripts/system/snapshot.js | 100 +++++++++++---------- 6 files changed, 95 insertions(+), 107 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 39a4b8ee7c..d5e1d2201b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6448,7 +6448,7 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa // Get a screenshot and save it QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio)); // If we're not doing an animated snapshot as well... - if (!includeAnimated || !(SnapshotAnimated::alsoTakeAnimatedSnapshot.get())) { + if (!includeAnimated) { // Tell the dependency manager that the capture of the still snapshot has taken place. emit DependencyManager::get()->stillSnapshotTaken(path, notify); } else { diff --git a/scripts/system/html/SnapshotReview.html b/scripts/system/html/SnapshotReview.html index 3d8387890a..e785fde69d 100644 --- a/scripts/system/html/SnapshotReview.html +++ b/scripts/system/html/SnapshotReview.html @@ -26,7 +26,7 @@ -
+
@@ -40,7 +40,7 @@

- +
diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index a0602b2575..58866feef0 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -16,41 +16,34 @@ body { /* // START styling of top bar and its contents */ -.snapsection { - padding-left: 8px; - padding-right: 8px; - padding-top: 12px; - margin: 8px; -} -.snapsection.title { - padding-top: 0; +.title { + padding: 6px 10px; text-align: left; - height: 24px; + height: 20px; + line-height: 20px; clear: both; } .title label { position: relative; - top: 10px; font-size: 18px; float: left; } #snapshotSettings { position: relative; - top: 4px; float: right; } #settingsLabel { position: relative; float: right; - top: 12px; font-family: Raleway-SemiBold; font-size: 14px; } .hifi-glyph { font-size: 30px; + top: -7px; } input[type=button].naked { color: #afafaf; @@ -71,7 +64,7 @@ input[type=button].naked:active { */ #snapshot-pane { width: 100%; - height: 510px; + height: 574px; display: flex; justify-content: center; align-items: center; @@ -169,17 +162,13 @@ input[type=button].naked:active { width: 100%; position: absolute; left: 0; - bottom: 14px; - margin-bottom: 4px; + margin-top: 8px; overflow: hidden; } #snap-settings { float: left; margin-left: 10px; } -#snap-settings label { - height: 50px; -} #snap-settings form input { margin-bottom: 10px; } diff --git a/scripts/system/html/css/hifi-style.css b/scripts/system/html/css/hifi-style.css index 52d4c72b23..e95ceca4da 100644 --- a/scripts/system/html/css/hifi-style.css +++ b/scripts/system/html/css/hifi-style.css @@ -77,9 +77,13 @@ body { hr { border: none; background: #404040 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAjSURBVBhXY1RVVf3PgARYjIyMoEwIYHRwcEBRwQSloYCBAQCwjgPMiI7W2QAAAABJRU5ErkJggg==) repeat-x top left; + padding: 1px; + -webkit-margin-before: 0; + -webkit-margin-after: 0; + -webkit-margin-start: 0; + -webkit-margin-end: 0; width: 100%; - margin: 21px -21px 0 -21px; - padding: 14px 21px 0 21px; + position: absolute; } .hifi-glyph { diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 21affd772b..3346635a6a 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -149,40 +149,25 @@ function handleCaptureSetting(setting) { stillAndGif.onclick = function () { EventBridge.emitWebEvent(JSON.stringify({ - type: "captureSettings", - action: true + type: "snapshot", + action: "captureStillAndGif" })); } stillOnly.onclick = function () { - EventBridge.emitWebEvent(JSON.stringify({ - type: "captureSettings", - action: false - })); - } - -} -function handleShareButtons(messageOptions) { - var openFeed = document.getElementById('openFeed'); - openFeed.checked = messageOptions.openFeedAfterShare; - openFeed.onchange = function () { EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", - action: (openFeed.checked ? "setOpenFeedTrue" : "setOpenFeedFalse") + action: "captureStillOnly" })); - }; - - if (!messageOptions.canShare) { - // this means you may or may not be logged in, but can't share - // because you are not in a public place. - document.getElementById("sharing").innerHTML = "

Snapshots can be shared when they're taken in shareable places."; } + } window.onload = function () { // TESTING FUNCTIONS START // Uncomment and modify the lines below to test SnapshotReview in a browser. - imageCount = 2; - addImage({ localPath: 'D:/Dropbox/Screenshots/High Fidelity Snapshots/2017-01-27 50 Avatars!/!!!.gif' }); - addImage({ localPath: 'http://lorempixel.com/553/255' }); + //imageCount = 2; + //addImage({ localPath: 'C:/Users/Zach Fox/Desktop/hifi-snap-by-zfox-on-2017-04-20_14-59-12.gif' }); + //addImage({ localPath: 'C:/Users/Zach Fox/Desktop/hifi-snap-by-zfox-on-2017-04-20_14-59-12.jpg' }); + //addImage({ localPath: 'http://lorempixel.com/553/255' }); //addImage({localPath: 'c:/Users/howar/OneDrive/Pictures/hifi-snap-by--on-2016-07-27_12-58-43.jpg'}); // TESTING FUNCTIONS END @@ -192,36 +177,42 @@ window.onload = function () { message = JSON.parse(message); - switch (message.type) { - case 'snapshot': + if (message.type !== "snapshot") { + return; + } + + switch (message.action) { + case 'addImages': // The last element of the message contents list contains a bunch of options, // including whether or not we can share stuff // The other elements of the list contain image paths. - var messageOptions = message.action.pop(); - handleShareButtons(messageOptions); + var messageOptions = message.options; if (messageOptions.containsGif) { if (messageOptions.processingGif) { - imageCount = message.action.length + 1; // "+1" for the GIF that'll finish processing soon - message.action.unshift({ localPath: messageOptions.loadingGifPath }); - message.action.forEach(addImage); + imageCount = message.data.length + 1; // "+1" for the GIF that'll finish processing soon + message.data.unshift({ localPath: messageOptions.loadingGifPath }); + message.data.forEach(addImage); } else { - var gifPath = message.action[0].localPath; + var gifPath = message.data[0].localPath; document.getElementById('p0img').src = gifPath; paths[0].localPath = gifPath; } } else { - imageCount = message.action.length; - message.action.forEach(addImage); + imageCount = message.data.length; + message.data.forEach(addImage); } break; - case 'snapshotSettings': - handleCaptureSetting(message.action); + case 'captureSettings': + handleCaptureSetting(message.setting); break; default: - return; + print("Unknown message action received in SnapshotReview.js."); + break; } + }); + EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", action: "ready" diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 266f90c704..97cd2c542e 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -43,7 +43,8 @@ function showFeedWindow() { } var outstanding; -var readyData; +var snapshotOptions; +var imageData; var shareAfterLogin = false; var snapshotToShareAfterLogin; function onMessage(message) { @@ -62,12 +63,15 @@ function onMessage(message) { switch (message.action) { case 'ready': // Send it. tablet.emitScriptEvent(JSON.stringify({ - type: "snapshotSettings", - action: Settings.getValue("alsoTakeAnimatedSnapshot", true) + type: "snapshot", + action: "captureSettings", + setting: Settings.getValue("alsoTakeAnimatedSnapshot", true) })); tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", - action: readyData + action: "addImages", + options: snapshotOptions, + data: imageData })); outstanding = 0; break; @@ -79,20 +83,19 @@ function onMessage(message) { tablet.loadQMLOnTop("TabletGeneralPreferences.qml"); } break; - case 'captureSettings': - Settings.setValue("alsoTakeAnimatedSnapshot", message.action); + case 'captureStillAndGif': + print("Changing Snapshot Capture Settings to Capture Still + GIF"); + Settings.setValue("alsoTakeAnimatedSnapshot", true); + break; + case 'captureStillOnly': + print("Changing Snapshot Capture Settings to Capture Still Only"); + Settings.setValue("alsoTakeAnimatedSnapshot", false); break; case 'takeSnapshot': // In settings, first store the paths to the last snapshot // onClicked(); break; - case 'setOpenFeedFalse': - Settings.setValue('openFeedAfterShare', false); - break; - case 'setOpenFeedTrue': - Settings.setValue('openFeedAfterShare', true); - break; default: //tablet.webEventReceived.disconnect(onMessage); // <<< It's probably this that's missing?! HMD.closeTablet(); @@ -135,9 +138,8 @@ function onMessage(message) { var SNAPSHOT_REVIEW_URL = Script.resolvePath("html/SnapshotReview.html"); var isInSnapshotReview = false; -function confirmShare(data) { +function reviewSnapshot() { tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL); - readyData = data; tablet.webEventReceived.connect(onMessage); HMD.openTablet(); isInSnapshotReview = true; @@ -182,7 +184,7 @@ function onClicked() { Script.setTimeout(function () { HMD.closeTablet(); Script.setTimeout(function () { - Window.takeSnapshot(false, true, 1.91); + Window.takeSnapshot(false, Settings.getValue("alsoTakeAnimatedSnapshot", true), 1.91); }, SNAPSHOT_DELAY); }, FINISH_SOUND_DELAY); } @@ -220,15 +222,14 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { // during which time the user may have moved. So stash that info in the dialog so that // it records the correct href. (We can also stash in .jpegs, but not .gifs.) // last element in data array tells dialog whether we can share or not - var confirmShareContents = [ - { localPath: pathStillSnapshot, href: href }, - { - containsGif: false, - processingGif: false, - canShare: !!isDomainOpen(domainId), - openFeedAfterShare: shouldOpenFeedAfterShare() - }]; - confirmShare(confirmShareContents); + snapshotOptions = { + containsGif: false, + processingGif: false, + canShare: !!isDomainOpen(domainId), + openFeedAfterShare: shouldOpenFeedAfterShare() + }; + imageData = [{ localPath: pathStillSnapshot, href: href }]; + reviewSnapshot(); if (clearOverlayWhenMoving) { MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog } @@ -237,8 +238,10 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { function processingGifStarted(pathStillSnapshot) { Window.processingGifStarted.disconnect(processingGifStarted); - button.clicked.disconnect(onClicked); - buttonConnected = false; + if (buttonConnected) { + button.clicked.disconnect(onClicked); + buttonConnected = false; + } // show hud Reticle.visible = reticleVisible; // show overlays if they were on @@ -246,16 +249,15 @@ function processingGifStarted(pathStillSnapshot) { Menu.setIsOptionChecked("Overlays", true); } - var confirmShareContents = [ - { localPath: pathStillSnapshot, href: href }, - { - containsGif: true, - processingGif: true, - loadingGifPath: Script.resolvePath(Script.resourcesPath() + 'icons/loadingDark.gif'), - canShare: !!isDomainOpen(domainId), - openFeedAfterShare: shouldOpenFeedAfterShare() - }]; - confirmShare(confirmShareContents); + snapshotOptions = { + containsGif: true, + processingGif: true, + loadingGifPath: Script.resolvePath(Script.resourcesPath() + 'icons/loadingDark.gif'), + canShare: !!isDomainOpen(domainId), + openFeedAfterShare: shouldOpenFeedAfterShare() + }; + imageData = [{ localPath: pathStillSnapshot, href: href }]; + reviewSnapshot(); if (clearOverlayWhenMoving) { MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog } @@ -264,22 +266,24 @@ function processingGifStarted(pathStillSnapshot) { function processingGifCompleted(pathAnimatedSnapshot) { Window.processingGifCompleted.disconnect(processingGifCompleted); - button.clicked.connect(onClicked); - buttonConnected = true; + if (!buttonConnected) { + button.clicked.connect(onClicked); + buttonConnected = true; + } - var confirmShareContents = [ - { localPath: pathAnimatedSnapshot, href: href }, - { - containsGif: true, - processingGif: false, - canShare: !!isDomainOpen(domainId), - openFeedAfterShare: shouldOpenFeedAfterShare() - }]; - readyData = confirmShareContents; + snapshotOptions = { + containsGif: true, + processingGif: false, + canShare: !!isDomainOpen(domainId), + openFeedAfterShare: shouldOpenFeedAfterShare() + } + imageData = [{ localPath: pathAnimatedSnapshot, href: href }]; tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", - action: readyData + action: "addImages", + options: snapshotOptions, + data: imageData })); } From 01e78612c7ece62d22d1bf9d16b583ac287e760d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 20 Apr 2017 17:17:35 -0700 Subject: [PATCH 062/134] Soooo much progress today --- scripts/system/html/SnapshotReview.html | 9 +++-- scripts/system/html/css/SnapshotReview.css | 10 +++--- scripts/system/html/css/hifi-style.css | 40 ++++++++++++++++++++++ scripts/system/html/js/SnapshotReview.js | 28 ++++++++++----- 4 files changed, 69 insertions(+), 18 deletions(-) diff --git a/scripts/system/html/SnapshotReview.html b/scripts/system/html/SnapshotReview.html index e785fde69d..400be4abdc 100644 --- a/scripts/system/html/SnapshotReview.html +++ b/scripts/system/html/SnapshotReview.html @@ -27,7 +27,7 @@

- +
@@ -40,10 +40,9 @@

- -
- - + +
+
diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 58866feef0..1e233b22f3 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -20,8 +20,8 @@ body { .title { padding: 6px 10px; text-align: left; - height: 20px; - line-height: 20px; + height: 26px; + line-height: 26px; clear: both; } @@ -43,7 +43,7 @@ body { } .hifi-glyph { font-size: 30px; - top: -7px; + top: -4px; } input[type=button].naked { color: #afafaf; @@ -64,7 +64,7 @@ input[type=button].naked:active { */ #snapshot-pane { width: 100%; - height: 574px; + height: 560px; display: flex; justify-content: center; align-items: center; @@ -170,7 +170,7 @@ input[type=button].naked:active { margin-left: 10px; } #snap-settings form input { - margin-bottom: 10px; + margin-bottom: 5px; } #snap-button { diff --git a/scripts/system/html/css/hifi-style.css b/scripts/system/html/css/hifi-style.css index e95ceca4da..41cda569c9 100644 --- a/scripts/system/html/css/hifi-style.css +++ b/scripts/system/html/css/hifi-style.css @@ -92,3 +92,43 @@ hr { //margin: -10px; padding: 0; } + +input[type=radio] { + width: 2em; + margin: 0; + padding: 0; + font-size: 1em; + opacity: 0; +} +input[type=radio] + label{ + display: inline-block; + margin-left: -2em; + line-height: 2em; +} +input[type=radio] + label > span{ + display: inline-block; + width: 20px; + height: 20px; + margin: 5px; + border-radius: 50%; + background: #6B6A6B; + background-image: linear-gradient(#7D7D7D, #6B6A6B); + vertical-align: bottom; +} +input[type=radio]:checked + label > span{ + background-image: linear-gradient(#7D7D7D, #6B6A6B); +} +input[type=radio]:active + label > span, +input[type=radio]:hover + label > span{ + background-image: linear-gradient(#FFFFFF, #AFAFAF); +} +input[type=radio]:checked + label > span > span, +input[type=radio]:active + label > span > span{ + display: block; + width: 10px; + height: 10px; + margin: 2.5px; + border: 2px solid #36CDFF; + border-radius: 50%; + background: #00B4EF; +} \ No newline at end of file diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 3346635a6a..aae1fc5787 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -21,7 +21,7 @@ function addImage(data) { div.id = id; img.id = id + "img"; div.style.width = "100%"; - div.style.height = "" + Math.floor(100 / imageCount) + "%"; + div.style.height = "" + 502 / imageCount + "px"; div.style.display = "flex"; div.style.justifyContent = "center"; div.style.alignItems = "center"; @@ -32,13 +32,16 @@ function addImage(data) { img.src = data.localPath; div.appendChild(img); document.getElementById("snapshot-images").appendChild(div); - div.appendChild(createShareOverlay(id, img.src.split('.').pop().toLowerCase() === "gif")); - img.onload = function () { - var shareBar = document.getElementById(id + "shareBar"); - shareBar.style.width = img.clientWidth; - shareBar.style.display = "inline"; + var isGif = img.src.split('.').pop().toLowerCase() === "gif"; + div.appendChild(createShareOverlay(id, isGif)); + if (!isGif) { + img.onload = function () { + var shareBar = document.getElementById(id + "shareBar"); + shareBar.style.width = img.clientWidth; + shareBar.style.display = "inline"; - document.getElementById(id).style.height = img.clientHeight; + document.getElementById(id).style.height = img.clientHeight; + } } paths.push(data); } @@ -195,7 +198,16 @@ window.onload = function () { message.data.forEach(addImage); } else { var gifPath = message.data[0].localPath; - document.getElementById('p0img').src = gifPath; + var p0img = document.getElementById('p0img'); + p0img.src = gifPath; + + p0img.onload = function () { + var shareBar = document.getElementById("p0shareBar"); + shareBar.style.width = p0img.clientWidth; + shareBar.style.display = "inline"; + document.getElementById('p0').style.height = p0img.clientHeight; + } + paths[0].localPath = gifPath; } } else { From c08b40b95009aec68759926e9212084f58450cae Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 21 Apr 2017 10:17:49 -0700 Subject: [PATCH 063/134] Initial sharing code hooked up --- scripts/system/html/js/SnapshotReview.js | 22 +++++----- scripts/system/snapshot.js | 56 +++++++++--------------- 2 files changed, 32 insertions(+), 46 deletions(-) diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index aae1fc5787..8fffd207ff 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -43,7 +43,7 @@ function addImage(data) { document.getElementById(id).style.height = img.clientHeight; } } - paths.push(data); + paths.push(data.localPath); } function createShareOverlay(parentID, isGif) { var shareOverlayContainer = document.createElement("DIV"); @@ -99,7 +99,7 @@ function createShareOverlay(parentID, isGif) { '
' + '
' + '
' + - '
' + + '
' + '' + '
' + '' + @@ -129,6 +129,15 @@ function selectImageToShare(selectedID) { shareOverlayBackground.style.display = "inline"; shareOverlay.style.display = "inline"; } +function shareWithEveryone(selectedID) { + selectedID = selectedID.id; // Why is this necessary? + + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "shareSnapshot", + data: paths[parseInt(selectedID.substring(1))] + })); +} function cancelSharing(selectedID) { selectedID = selectedID.id; // Why is this necessary? var shareOverlayContainer = document.getElementById(selectedID + "shareOverlayContainer"); @@ -208,7 +217,7 @@ window.onload = function () { document.getElementById('p0').style.height = p0img.clientHeight; } - paths[0].localPath = gifPath; + paths[0] = gifPath; } } else { imageCount = message.data.length; @@ -232,13 +241,6 @@ window.onload = function () { }); }; -// beware of bug: Cannot send objects at top level. (Nested in arrays is fine.) -function shareSelected() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "snapshot", - action: paths - })); -} function doNotShare() { EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 97cd2c542e..7f71336dcf 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -96,43 +96,31 @@ function onMessage(message) { // onClicked(); break; - default: - //tablet.webEventReceived.disconnect(onMessage); // <<< It's probably this that's missing?! - HMD.closeTablet(); + case 'shareSnapshot': isLoggedIn = Account.isLoggedIn(); - message.action.forEach(function (submessage) { - if (submessage.share && !isLoggedIn) { - needsLogin = true; - submessage.share = false; - shareAfterLogin = true; - snapshotToShareAfterLogin = {path: submessage.localPath, href: submessage.href || href}; - } - if (submessage.share) { - print('sharing', submessage.localPath); - outstanding = true; - Window.shareSnapshot(submessage.localPath, submessage.href || href); - } else { - print('not sharing', submessage.localPath); - } - - }); - if (outstanding && shouldOpenFeedAfterShare()) { - showFeedWindow(); - outstanding = false; + if (!isLoggedIn) { + needsLogin = true; + shareAfterLogin = true; + snapshotToShareAfterLogin = { path: message.data, href: message.href || href }; + } else { + print('sharing', message.data); + outstanding++; + Window.shareSnapshot(message.data, message.href || href); } - if (needsLogin) { // after the possible feed, so that the login is on top - var isLoggedIn = Account.isLoggedIn(); - if (!isLoggedIn) { - if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar")) - || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar"))) { - Menu.triggerOption("Login / Sign Up"); - } else { - tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); - HMD.openTablet(); - } + if (needsLogin) { + if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar")) + || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar"))) { + Menu.triggerOption("Login / Sign Up"); + } else { + tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); + HMD.openTablet(); } } + break; + default: + print('Unknown message action received by snapshot.js!'); + break; } } @@ -298,11 +286,7 @@ function onConnected() { print('sharing', snapshotToShareAfterLogin.path); Window.shareSnapshot(snapshotToShareAfterLogin.path, snapshotToShareAfterLogin.href); shareAfterLogin = false; - if (shouldOpenFeedAfterShare()) { - showFeedWindow(); - } } - } button.clicked.connect(onClicked); From 880bcf3b1ee2558c8053d66615d28f232118977c Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 21 Apr 2017 15:42:41 -0700 Subject: [PATCH 064/134] Checkpoint --- .../src/scripting/WindowScriptingInterface.h | 2 +- interface/src/ui/SnapshotUploader.cpp | 15 +- scripts/system/html/js/SnapshotReview.js | 31 +++- scripts/system/snapshot.js | 138 ++++++++++++------ 4 files changed, 133 insertions(+), 53 deletions(-) diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index eb7dc02627..7641110972 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -74,7 +74,7 @@ signals: void svoImportRequested(const QString& url); void domainConnectionRefused(const QString& reasonMessage, int reasonCode, const QString& extraInfo); void stillSnapshotTaken(const QString& pathStillSnapshot, bool notify); - void snapshotShared(const QString& error); + void snapshotShared(bool isError, const QString& reply); void processingGifStarted(const QString& pathStillSnapshot); void processingGifCompleted(const QString& pathAnimatedSnapshot); diff --git a/interface/src/ui/SnapshotUploader.cpp b/interface/src/ui/SnapshotUploader.cpp index 411e892de5..54faa822b8 100644 --- a/interface/src/ui/SnapshotUploader.cpp +++ b/interface/src/ui/SnapshotUploader.cpp @@ -49,6 +49,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { userStoryObject.insert("place_name", placeName); userStoryObject.insert("path", currentPath); userStoryObject.insert("action", "snapshot"); + userStoryObject.insert("audience", "for_url"); rootObject.insert("user_story", userStoryObject); auto accountManager = DependencyManager::get(); @@ -61,7 +62,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { QJsonDocument(rootObject).toJson()); } else { - emit DependencyManager::get()->snapshotShared(contents); + emit DependencyManager::get()->snapshotShared(true, contents); delete this; } } @@ -72,12 +73,18 @@ void SnapshotUploader::uploadFailure(QNetworkReply& reply) { if (replyString.size() == 0) { replyString = reply.errorString(); } - emit DependencyManager::get()->snapshotShared(replyString); // maybe someday include _inWorldLocation, _filename? + emit DependencyManager::get()->snapshotShared(true, replyString); // maybe someday include _inWorldLocation, _filename? delete this; } void SnapshotUploader::createStorySuccess(QNetworkReply& reply) { - emit DependencyManager::get()->snapshotShared(QString()); + QString replyString = reply.readAll(); + // oh no QT pls + // let's write our own JSON parser??? + QJsonDocument jsonResponse = QJsonDocument::fromJson(replyString.toUtf8()); + QJsonObject object = jsonResponse.object()["user_story"].toObject(); + QString storyId = QString::number(object["id"].toInt()); + emit DependencyManager::get()->snapshotShared(false, storyId); delete this; } @@ -87,7 +94,7 @@ void SnapshotUploader::createStoryFailure(QNetworkReply& reply) { if (replyString.size() == 0) { replyString = reply.errorString(); } - emit DependencyManager::get()->snapshotShared(replyString); + emit DependencyManager::get()->snapshotShared(true, replyString); delete this; } diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 8fffd207ff..042bc55fb5 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -11,7 +11,7 @@ // var paths = [], idCounter = 0, imageCount = 1; -function addImage(data) { +function addImage(data, isGifLoading) { if (!data.localPath) { return; } @@ -38,12 +38,14 @@ function addImage(data) { img.onload = function () { var shareBar = document.getElementById(id + "shareBar"); shareBar.style.width = img.clientWidth; - shareBar.style.display = "inline"; document.getElementById(id).style.height = img.clientHeight; } } paths.push(data.localPath); + if (!isGifLoading) { + shareForUrl(id); + } } function createShareOverlay(parentID, isGif) { var shareOverlayContainer = document.createElement("DIV"); @@ -99,7 +101,7 @@ function createShareOverlay(parentID, isGif) { '
' + '
' + '
' + - '
' + + '
' + '' + '
' + '' + @@ -129,12 +131,19 @@ function selectImageToShare(selectedID) { shareOverlayBackground.style.display = "inline"; shareOverlay.style.display = "inline"; } +function shareForUrl(selectedID) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "shareSnapshotForUrl", + data: paths[parseInt(selectedID.substring(1))] + })); +} function shareWithEveryone(selectedID) { selectedID = selectedID.id; // Why is this necessary? EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", - action: "shareSnapshot", + action: "shareSnapshotWithEveryone", data: paths[parseInt(selectedID.substring(1))] })); } @@ -204,7 +213,9 @@ window.onload = function () { if (messageOptions.processingGif) { imageCount = message.data.length + 1; // "+1" for the GIF that'll finish processing soon message.data.unshift({ localPath: messageOptions.loadingGifPath }); - message.data.forEach(addImage); + message.data.forEach(function (element, idx, array) { + addImage(element, idx === 0); + }); } else { var gifPath = message.data[0].localPath; var p0img = document.getElementById('p0img'); @@ -213,20 +224,26 @@ window.onload = function () { p0img.onload = function () { var shareBar = document.getElementById("p0shareBar"); shareBar.style.width = p0img.clientWidth; - shareBar.style.display = "inline"; document.getElementById('p0').style.height = p0img.clientHeight; } paths[0] = gifPath; + shareForUrl("p0"); } } else { imageCount = message.data.length; - message.data.forEach(addImage); + message.data.forEach(function (element, idx, array) { + addImage(element, false); + }); } break; case 'captureSettings': handleCaptureSetting(message.setting); break; + case 'enableShareButtons': + var shareBar = document.getElementById("p0shareBar"); + shareBar.style.display = "inline"; + break; default: print("Unknown message action received in SnapshotReview.js."); break; diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 7f71336dcf..37f3070dc0 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -28,25 +28,61 @@ var button = tablet.addButton({ sortOrder: 5 }); -function shouldOpenFeedAfterShare() { - var persisted = Settings.getValue('openFeedAfterShare', true); // might answer true, false, "true", or "false" - return persisted && (persisted !== 'false'); -} -function showFeedWindow() { - if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar")) - || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar"))) { - tablet.loadQMLSource("TabletAddressDialog.qml"); - } else { - tablet.initialScreen("TabletAddressDialog.qml"); - HMD.openTablet(); - } -} - -var outstanding; var snapshotOptions; var imageData; var shareAfterLogin = false; -var snapshotToShareAfterLogin; +var snapshotToShareAfterLogin; +var METAVERSE_BASE = location.metaverseServerUrl; + +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. + httpRequest.onreadystatechange = function () { + var READY_STATE_DONE = 4; + var HTTP_OK = 200; + if (httpRequest.readyState >= READY_STATE_DONE) { + var error = (httpRequest.status !== HTTP_OK) && httpRequest.status.toString() + ':' + httpRequest.statusText, + response = !error && httpRequest.responseText, + contentType = !error && httpRequest.getResponseHeader('content-type'); + if (!error && contentType.indexOf('application/json') === 0) { // ignoring charset, etc. + try { + response = JSON.parse(response); + } catch (e) { + error = e; + } + } + callback(error, response); + } + }; + if (typeof options === 'string') { + options = { uri: options }; + } + if (options.url) { + options.uri = options.url; + } + if (!options.method) { + options.method = 'GET'; + } + if (options.body && (options.method === 'GET')) { // add query parameters + var params = [], appender = (-1 === options.uri.search('?')) ? '?' : '&'; + for (key in options.body) { + params.push(key + '=' + options.body[key]); + } + options.uri += appender + params.join('&'); + delete options.body; + } + if (options.json) { + options.headers = options.headers || {}; + options.headers["Content-type"] = "application/json"; + options.body = JSON.stringify(options.body); + } + for (key in options.headers || {}) { + httpRequest.setRequestHeader(key, options.headers[key]); + } + httpRequest.open(options.method, options.uri, true); + httpRequest.send(options.body); +} + function onMessage(message) { // Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following: // 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.) @@ -73,7 +109,6 @@ function onMessage(message) { options: snapshotOptions, data: imageData })); - outstanding = 0; break; case 'openSettings': if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar")) @@ -96,18 +131,41 @@ function onMessage(message) { // onClicked(); break; - case 'shareSnapshot': + case 'shareSnapshotForUrl': isLoggedIn = Account.isLoggedIn(); - if (!isLoggedIn) { + if (isLoggedIn) { + print('Sharing snapshot with audience "for_url":', message.data); + Window.shareSnapshot(message.data, message.href || href); + } else { + // TODO? + } + break; + case 'shareSnapshotWithEveryone': + isLoggedIn = Account.isLoggedIn(); + if (isLoggedIn) { + print('sharing', message.data); + request({ + uri: METAVERSE_BASE + '/api/v1/user_stories/' + story_id, + method: 'PUT', + json: true, + body: { + audience: "for_feed", + } + }, function (error, response) { + if (error || (response.status !== 'success')) { + print("Error changing audience: ", error || response.status); + return; + } + }); + } else { + // TODO + /* needsLogin = true; shareAfterLogin = true; snapshotToShareAfterLogin = { path: message.data, href: message.href || href }; - } else { - print('sharing', message.data); - outstanding++; - Window.shareSnapshot(message.data, message.href || href); + */ } - + /* if (needsLogin) { if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar")) || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar"))) { @@ -116,7 +174,7 @@ function onMessage(message) { tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); HMD.openTablet(); } - } + }*/ break; default: print('Unknown message action received by snapshot.js!'); @@ -133,14 +191,15 @@ function reviewSnapshot() { isInSnapshotReview = true; } -function snapshotShared(errorMessage) { - if (!errorMessage) { - print('snapshot uploaded and shared'); +function snapshotUploaded(isError, reply) { + if (!isError) { + print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', reply); + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "enableShareButtons" + })); } else { - print(errorMessage); - } - if ((--outstanding <= 0) && shouldOpenFeedAfterShare()) { - showFeedWindow(); + print(reply); } } var href, domainId; @@ -213,8 +272,7 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { snapshotOptions = { containsGif: false, processingGif: false, - canShare: !!isDomainOpen(domainId), - openFeedAfterShare: shouldOpenFeedAfterShare() + canShare: !!isDomainOpen(domainId) }; imageData = [{ localPath: pathStillSnapshot, href: href }]; reviewSnapshot(); @@ -241,8 +299,7 @@ function processingGifStarted(pathStillSnapshot) { containsGif: true, processingGif: true, loadingGifPath: Script.resolvePath(Script.resourcesPath() + 'icons/loadingDark.gif'), - canShare: !!isDomainOpen(domainId), - openFeedAfterShare: shouldOpenFeedAfterShare() + canShare: !!isDomainOpen(domainId) }; imageData = [{ localPath: pathStillSnapshot, href: href }]; reviewSnapshot(); @@ -262,8 +319,7 @@ function processingGifCompleted(pathAnimatedSnapshot) { snapshotOptions = { containsGif: true, processingGif: false, - canShare: !!isDomainOpen(domainId), - openFeedAfterShare: shouldOpenFeedAfterShare() + canShare: !!isDomainOpen(domainId) } imageData = [{ localPath: pathAnimatedSnapshot, href: href }]; @@ -283,7 +339,7 @@ function onTabletScreenChanged(type, url) { } function onConnected() { if (shareAfterLogin && Account.isLoggedIn()) { - print('sharing', snapshotToShareAfterLogin.path); + print('Sharing snapshot after login:', snapshotToShareAfterLogin.path); Window.shareSnapshot(snapshotToShareAfterLogin.path, snapshotToShareAfterLogin.href); shareAfterLogin = false; } @@ -291,7 +347,7 @@ function onConnected() { button.clicked.connect(onClicked); buttonConnected = true; -Window.snapshotShared.connect(snapshotShared); +Window.snapshotShared.connect(snapshotUploaded); tablet.screenChanged.connect(onTabletScreenChanged); Account.usernameChanged.connect(onConnected); Script.scriptEnding.connect(function () { @@ -302,7 +358,7 @@ Script.scriptEnding.connect(function () { if (tablet) { tablet.removeButton(button); } - Window.snapshotShared.disconnect(snapshotShared); + Window.snapshotShared.disconnect(snapshotUploaded); tablet.screenChanged.disconnect(onTabletScreenChanged); }); From eaa699bbfdba3fe875bfe78b1cf5ab2f6147c083 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 21 Apr 2017 17:09:14 -0700 Subject: [PATCH 065/134] Wow, it's really coming together! --- interface/src/ui/SnapshotUploader.cpp | 7 +---- scripts/system/html/js/SnapshotReview.js | 37 +++++++++--------------- scripts/system/snapshot.js | 16 ++++++---- 3 files changed, 26 insertions(+), 34 deletions(-) diff --git a/interface/src/ui/SnapshotUploader.cpp b/interface/src/ui/SnapshotUploader.cpp index 54faa822b8..aa37608476 100644 --- a/interface/src/ui/SnapshotUploader.cpp +++ b/interface/src/ui/SnapshotUploader.cpp @@ -79,12 +79,7 @@ void SnapshotUploader::uploadFailure(QNetworkReply& reply) { void SnapshotUploader::createStorySuccess(QNetworkReply& reply) { QString replyString = reply.readAll(); - // oh no QT pls - // let's write our own JSON parser??? - QJsonDocument jsonResponse = QJsonDocument::fromJson(replyString.toUtf8()); - QJsonObject object = jsonResponse.object()["user_story"].toObject(); - QString storyId = QString::number(object["id"].toInt()); - emit DependencyManager::get()->snapshotShared(false, storyId); + emit DependencyManager::get()->snapshotShared(false, replyString); delete this; } diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 042bc55fb5..6e15dad567 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -33,21 +33,12 @@ function addImage(data, isGifLoading) { div.appendChild(img); document.getElementById("snapshot-images").appendChild(div); var isGif = img.src.split('.').pop().toLowerCase() === "gif"; - div.appendChild(createShareOverlay(id, isGif)); - if (!isGif) { - img.onload = function () { - var shareBar = document.getElementById(id + "shareBar"); - shareBar.style.width = img.clientWidth; - - document.getElementById(id).style.height = img.clientHeight; - } - } paths.push(data.localPath); if (!isGifLoading) { shareForUrl(id); } } -function createShareOverlay(parentID, isGif) { +function createShareOverlay(parentID, isGif, shareURL) { var shareOverlayContainer = document.createElement("DIV"); shareOverlayContainer.id = parentID + "shareOverlayContainer"; shareOverlayContainer.style.position = "absolute"; @@ -60,7 +51,7 @@ function createShareOverlay(parentID, isGif) { var shareBar = document.createElement("div"); shareBar.id = parentID + "shareBar" - shareBar.style.display = "none"; + shareBar.style.display = "inline"; shareBar.style.width = "100%"; shareBar.style.height = "60px"; shareBar.style.lineHeight = "60px"; @@ -107,8 +98,8 @@ function createShareOverlay(parentID, isGif) { '' + '
' + '
' + - '' + - '' + + '' + + '' + '
' + '
'; shareOverlayContainer.appendChild(shareOverlay); @@ -144,7 +135,7 @@ function shareWithEveryone(selectedID) { EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", action: "shareSnapshotWithEveryone", - data: paths[parseInt(selectedID.substring(1))] + story_id: document.getElementById(selectedID).getAttribute("data-story-id") })); } function cancelSharing(selectedID) { @@ -221,12 +212,6 @@ window.onload = function () { var p0img = document.getElementById('p0img'); p0img.src = gifPath; - p0img.onload = function () { - var shareBar = document.getElementById("p0shareBar"); - shareBar.style.width = p0img.clientWidth; - document.getElementById('p0').style.height = p0img.clientHeight; - } - paths[0] = gifPath; shareForUrl("p0"); } @@ -240,9 +225,15 @@ window.onload = function () { case 'captureSettings': handleCaptureSetting(message.setting); break; - case 'enableShareButtons': - var shareBar = document.getElementById("p0shareBar"); - shareBar.style.display = "inline"; + case 'snapshotUploadComplete': + var isGif = message.shareable_url.split('.').pop().toLowerCase() === "gif"; + var id = "p0" + if (imageCount > 1 && !isGif) { + id = "p1"; + } + var parentDiv = document.getElementById(id); + parentDiv.setAttribute('data-story-id', message.id); + document.getElementById(id).appendChild(createShareOverlay(id, isGif, message.story_url)); break; default: print("Unknown message action received in SnapshotReview.js."); diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 37f3070dc0..6b0b8e1214 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -143,9 +143,9 @@ function onMessage(message) { case 'shareSnapshotWithEveryone': isLoggedIn = Account.isLoggedIn(); if (isLoggedIn) { - print('sharing', message.data); + print('Modifying audience of story ID', message.story_id, "to 'for_feed'"); request({ - uri: METAVERSE_BASE + '/api/v1/user_stories/' + story_id, + uri: METAVERSE_BASE + '/api/v1/user_stories/' + message.story_id, method: 'PUT', json: true, body: { @@ -153,8 +153,10 @@ function onMessage(message) { } }, function (error, response) { if (error || (response.status !== 'success')) { - print("Error changing audience: ", error || response.status); + print("ERROR changing audience: ", error || response.status); return; + } else { + print("SUCCESS changing audience!"); } }); } else { @@ -193,10 +195,14 @@ function reviewSnapshot() { function snapshotUploaded(isError, reply) { if (!isError) { - print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', reply); + print('SUCCESS: Snapshot uploaded! Story with audience:for_url created!'); + var replyJson = JSON.parse(reply); tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", - action: "enableShareButtons" + action: "snapshotUploadComplete", + id: replyJson.user_story.id, + story_url: "https://highfidelity.com/user_stories/" + replyJson.user_story.id, + shareable_url: replyJson.user_story.details.shareable_url, })); } else { print(reply); From fad470eeea20dd9609f62764a99241f073648854 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 24 Apr 2017 14:35:05 -0700 Subject: [PATCH 066/134] Checkpoint (this is complicated) --- scripts/system/html/js/SnapshotReview.js | 86 +++++++++++------ scripts/system/snapshot.js | 112 ++++++++++++++++++----- 2 files changed, 148 insertions(+), 50 deletions(-) diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 6e15dad567..cf1f881a46 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -10,9 +10,20 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var paths = [], idCounter = 0, imageCount = 1; -function addImage(data, isGifLoading) { - if (!data.localPath) { +var paths = []; +var idCounter = 0; +var imageCount = 0; +function clearImages() { + var snapshotImagesDiv = document.getElementById("snapshot-images"); + while (snapshotImagesDiv.hasChildNodes()) { + snapshotImagesDiv.removeChild(snapshotImagesDiv.lastChild); + } + paths = []; + imageCount = 0; + idCounter = 0; +} +function addImage(image_data, isGifLoading, isShowingPreviousImages) { + if (!image_data.localPath) { return; } var div = document.createElement("DIV"); @@ -29,15 +40,26 @@ function addImage(data, isGifLoading) { if (imageCount > 1) { img.setAttribute("class", "multiple"); } - img.src = data.localPath; + img.src = image_data.localPath; div.appendChild(img); document.getElementById("snapshot-images").appendChild(div); var isGif = img.src.split('.').pop().toLowerCase() === "gif"; - paths.push(data.localPath); - if (!isGifLoading) { + paths.push(image_data.localPath); + if (!isGifLoading && !isShowingPreviousImages) { shareForUrl(id); + } else if (isShowingPreviousImages) { + appendShareBar(id, image_data.story_id, isGif) } } +function appendShareBar(divID, story_id, isGif) { + var story_url = "https://highfidelity.com/user_stories/" + story_id; + var parentDiv = document.getElementById(divID); + parentDiv.setAttribute('data-story-id', story_id); + document.getElementById(divID).appendChild(createShareOverlay(divID, isGif, story_url)); + twttr.events.bind('click', function (event) { + shareButtonClicked(divID); + }); +} function createShareOverlay(parentID, isGif, shareURL) { var shareOverlayContainer = document.createElement("DIV"); shareOverlayContainer.id = parentID + "shareOverlayContainer"; @@ -138,6 +160,13 @@ function shareWithEveryone(selectedID) { story_id: document.getElementById(selectedID).getAttribute("data-story-id") })); } +function shareButtonClicked(selectedID) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "shareButtonClicked", + story_id: document.getElementById(selectedID).getAttribute("data-story-id") + })); +} function cancelSharing(selectedID) { selectedID = selectedID.id; // Why is this necessary? var shareOverlayContainer = document.getElementById(selectedID + "shareOverlayContainer"); @@ -176,9 +205,10 @@ function handleCaptureSetting(setting) { window.onload = function () { // TESTING FUNCTIONS START // Uncomment and modify the lines below to test SnapshotReview in a browser. - //imageCount = 2; + //imageCount = 1; //addImage({ localPath: 'C:/Users/Zach Fox/Desktop/hifi-snap-by-zfox-on-2017-04-20_14-59-12.gif' }); - //addImage({ localPath: 'C:/Users/Zach Fox/Desktop/hifi-snap-by-zfox-on-2017-04-20_14-59-12.jpg' }); + //addImage({ localPath: 'C:/Users/Zach Fox/Desktop/hifi-snap-by-zfox-on-2017-04-24_10-49-20.jpg' }); + //document.getElementById('p0').appendChild(createShareOverlay('p0', false, '')); //addImage({ localPath: 'http://lorempixel.com/553/255' }); //addImage({localPath: 'c:/Users/howar/OneDrive/Pictures/hifi-snap-by--on-2016-07-27_12-58-43.jpg'}); // TESTING FUNCTIONS END @@ -194,6 +224,17 @@ window.onload = function () { } switch (message.action) { + case 'clearPreviousImages': + clearImages(); + break; + case 'showPreviousImages': + clearImages(); + var messageOptions = message.options; + imageCount = message.image_data.length; + message.image_data.forEach(function (element, idx, array) { + addImage(element, true, true); + }); + break; case 'addImages': // The last element of the message contents list contains a bunch of options, // including whether or not we can share stuff @@ -202,13 +243,13 @@ window.onload = function () { if (messageOptions.containsGif) { if (messageOptions.processingGif) { - imageCount = message.data.length + 1; // "+1" for the GIF that'll finish processing soon - message.data.unshift({ localPath: messageOptions.loadingGifPath }); - message.data.forEach(function (element, idx, array) { + imageCount = message.image_data.length + 1; // "+1" for the GIF that'll finish processing soon + message.image_data.unshift({ localPath: messageOptions.loadingGifPath }); + message.image_data.forEach(function (element, idx, array) { addImage(element, idx === 0); }); } else { - var gifPath = message.data[0].localPath; + var gifPath = message.image_data[0].localPath; var p0img = document.getElementById('p0img'); p0img.src = gifPath; @@ -216,8 +257,8 @@ window.onload = function () { shareForUrl("p0"); } } else { - imageCount = message.data.length; - message.data.forEach(function (element, idx, array) { + imageCount = message.image_data.length; + message.image_data.forEach(function (element, idx, array) { addImage(element, false); }); } @@ -227,19 +268,12 @@ window.onload = function () { break; case 'snapshotUploadComplete': var isGif = message.shareable_url.split('.').pop().toLowerCase() === "gif"; - var id = "p0" - if (imageCount > 1 && !isGif) { - id = "p1"; - } - var parentDiv = document.getElementById(id); - parentDiv.setAttribute('data-story-id', message.id); - document.getElementById(id).appendChild(createShareOverlay(id, isGif, message.story_url)); + appendShareBar(isGif || imageCount === 1 ? "p0" : "p1", message.story_id, isGif); break; default: - print("Unknown message action received in SnapshotReview.js."); + console.log("Unknown message action received in SnapshotReview.js."); break; } - }); EventBridge.emitWebEvent(JSON.stringify({ @@ -249,12 +283,6 @@ window.onload = function () { }); }; -function doNotShare() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "snapshot", - action: [] - })); -} function snapshotSettings() { EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 6b0b8e1214..1af35f4061 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -29,7 +29,8 @@ var button = tablet.addButton({ }); var snapshotOptions; -var imageData; +var imageData = []; +var storyIDsToMaybeDelete = []; var shareAfterLogin = false; var snapshotToShareAfterLogin; var METAVERSE_BASE = location.metaverseServerUrl; @@ -105,9 +106,9 @@ function onMessage(message) { })); tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", - action: "addImages", + action: "showPreviousImages", options: snapshotOptions, - data: imageData + image_data: imageData })); break; case 'openSettings': @@ -127,9 +128,7 @@ function onMessage(message) { Settings.setValue("alsoTakeAnimatedSnapshot", false); break; case 'takeSnapshot': - // In settings, first store the paths to the last snapshot - // - onClicked(); + takeSnapshot(); break; case 'shareSnapshotForUrl': isLoggedIn = Account.isLoggedIn(); @@ -142,6 +141,7 @@ function onMessage(message) { break; case 'shareSnapshotWithEveryone': isLoggedIn = Account.isLoggedIn(); + storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1); if (isLoggedIn) { print('Modifying audience of story ID', message.story_id, "to 'for_feed'"); request({ @@ -178,6 +178,10 @@ function onMessage(message) { } }*/ break; + case 'shareButtonClicked': + print('Twitter or FB "Share" button clicked! Removing ID', message.story_id, 'from storyIDsToMaybeDelete[].') + storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1); + break; default: print('Unknown message action received by snapshot.js!'); break; @@ -186,7 +190,23 @@ function onMessage(message) { var SNAPSHOT_REVIEW_URL = Script.resolvePath("html/SnapshotReview.html"); var isInSnapshotReview = false; -function reviewSnapshot() { +function openSnapApp() { + var previousStillSnapPath = Settings.getValue("previousStillSnapPath"); + var previousStillSnapStoryID = Settings.getValue("previousStillSnapStoryID"); + var previousAnimatedSnapPath = Settings.getValue("previousAnimatedSnapPath"); + var previousAnimatedSnapStoryID = Settings.getValue("previousAnimatedSnapStoryID"); + snapshotOptions = { + containsGif: previousAnimatedSnapPath !== "", + processingGif: false, + shouldUpload: false + } + imageData = []; + if (previousAnimatedSnapPath !== "") { + imageData.push({ localPath: previousAnimatedSnapPath, story_id: previousAnimatedSnapStoryID }); + } + if (previousStillSnapPath !== "") { + imageData.push({ localPath: previousStillSnapPath, story_id: previousStillSnapStoryID }); + } tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL); tablet.webEventReceived.connect(onMessage); HMD.openTablet(); @@ -195,21 +215,28 @@ function reviewSnapshot() { function snapshotUploaded(isError, reply) { if (!isError) { - print('SUCCESS: Snapshot uploaded! Story with audience:for_url created!'); var replyJson = JSON.parse(reply); + var storyID = replyJson.user_story.id; + var shareableURL = replyJson.user_story.details.shareable_url; + var isGif = shareableURL.split('.').pop().toLowerCase() === "gif"; + print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', storyID); tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", action: "snapshotUploadComplete", - id: replyJson.user_story.id, - story_url: "https://highfidelity.com/user_stories/" + replyJson.user_story.id, - shareable_url: replyJson.user_story.details.shareable_url, + story_id: storyID, + shareable_url: shareableURL, })); + if (isGif) { + Settings.setValue("previousAnimatedSnapStoryID", storyID); + } else { + Settings.setValue("previousStillSnapStoryID", storyID); + } } else { print(reply); } } var href, domainId; -function onClicked() { +function takeSnapshot() { // Raising the desktop for the share dialog at end will interact badly with clearOverlayWhenMoving. // Turn it off now, before we start futzing with things (and possibly moving). clearOverlayWhenMoving = MyAvatar.getClearOverlayWhenMoving(); // Do not use Settings. MyAvatar keeps a separate copy. @@ -220,8 +247,16 @@ function onClicked() { href = location.href; domainId = location.domainId; + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "clearPreviousImages" + })); + maybeDeleteSnapshotStories(); + Settings.setValue("previousStillSnapPath", ""); + Settings.setValue("previousAnimatedSnapPath", ""); + // update button states - resetOverlays = Menu.isOptionChecked("Overlays"); // For completness. Certainly true if the button is visible to be clicke. + resetOverlays = Menu.isOptionChecked("Overlays"); // For completness. Certainly true if the button is visible to be clicked. reticleVisible = Reticle.visible; Reticle.visible = false; Window.stillSnapshotTaken.connect(stillSnapshotTaken); @@ -281,7 +316,15 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { canShare: !!isDomainOpen(domainId) }; imageData = [{ localPath: pathStillSnapshot, href: href }]; - reviewSnapshot(); + Settings.setValue("previousStillSnapPath", pathStillSnapshot); + + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "addImages", + options: snapshotOptions, + image_data: imageData + })); + if (clearOverlayWhenMoving) { MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog } @@ -291,7 +334,7 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { function processingGifStarted(pathStillSnapshot) { Window.processingGifStarted.disconnect(processingGifStarted); if (buttonConnected) { - button.clicked.disconnect(onClicked); + button.clicked.disconnect(openSnapApp); buttonConnected = false; } // show hud @@ -308,7 +351,15 @@ function processingGifStarted(pathStillSnapshot) { canShare: !!isDomainOpen(domainId) }; imageData = [{ localPath: pathStillSnapshot, href: href }]; - reviewSnapshot(); + Settings.setValue("previousStillSnapPath", pathStillSnapshot); + + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "addImages", + options: snapshotOptions, + image_data: imageData + })); + if (clearOverlayWhenMoving) { MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog } @@ -318,7 +369,7 @@ function processingGifStarted(pathStillSnapshot) { function processingGifCompleted(pathAnimatedSnapshot) { Window.processingGifCompleted.disconnect(processingGifCompleted); if (!buttonConnected) { - button.clicked.connect(onClicked); + button.clicked.connect(openSnapApp); buttonConnected = true; } @@ -328,15 +379,34 @@ function processingGifCompleted(pathAnimatedSnapshot) { canShare: !!isDomainOpen(domainId) } imageData = [{ localPath: pathAnimatedSnapshot, href: href }]; + Settings.setValue("previousAnimatedSnapPath", pathAnimatedSnapshot); tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", action: "addImages", options: snapshotOptions, - data: imageData + image_data: imageData })); } - +function maybeDeleteSnapshotStories() { + if (storyIDsToMaybeDelete.length > 0) { + print("User took new snapshot & didn't share old one(s); deleting old snapshot stories"); + storyIDsToMaybeDelete.forEach(function (element, idx, array) { + request({ + uri: METAVERSE_BASE + '/api/v1/user_stories/' + element, + method: 'DELETE' + }, function (error, response) { + if (error || (response.status !== 'success')) { + print("ERROR deleting snapshot story: ", error || response.status); + return; + } else { + print("SUCCESS deleting snapshot story with ID", element); + } + }) + }); + storyIDsToMaybeDelete = []; + } +} function onTabletScreenChanged(type, url) { if (isInSnapshotReview) { tablet.webEventReceived.disconnect(onMessage); @@ -351,14 +421,14 @@ function onConnected() { } } -button.clicked.connect(onClicked); +button.clicked.connect(openSnapApp); buttonConnected = true; Window.snapshotShared.connect(snapshotUploaded); tablet.screenChanged.connect(onTabletScreenChanged); Account.usernameChanged.connect(onConnected); Script.scriptEnding.connect(function () { if (buttonConnected) { - button.clicked.disconnect(onClicked); + button.clicked.disconnect(openSnapApp); buttonConnected = false; } if (tablet) { From 7c5de1e60b3dca11aadb56cfed6a44388b9e997d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 24 Apr 2017 15:19:59 -0700 Subject: [PATCH 067/134] Getting closer --- scripts/system/html/js/SnapshotReview.js | 11 ++++++----- scripts/system/snapshot.js | 22 ++++++++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index cf1f881a46..d5cc9faf74 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -18,11 +18,12 @@ function clearImages() { while (snapshotImagesDiv.hasChildNodes()) { snapshotImagesDiv.removeChild(snapshotImagesDiv.lastChild); } + twttr.events.unbind('click'); paths = []; imageCount = 0; idCounter = 0; } -function addImage(image_data, isGifLoading, isShowingPreviousImages) { +function addImage(image_data, isGifLoading, canSharePreviousImages) { if (!image_data.localPath) { return; } @@ -45,9 +46,9 @@ function addImage(image_data, isGifLoading, isShowingPreviousImages) { document.getElementById("snapshot-images").appendChild(div); var isGif = img.src.split('.').pop().toLowerCase() === "gif"; paths.push(image_data.localPath); - if (!isGifLoading && !isShowingPreviousImages) { + if (!isGifLoading && !canSharePreviousImages) { shareForUrl(id); - } else if (isShowingPreviousImages) { + } else if (canSharePreviousImages) { appendShareBar(id, image_data.story_id, isGif) } } @@ -232,7 +233,7 @@ window.onload = function () { var messageOptions = message.options; imageCount = message.image_data.length; message.image_data.forEach(function (element, idx, array) { - addImage(element, true, true); + addImage(element, true, message.canShare); }); break; case 'addImages': @@ -267,7 +268,7 @@ window.onload = function () { handleCaptureSetting(message.setting); break; case 'snapshotUploadComplete': - var isGif = message.shareable_url.split('.').pop().toLowerCase() === "gif"; + var isGif = message.image_url.split('.').pop().toLowerCase() === "gif"; appendShareBar(isGif || imageCount === 1 ? "p0" : "p1", message.story_id, isGif); break; default: diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 1af35f4061..39fc45556e 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -108,7 +108,8 @@ function onMessage(message) { type: "snapshot", action: "showPreviousImages", options: snapshotOptions, - image_data: imageData + image_data: imageData, + canShare: !!isDomainOpen(Settings.getValue("previousSnapshotDomainID")) })); break; case 'openSettings': @@ -179,8 +180,9 @@ function onMessage(message) { }*/ break; case 'shareButtonClicked': - print('Twitter or FB "Share" button clicked! Removing ID', message.story_id, 'from storyIDsToMaybeDelete[].') + print('Twitter or FB "Share" button clicked! Removing ID', message.story_id, 'from storyIDsToMaybeDelete[].'); storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1); + print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete)); break; default: print('Unknown message action received by snapshot.js!'); @@ -217,14 +219,15 @@ function snapshotUploaded(isError, reply) { if (!isError) { var replyJson = JSON.parse(reply); var storyID = replyJson.user_story.id; - var shareableURL = replyJson.user_story.details.shareable_url; - var isGif = shareableURL.split('.').pop().toLowerCase() === "gif"; + storyIDsToMaybeDelete.push(storyID); + var imageURL = replyJson.user_story.details.image_url; + var isGif = imageURL.split('.').pop().toLowerCase() === "gif"; print('SUCCESS: Snapshot uploaded! Story with audience:for_url created! ID:', storyID); tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", action: "snapshotUploadComplete", story_id: storyID, - shareable_url: shareableURL, + image_url: imageURL, })); if (isGif) { Settings.setValue("previousAnimatedSnapStoryID", storyID); @@ -246,14 +249,17 @@ function takeSnapshot() { // Even the domainId could change (e.g., if the user falls into a teleporter while recording). href = location.href; domainId = location.domainId; + Settings.setValue("previousSnapshotDomainID", domainId); + maybeDeleteSnapshotStories(); tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", action: "clearPreviousImages" })); - maybeDeleteSnapshotStories(); Settings.setValue("previousStillSnapPath", ""); + Settings.setValue("previousStillSnapStoryID", ""); Settings.setValue("previousAnimatedSnapPath", ""); + Settings.setValue("previousAnimatedSnapStoryID", ""); // update button states resetOverlays = Menu.isOptionChecked("Overlays"); // For completness. Certainly true if the button is visible to be clicked. @@ -278,6 +284,10 @@ function takeSnapshot() { } function isDomainOpen(id) { + print("Checking open status of domain with ID:", id); + if (!id) { + return false; + } var request = new XMLHttpRequest(); var options = [ 'now=' + new Date().toISOString(), From f838e87f4891627ca64637395148964174a301fd Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 24 Apr 2017 15:59:19 -0700 Subject: [PATCH 068/134] Pretty stable... --- scripts/system/html/css/SnapshotReview.css | 1 + scripts/system/html/js/SnapshotReview.js | 12 +++--- scripts/system/snapshot.js | 44 +++++++++++++--------- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 1e233b22f3..859b4d8c90 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -11,6 +11,7 @@ body { padding: 0; margin: 0; + overflow: hidden; } /* diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index d5cc9faf74..9fa58e6d33 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -23,7 +23,7 @@ function clearImages() { imageCount = 0; idCounter = 0; } -function addImage(image_data, isGifLoading, canSharePreviousImages) { +function addImage(image_data, isGifLoading, isShowingPreviousImages, canSharePreviousImages) { if (!image_data.localPath) { return; } @@ -46,9 +46,9 @@ function addImage(image_data, isGifLoading, canSharePreviousImages) { document.getElementById("snapshot-images").appendChild(div); var isGif = img.src.split('.').pop().toLowerCase() === "gif"; paths.push(image_data.localPath); - if (!isGifLoading && !canSharePreviousImages) { + if (!isGifLoading && !isShowingPreviousImages) { shareForUrl(id); - } else if (canSharePreviousImages) { + } else if (isShowingPreviousImages && canSharePreviousImages) { appendShareBar(id, image_data.story_id, isGif) } } @@ -233,7 +233,7 @@ window.onload = function () { var messageOptions = message.options; imageCount = message.image_data.length; message.image_data.forEach(function (element, idx, array) { - addImage(element, true, message.canShare); + addImage(element, true, true, message.canShare); }); break; case 'addImages': @@ -247,7 +247,7 @@ window.onload = function () { imageCount = message.image_data.length + 1; // "+1" for the GIF that'll finish processing soon message.image_data.unshift({ localPath: messageOptions.loadingGifPath }); message.image_data.forEach(function (element, idx, array) { - addImage(element, idx === 0); + addImage(element, idx === 0, false, false); }); } else { var gifPath = message.image_data[0].localPath; @@ -260,7 +260,7 @@ window.onload = function () { } else { imageCount = message.image_data.length; message.image_data.forEach(function (element, idx, array) { - addImage(element, false); + addImage(element, false, false, false); }); } break; diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 39fc45556e..422c557391 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -240,6 +240,15 @@ function snapshotUploaded(isError, reply) { } var href, domainId; function takeSnapshot() { + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "clearPreviousImages" + })); + Settings.setValue("previousStillSnapPath", ""); + Settings.setValue("previousStillSnapStoryID", ""); + Settings.setValue("previousAnimatedSnapPath", ""); + Settings.setValue("previousAnimatedSnapStoryID", ""); + // Raising the desktop for the share dialog at end will interact badly with clearOverlayWhenMoving. // Turn it off now, before we start futzing with things (and possibly moving). clearOverlayWhenMoving = MyAvatar.getClearOverlayWhenMoving(); // Do not use Settings. MyAvatar keeps a separate copy. @@ -252,22 +261,22 @@ function takeSnapshot() { Settings.setValue("previousSnapshotDomainID", domainId); maybeDeleteSnapshotStories(); - tablet.emitScriptEvent(JSON.stringify({ - type: "snapshot", - action: "clearPreviousImages" - })); - Settings.setValue("previousStillSnapPath", ""); - Settings.setValue("previousStillSnapStoryID", ""); - Settings.setValue("previousAnimatedSnapPath", ""); - Settings.setValue("previousAnimatedSnapStoryID", ""); // update button states - resetOverlays = Menu.isOptionChecked("Overlays"); // For completness. Certainly true if the button is visible to be clicked. + resetOverlays = Menu.isOptionChecked("Overlays"); // For completeness. Certainly true if the button is visible to be clicked. reticleVisible = Reticle.visible; Reticle.visible = false; - Window.stillSnapshotTaken.connect(stillSnapshotTaken); - Window.processingGifStarted.connect(processingGifStarted); - Window.processingGifCompleted.connect(processingGifCompleted); + + var includeAnimated = Settings.getValue("alsoTakeAnimatedSnapshot", true); + if (includeAnimated) { + Window.processingGifStarted.connect(processingGifStarted); + } else { + Window.stillSnapshotTaken.connect(stillSnapshotTaken); + } + if (buttonConnected) { + button.clicked.disconnect(openSnapApp); + buttonConnected = false; + } // hide overlays if they are on if (resetOverlays) { @@ -278,7 +287,7 @@ function takeSnapshot() { Script.setTimeout(function () { HMD.closeTablet(); Script.setTimeout(function () { - Window.takeSnapshot(false, Settings.getValue("alsoTakeAnimatedSnapshot", true), 1.91); + Window.takeSnapshot(false, includeAnimated, 1.91); }, SNAPSHOT_DELAY); }, FINISH_SOUND_DELAY); } @@ -315,6 +324,10 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { Menu.setIsOptionChecked("Overlays", true); } Window.stillSnapshotTaken.disconnect(stillSnapshotTaken); + if (!buttonConnected) { + button.clicked.connect(openSnapApp); + buttonConnected = true; + } // A Snapshot Review dialog might be left open indefinitely after taking the picture, // during which time the user may have moved. So stash that info in the dialog so that @@ -343,10 +356,7 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { function processingGifStarted(pathStillSnapshot) { Window.processingGifStarted.disconnect(processingGifStarted); - if (buttonConnected) { - button.clicked.disconnect(openSnapApp); - buttonConnected = false; - } + Window.processingGifCompleted.connect(processingGifCompleted); // show hud Reticle.visible = reticleVisible; // show overlays if they were on From e1789996fbe3d69f779ab20d613a2c768079d8eb Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 24 Apr 2017 16:34:58 -0700 Subject: [PATCH 069/134] Add announcements support --- scripts/system/html/js/SnapshotReview.js | 25 +++++++++++-------- scripts/system/snapshot.js | 31 +++++++++++++++++++----- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 9fa58e6d33..4c5419869d 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -23,7 +23,7 @@ function clearImages() { imageCount = 0; idCounter = 0; } -function addImage(image_data, isGifLoading, isShowingPreviousImages, canSharePreviousImages) { +function addImage(image_data, isGifLoading, isShowingPreviousImages, canSharePreviousImages, hifiShareButtonsDisabled) { if (!image_data.localPath) { return; } @@ -49,19 +49,19 @@ function addImage(image_data, isGifLoading, isShowingPreviousImages, canSharePre if (!isGifLoading && !isShowingPreviousImages) { shareForUrl(id); } else if (isShowingPreviousImages && canSharePreviousImages) { - appendShareBar(id, image_data.story_id, isGif) + appendShareBar(id, image_data.story_id, isGif, hifiShareButtonsDisabled) } } -function appendShareBar(divID, story_id, isGif) { +function appendShareBar(divID, story_id, isGif, hifiShareButtonsDisabled) { var story_url = "https://highfidelity.com/user_stories/" + story_id; var parentDiv = document.getElementById(divID); parentDiv.setAttribute('data-story-id', story_id); - document.getElementById(divID).appendChild(createShareOverlay(divID, isGif, story_url)); + document.getElementById(divID).appendChild(createShareOverlay(divID, isGif, story_url, hifiShareButtonsDisabled)); twttr.events.bind('click', function (event) { shareButtonClicked(divID); }); } -function createShareOverlay(parentID, isGif, shareURL) { +function createShareOverlay(parentID, isGif, shareURL, hifiShareButtonsDisabled) { var shareOverlayContainer = document.createElement("DIV"); shareOverlayContainer.id = parentID + "shareOverlayContainer"; shareOverlayContainer.style.position = "absolute"; @@ -115,8 +115,8 @@ function createShareOverlay(parentID, isGif, shareURL) { '
' + '
' + '
' + - '
' + - '' + + '
' + + '' + '
' + '' + '
' + @@ -152,13 +152,18 @@ function shareForUrl(selectedID) { data: paths[parseInt(selectedID.substring(1))] })); } -function shareWithEveryone(selectedID) { +function shareWithEveryone(selectedID, isGif) { selectedID = selectedID.id; // Why is this necessary? + document.getElementById(selectedID + "shareWithEveryoneButton").setAttribute("disabled", "disabled"); + document.getElementById(selectedID + "inviteConnectionsCheckbox").setAttribute("disabled", "disabled"); + EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", action: "shareSnapshotWithEveryone", - story_id: document.getElementById(selectedID).getAttribute("data-story-id") + story_id: document.getElementById(selectedID).getAttribute("data-story-id"), + isAnnouncement: document.getElementById(selectedID + "inviteConnectionsCheckbox").getAttribute("checked"), + isGif: isGif })); } function shareButtonClicked(selectedID) { @@ -233,7 +238,7 @@ window.onload = function () { var messageOptions = message.options; imageCount = message.image_data.length; message.image_data.forEach(function (element, idx, array) { - addImage(element, true, true, message.canShare); + addImage(element, true, true, message.canShare, message.image_data[idx].buttonDisabled); }); break; case 'addImages': diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 422c557391..95a76a9188 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -143,21 +143,38 @@ function onMessage(message) { case 'shareSnapshotWithEveryone': isLoggedIn = Account.isLoggedIn(); storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1); + if (message.isGif) { + Settings.setValue("previousAnimatedSnapSharingDisabled", true); + } else { + Settings.setValue("previousStillSnapSharingDisabled", true); + } + + var requestBody = { + audience: "for_feed" + } + + if (message.isAnnouncement) { + requestBody.action = "announcement"; + } + if (isLoggedIn) { print('Modifying audience of story ID', message.story_id, "to 'for_feed'"); request({ uri: METAVERSE_BASE + '/api/v1/user_stories/' + message.story_id, method: 'PUT', json: true, - body: { - audience: "for_feed", - } + body: requestBody }, function (error, response) { if (error || (response.status !== 'success')) { print("ERROR changing audience: ", error || response.status); + if (message.isGif) { + Settings.setValue("previousAnimatedSnapSharingDisabled", false); + } else { + Settings.setValue("previousStillSnapSharingDisabled", false); + } return; } else { - print("SUCCESS changing audience!"); + print("SUCCESS changing audience" + (message.isAnnouncement ? " and posting announcement!" : "!")); } }); } else { @@ -195,8 +212,10 @@ var isInSnapshotReview = false; function openSnapApp() { var previousStillSnapPath = Settings.getValue("previousStillSnapPath"); var previousStillSnapStoryID = Settings.getValue("previousStillSnapStoryID"); + var previousStillSnapSharingDisabled = Settings.getValue("previousStillSnapSharingDisabled"); var previousAnimatedSnapPath = Settings.getValue("previousAnimatedSnapPath"); var previousAnimatedSnapStoryID = Settings.getValue("previousAnimatedSnapStoryID"); + var previousAnimatedSnapSharingDisabled = Settings.getValue("previousAnimatedSnapSharingDisabled"); snapshotOptions = { containsGif: previousAnimatedSnapPath !== "", processingGif: false, @@ -204,10 +223,10 @@ function openSnapApp() { } imageData = []; if (previousAnimatedSnapPath !== "") { - imageData.push({ localPath: previousAnimatedSnapPath, story_id: previousAnimatedSnapStoryID }); + imageData.push({ localPath: previousAnimatedSnapPath, story_id: previousAnimatedSnapStoryID, buttonDisabled: previousAnimatedSnapSharingDisabled }); } if (previousStillSnapPath !== "") { - imageData.push({ localPath: previousStillSnapPath, story_id: previousStillSnapStoryID }); + imageData.push({ localPath: previousStillSnapPath, story_id: previousStillSnapStoryID, buttonDisabled: previousStillSnapSharingDisabled }); } tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL); tablet.webEventReceived.connect(onMessage); From ef2e5f78fff6c01a6badc1319729652acc7b6f3c Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 24 Apr 2017 17:41:37 -0700 Subject: [PATCH 070/134] Startings of needsLogin support --- scripts/system/snapshot.js | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 95a76a9188..0ac809f8a7 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -149,16 +149,16 @@ function onMessage(message) { Settings.setValue("previousStillSnapSharingDisabled", true); } - var requestBody = { - audience: "for_feed" - } - - if (message.isAnnouncement) { - requestBody.action = "announcement"; - } - if (isLoggedIn) { print('Modifying audience of story ID', message.story_id, "to 'for_feed'"); + var requestBody = { + audience: "for_feed" + } + + if (message.isAnnouncement) { + requestBody.action = "announcement"; + print('...Also announcing!'); + } request({ uri: METAVERSE_BASE + '/api/v1/user_stories/' + message.story_id, method: 'PUT', @@ -178,14 +178,10 @@ function onMessage(message) { } }); } else { - // TODO - /* needsLogin = true; shareAfterLogin = true; snapshotToShareAfterLogin = { path: message.data, href: message.href || href }; - */ } - /* if (needsLogin) { if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar")) || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar"))) { @@ -194,7 +190,7 @@ function onMessage(message) { tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); HMD.openTablet(); } - }*/ + } break; case 'shareButtonClicked': print('Twitter or FB "Share" button clicked! Removing ID', message.story_id, 'from storyIDsToMaybeDelete[].'); @@ -265,8 +261,10 @@ function takeSnapshot() { })); Settings.setValue("previousStillSnapPath", ""); Settings.setValue("previousStillSnapStoryID", ""); + Settings.setValue("previousStillSnapSharingDisabled", false); Settings.setValue("previousAnimatedSnapPath", ""); Settings.setValue("previousAnimatedSnapStoryID", ""); + Settings.setValue("previousAnimatedSnapSharingDisabled", false); // Raising the desktop for the share dialog at end will interact badly with clearOverlayWhenMoving. // Turn it off now, before we start futzing with things (and possibly moving). @@ -452,7 +450,7 @@ function onTabletScreenChanged(type, url) { isInSnapshotReview = false; } } -function onConnected() { +function onUsernameChanged() { if (shareAfterLogin && Account.isLoggedIn()) { print('Sharing snapshot after login:', snapshotToShareAfterLogin.path); Window.shareSnapshot(snapshotToShareAfterLogin.path, snapshotToShareAfterLogin.href); @@ -464,7 +462,7 @@ button.clicked.connect(openSnapApp); buttonConnected = true; Window.snapshotShared.connect(snapshotUploaded); tablet.screenChanged.connect(onTabletScreenChanged); -Account.usernameChanged.connect(onConnected); +Account.usernameChanged.connect(onUsernameChanged); Script.scriptEnding.connect(function () { if (buttonConnected) { button.clicked.disconnect(openSnapApp); From d7348aabc882e6284c9eeb77b86d00d2444165c7 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 25 Apr 2017 10:10:58 -0700 Subject: [PATCH 071/134] Quick comment updates --- scripts/system/html/js/SnapshotReview.js | 6 +++--- scripts/system/snapshot.js | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 4c5419869d..c3fb20a2f9 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -131,7 +131,7 @@ function createShareOverlay(parentID, isGif, shareURL, hifiShareButtonsDisabled) return shareOverlayContainer; } function selectImageToShare(selectedID) { - selectedID = selectedID.id; // Why is this necessary? + selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID var shareOverlayContainer = document.getElementById(selectedID + "shareOverlayContainer"); var shareBar = document.getElementById(selectedID + "shareBar"); var shareOverlayBackground = document.getElementById(selectedID + "shareOverlayBackground"); @@ -153,7 +153,7 @@ function shareForUrl(selectedID) { })); } function shareWithEveryone(selectedID, isGif) { - selectedID = selectedID.id; // Why is this necessary? + selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID document.getElementById(selectedID + "shareWithEveryoneButton").setAttribute("disabled", "disabled"); document.getElementById(selectedID + "inviteConnectionsCheckbox").setAttribute("disabled", "disabled"); @@ -174,7 +174,7 @@ function shareButtonClicked(selectedID) { })); } function cancelSharing(selectedID) { - selectedID = selectedID.id; // Why is this necessary? + selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID var shareOverlayContainer = document.getElementById(selectedID + "shareOverlayContainer"); var shareBar = document.getElementById(selectedID + "shareBar"); var shareOverlayBackground = document.getElementById(selectedID + "shareOverlayBackground"); diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 0ac809f8a7..fe6ab3f28f 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -98,7 +98,7 @@ function onMessage(message) { var isLoggedIn; var needsLogin = false; switch (message.action) { - case 'ready': // Send it. + case 'ready': // DOM is ready and page has loaded tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", action: "captureSettings", @@ -137,7 +137,7 @@ function onMessage(message) { print('Sharing snapshot with audience "for_url":', message.data); Window.shareSnapshot(message.data, message.href || href); } else { - // TODO? + // TODO } break; case 'shareSnapshotWithEveryone': From d461723012e180b1b84db48292a8951172ebda1c Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 25 Apr 2017 11:40:01 -0700 Subject: [PATCH 072/134] Thanks to Jess, slightly different Tweet language --- scripts/system/html/js/SnapshotReview.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index c3fb20a2f9..eec08a0e1e 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -122,7 +122,7 @@ function createShareOverlay(parentID, isGif, shareURL, hifiShareButtonsDisabled) '
' + '
' + '' + - '' + + '' + '
' + '
'; shareOverlayContainer.appendChild(shareOverlay); From 8c9cd0fad0c3b38193493c8a79b5927ae47fbdc6 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 25 Apr 2017 13:56:32 -0700 Subject: [PATCH 073/134] Custom social buttons; better testing functionality --- scripts/system/html/SnapshotReview.html | 18 ------------ scripts/system/html/css/SnapshotReview.css | 18 ++++++++++-- scripts/system/html/img/fb_logo.png | Bin 0 -> 1257 bytes scripts/system/html/img/twitter_logo.png | Bin 0 -> 552 bytes scripts/system/html/js/SnapshotReview.js | 32 +++++++++------------ 5 files changed, 29 insertions(+), 39 deletions(-) create mode 100644 scripts/system/html/img/fb_logo.png create mode 100644 scripts/system/html/img/twitter_logo.png diff --git a/scripts/system/html/SnapshotReview.html b/scripts/system/html/SnapshotReview.html index 400be4abdc..940adddd73 100644 --- a/scripts/system/html/SnapshotReview.html +++ b/scripts/system/html/SnapshotReview.html @@ -5,24 +5,6 @@ - diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 859b4d8c90..0d1bd737e5 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -140,12 +140,26 @@ input[type=button].naked:active { width: 40%; margin-left: 10%; } -.externalShareControls { +.buttonShareControls { text-align: left; + height: 50px; + line-height: 50px; width: 40%; margin-right: 10%; } -.cancelShare { +.facebookButton { + background-image: url(../img/fb_logo.png); + width: 29px; + height: 29px; + display: inline-block; + margin-right: 8px; +} +.twitterButton { + background-image: url(../img/twitter_logo.png); + width: 29px; + height: 29px; + display: inline-block; + margin-right: 8px; } /* // END styling of share overlay diff --git a/scripts/system/html/img/fb_logo.png b/scripts/system/html/img/fb_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1de20bacd8fb5719db3291365576d6368a5ff510 GIT binary patch literal 1257 zcmbVMTWl0n7(QC5q@|jWMiwtH8D1>y?#%4$&dv;bo1NJ$YuM6kx44mqGMzcw9oo4# zGj*qp1_ML_4>k(n5-tsiiSfl4LTZe`R*D1>$y#dyO^uc(e5f=9v2sbxlwI(l;lY!f z8-82B4;>;C8A*8a< zC=xd10-F_5m0YG=h@eH=HdqWRl}f%6>C?>~j9?hX)$sehrd;q^1DY*5Ud`H2RS=*h znTla6x`w!lVz=IJM^LnCK{Ya&$HJObX_Ta^m?Ih(;lnXib$u-vw(Kl?QpU1qD>q<3 zEDJ5Y-;`h$7B*DLj%qwTkqb038Fs6w^eCFxC+JeY3N#1l72don)SDk?NqHgO}Q8y62gF;@OxAIXydJhLV(a5-nH`|JzDKU^9cYk%%SL;T8keAC>Dovm2? z*1frSCL1C3k&nJYi8fw*!=Cj^TVTe!(4sS$f09vJMZ5g`mMPB z)0@99?y9}F{f&crZ8^C*JwLkmKr;4sU6}j(R_xwCcYeA(h_ W+`9J^=-T9dKIvqe@Ofg#?tcLbL$|sB literal 0 HcmV?d00001 diff --git a/scripts/system/html/img/twitter_logo.png b/scripts/system/html/img/twitter_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..59fd027c2a59cffb4c4f353dbc0ddce2cc3e92d4 GIT binary patch literal 552 zcmV+@0@wYCP)ca5h;T6e`N!ZW%_7Fh zh-L^Db6)RjfuD}F`pZz7)+*=?1MeO>1_Vr(aX5V=K z@A6xs&1GO<5aVQ2;b%fmI~ZW%B^;rP%iP^h{_K772fv|vpZwYO5T_dK=KlZBP{N0|RFI72{;g)@5^- zWs&5@629+${-1W`*WByB{$NjC2*8N7-ZT_pc9Uh%6JeI*Vq|7y`0(@p?T`PCJpa4v q@gE|SAa-*p2ScsC7&Vu+<^lkzl%{SoyW=zf0000' + '
' + @@ -118,15 +116,14 @@ function createShareOverlay(parentID, isGif, shareURL, hifiShareButtonsDisabled) '
' + '' + '
' + - '' + + '' + '
' + - '
' + - '' + - '' + + '
' + + '' + + '' + '
' + '
'; shareOverlayContainer.appendChild(shareOverlay); - twttr.widgets.load(shareOverlay); return shareOverlayContainer; } @@ -167,6 +164,7 @@ function shareWithEveryone(selectedID, isGif) { })); } function shareButtonClicked(selectedID) { + selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", action: "shareButtonClicked", @@ -209,16 +207,6 @@ function handleCaptureSetting(setting) { } window.onload = function () { - // TESTING FUNCTIONS START - // Uncomment and modify the lines below to test SnapshotReview in a browser. - //imageCount = 1; - //addImage({ localPath: 'C:/Users/Zach Fox/Desktop/hifi-snap-by-zfox-on-2017-04-20_14-59-12.gif' }); - //addImage({ localPath: 'C:/Users/Zach Fox/Desktop/hifi-snap-by-zfox-on-2017-04-24_10-49-20.jpg' }); - //document.getElementById('p0').appendChild(createShareOverlay('p0', false, '')); - //addImage({ localPath: 'http://lorempixel.com/553/255' }); - //addImage({localPath: 'c:/Users/howar/OneDrive/Pictures/hifi-snap-by--on-2016-07-27_12-58-43.jpg'}); - // TESTING FUNCTIONS END - openEventBridge(function () { // Set up a handler for receiving the data, and tell the .js we are ready to receive it. EventBridge.scriptEventReceived.connect(function (message) { @@ -301,3 +289,9 @@ function takeSnapshot() { action: "takeSnapshot" })); } + +function testInBrowser() { + imageCount = 1; + //addImage({ localPath: 'http://lorempixel.com/553/255' }); + addImage({ localPath: 'C:/Users/Zach Fox/Desktop/hifi-snap-by-zfox-on-2017-04-25_11-35-13.jpg' }, false, true, true, false); +} From 92187e2424b952952f018fa27576e29fa5bee40e Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 26 Apr 2017 12:08:22 -0700 Subject: [PATCH 074/134] Making progress towards file chooser dialog --- scripts/system/html/SnapshotReview.html | 4 +- scripts/system/html/css/SnapshotReview.css | 60 ++++++++++++++++----- scripts/system/html/img/snapshotIcon.png | Bin 0 -> 16358 bytes scripts/system/html/js/SnapshotReview.js | 44 ++++++++++++--- scripts/system/snapshot.js | 32 ++++++++--- 5 files changed, 110 insertions(+), 30 deletions(-) create mode 100644 scripts/system/html/img/snapshotIcon.png diff --git a/scripts/system/html/SnapshotReview.html b/scripts/system/html/SnapshotReview.html index 940adddd73..9469a9d313 100644 --- a/scripts/system/html/SnapshotReview.html +++ b/scripts/system/html/SnapshotReview.html @@ -27,8 +27,8 @@
-
-
+ +
diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 0d1bd737e5..9db7e35b9f 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -8,12 +8,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html */ -body { - padding: 0; - margin: 0; - overflow: hidden; -} - /* // START styling of top bar and its contents */ @@ -60,6 +54,17 @@ input[type=button].naked:active { // END styling of top bar and its contents */ +/* +// START styling of snapshot instructions panel +*/ +.snapshotInstructions { + font-family: Raleway-Regular; + margin: 0 20px; +} +/* +// END styling of snapshot instructions panel +*/ + /* // START styling of snapshot pane and its contents */ @@ -172,17 +177,18 @@ input[type=button].naked:active { // START styling of snapshot controls (bottom panel) and its contents */ #snapshot-controls { - padding-left: 8px; - padding-right: 8px; width: 100%; position: absolute; left: 0; margin-top: 8px; overflow: hidden; + display: flex; + justify-content: center; } #snap-settings { - float: left; - margin-left: 10px; + display: inline; + width: 150px; + margin: auto; } #snap-settings form input { margin-bottom: 5px; @@ -191,18 +197,28 @@ input[type=button].naked:active { #snap-button { width: 65px; height: 65px; + padding: 0; border-radius: 50%; background: #EA4C5F; border: 3px solid white; - margin-left: auto; - margin-right: auto; + margin: auto; + box-sizing: content-box; + display: inline; } -#snap-button:hover { +#snap-button:disabled { + background: gray; +} +#snap-button:hover:enabled { background: #C62147; } -#snap-button:active { +#snap-button:active:enabled { background: #EA4C5F; } +#snap-settings-right { + display: inline; + width: 150px; + margin: auto; +} /* // END styling of snapshot controls (bottom panel) and its contents */ @@ -210,6 +226,22 @@ input[type=button].naked:active { /* // START misc styling */ +body { + padding: 0; + margin: 0; + overflow: hidden; +} +p { + margin: 2px 0; +} +h4 { + margin: 14px 0 0 0; +} +.centeredImage { + margin: 0 auto; + display: block; +} + .prompt { font-family: Raleway-SemiBold; font-size: 14px; diff --git a/scripts/system/html/img/snapshotIcon.png b/scripts/system/html/img/snapshotIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..5cb2742a32bf362a6cf1e230e9ebad1ac8feffba GIT binary patch literal 16358 zcmeI3X;>529>yn76mUTV1Qj&~H^h)hAcTxz51SIi2m&I=FqwoAk_pKGVTbCiQmED{ z6_p~bC>2-S6_;ogyHHWE(t2G`^ja-eDpstw$bG^Z5WLQZew+7sk`L#9-uImIn{(#m zOY%d4{l}P2G6eu&OkjXe3;=+q?qfUx0Dwv?=?(w@CdzrMF`iYQW6&{)8zY=60!2z6G#(2` zSD{PNgF}(@1cWbPc(|JiG>}HFl%oVp*T@qUYDnY8=*oo@YDm}2V$i#~5D9J!FWrFj z2vG>#2UnqVuA>VRVLQ3fd3;AUm+#7Po=4|^Y!?>DXR)1`AR7Yt5Xh$Yh~aKZr_%*0 zi4+nG{d$H|Nu+L!c!E$uELLi2s$;5?Bd(IM*nB>p1#(y%4pSwOGSz7c0@g4UYP;@` z-W(ySMpT%Rz;Fd!mkYH>3^aj>&17i64H=k4nw^yT!%>RI28 zMO9b|D)Cd{@~$(kgjH%(cXZqsy=O;%7wBFMU55|C-d<|Ye7uLB&s*01`Sd4ChFz)8 z|4Y`h_9ypY=wGUSRU_A-U!=lhm;z4JW2Jk7WH1jAp*M-{VFEBUfveK=3cWAeC1}#X z*4{!Q_MQ)>TY|1v8U!P{*S5PFPC+FC)_}JDN%g76G{iG~zwO(duV){AjMR8kG5EOa zAJy)tu3p23sBtNg3ad~r8LA-O=S|d|I?y(JJQ5EpWT?cQ)l06oVX#HO(%p-R*uX+} z>%a%vy9wXk00IdDNpY1NCfqSOEJImJg-pQeZ|oma(03O=K6oOo65$fm-AT}YMd&r_ zxpcRHZgZjv0`A%rY(dw}FraO4{|MdbPfYIW&*Oj~2(s9^?;fjud9XF|-B$hbU~9iK zOo0*ZoSxAJHS4)_S5^0_mndNbb$8)%ow$Oo8%)1>upfa!krIWAlqg)JMBySO3KuC+xJZe@MM@MdQlfB? z5`~MDC|smO;UXmp7b#J=NQuHlN)#?qqHvKCg^QFZT%<(dA|(nJDN(pciNZxn6fRPt zaFG&)ih|C^0`t zjNf`>S$=+b{#T2e3f6k2Z}f)oz2Ja)&vO$VOPX=weXJ@uJed&ZT{H@~}?wKw_;fBi9ZSy@_*!EuA*zdozd zf=!d;=Zs5e**EHqGb_$z>@|=Yw&hjl+I!wz-8?@R&#TLmYiEZWN~YBv(0-ytv_4u< zY1JFx^q9mE*)M9sgqfQbnE%4~*A6BmleR>LYk?-Crrm3^{d2RlUS%fnM!|+T*nd4^ zKL{BcG(urmnYYdE)eYL7yte2ngQ?>-G*9oW&o)o<6IOC+7w;6jTvMxFURA!;j(PCU z>A80-HY{6oVCli@bDw2~h8r8bu-@=R8qhLs!^CheWt8;S<&HOZ+Z_1V!0+RLjYz$V zdE`>yygYK3I%=hOZ;Tb_Eev*@l`w9@wPAnnJ{c8FKNX%$2VDc08W1*%(&~7fnAOL%lqilq@T3BymSZf z`=cf|ig(uAAo=@5Rhy!GAB352nayYxO4hF3l>aSZ9_h%a7Pl_9%sYJT#et-!w8^51 zF!P|<<-n<6qjlnz4#x8=gR}m2ul8{|c&99t;JyFwSYNibStw$6GM*;|-Fd{g52QE= z;;ljykZl1?nPPD7(MPQlXEMI=zS(}cDQAIjgkO`Jv}V)DMR}OC`PGGH#}y-6z&ZsG zW0-9a9FGB?Lp61a_kgAgEvGs(94Yy6y4y`nv8~Zi#Sh{m8;``EYY7m%Rwfk9HD>MR&522+5mbRDW-pzA4 z{Pz#z3y+_?)G+anl2pmj{jEls;+&4gRkQH%on^Pr*Uc|ljm@?jQ=b9wp&cjBpSa%i ztw8o85>v%cnZ`SPf|gne8m>3i*fttxN=tu|T#Q3kj+)z4-hAS@*ZOU*=WCGQPwI;vR5*&5n8$pJR#=b7Q)k<02TpRO<_JeRuM#o*NEwdUT9wu#1 zv$m0y?h`N3tUDHcbq`o=+Z>Z4hz!11-Z9?s{G_9!w;Rn`{UQp; ztUQ^2;=0M-Q|ixxcg-gkn%pSfQIVhX?9Ycg>?fY7IkVFG?@HN=8Oe(;?|i^@xF0bl z{dB5fb#(rQ32mjJ{-?wJ%QHeE?(e$!LT)mu;2iuUf#H1a$m9n!kEGD zvUP3d9L7^d{dC4=(-4Swlru9S?qZSc?aG=3x7r>2zu_Kgh|JA7W7aykRVF;=w!HLO zwuNR!qS341bt!1z7q-4am*PC@xSF==g%iIjUUlS4jn{>HIh$77OenD3FV8X?clOS` zk67PNF0_yNY;Md(hnP+CVv3kCpF75EUbOs4`fnpGI;$5+Kb|JC^4Rv' + + '
' + + '

This app lets you take and share snaps and GIFs with your connections in High Fidelity.

' + + "

Setup Instructions

" + + "

Before you can begin taking snaps, please choose where you'd like to save snaps on your computer:

" + + '
' + + ''; + document.getElementById("snap-button").disabled = true; +} +function chooseSnapshotLocation() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "chooseSnapshotLocation" + })); +} +function clearImages() { + document.getElementById("snap-button").disabled = false; + var snapshotImagesDiv = document.getElementById("snapshot-images"); + snapshotImagesDiv.classList.remove("snapshotInstructions"); while (snapshotImagesDiv.hasChildNodes()) { snapshotImagesDiv.removeChild(snapshotImagesDiv.lastChild); } @@ -207,6 +227,7 @@ function handleCaptureSetting(setting) { } window.onload = function () { + testInBrowser(false); openEventBridge(function () { // Set up a handler for receiving the data, and tell the .js we are ready to receive it. EventBridge.scriptEventReceived.connect(function (message) { @@ -218,6 +239,12 @@ window.onload = function () { } switch (message.action) { + case 'showSetupInstructions': + showSetupInstructions(); + break; + case 'snapshotLocationChosen': + clearImages(); + break; case 'clearPreviousImages': clearImages(); break; @@ -274,8 +301,7 @@ window.onload = function () { type: "snapshot", action: "ready" })); - }); - + });; }; function snapshotSettings() { EventBridge.emitWebEvent(JSON.stringify({ @@ -290,8 +316,12 @@ function takeSnapshot() { })); } -function testInBrowser() { - imageCount = 1; - //addImage({ localPath: 'http://lorempixel.com/553/255' }); - addImage({ localPath: 'C:/Users/Zach Fox/Desktop/hifi-snap-by-zfox-on-2017-04-25_11-35-13.jpg' }, false, true, true, false); +function testInBrowser(isTestingSetupInstructions) { + if (isTestingSetupInstructions) { + showSetupInstructions(); + } else { + imageCount = 1; + //addImage({ localPath: 'http://lorempixel.com/553/255' }); + addImage({ localPath: 'C:/Users/valef/Desktop/hifi-snap-by-zfox-on-2017-04-26_10-26-53.jpg' }, false, true, true, false); + } } diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index fe6ab3f28f..1f685924f6 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -104,13 +104,31 @@ function onMessage(message) { action: "captureSettings", setting: Settings.getValue("alsoTakeAnimatedSnapshot", true) })); - tablet.emitScriptEvent(JSON.stringify({ - type: "snapshot", - action: "showPreviousImages", - options: snapshotOptions, - image_data: imageData, - canShare: !!isDomainOpen(Settings.getValue("previousSnapshotDomainID")) - })); + if (Settings.getValue("snapshotsLocation", "") !== "") { + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "showPreviousImages", + options: snapshotOptions, + image_data: imageData, + canShare: !!isDomainOpen(Settings.getValue("previousSnapshotDomainID")) + })); + } else { + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "showSetupInstructions" + })); + } + break; + case 'chooseSnapshotLocation': + var snapshotPath = Window.browse("Choose Snapshots Directory","",""); + + if (!snapshotPath.isEmpty()) { // not cancelled + Settings.setValue("snapshotsLocation", snapshotPath); + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "snapshotLocationChosen" + })); + } break; case 'openSettings': if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar")) From 322121428398fb926658c73ba2c1571471a11699 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 26 Apr 2017 12:38:38 -0700 Subject: [PATCH 075/134] New Snap FTUE flow --- .../scripting/WindowScriptingInterface.cpp | 22 +++++++++++++++++++ .../src/scripting/WindowScriptingInterface.h | 1 + scripts/system/html/css/SnapshotReview.css | 1 + scripts/system/html/js/SnapshotReview.js | 9 ++++++++ scripts/system/snapshot.js | 10 +++++++-- 5 files changed, 41 insertions(+), 2 deletions(-) diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 2df825d643..1e14c24da3 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -168,6 +168,28 @@ void WindowScriptingInterface::ensureReticleVisible() const { } } +/// Display a "browse to directory" dialog. If `directory` is an invalid file or directory the browser will start at the current +/// working directory. +/// \param const QString& title title of the window +/// \param const QString& directory directory to start the file browser at +/// \param const QString& nameFilter filter to filter filenames by - see `QFileDialog` +/// \return QScriptValue file path as a string if one was selected, otherwise `QScriptValue::NullValue` +QScriptValue WindowScriptingInterface::browseDir(const QString& title, const QString& directory) { + ensureReticleVisible(); + QString path = directory; + if (path.isEmpty()) { + path = getPreviousBrowseLocation(); + } +#ifndef Q_OS_WIN + path = fixupPathForMac(directory); +#endif + QString result = OffscreenUi::getExistingDirectory(nullptr, title, path); + if (!result.isEmpty()) { + setPreviousBrowseLocation(QFileInfo(result).absolutePath()); + } + return result.isEmpty() ? QScriptValue::NullValue : QScriptValue(result); +} + /// Display an open file dialog. If `directory` is an invalid file or directory the browser will start at the current /// working directory. /// \param const QString& title title of the window diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 7641110972..2b1e48d918 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -51,6 +51,7 @@ public slots: QScriptValue confirm(const QString& message = ""); QScriptValue prompt(const QString& message = "", const QString& defaultText = ""); CustomPromptResult customPrompt(const QVariant& config); + QScriptValue browseDir(const QString& title = "", const QString& directory = ""); QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); QScriptValue browseAssets(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 9db7e35b9f..ef737870e0 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -204,6 +204,7 @@ input[type=button].naked:active { margin: auto; box-sizing: content-box; display: inline; + outline:none; } #snap-button:disabled { background: gray; diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 9ee45281ac..418e623aa8 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -25,6 +25,14 @@ function showSetupInstructions() { ''; document.getElementById("snap-button").disabled = true; } +function showSetupComplete() { + var snapshotImagesDiv = document.getElementById("snapshot-images"); + snapshotImagesDiv.className = "snapshotInstructions"; + snapshotImagesDiv.innerHTML = 'Snapshot Instructions' + + '
' + + "

You're all set!

" + + '

Try taking a snapshot by pressing the button below.

'; +} function chooseSnapshotLocation() { EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", @@ -244,6 +252,7 @@ window.onload = function () { break; case 'snapshotLocationChosen': clearImages(); + showSetupComplete(); break; case 'clearPreviousImages': clearImages(); diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 1f685924f6..88014d5c50 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -117,12 +117,18 @@ function onMessage(message) { type: "snapshot", action: "showSetupInstructions" })); + Settings.setValue("previousStillSnapPath", ""); + Settings.setValue("previousStillSnapStoryID", ""); + Settings.setValue("previousStillSnapSharingDisabled", false); + Settings.setValue("previousAnimatedSnapPath", ""); + Settings.setValue("previousAnimatedSnapStoryID", ""); + Settings.setValue("previousAnimatedSnapSharingDisabled", false); } break; case 'chooseSnapshotLocation': - var snapshotPath = Window.browse("Choose Snapshots Directory","",""); + var snapshotPath = Window.browseDir("Choose Snapshots Directory", "", ""); - if (!snapshotPath.isEmpty()) { // not cancelled + if (snapshotPath) { // not cancelled Settings.setValue("snapshotsLocation", snapshotPath); tablet.emitScriptEvent(JSON.stringify({ type: "snapshot", From 6711e8cbc2cdf3217ef3ae4d0250daa3eabc9be8 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 26 Apr 2017 13:01:02 -0700 Subject: [PATCH 076/134] Remove duplicate setting; Make settings page open; button state --- interface/src/ui/PreferencesDialog.cpp | 5 -- scripts/system/snapshot.js | 64 ++++++++++++++------------ 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index a12d9020ae..617ac1ed1c 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -116,11 +116,6 @@ void setupPreferences() { auto preference = new BrowsePreference(SNAPSHOTS, "Put my snapshots here", getter, setter); preferences->addPreference(preference); } - { - auto getter = []()->bool { return SnapshotAnimated::alsoTakeAnimatedSnapshot.get(); }; - auto setter = [](bool value) { SnapshotAnimated::alsoTakeAnimatedSnapshot.set(value); }; - preferences->addPreference(new CheckPreference(SNAPSHOTS, "Take Animated GIF Snapshot", getter, setter)); - } { auto getter = []()->float { return SnapshotAnimated::snapshotAnimatedDuration.get(); }; auto setter = [](float value) { SnapshotAnimated::snapshotAnimatedDuration.set(value); }; diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 88014d5c50..4cb1232a58 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -24,6 +24,7 @@ var buttonConnected = false; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var button = tablet.addButton({ icon: "icons/tablet-icons/snap-i.svg", + activeIcon: "icons/tablet-icons/snap-a.svg", text: buttonName, sortOrder: 5 }); @@ -137,8 +138,7 @@ function onMessage(message) { } break; case 'openSettings': - if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar")) - || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar"))) { + if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", true)) || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", false))) { Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "General Preferences"); } else { tablet.loadQMLOnTop("TabletGeneralPreferences.qml"); @@ -229,29 +229,34 @@ function onMessage(message) { var SNAPSHOT_REVIEW_URL = Script.resolvePath("html/SnapshotReview.html"); var isInSnapshotReview = false; -function openSnapApp() { - var previousStillSnapPath = Settings.getValue("previousStillSnapPath"); - var previousStillSnapStoryID = Settings.getValue("previousStillSnapStoryID"); - var previousStillSnapSharingDisabled = Settings.getValue("previousStillSnapSharingDisabled"); - var previousAnimatedSnapPath = Settings.getValue("previousAnimatedSnapPath"); - var previousAnimatedSnapStoryID = Settings.getValue("previousAnimatedSnapStoryID"); - var previousAnimatedSnapSharingDisabled = Settings.getValue("previousAnimatedSnapSharingDisabled"); - snapshotOptions = { - containsGif: previousAnimatedSnapPath !== "", - processingGif: false, - shouldUpload: false +function onButtonClicked() { + if (isInSnapshotReview){ + // for toolbar-mode: go back to home screen, this will close the window. + tablet.gotoHomeScreen(); + } else { + var previousStillSnapPath = Settings.getValue("previousStillSnapPath"); + var previousStillSnapStoryID = Settings.getValue("previousStillSnapStoryID"); + var previousStillSnapSharingDisabled = Settings.getValue("previousStillSnapSharingDisabled"); + var previousAnimatedSnapPath = Settings.getValue("previousAnimatedSnapPath"); + var previousAnimatedSnapStoryID = Settings.getValue("previousAnimatedSnapStoryID"); + var previousAnimatedSnapSharingDisabled = Settings.getValue("previousAnimatedSnapSharingDisabled"); + snapshotOptions = { + containsGif: previousAnimatedSnapPath !== "", + processingGif: false, + shouldUpload: false + } + imageData = []; + if (previousAnimatedSnapPath !== "") { + imageData.push({ localPath: previousAnimatedSnapPath, story_id: previousAnimatedSnapStoryID, buttonDisabled: previousAnimatedSnapSharingDisabled }); + } + if (previousStillSnapPath !== "") { + imageData.push({ localPath: previousStillSnapPath, story_id: previousStillSnapStoryID, buttonDisabled: previousStillSnapSharingDisabled }); + } + tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL); + tablet.webEventReceived.connect(onMessage); + HMD.openTablet(); + isInSnapshotReview = true; } - imageData = []; - if (previousAnimatedSnapPath !== "") { - imageData.push({ localPath: previousAnimatedSnapPath, story_id: previousAnimatedSnapStoryID, buttonDisabled: previousAnimatedSnapSharingDisabled }); - } - if (previousStillSnapPath !== "") { - imageData.push({ localPath: previousStillSnapPath, story_id: previousStillSnapStoryID, buttonDisabled: previousStillSnapSharingDisabled }); - } - tablet.gotoWebScreen(SNAPSHOT_REVIEW_URL); - tablet.webEventReceived.connect(onMessage); - HMD.openTablet(); - isInSnapshotReview = true; } function snapshotUploaded(isError, reply) { @@ -315,7 +320,7 @@ function takeSnapshot() { Window.stillSnapshotTaken.connect(stillSnapshotTaken); } if (buttonConnected) { - button.clicked.disconnect(openSnapApp); + button.clicked.disconnect(onButtonClicked); buttonConnected = false; } @@ -366,7 +371,7 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { } Window.stillSnapshotTaken.disconnect(stillSnapshotTaken); if (!buttonConnected) { - button.clicked.connect(openSnapApp); + button.clicked.connect(onButtonClicked); buttonConnected = true; } @@ -430,7 +435,7 @@ function processingGifStarted(pathStillSnapshot) { function processingGifCompleted(pathAnimatedSnapshot) { Window.processingGifCompleted.disconnect(processingGifCompleted); if (!buttonConnected) { - button.clicked.connect(openSnapApp); + button.clicked.connect(onButtonClicked); buttonConnected = true; } @@ -469,6 +474,7 @@ function maybeDeleteSnapshotStories() { } } function onTabletScreenChanged(type, url) { + button.editProperties({ isActive: !isInSnapshotReview }); if (isInSnapshotReview) { tablet.webEventReceived.disconnect(onMessage); isInSnapshotReview = false; @@ -482,14 +488,14 @@ function onUsernameChanged() { } } -button.clicked.connect(openSnapApp); +button.clicked.connect(onButtonClicked); buttonConnected = true; Window.snapshotShared.connect(snapshotUploaded); tablet.screenChanged.connect(onTabletScreenChanged); Account.usernameChanged.connect(onUsernameChanged); Script.scriptEnding.connect(function () { if (buttonConnected) { - button.clicked.disconnect(openSnapApp); + button.clicked.disconnect(onButtonClicked); buttonConnected = false; } if (tablet) { From b26f31704b7591e735e6b8fd393407aafa543758 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 26 Apr 2017 13:17:00 -0700 Subject: [PATCH 077/134] Fix button state; use request() --- scripts/system/snapshot.js | 68 ++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 4cb1232a58..06d519969e 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -111,7 +111,7 @@ function onMessage(message) { action: "showPreviousImages", options: snapshotOptions, image_data: imageData, - canShare: !!isDomainOpen(Settings.getValue("previousSnapshotDomainID")) + canShare: !isDomainOpen(Settings.getValue("previousSnapshotDomainID")) })); } else { tablet.emitScriptEvent(JSON.stringify({ @@ -229,11 +229,13 @@ function onMessage(message) { var SNAPSHOT_REVIEW_URL = Script.resolvePath("html/SnapshotReview.html"); var isInSnapshotReview = false; +var shouldActivateButton = false; function onButtonClicked() { if (isInSnapshotReview){ // for toolbar-mode: go back to home screen, this will close the window. tablet.gotoHomeScreen(); } else { + shouldActivateButton = true; var previousStillSnapPath = Settings.getValue("previousStillSnapPath"); var previousStillSnapStoryID = Settings.getValue("previousStillSnapStoryID"); var previousStillSnapSharingDisabled = Settings.getValue("previousStillSnapSharingDisabled"); @@ -343,7 +345,7 @@ function isDomainOpen(id) { if (!id) { return false; } - var request = new XMLHttpRequest(); + var options = [ 'now=' + new Date().toISOString(), 'include_actions=concurrency', @@ -351,15 +353,19 @@ function isDomainOpen(id) { 'restriction=open,hifi' // If we're sharing, we're logged in // If we're here, protocol matches, and it is online ]; - var url = location.metaverseServerUrl + "/api/v1/user_stories?" + options.join('&'); - request.open("GET", url, false); - request.send(); - if (request.status !== 200) { - return false; - } - var response = JSON.parse(request.response); // Not parsed for us. - return (response.status === 'success') && - response.total_entries; + var url = METAVERSE_BASE + "/api/v1/user_stories?" + options.join('&'); + + return request({ + uri: url, + method: 'GET' + }, function (error, response) { + if (error || (response.status !== 'success')) { + print("ERROR getting open status of domain: ", error || response.status); + return false; + } else { + return response.total_entries; + } + }); } function stillSnapshotTaken(pathStillSnapshot, notify) { @@ -382,7 +388,7 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { snapshotOptions = { containsGif: false, processingGif: false, - canShare: !!isDomainOpen(domainId) + canShare: !isDomainOpen(domainId) }; imageData = [{ localPath: pathStillSnapshot, href: href }]; Settings.setValue("previousStillSnapPath", pathStillSnapshot); @@ -414,7 +420,7 @@ function processingGifStarted(pathStillSnapshot) { containsGif: true, processingGif: true, loadingGifPath: Script.resolvePath(Script.resourcesPath() + 'icons/loadingDark.gif'), - canShare: !!isDomainOpen(domainId) + canShare: !isDomainOpen(domainId) }; imageData = [{ localPath: pathStillSnapshot, href: href }]; Settings.setValue("previousStillSnapPath", pathStillSnapshot); @@ -442,7 +448,7 @@ function processingGifCompleted(pathAnimatedSnapshot) { snapshotOptions = { containsGif: true, processingGif: false, - canShare: !!isDomainOpen(domainId) + canShare: !isDomainOpen(domainId) } imageData = [{ localPath: pathAnimatedSnapshot, href: href }]; Settings.setValue("previousAnimatedSnapPath", pathAnimatedSnapshot); @@ -455,26 +461,24 @@ function processingGifCompleted(pathAnimatedSnapshot) { })); } function maybeDeleteSnapshotStories() { - if (storyIDsToMaybeDelete.length > 0) { - print("User took new snapshot & didn't share old one(s); deleting old snapshot stories"); - storyIDsToMaybeDelete.forEach(function (element, idx, array) { - request({ - uri: METAVERSE_BASE + '/api/v1/user_stories/' + element, - method: 'DELETE' - }, function (error, response) { - if (error || (response.status !== 'success')) { - print("ERROR deleting snapshot story: ", error || response.status); - return; - } else { - print("SUCCESS deleting snapshot story with ID", element); - } - }) - }); - storyIDsToMaybeDelete = []; - } + storyIDsToMaybeDelete.forEach(function (element, idx, array) { + request({ + uri: METAVERSE_BASE + '/api/v1/user_stories/' + element, + method: 'DELETE' + }, function (error, response) { + if (error || (response.status !== 'success')) { + print("ERROR deleting snapshot story: ", error || response.status); + return; + } else { + print("SUCCESS deleting snapshot story with ID", element); + } + }) + }); + storyIDsToMaybeDelete = []; } function onTabletScreenChanged(type, url) { - button.editProperties({ isActive: !isInSnapshotReview }); + button.editProperties({ isActive: shouldActivateButton }); + shouldActivateButton = false; if (isInSnapshotReview) { tablet.webEventReceived.disconnect(onMessage); isInSnapshotReview = false; From a24a48843fdb6c692d098bdb7ffcbfc6b031bee1 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 26 Apr 2017 16:22:01 -0700 Subject: [PATCH 078/134] Checkpoint --- scripts/system/html/css/SnapshotReview.css | 133 ++++++++++------ scripts/system/html/img/shareIcon.png | Bin 0 -> 15201 bytes scripts/system/html/js/SnapshotReview.js | 177 ++++++++++----------- scripts/system/snapshot.js | 83 ++++++++-- 4 files changed, 234 insertions(+), 159 deletions(-) create mode 100644 scripts/system/html/img/shareIcon.png diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index ef737870e0..fdfc3bfca9 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -86,71 +86,49 @@ input[type=button].naked:active { } .gifLabel { + position:absolute; + left: 15px; + top: 10px; font-family: Raleway-SemiBold; font-size: 18px; color: white; - float: left; text-shadow: 2px 2px 3px #000000; - margin-left: 20px; -} -.shareButtonDiv { - display: flex; - align-items: center; - font-family: Raleway-SemiBold; - font-size: 14px; - color: white; - float: right; - text-shadow: 2px 2px 3px #000000; - width: 100px; - height: 100%; - margin-right: 10px; -} -.shareButtonLabel { - vertical-align: middle; -} -.shareButton { - background-color: white; - width: 40px; - height: 40px; - border-radius: 50%; - border-width: 0; - margin-left: 5px; -} -.shareButton:hover { - background-color: #afafaf; -} -.shareButton:active { - background-color: white; } +/* +// END styling of snapshot pane and its contents +*/ /* -// START styling of share overlay +// START styling of share bar */ -.shareOverlayDiv { - text-align: center; -} .shareControls { - text-align: left; display: flex; - justify-content: center; + justify-content: space-between; flex-direction: row; - align-items: flex-start; + align-items: center; height: 50px; + line-height: 60px; + width: calc(100% - 8px); + position: absolute; + bottom: 4px; + left: 4px; + right: 4px; } -.shareOverlayLabel { - line-height: 75px; +.shareButtons { + display: flex; + align-items: center; + margin-left: 40px; + height: 100%; } -.hifiShareControls { +.blastToConnections { text-align: left; - width: 40%; - margin-left: 10%; + margin-right: 25px; + height: 29px; } -.buttonShareControls { +.shareWithEveryone { text-align: left; - height: 50px; - line-height: 50px; - width: 40%; - margin-right: 10%; + margin-right: 8px; + height: 29px; } .facebookButton { background-image: url(../img/fb_logo.png); @@ -166,12 +144,65 @@ input[type=button].naked:active { display: inline-block; margin-right: 8px; } +.showShareButtonsButtonDiv { + display: inline-flex; + align-items: center; + font-family: Raleway-SemiBold; + font-size: 14px; + color: white; + text-shadow: 2px 2px 3px #000000; + height: 100%; + margin-right: 10px; +} +.showShareButton { + width: 40px; + height: 40px; + border-radius: 50%; + border-width: 0; + margin-left: 5px; + outline: none; +} +.showShareButton.active { + border-color: #00b4ef; + border-width: 3px; + background-color: white; +} +.showShareButton.active:hover { + background-color: #afafaf; +} +.showShareButton.active:active { + background-color: white; +} +.showShareButton.inactive { + border-width: 0; + background-color: white; +} +.showShareButton.inactive:hover { + background-color: #afafaf; +} +.showShareButton.inactive:active { + background-color: white; +} +.showShareButtonDots { + display: flex; + width: 32px; + height: 40px; + position: absolute; + right: 14px; + pointer-events: none; +} +.showShareButtonDots > span { + width: 10px; + height: 10px; + margin: auto; + background-color: #0093C5; + border-radius: 50%; + border-width: 0; + display: inline; +} /* // END styling of share overlay */ -/* -// END styling of snapshot pane and its contents -*/ /* // START styling of snapshot controls (bottom panel) and its contents diff --git a/scripts/system/html/img/shareIcon.png b/scripts/system/html/img/shareIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..0486ac920222964fc18287bd03131bc70ed942bd GIT binary patch literal 15201 zcmeI3TZ|J`7{|{F5-IM!pkbp3!>mRyPUq6=jMFYlyU<3uuFINTObkqCPP-G@nPFzS z?d}u0(FBA@l&l7fl1PX?NRS6_5Wx_m5i}A*P*CK-#E1%-5LbvsGrhOF-RueB#hhQ# zK78N#f8U({xlCWWy}xf=yXzho0H8e{iw*z)I-SoQ3jhF@@}a)~01J)SMhk#Ni=9s! z*!yZH0Q|4&;IKWMSTD$W)-5S|61odn12~A?1w)dzK$}Ryw5o;3t4EKMgsOzdAzy+? z7!f$4#>P#!VZ3ip9^WGK3fUcYi3OpMHL}o_h(b1_SwbO17V8R{B{*T4B#J}qEg`bU zX-Etw`iY2cLc-_vQ!?ub5&_=L`uHH{eVE`F)=x7$&3Y+@6&PM%SfUIWb`b<2no3F- zh^{TSGnG_`9I>ll3fJkPMo2Gjt14A~Cxxo1IZ<*}bFo>`H5J z%z*R&wDg=Q!`@M-*`q?#MxB=V&#yk zDSF;oCDP53+IrWj!Kh@zu%o0HFU9!>S)btJ1h0=`0s_O-6)BBus*!-Uq)4_jmnsco zo9pO`ni`u^mD z&&HEgxe%tGL{X82lx}7vJFI4r~Fm z$+;z$%@4nRzs|L{IGM{Gr8jU0LL&4L$l6zSz4cNmTsI~TAyx~)|5|ZsvYLa z-I~I6D(4Man)L=ll402I^Lcz?@x@vnZdUZG1(+F*s#&O6&V9vs%bMoIjKYnrgmsCs zu(TEGewGyzrRYF(ZP+E!)gPP{&8?+2qgx1Wf&{h;32YY<*e)cnT}WWNkid2!f$c&9 z+l2(S3khr&64)*zuw6)CyO6+kA%X2e0^5ZIwhIYt7ZTVmB(PmbV7rjOb|Hc7LIT@` z1hxwaY!?#PE+nvBNMO5=z;+>l?Lq?Eg#@+>32YY<*e)cnT}WWNkid2!F{@p!+Fv-K zri92m*&Y7qaNr;SfRJMY2>`|)0ASk$0N4KoVB60C@K3jnwp zkM;}}&L5i$bxbgKKHL4_DBK^>=ZEfo^uk-;zrViYtBcQdpIPazUb@M4o2UV zyDxnFET7E(;a(aLp!HVuUXpp^t{Ep+SfgPU~B)pPj@|X zwk`DK>J?1K{vE-#Wvd^IeX*O|{$29Y07Op|9@#PE+O?#wXJ6;kTD-d+)JR UXL=U)Ie&tU_x42(t=as{e=9bQEdT%j literal 0 HcmV?d00001 diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 418e623aa8..8a266198a5 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -31,7 +31,7 @@ function showSetupComplete() { snapshotImagesDiv.innerHTML = 'Snapshot Instructions' + '
' + "

You're all set!

" + - '

Try taking a snapshot by pressing the button below.

'; + '

Try taking a snapshot by pressing the red button below.

'; } function chooseSnapshotLocation() { EventBridge.emitWebEvent(JSON.stringify({ @@ -54,25 +54,30 @@ function addImage(image_data, isGifLoading, isShowingPreviousImages, canSharePre if (!image_data.localPath) { return; } - var div = document.createElement("DIV"); var id = "p" + idCounter++; + // imageContainer setup + var imageContainer = document.createElement("DIV"); + imageContainer.id = id; + imageContainer.style.width = "100%"; + imageContainer.style.height = "251px"; + imageContainer.style.display = "flex"; + imageContainer.style.justifyContent = "center"; + imageContainer.style.alignItems = "center"; + imageContainer.style.position = "relative"; + // img setup var img = document.createElement("IMG"); - div.id = id; img.id = id + "img"; - div.style.width = "100%"; - div.style.height = "" + 502 / imageCount + "px"; - div.style.display = "flex"; - div.style.justifyContent = "center"; - div.style.alignItems = "center"; - div.style.position = "relative"; if (imageCount > 1) { img.setAttribute("class", "multiple"); } img.src = image_data.localPath; - div.appendChild(img); - document.getElementById("snapshot-images").appendChild(div); - var isGif = img.src.split('.').pop().toLowerCase() === "gif"; + imageContainer.appendChild(img); + document.getElementById("snapshot-images").appendChild(imageContainer); paths.push(image_data.localPath); + var isGif = img.src.split('.').pop().toLowerCase() === "gif"; + if (isGif) { + imageContainer.innerHTML += 'GIF'; + } if (!isGifLoading && !isShowingPreviousImages) { shareForUrl(id); } else if (isShowingPreviousImages && canSharePreviousImages) { @@ -83,92 +88,68 @@ function appendShareBar(divID, story_id, isGif, hifiShareButtonsDisabled) { var story_url = "https://highfidelity.com/user_stories/" + story_id; var parentDiv = document.getElementById(divID); parentDiv.setAttribute('data-story-id', story_id); - document.getElementById(divID).appendChild(createShareOverlay(divID, isGif, story_url, hifiShareButtonsDisabled)); + document.getElementById(divID).appendChild(createShareBar(divID, isGif, story_url, hifiShareButtonsDisabled)); } -function createShareOverlay(parentID, isGif, shareURL, hifiShareButtonsDisabled) { - var shareOverlayContainer = document.createElement("DIV"); - shareOverlayContainer.id = parentID + "shareOverlayContainer"; - shareOverlayContainer.style.position = "absolute"; - shareOverlayContainer.style.top = "0px"; - shareOverlayContainer.style.left = "0px"; - shareOverlayContainer.style.display = "flex"; - shareOverlayContainer.style.alignItems = "flex-end"; - shareOverlayContainer.style.width = "100%"; - shareOverlayContainer.style.height = "100%"; - +function createShareBar(parentID, isGif, shareURL, hifiShareButtonsDisabled) { var shareBar = document.createElement("div"); - shareBar.id = parentID + "shareBar" - shareBar.style.display = "inline"; - shareBar.style.width = "100%"; - shareBar.style.height = "60px"; - shareBar.style.lineHeight = "60px"; - shareBar.style.clear = "both"; - shareBar.style.marginLeft = "auto"; - shareBar.style.marginRight = "auto"; - shareBar.innerHTML = isGif ? 'GIF' : ""; - var shareButtonID = parentID + "shareButton"; - shareBar.innerHTML += '
' + - '' + - '' + - '
' - shareOverlayContainer.appendChild(shareBar); - - var shareOverlayBackground = document.createElement("div"); - shareOverlayBackground.id = parentID + "shareOverlayBackground"; - shareOverlayBackground.style.display = "none"; - shareOverlayBackground.style.position = "absolute"; - shareOverlayBackground.style.zIndex = "1"; - shareOverlayBackground.style.top = "0px"; - shareOverlayBackground.style.left = "0px"; - shareOverlayBackground.style.backgroundColor = "black"; - shareOverlayBackground.style.opacity = "0.5"; - shareOverlayBackground.style.width = "100%"; - shareOverlayBackground.style.height = "100%"; - shareOverlayContainer.appendChild(shareOverlayBackground); - - var shareOverlay = document.createElement("div"); - shareOverlay.id = parentID + "shareOverlay"; - shareOverlay.className = "shareOverlayDiv"; - shareOverlay.style.display = "none"; - shareOverlay.style.width = "100%"; - shareOverlay.style.height = "100%"; - shareOverlay.style.zIndex = "2"; + shareBar.id = parentID + "shareBar"; + shareBar.className = "shareControls"; + var shareButtonsDivID = parentID + "shareButtonsDiv"; + var showShareButtonsButtonDivID = parentID + "showShareButtonsButtonDiv"; + var showShareButtonsButtonID = parentID + "showShareButtonsButton"; + var showShareButtonsLabelID = parentID + "showShareButtonsLabel"; + var blastToConnectionsButtonID = parentID + "blastToConnectionsButton"; var shareWithEveryoneButtonID = parentID + "shareWithEveryoneButton"; - var inviteConnectionsCheckboxID = parentID + "inviteConnectionsCheckbox"; var facebookButtonID = parentID + "facebookButton"; var twitterButtonID = parentID + "twitterButton"; - shareOverlay.innerHTML = '' + - '
' + - '
' + - '
' + - '
' + - '' + - '
' + - '' + - '
' + - '
' + - '' + - '' + + shareBar.innerHTML += '' + + '' + + '
' + + '' + + '' + + '
' + + '' + '
' + '
'; - shareOverlayContainer.appendChild(shareOverlay); - return shareOverlayContainer; + return shareBar; } -function selectImageToShare(selectedID) { - selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID - var shareOverlayContainer = document.getElementById(selectedID + "shareOverlayContainer"); +function selectImageToShare(selectedID, isSelected) { + if (selectedID.id) { + selectedID = selectedID.id; // sometimes (?), `selectedID` is passed as an HTML object to these functions; we just want the ID + } + var imageContainer = document.getElementById(selectedID); var shareBar = document.getElementById(selectedID + "shareBar"); - var shareOverlayBackground = document.getElementById(selectedID + "shareOverlayBackground"); - var shareOverlay = document.getElementById(selectedID + "shareOverlay"); + var shareButtonsDiv = document.getElementById(selectedID + "shareButtonsDiv"); + var showShareButtonsButton = document.getElementById(selectedID + "showShareButtonsButton"); - shareOverlay.style.outline = "4px solid #00b4ef"; - shareOverlay.style.outlineOffset = "-4px"; + if (isSelected) { + showShareButtonsButton.onclick = function () { selectImageToShare(selectedID, false) }; + showShareButtonsButton.classList.remove("inactive"); + showShareButtonsButton.classList.add("active"); - shareBar.style.display = "none"; + imageContainer.style.outline = "4px solid #00b4ef"; + imageContainer.style.outlineOffset = "-4px"; - shareOverlayBackground.style.display = "inline"; - shareOverlay.style.display = "inline"; + shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.5)"; + + shareButtonsDiv.style.opacity = "1.0"; + } else { + showShareButtonsButton.onclick = function () { selectImageToShare(selectedID, true) }; + showShareButtonsButton.classList.remove("active"); + showShareButtonsButton.classList.add("inactive"); + + imageContainer.style.outline = "none"; + + shareBar.style.backgroundColor = "rgba(0, 0, 0, 0.0)"; + + shareButtonsDiv.style.opacity = "0.0"; + } } function shareForUrl(selectedID) { EventBridge.emitWebEvent(JSON.stringify({ @@ -177,17 +158,29 @@ function shareForUrl(selectedID) { data: paths[parseInt(selectedID.substring(1))] })); } +function blastToConnections(selectedID, isGif) { + selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID + + document.getElementById(selectedID + "blastToConnectionsButton").setAttribute("disabled", "disabled"); + document.getElementById(selectedID + "shareWithEveryoneButton").setAttribute("disabled", "disabled"); + + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "blastToConnections", + story_id: document.getElementById(selectedID).getAttribute("data-story-id"), + isGif: isGif + })); +} function shareWithEveryone(selectedID, isGif) { selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID + document.getElementById(selectedID + "blastToConnectionsButton").setAttribute("disabled", "disabled"); document.getElementById(selectedID + "shareWithEveryoneButton").setAttribute("disabled", "disabled"); - document.getElementById(selectedID + "inviteConnectionsCheckbox").setAttribute("disabled", "disabled"); EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", action: "shareSnapshotWithEveryone", story_id: document.getElementById(selectedID).getAttribute("data-story-id"), - isAnnouncement: document.getElementById(selectedID + "inviteConnectionsCheckbox").getAttribute("checked"), isGif: isGif })); } @@ -201,17 +194,9 @@ function shareButtonClicked(selectedID) { } function cancelSharing(selectedID) { selectedID = selectedID.id; // `selectedID` is passed as an HTML object to these functions; we just want the ID - var shareOverlayContainer = document.getElementById(selectedID + "shareOverlayContainer"); var shareBar = document.getElementById(selectedID + "shareBar"); - var shareOverlayBackground = document.getElementById(selectedID + "shareOverlayBackground"); - var shareOverlay = document.getElementById(selectedID + "shareOverlay"); - - shareOverlay.style.outline = "none"; shareBar.style.display = "inline"; - - shareOverlayBackground.style.display = "none"; - shareOverlay.style.display = "none"; } function handleCaptureSetting(setting) { @@ -331,6 +316,6 @@ function testInBrowser(isTestingSetupInstructions) { } else { imageCount = 1; //addImage({ localPath: 'http://lorempixel.com/553/255' }); - addImage({ localPath: 'C:/Users/valef/Desktop/hifi-snap-by-zfox-on-2017-04-26_10-26-53.jpg' }, false, true, true, false); + addImage({ localPath: 'C:/Users/valef/Desktop/hifi-snap-by-zfox-on-2017-04-26_10-26-53.gif' }, false, true, true, false); } } diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 06d519969e..9ec81eec10 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -85,6 +85,16 @@ function request(options, callback) { // cb(error, responseOfCorrectContentType) httpRequest.send(options.body); } +function openLoginWindow() { + if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false)) + || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { + Menu.triggerOption("Login / Sign Up"); + } else { + tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); + HMD.openTablet(); + } +} + function onMessage(message) { // Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following: // 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.) @@ -97,7 +107,6 @@ function onMessage(message) { } var isLoggedIn; - var needsLogin = false; switch (message.action) { case 'ready': // DOM is ready and page has loaded tablet.emitScriptEvent(JSON.stringify({ @@ -138,7 +147,8 @@ function onMessage(message) { } break; case 'openSettings': - if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", true)) || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", false))) { + if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false)) + || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { Desktop.show("hifi/dialogs/GeneralPreferencesDialog.qml", "General Preferences"); } else { tablet.loadQMLOnTop("TabletGeneralPreferences.qml"); @@ -164,6 +174,64 @@ function onMessage(message) { // TODO } break; + case 'blastToConnections': + isLoggedIn = Account.isLoggedIn(); + storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1); + if (message.isGif) { + Settings.setValue("previousAnimatedSnapSharingDisabled", true); + } else { + Settings.setValue("previousStillSnapSharingDisabled", true); + } + + if (isLoggedIn) { + print('Uploading new story for announcement!'); + + request({ + uri: METAVERSE_BASE + '/api/v1/user_stories/' + message.story_id, + method: 'GET' + }, function (error, response) { + if (error || (response.status !== 'success')) { + print("ERROR getting details about existing snapshot story:", error || response.status); + return; + } else { + var requestBody = { + user_story: { + audience: "for_feed", + action: "announcement", + path: response.user_story.path, + place_name: response.user_story.place_name, + thumbnail_url: response.user_story.thumbnail_url, + details: { + shareable_url: response.user_story.details.shareable_url, + image_url: response.user_story.details.image_url + } + } + } + request({ + uri: METAVERSE_BASE + '/api/v1/user_stories', + method: 'POST', + json: true, + body: requestBody + }, function (error, response) { + if (error || (response.status !== 'success')) { + print("ERROR uploading announcement story: ", error || response.status); + if (message.isGif) { + Settings.setValue("previousAnimatedSnapSharingDisabled", false); + } else { + Settings.setValue("previousStillSnapSharingDisabled", false); + } + return; + } else { + print("SUCCESS uploading announcement story! Story ID:", response.user_story.id); + } + }); + } + }); + + } else { + openLoginWindow(); + } + break; case 'shareSnapshotWithEveryone': isLoggedIn = Account.isLoggedIn(); storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1); @@ -202,19 +270,10 @@ function onMessage(message) { } }); } else { - needsLogin = true; + openLoginWindow(); shareAfterLogin = true; snapshotToShareAfterLogin = { path: message.data, href: message.href || href }; } - if (needsLogin) { - if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar")) - || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar"))) { - Menu.triggerOption("Login / Sign Up"); - } else { - tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); - HMD.openTablet(); - } - } break; case 'shareButtonClicked': print('Twitter or FB "Share" button clicked! Removing ID', message.story_id, 'from storyIDsToMaybeDelete[].'); From b6601feb1d20fe7e19630ff3a438b224eef280ed Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 26 Apr 2017 16:50:11 -0700 Subject: [PATCH 079/134] It's 5 already? --- scripts/system/html/css/SnapshotReview.css | 36 +++++---------------- scripts/system/html/css/hifi-style.css | 36 ++++++++++++++++++++- scripts/system/html/img/shareToFeed.png | Bin 0 -> 15486 bytes scripts/system/html/js/SnapshotReview.js | 4 +-- 4 files changed, 45 insertions(+), 31 deletions(-) create mode 100644 scripts/system/html/img/shareToFeed.png diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index fdfc3bfca9..9258aa7f1a 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -126,9 +126,13 @@ input[type=button].naked:active { height: 29px; } .shareWithEveryone { + background: #DDDDDD url(../img/shareToFeed.png) no-repeat scroll center; + border-width: 0px; text-align: left; margin-right: 8px; height: 29px; + width: 30px; + border-radius: 3px; } .facebookButton { background-image: url(../img/fb_logo.png); @@ -143,6 +147,7 @@ input[type=button].naked:active { height: 29px; display: inline-block; margin-right: 8px; + border-radius: 3px; } .showShareButtonsButtonDiv { display: inline-flex; @@ -188,6 +193,7 @@ input[type=button].naked:active { width: 32px; height: 40px; position: absolute; + top: 5px; right: 14px; pointer-events: none; } @@ -226,8 +232,8 @@ input[type=button].naked:active { } #snap-button { - width: 65px; - height: 65px; + width: 75px; + height: 75px; padding: 0; border-radius: 50%; background: #EA4C5F; @@ -273,32 +279,6 @@ h4 { margin: 0 auto; display: block; } - -.prompt { - font-family: Raleway-SemiBold; - font-size: 14px; -} - -.compound-button { - position: relative; - height: auto; -} - -.compound-button input { - padding-left: 40px; -} - -.compound-button { - display: inline-block; - position: absolute; - left: 12px; - top: 16px; - width: 23px; - height: 23px; - background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgaGVpZ2h0PSI0MCIKICAgd2lkdGg9IjQwIgogICBpZD0ic3ZnMiIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIKICAgdmlld0JveD0iMCAwIDQwIDQwIgogICB5PSIwcHgiCiAgIHg9IjBweCIKICAgdmVyc2lvbj0iMS4xIj48bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGEzNCI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz48ZGM6dGl0bGU+PC9kYzp0aXRsZT48L2NjOldvcms+PC9yZGY6UkRGPjwvbWV0YWRhdGE+PGRlZnMKICAgICBpZD0iZGVmczMyIiAvPjxzdHlsZQogICAgIGlkPSJzdHlsZTQiCiAgICAgdHlwZT0idGV4dC9jc3MiPgoJLnN0MHtmaWxsOiM0MTQwNDI7fQoJLnN0MXtmaWxsOiNDQ0NDQ0M7fQoJLnN0MntmaWxsOiMxMzk4QkI7fQoJLnN0M3tmaWxsOiMzMUQ4RkY7fQo8L3N0eWxlPjxnCiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwtMTEwKSIKICAgICBpZD0iTGF5ZXJfMSI+PGNpcmNsZQogICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MSIKICAgICAgIGlkPSJjaXJjbGUxMyIKICAgICAgIHI9IjQuNDQwMDAwMSIKICAgICAgIGN5PSIxMjYuMTciCiAgICAgICBjeD0iMjAuNTQwMDAxIgogICAgICAgY2xhc3M9InN0MSIgLz48cGF0aAogICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MSIKICAgICAgIGlkPSJwYXRoMTUiCiAgICAgICBkPSJtIDI4Ljg3LDEzOS4yNiBjIDAuMDEsLTAuMDEgMC4wMiwtMC4wMiAwLjAzLC0wLjAzIGwgMCwtMS44NiBjIDAsLTIuNjggLTIuMzMsLTQuNzcgLTUsLTQuNzcgbCAtNi40MiwwIGMgLTIuNjgsMCAtNC44NSwyLjA5IC00Ljg1LDQuNzcgbCAwLDEuODggMTYuMjQsMCB6IgogICAgICAgY2xhc3M9InN0MSIgLz48cGF0aAogICAgICAgc3R5bGU9ImZpbGw6I2ZmZmZmZjtmaWxsLW9wYWNpdHk6MSIKICAgICAgIGlkPSJwYXRoMTciCiAgICAgICBkPSJtIDM4LjE3LDEyMy40MiBjIDAsLTMuOTcgLTMuMjIsLTcuMTkgLTcuMTksLTcuMTkgbCAtMjAuMzEsMCBjIC0zLjk3LDAgLTcuMTksMy4yMiAtNy4xOSw3LjE5IGwgMCwxNC4xOCBjIDAsMy45NyAzLjIyLDcuMTkgNy4xOSw3LjE5IGwgMjAuMzEsMCBjIDMuOTcsMCA3LjE5LC0zLjIyIDcuMTksLTcuMTkgbCAwLC0xNC4xOCB6IG0gLTEuNzgsMTQuMjcgYyAwLDMuMDMgLTIuNDYsNS40OSAtNS40OSw1LjQ5IGwgLTIwLjMyLDAgYyAtMy4wMywwIC01LjQ5LC0yLjQ2IC01LjQ5LC01LjQ5IGwgMCwtMTQuMTkgYyAwLC0zLjAzIDIuNDYsLTUuNDkgNS40OSwtNS40OSBsIDIwLjMzLDAgYyAzLjAzLDAgNS40OSwyLjQ2IDUuNDksNS40OSBsIDAsMTQuMTkgeiIKICAgICAgIGNsYXNzPSJzdDEiIC8+PC9nPjxnCiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwtMTEwKSIKICAgICBpZD0iTGF5ZXJfMiIgLz48L3N2Zz4=); - background-repeat: no-repeat; - background-size: 23px 23px; -} /* // END misc styling */ diff --git a/scripts/system/html/css/hifi-style.css b/scripts/system/html/css/hifi-style.css index 41cda569c9..6e8dd7c710 100644 --- a/scripts/system/html/css/hifi-style.css +++ b/scripts/system/html/css/hifi-style.css @@ -127,8 +127,42 @@ input[type=radio]:active + label > span > span{ display: block; width: 10px; height: 10px; - margin: 2.5px; + margin: 3px; border: 2px solid #36CDFF; border-radius: 50%; background: #00B4EF; +} + +.grayButton { + font-family: FiraSans-SemiBold; + color: white; + padding: 0px 10px; + border-width: 0px; + background-image: linear-gradient(#FFFFFF, #AFAFAF); +} +.grayButton:hover { + background-image: linear-gradient(#FFFFFF, #FFFFFF); +} +.grayButton:active { + background-image: linear-gradient(#AFAFAF, #AFAFAF); +} +.grayButton:disabled { + background-image: linear-gradient(#FFFFFF, ##AFAFAF); +} +.blueButton { + font-family: FiraSans-SemiBold; + color: white; + padding: 0px 10px; + border-radius: 3px; + border-width: 0px; + background-image: linear-gradient(#00B4EF, #1080B8); +} +.blueButton:hover { + background-image: linear-gradient(#00B4EF, #00B4EF); +} +.blueButton:active { + background-image: linear-gradient(#1080B8, #1080B8); +} +.blueButton:disabled { + background-image: linear-gradient(#FFFFFF, ##AFAFAF); } \ No newline at end of file diff --git a/scripts/system/html/img/shareToFeed.png b/scripts/system/html/img/shareToFeed.png new file mode 100644 index 0000000000000000000000000000000000000000..f681c49d8f83a3f77ec8db52f62ff2bd1e6ee628 GIT binary patch literal 15486 zcmeI3ZEO_B8OH}h;@XV~#K_=)(De}10rvLg-n^^3a~z+I-E!tIZn0Y+-rJqCx4zrm z?yh}j%OR&yC{C=pA*!IHp$$@6wIz{h(S%v{3E6 z_+D%iMo~UQnxABUn3@0c?EL3>X79@m2AfxvmEK(n04Qr}^tS*2EC7H-U%DLt0FFFw z1pt(&jqN%BUoJQQW`Ny4T>t?4rqtSIv;|gkf)cgyq7s6(cvLk-z*7@fd7&E`mJsZe zWUuvK2mfrfNTS!e-Wi|*Y8~v78v8Z4uD`ie=zp}ES}n;XMz_~mYbLa`1%j43MS~Wn%|!^bz1retZM2iEW*jRm3`M(0iX~|W zLD3w=aujVzWA&9FG&T-#PLI2&?ME66xm|NPn9$!1`V&8%w5o2vL}bqxNW#q(7oS!U&8) z+0aSaMo;+&YL{XtdY3ZIz9*)q-Mt}&vxk%CnK?79492BtWadmtmMn!7o_JK{>NLn3 ziq@(q5nrmxf|;kc)YX|)<+fCGycZLP_?#v(ccRH=xf{5|o2rT&=W=bGQh~ z%~4cNgw(Qv00C(5B5&}Y8X|9DVGu=>!hN3}BD*X&J8QJHTe^L~J`!6*BY_9Wj>d1Hm^+sr-om$?!4hYz4zPD)W_@+Vwh>7ogAOblRPs3q&0;TQwrO&LZ&)bm@|FHQM5D@RfSViC>r*&F4#Y zN1r1&2@=>YB(PmbV7rjOb|Hc7LIT@`1hxwaY!?#PE+nvBNMO5=z;+>l?Lq?Eg#@+> z32YY<*e)cnT}WWNkid2!f$c&9+l2(S3khr&64)*zuw6)CyO6+kA%X2e0^5ZIwhIYt z7ZTVmB(PmbV7rjOb|Hc7LIT@`#7*rg&3>~9WzlPmS!;YN7oKi3KL-^WTLJ*|F9jg6 z1%PWe07$$GKo14LdmR99y8xK4?706}0{}$}oBXw{@z>u!>+Nc-om2epw{BZobZz+qU??!Ptvi9hLDn4!!pHPi~w%aXohb6B}&bUL);4_1hU8>%Mu$`H|;= zHy`T0cyw{(#dl*Rk1lv&&gS*YF8pWBg;V#IyhSJeS2k;Y_nl*|UmRUux3r3%_jurM z+N!I|-??q1Y&f|6<6lu9+_CZN``9gqvFX`jl?@(sFFLYe ztgF~IR@A#~^Xh+!byN3__aOiqHW4kcpN4=j6gr82la(CI;-5)jASM0cS;pMx| zghrp)J#b=Hge$jo6fLQB-E(Q^e0yR8TmH%;57stsxjJ<2^n=^azqCJm5-h5%neF$k zsC@s}(t(oq9(cY2%pT{%JH;m#eo}PevGA3@$akDA*&~m?J<>im@#Kj^^Y@*7>E1u| zA8FXOtKu@)wfguAV;}#wboLVuogDPLAN^(V@$ssy2M_erMATX;^=I;|6j0zW$m!6R%wyJM;6G<)NoOY!1%;+3)&dhZmf3e(g8T zS1(6?U$eG*p>I&QviQ)LGPCx^`SK6GKm6;;m63s6``n2eGr``uQv0)ehPRnt2shO? L`=7qA<9q)H#YG)p literal 0 HcmV?d00001 diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 8a266198a5..eb28aa4be5 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -104,8 +104,8 @@ function createShareBar(parentID, isGif, shareURL, hifiShareButtonsDisabled) { var twitterButtonID = parentID + "twitterButton"; shareBar.innerHTML += '' + '' + From 0ac660d4ab5872c30549ffcd031c8af888cecf59 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 26 Apr 2017 16:55:20 -0700 Subject: [PATCH 080/134] Cleanup --- scripts/system/html/css/SnapshotReview.css | 2 +- scripts/system/snapshot.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index 9258aa7f1a..d86befc30a 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -117,7 +117,7 @@ input[type=button].naked:active { .shareButtons { display: flex; align-items: center; - margin-left: 40px; + margin-left: 30px; height: 100%; } .blastToConnections { diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 9ec81eec10..ecb7aba982 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -36,6 +36,8 @@ var shareAfterLogin = false; var snapshotToShareAfterLogin; var METAVERSE_BASE = location.metaverseServerUrl; +// It's totally unnecessary to return to C++ to perform many of these requests, such as DELETEing an old story, +// POSTING a new one, PUTTING a new audience, or GETTING story data. It's far more efficient to do all of that within JS 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. From 7297355183b7fccbdc40f796b40e4012ba08532e Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 26 Apr 2017 17:16:01 -0700 Subject: [PATCH 081/134] Open ShareBar on P0 by default --- scripts/system/html/SnapshotReview.html | 2 +- scripts/system/html/js/SnapshotReview.js | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/system/html/SnapshotReview.html b/scripts/system/html/SnapshotReview.html index 9469a9d313..fb40c04d05 100644 --- a/scripts/system/html/SnapshotReview.html +++ b/scripts/system/html/SnapshotReview.html @@ -9,7 +9,7 @@
- +
diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index eb28aa4be5..8a204840ac 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -89,6 +89,9 @@ function appendShareBar(divID, story_id, isGif, hifiShareButtonsDisabled) { var parentDiv = document.getElementById(divID); parentDiv.setAttribute('data-story-id', story_id); document.getElementById(divID).appendChild(createShareBar(divID, isGif, story_url, hifiShareButtonsDisabled)); + if (divID === "p0") { + selectImageToShare(divID, true); + } } function createShareBar(parentID, isGif, shareURL, hifiShareButtonsDisabled) { var shareBar = document.createElement("div"); From 832e16ef8644bcc9b29735f52eb318040a00480d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 27 Apr 2017 11:18:23 -0700 Subject: [PATCH 082/134] Visual tweaks; fix active share buttons; comment out tests --- scripts/system/html/css/SnapshotReview.css | 13 ++++++++----- scripts/system/html/css/hifi-style.css | 2 ++ scripts/system/html/js/SnapshotReview.js | 14 +++++++++----- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/scripts/system/html/css/SnapshotReview.css b/scripts/system/html/css/SnapshotReview.css index d86befc30a..12b91d372b 100644 --- a/scripts/system/html/css/SnapshotReview.css +++ b/scripts/system/html/css/SnapshotReview.css @@ -60,6 +60,8 @@ input[type=button].naked:active { .snapshotInstructions { font-family: Raleway-Regular; margin: 0 20px; + width: 100%; + height: 50%; } /* // END styling of snapshot instructions panel @@ -119,6 +121,7 @@ input[type=button].naked:active { align-items: center; margin-left: 30px; height: 100%; + width: 80%; } .blastToConnections { text-align: left; @@ -158,6 +161,7 @@ input[type=button].naked:active { text-shadow: 2px 2px 3px #000000; height: 100%; margin-right: 10px; + width: 20%; } .showShareButton { width: 40px; @@ -217,7 +221,6 @@ input[type=button].naked:active { width: 100%; position: absolute; left: 0; - margin-top: 8px; overflow: hidden; display: flex; justify-content: center; @@ -225,20 +228,20 @@ input[type=button].naked:active { #snap-settings { display: inline; width: 150px; - margin: auto; + margin: 2px auto 0 auto; } #snap-settings form input { margin-bottom: 5px; } #snap-button { - width: 75px; - height: 75px; + width: 72px; + height: 72px; padding: 0; border-radius: 50%; background: #EA4C5F; border: 3px solid white; - margin: auto; + margin: 2px auto 0 auto; box-sizing: content-box; display: inline; outline:none; diff --git a/scripts/system/html/css/hifi-style.css b/scripts/system/html/css/hifi-style.css index 6e8dd7c710..37810707e0 100644 --- a/scripts/system/html/css/hifi-style.css +++ b/scripts/system/html/css/hifi-style.css @@ -156,6 +156,8 @@ input[type=radio]:active + label > span > span{ border-radius: 3px; border-width: 0px; background-image: linear-gradient(#00B4EF, #1080B8); + min-height: 30px; + } .blueButton:hover { background-image: linear-gradient(#00B4EF, #00B4EF); diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 8a204840ac..f5aaa59d90 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -22,7 +22,9 @@ function showSetupInstructions() { "

Setup Instructions

" + "

Before you can begin taking snaps, please choose where you'd like to save snaps on your computer:

" + '
' + - ''; + '
' + + '' + + '
'; document.getElementById("snap-button").disabled = true; } function showSetupComplete() { @@ -106,7 +108,7 @@ function createShareBar(parentID, isGif, shareURL, hifiShareButtonsDisabled) { var facebookButtonID = parentID + "facebookButton"; var twitterButtonID = parentID + "twitterButton"; shareBar.innerHTML += '' + - ' diff --git a/domain-server/resources/web/settings/js/sweetalert.min.js b/domain-server/resources/web/js/sweetalert.min.js similarity index 100% rename from domain-server/resources/web/settings/js/sweetalert.min.js rename to domain-server/resources/web/js/sweetalert.min.js diff --git a/domain-server/resources/web/settings/index.shtml b/domain-server/resources/web/settings/index.shtml index 3eb7a53726..1812c52dad 100644 --- a/domain-server/resources/web/settings/index.shtml +++ b/domain-server/resources/web/settings/index.shtml @@ -99,7 +99,7 @@ - + diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 620b11d8ad..233cb02dff 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1633,6 +1633,15 @@ QString pathForAssignmentScript(const QUuid& assignmentUUID) { return directory.absoluteFilePath(uuidStringWithoutCurlyBraces(assignmentUUID)); } +QString DomainServer::pathForRedirect(QString path) const { + // make sure the passed path has a leading slash + if (!path.startsWith('/')) { + path.insert(0, '/'); + } + + return "http://" + _hostname + ":" + QString::number(_httpManager.serverPort()) + path; +} + const QString URI_OAUTH = "/oauth"; bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) { const QString JSON_MIME_TYPE = "application/json"; @@ -1640,6 +1649,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url const QString URI_ASSIGNMENT = "/assignment"; const QString URI_NODES = "/nodes"; const QString URI_SETTINGS = "/settings"; + const QString URI_ENTITY_FILE_UPLOAD = "/content/upload"; const QString UUID_REGEX_STRING = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"; @@ -1869,6 +1879,25 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url // respond with a 200 code for successful upload connection->respond(HTTPConnection::StatusCode200); + return true; + } else if (url.path() == URI_ENTITY_FILE_UPLOAD) { + // this is an entity file upload, ask the HTTPConnection to parse the data + QList formData = connection->parseFormData(); + + Headers redirectHeaders; + + if (formData.size() > 0 && formData[0].second.size() > 0) { + // invoke our method to hand the new octree file off to the octree server + QMetaObject::invokeMethod(this, "handleOctreeFileReplacement", + Qt::QueuedConnection, Q_ARG(QByteArray, formData[0].second)); + + // respond with a 200 for success + connection->respond(HTTPConnection::StatusCode200); + } else { + // respond with a 400 for failure + connection->respond(HTTPConnection::StatusCode400); + } + return true; } } else if (connection->requestOperation() == QNetworkAccessManager::DeleteOperation) { @@ -2159,8 +2188,7 @@ Headers DomainServer::setupCookieHeadersFromProfileReply(QNetworkReply* profileR cookieHeaders.insert("Set-Cookie", cookieString.toUtf8()); // redirect the user back to the homepage so they can present their cookie and be authenticated - QString redirectString = "http://" + _hostname + ":" + QString::number(_httpManager.serverPort()); - cookieHeaders.insert("Location", redirectString.toUtf8()); + cookieHeaders.insert("Location", pathForRedirect().toUtf8()); return cookieHeaders; } @@ -2560,3 +2588,20 @@ void DomainServer::setupGroupCacheRefresh() { _metaverseGroupCacheTimer->start(REFRESH_GROUPS_INTERVAL_MSECS); } } + +void DomainServer::handleOctreeFileReplacement(QByteArray octreeFile) { + // enumerate the nodes and find any octree type servers with active sockets + + auto limitedNodeList = DependencyManager::get(); + limitedNodeList->eachMatchingNode([](const SharedNodePointer& node){ + return node->getType() == NodeType::EntityServer && node->getActiveSocket(); + }, [&octreeFile, limitedNodeList](const SharedNodePointer& octreeNode){ + // setup a packet to send to this octree server with the new octree file data + auto octreeFilePacketList = NLPacketList::create(PacketType::OctreeFileReplacement, QByteArray(), true, true); + octreeFilePacketList->write(octreeFile); + + qDebug() << "Sending an octree file replacement of" << octreeFile.size() << "bytes to" << octreeNode; + + limitedNodeList->sendPacketList(std::move(octreeFilePacketList), *octreeNode); + }); +} diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 4c5c42acee..63b82cb37d 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -100,6 +100,8 @@ private slots: void handleSuccessfulICEServerAddressUpdate(QNetworkReply& requestReply); void handleFailedICEServerAddressUpdate(QNetworkReply& requestReply); + void handleOctreeFileReplacement(QByteArray octreeFile); + signals: void iceServerChanged(); void userConnected(); @@ -161,6 +163,8 @@ private: void setupGroupCacheRefresh(); + QString pathForRedirect(QString path = QString()) const; + SubnetList _acSubnetWhitelist; DomainGatekeeper _gatekeeper; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index bd30cdd29c..adaa7a848c 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -39,7 +39,7 @@ const QSet NON_SOURCED_PACKETS = QSet() << PacketType::ICEServerPeerInformation << PacketType::ICEServerQuery << PacketType::ICEServerHeartbeat << PacketType::ICEServerHeartbeatACK << PacketType::ICEPing << PacketType::ICEPingReply << PacketType::ICEServerHeartbeatDenied << PacketType::AssignmentClientStatus << PacketType::StopNode - << PacketType::DomainServerRemovedNode << PacketType::UsernameFromIDReply; + << PacketType::DomainServerRemovedNode << PacketType::UsernameFromIDReply << PacketType::OctreeFileReplacement; PacketVersion versionForPacketType(PacketType packetType) { switch (packetType) { diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index d312427ca7..f803b83887 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -113,7 +113,8 @@ public: EntityPhysics, EntityServerScriptLog, AdjustAvatarSorting, - LAST_PACKET_TYPE = AdjustAvatarSorting + OctreeFileReplacement, + LAST_PACKET_TYPE = OctreeFileReplacement }; }; diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index 7034790eaf..b79ce5537f 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -33,6 +33,7 @@ #include "OctreePersistThread.h" const int OctreePersistThread::DEFAULT_PERSIST_INTERVAL = 1000 * 30; // every 30 seconds +const QString OctreePersistThread::REPLACEMENT_FILE_EXTENSION = ".replace"; OctreePersistThread::OctreePersistThread(OctreePointer tree, const QString& filename, const QString& backupDirectory, int persistInterval, bool wantBackup, const QJsonObject& settings, bool debugTimestampNow, @@ -131,10 +132,46 @@ quint64 OctreePersistThread::getMostRecentBackupTimeInUsecs(const QString& forma return mostRecentBackupInUsecs; } +void OctreePersistThread::possiblyReplaceContent() { + // before we load the normal file, check if there's a pending replacement file + auto replacementFileName = _filename + REPLACEMENT_FILE_EXTENSION; + + QFile replacementFile { replacementFileName }; + if (replacementFile.exists()) { + // we have a replacement file to process + qDebug() << "Replacing models file with" << replacementFileName; + + // first take the current models file and move it to a different filename, appended with the timestamp + QFile currentFile { _filename }; + if (currentFile.exists()) { + static const QString FILENAME_TIMESTAMP_FORMAT = "yyyyMMdd-hhmmss"; + auto backupFileName = _filename + ".backup." + QDateTime::currentDateTime().toString(FILENAME_TIMESTAMP_FORMAT); + + if (currentFile.rename(backupFileName)) { + qDebug() << "Moved previous models file to" << backupFileName; + } else { + qWarning() << "Could not backup previous models file to" << backupFileName << "- removing replacement models file"; + + if (!QFile::remove(replacementFileName)) { + qWarning() << "Could not remove replacement models file from" << replacementFileName + << "- replacement will be re-attempted on next server restart"; + } + } + } + + // rename the replacement file to match what the persist thread is just about to read + if (!replacementFile.rename(_filename)) { + qWarning() << "Could not replace models file with" << replacementFileName << "- starting with empty models file"; + } + } +} + bool OctreePersistThread::process() { if (!_initialLoadComplete) { + possiblyReplaceContent(); + quint64 loadStarted = usecTimestampNow(); qCDebug(octree) << "loading Octrees from file: " << _filename << "..."; diff --git a/libraries/octree/src/OctreePersistThread.h b/libraries/octree/src/OctreePersistThread.h index 927304e862..2441223467 100644 --- a/libraries/octree/src/OctreePersistThread.h +++ b/libraries/octree/src/OctreePersistThread.h @@ -32,6 +32,7 @@ public: }; static const int DEFAULT_PERSIST_INTERVAL; + static const QString REPLACEMENT_FILE_EXTENSION; OctreePersistThread(OctreePointer tree, const QString& filename, const QString& backupDirectory, int persistInterval = DEFAULT_PERSIST_INTERVAL, bool wantBackup = false, @@ -60,6 +61,7 @@ protected: bool getMostRecentBackup(const QString& format, QString& mostRecentBackupFileName, QDateTime& mostRecentBackupTime); quint64 getMostRecentBackupTimeInUsecs(const QString& format); void parseSettings(const QJsonObject& settings); + void possiblyReplaceContent(); private: OctreePointer _tree; From aed762e986ba38fd42de41274050725e842e6c7e Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Thu, 27 Apr 2017 17:16:03 -0700 Subject: [PATCH 088/134] ux --- interface/resources/qml/hifi/Card.qml | 70 ++++++----- interface/resources/qml/hifi/Feed.qml | 34 +++-- .../qml/hifi/tablet/TabletAddressDialog.qml | 116 ++++++++++-------- 3 files changed, 127 insertions(+), 93 deletions(-) diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index 59fa66af0b..0b34a8f9ac 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -17,7 +17,7 @@ import QtGraphicalEffects 1.0 import "toolbars" import "../styles-uit" -Rectangle { +Item { id: root; property string userName: ""; property string placeName: ""; @@ -45,7 +45,7 @@ Rectangle { property int textSizeSmall: 18; property int stackShadowNarrowing: 5; property string defaultThumbnail: Qt.resolvedUrl("../../images/default-domain.gif"); - property int shadowHeight: 20; + property int shadowHeight: 10; HifiConstants { id: hifi } function pastTime(timestamp) { // Answer a descriptive string @@ -70,6 +70,40 @@ Rectangle { } property bool hasGif: imageUrl.indexOf('.gif') === (imageUrl.length - 4); + + DropShadow { + visible: isStacked; + anchors.fill: shadow1; + source: shadow1; + verticalOffset: 2; + radius: 4; + samples: 9; + color: hifi.colors.baseGrayShadow; + } + Rectangle { + id: shadow1; + visible: isStacked; + width: parent.width - stackShadowNarrowing; + height: shadowHeight; + anchors { + top: parent.bottom; + horizontalCenter: parent.horizontalCenter; + } + } + DropShadow { + anchors.fill: base; + source: base; + verticalOffset: 2; + radius: 4; + samples: 9; + color: hifi.colors.baseGrayShadow; + } + Rectangle { + id: base; + color: "white"; + anchors.fill: parent; + } + AnimatedImage { id: animation; // Always visible, to drive loading, but initially covered up by lobby during load. @@ -96,34 +130,6 @@ Rectangle { } } } - Rectangle { - id: shadow1; - visible: isStacked; - width: parent.width - stackShadowNarrowing; - height: shadowHeight / 2; - anchors { - top: parent.bottom; - horizontalCenter: parent.horizontalCenter; - } - gradient: Gradient { - GradientStop { position: 0.0; color: "gray" } - GradientStop { position: 1.0; color: "white" } - } - } - Rectangle { - id: shadow2; - visible: isStacked; - width: shadow1.width - stackShadowNarrowing; - height: shadowHeight / 2; - anchors { - top: shadow1.bottom; - horizontalCenter: parent.horizontalCenter; - } - gradient: Gradient { - GradientStop { position: 0.0; color: "gray" } - GradientStop { position: 1.0; color: "white" } - } - } property int dropHorizontalOffset: 0; property int dropVerticalOffset: 1; property int dropRadius: 2; @@ -168,7 +174,7 @@ Rectangle { source: "../../images/snap-icon.svg" width: 40; height: 40; - visible: action === 'snapshot'; + visible: (action === 'snapshot') && (messageHeight >= 40); } RalewayRegular { id: message; @@ -209,7 +215,7 @@ Rectangle { StateImage { id: actionIcon; imageURL: "../../images/info-icon-2-state.svg"; - size: 32; + size: 30; buttonState: messageArea.containsMouse ? 1 : 0; anchors { bottom: parent.bottom; diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index d95518b891..8cae42f21c 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -23,7 +23,15 @@ Column { property int cardWidth: 212; property int cardHeight: 152; - property int stackedCardShadowHeight: 10; + property int textPadding: 10; + property int smallMargin: 4; + property int messageHeight: 40; + property int textSize: 24; + property int textSizeSmall: 18; + property int stackShadowNarrowing: 5; + property int stackedCardShadowHeight: 4; + property int labelSize: 20; + property string metaverseServerUrl: ''; property string actions: 'snapshot'; onActionsChanged: fillDestinations(); @@ -161,25 +169,27 @@ Column { root.visible = !!suggestions.count; } - RalewayLight { + RalewayBold { id: label; text: labelText; - color: hifi.colors.white; - size: 28; + color: hifi.colors.blueAccent; + size: labelSize; } ListView { id: scroll; - clip: true; + //fixme clip: true; model: suggestions; orientation: ListView.Horizontal; highlightMoveDuration: -1; highlightMoveVelocity: -1; - highlight: Rectangle { color: "transparent"; border.width: 4; border.color: hifiStyleConstants.colors.blueHighlight; z: 1; } + highlight: Rectangle { color: "transparent"; border.width: 4; border.color: hifiStyleConstants.colors.primaryHighlight; z: 1; } + currentIndex: -1; - spacing: 14; + spacing: 12; width: parent.width; height: cardHeight + stackedCardShadowHeight; delegate: Card { + id: card; width: cardWidth; height: cardHeight; goFunction: root.goFunction; @@ -193,7 +203,15 @@ Column { onlineUsers: model.online_users; storyId: model.metaverseId; drillDownToPlace: model.drillDownToPlace; - shadowHeight: stackedCardShadowHeight; + + textPadding: root.textPadding; + smallMargin: root.smallMargin; + messageHeight: root.messageHeight; + textSize: root.textSize; + textSizeSmall: root.textSizeSmall; + stackShadowNarrowing: root.stackShadowNarrowing; + shadowHeight: root.stackedCardShadowHeight; + hoverThunk: function () { scroll.currentIndex = index; } unhoverThunk: function () { scroll.currentIndex = -1; } } diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 5578b94168..3ccdd93542 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -154,7 +154,7 @@ StackView { left: parent.left; } - HifiStyles.RalewayLight { + HifiStyles.RalewayRegular { id: notice; font.pixelSize: hifi.fonts.pixelSize * 0.7; anchors { @@ -224,63 +224,73 @@ StackView { Rectangle { id: bgMain; - color: hifiStyleConstants.colors.faintGray50; anchors { top: addressBar.bottom; bottom: parent.keyboardEnabled ? keyboard.top : parent.bottom; left: parent.left; right: parent.right; } - ScrollView { - anchors.fill: bgMain; - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff; - verticalScrollBarPolicy: Qt.ScrollBarAsNeeded; - Rectangle { // Column margins require QtQuick 2.7, which we don't use yet. - id: column; - property real pad: 10; - width: bgMain.width - column.pad; - height: stack.height; - color: "transparent"; - anchors { - left: parent.left; - leftMargin: column.pad; - topMargin: column.pad; + Rectangle { + id: addressShadow; + width: parent.width; + height: 42 - 33; + gradient: Gradient { + GradientStop { position: 0.0; color: "gray" } + GradientStop { position: 1.0; color: "white" } + } + } + Rectangle { // Column margins require QtQuick 2.7, which we don't use yet. + id: column; + property real pad: 10; + width: bgMain.width - column.pad; + height: stack.height; + color: "transparent"; + anchors { + left: parent.left; + leftMargin: column.pad; + top: addressShadow.bottom; + topMargin: column.pad; + } + Column { + id: stack; + width: column.width; + spacing: 33 - places.labelSize; + Feed { + id: happeningNow; + width: parent.width; + cardWidth: 312 + (2 * 4); + 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; } - Column { - id: stack; - width: column.width; - spacing: column.pad; - Feed { - id: happeningNow; - width: parent.width; - property real cardScale: 1.5; - cardWidth: places.cardWidth * happeningNow.cardScale; - cardHeight: places.cardHeight * happeningNow.cardScale; - 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; - } - Feed { - id: places; - width: parent.width; - metaverseServerUrl: addressBarDialog.metaverseServerUrl; - labelText: 'Places'; - actions: 'concurrency'; - filter: addressLine.text; - goFunction: goCard; - } - Feed { - id: snapshots; - width: parent.width; - metaverseServerUrl: addressBarDialog.metaverseServerUrl; - labelText: 'Recent Activity'; - actions: 'snapshot'; - filter: addressLine.text; - goFunction: goCard; - } + Feed { + id: places; + width: parent.width; + cardWidth: 210; + cardHeight: 110 + messageHeight; + messageHeight: 44; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + labelText: 'PLACES'; + actions: 'concurrency'; + filter: addressLine.text; + goFunction: goCard; + } + Feed { + id: snapshots; + width: parent.width; + cardWidth: 143 + (2 * 4); + cardHeight: 75 + messageHeight + 4; + messageHeight: 32; + textPadding: 6; + metaverseServerUrl: addressBarDialog.metaverseServerUrl; + labelText: 'RECENT SNAPS'; + actions: 'snapshot'; + filter: addressLine.text; + goFunction: goCard; } } } @@ -369,8 +379,8 @@ StackView { notice.text = "Go To a place, @user, path, or network address:"; notice.color = hifiStyleConstants.colors.baseGrayHighlight; } else { - notice.text = AddressManager.isConnected ? "Your location:" : "Not Connected"; - notice.color = AddressManager.isConnected ? hifiStyleConstants.colors.baseGrayHighlight : hifiStyleConstants.colors.redHighlight; + notice.text = AddressManager.isConnected ? "YOUR LOCATION" : "NOT CONNECTED"; + notice.color = AddressManager.isConnected ? hifiStyleConstants.colors.blueHighlight : hifiStyleConstants.colors.redHighlight; // Display hostname, which includes ip address, localhost, and other non-placenames. location.text = (AddressManager.placename || AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : ''); } From 764f6c69eaa4dbed7a06f0104b93864bb28c9367 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 27 Apr 2017 17:39:25 -0700 Subject: [PATCH 089/134] 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 038ac745c8af5fe8ced7d1bd4e3bc093d71fff50 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 27 Apr 2017 17:47:18 -0700 Subject: [PATCH 090/134] clear domain octree details if ES killed --- interface/src/Application.cpp | 38 ++++++++--------------------------- interface/src/Application.h | 1 + 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 39a4b8ee7c..c8e61d87dc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -797,7 +797,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain())); connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); - connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); + connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &Application::clearDomainAvatars); connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this]() { getOverlays().deleteOverlay(getTabletScreenID()); getOverlays().deleteOverlay(getTabletHomeButtonID()); @@ -5196,14 +5196,17 @@ void Application::clearDomainOctreeDetails() { skyStage->setBackgroundMode(model::SunSkyStage::SKY_DEFAULT); _recentlyClearedDomain = true; - - DependencyManager::get()->clearOtherAvatars(); + DependencyManager::get()->clearUnusedResources(); DependencyManager::get()->clearUnusedResources(); DependencyManager::get()->clearUnusedResources(); DependencyManager::get()->clearUnusedResources(); } +void Application::clearDomainAvatars() { + DependencyManager::get()->clearOtherAvatars(); +} + void Application::domainChanged(const QString& domainHostname) { updateWindowTitle(); // disable physics until we have enough information about our new location to not cause craziness. @@ -5273,33 +5276,8 @@ void Application::nodeKilled(SharedNodePointer node) { if (node->getType() == NodeType::AudioMixer) { QMetaObject::invokeMethod(DependencyManager::get().data(), "audioMixerKilled"); } else if (node->getType() == NodeType::EntityServer) { - QUuid nodeUUID = node->getUUID(); - // see if this is the first we've heard of this node... - _entityServerJurisdictions.withReadLock([&] { - if (_entityServerJurisdictions.find(nodeUUID) == _entityServerJurisdictions.end()) { - return; - } - - auto rootCode = _entityServerJurisdictions[nodeUUID].getRootOctalCode(); - VoxelPositionSize rootDetails; - voxelDetailsForCode(rootCode.get(), rootDetails); - - qCDebug(interfaceapp, "model server going away...... v[%f, %f, %f, %f]", - (double)rootDetails.x, (double)rootDetails.y, (double)rootDetails.z, (double)rootDetails.s); - - }); - - // If the model server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server - _entityServerJurisdictions.withWriteLock([&] { - _entityServerJurisdictions.erase(_entityServerJurisdictions.find(nodeUUID)); - }); - - // also clean up scene stats for that server - _octreeServerSceneStats.withWriteLock([&] { - if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) { - _octreeServerSceneStats.erase(nodeUUID); - } - }); + // we lost an entity server, clear all of the domain octree details + clearDomainOctreeDetails(); } else if (node->getType() == NodeType::AvatarMixer) { // our avatar mixer has gone away - clear the hash of avatars DependencyManager::get()->clearOtherAvatars(); diff --git a/interface/src/Application.h b/interface/src/Application.h index dff1de2860..041f1f8930 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -409,6 +409,7 @@ public slots: private slots: void showDesktop(); void clearDomainOctreeDetails(); + void clearDomainAvatars(); void aboutToQuit(); void resettingDomain(); From c61e6a8a9e0e392a9455d96f082d7b4923292273 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 27 Apr 2017 18:24:21 -0700 Subject: [PATCH 091/134] AnimInverseKinematics: reduce number of magic constants --- .../animation/src/AnimInverseKinematics.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index b36abebb1b..6edd969568 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -568,8 +568,10 @@ void AnimInverseKinematics::computeHipsOffset(const std::vector& targe newHipsOffset += targetPosition - actualPosition; // Add downward pressure on the hips - newHipsOffset *= 0.95f; - newHipsOffset -= 1.0f; + const float PRESSURE_SCALE_FACTOR = 0.95f; + const float PRESSURE_TRANSLATION_OFFSET = 1.0f; + newHipsOffset *= PRESSURE_SCALE_FACTOR; + newHipsOffset -= PRESSURE_TRANSLATION_OFFSET; } } else if (target.getType() == IKTarget::Type::RotationAndPosition) { glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans(); @@ -627,7 +629,7 @@ static void setEllipticalSwingLimits(SwingTwistConstraint* stConstraint, float l const int NUM_SUBDIVISIONS = 8; std::vector minDots; minDots.reserve(NUM_SUBDIVISIONS); - float dTheta = (2.0f * PI) / NUM_SUBDIVISIONS; + float dTheta = TWO_PI / NUM_SUBDIVISIONS; float theta = 0.0f; for (int i = 0; i < NUM_SUBDIVISIONS; i++) { minDots.push_back(cosf(glm::length(glm::vec2(anteriorSwingTheta * cosf(theta), lateralSwingTheta * sinf(theta))))); @@ -829,7 +831,9 @@ void AnimInverseKinematics::initConstraints() { stConstraint->setTwistLimits(-MAX_SPINE_TWIST, MAX_SPINE_TWIST); // limit lateral swings more then forward-backward swings - setEllipticalSwingLimits(stConstraint, PI / 30.0f, PI / 20.0f); + const float MAX_SPINE_LATERAL_SWING = PI / 30.0f; + const float MAX_SPINE_ANTERIOR_SWING = PI / 20.0f; + setEllipticalSwingLimits(stConstraint, MAX_SPINE_LATERAL_SWING, MAX_SPINE_ANTERIOR_SWING); if (0 == baseName.compare("Spine1", Qt::CaseSensitive) || 0 == baseName.compare("Spine", Qt::CaseSensitive)) { @@ -844,7 +848,10 @@ void AnimInverseKinematics::initConstraints() { const float MAX_NECK_TWIST = PI / 10.0f; stConstraint->setTwistLimits(-MAX_NECK_TWIST, MAX_NECK_TWIST); - setEllipticalSwingLimits(stConstraint, PI / 10.0f, PI / 8.0f); + // limit lateral swings more then forward-backward swings + const float MAX_NECK_LATERAL_SWING = PI / 10.0f; + const float MAX_NECK_ANTERIOR_SWING = PI / 8.0f; + setEllipticalSwingLimits(stConstraint, MAX_NECK_LATERAL_SWING, MAX_NECK_ANTERIOR_SWING); constraint = static_cast(stConstraint); } else if (0 == baseName.compare("Head", Qt::CaseSensitive)) { From 0ad8f4d985ed7727f9d8cf76bf5d7fec5935c439 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 28 Apr 2017 07:15:22 -0700 Subject: [PATCH 092/134] remove debugging --- interface/resources/qml/hifi/Feed.qml | 3 +-- interface/resources/qml/hifi/tablet/TabletAddressDialog.qml | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index 8cae42f21c..b03144644a 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -126,7 +126,7 @@ Column { } function makeFilteredStoryProcessor() { // answer a function(storyData) that adds it to suggestions if it matches var words = filter.toUpperCase().split(/\s+/).filter(identity); - function suggestable(story) { // fixme add to makeFilteredStoryProcessor + function suggestable(story) { if (story.action === 'snapshot') { return true; } @@ -177,7 +177,6 @@ Column { } ListView { id: scroll; - //fixme clip: true; model: suggestions; orientation: ListView.Horizontal; highlightMoveDuration: -1; diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 3ccdd93542..9689583649 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -262,8 +262,8 @@ 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'; + //actions: 'concurrency,snapshot'; // uncomment this line instead of next to produce fake announcement data for testing. + actions: 'announcement'; filter: addressLine.text; goFunction: goCard; } From 9bc0609b1e2e6534d510354b234c330cbc72a1a5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 28 Apr 2017 10:17:48 -0700 Subject: [PATCH 093/134] address code review comments --- domain-server/resources/web/content/index.shtml | 11 +++++++---- domain-server/src/DomainServer.cpp | 4 ++-- interface/src/Application.cpp | 2 +- libraries/octree/src/OctreePersistThread.cpp | 3 ++- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/domain-server/resources/web/content/index.shtml b/domain-server/resources/web/content/index.shtml index 1d8d3fe1ab..c7eb765878 100644 --- a/domain-server/resources/web/content/index.shtml +++ b/domain-server/resources/web/content/index.shtml @@ -16,20 +16,23 @@

- Upload an entities file (e.g.: models.json.gz) to replace the content of this domain.
+ Upload an entities file (e.g.: models.json.gz) to replace the content of this domain.
Note: Your domain's content will be replaced by the content you upload, but the backup files of your domain's content will not immediately be changed.

- If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored in C:\Users\[user_name]\AppData\Roaming\High Fidelity\assignment-client. + If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:
+

C:\Users\[username]\AppData\Roaming\High Fidelity\assignment-client/entities/models.json.gz
+
/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz
+
/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz

- +
+
-
diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 233cb02dff..782c54419d 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2593,9 +2593,9 @@ void DomainServer::handleOctreeFileReplacement(QByteArray octreeFile) { // enumerate the nodes and find any octree type servers with active sockets auto limitedNodeList = DependencyManager::get(); - limitedNodeList->eachMatchingNode([](const SharedNodePointer& node){ + limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) { return node->getType() == NodeType::EntityServer && node->getActiveSocket(); - }, [&octreeFile, limitedNodeList](const SharedNodePointer& octreeNode){ + }, [&octreeFile, limitedNodeList](const SharedNodePointer& octreeNode) { // setup a packet to send to this octree server with the new octree file data auto octreeFilePacketList = NLPacketList::create(PacketType::OctreeFileReplacement, QByteArray(), true, true); octreeFilePacketList->write(octreeFile); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c8e61d87dc..9fa66262cc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5177,7 +5177,6 @@ void Application::clearDomainOctreeDetails() { qCDebug(interfaceapp) << "Clearing domain octree details..."; resetPhysicsReadyInformation(); - getMyAvatar()->setAvatarEntityDataChanged(true); // to recreate worn entities // reset our node to stats and node to jurisdiction maps... since these must be changing... _entityServerJurisdictions.withWriteLock([&] { @@ -5204,6 +5203,7 @@ void Application::clearDomainOctreeDetails() { } void Application::clearDomainAvatars() { + getMyAvatar()->setAvatarEntityDataChanged(true); // to recreate worn entities DependencyManager::get()->clearOtherAvatars(); } diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index b79ce5537f..ea6bd28fc4 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -152,9 +152,10 @@ void OctreePersistThread::possiblyReplaceContent() { } else { qWarning() << "Could not backup previous models file to" << backupFileName << "- removing replacement models file"; - if (!QFile::remove(replacementFileName)) { + if (!replacementFile.remove()) { qWarning() << "Could not remove replacement models file from" << replacementFileName << "- replacement will be re-attempted on next server restart"; + return; } } } From 4b0e7ceef5360827aae25c7a72eb97db70b34359 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 28 Apr 2017 10:24:04 -0700 Subject: [PATCH 094/134] 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 b44a56db803889ac12ce93b01a70107434a60de3 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 28 Apr 2017 10:32:06 -0700 Subject: [PATCH 095/134] don't save ATP requests to cache if they are range requests --- libraries/networking/src/AssetRequest.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/AssetRequest.cpp b/libraries/networking/src/AssetRequest.cpp index 341c3b45da..9c756b0060 100644 --- a/libraries/networking/src/AssetRequest.cpp +++ b/libraries/networking/src/AssetRequest.cpp @@ -113,8 +113,10 @@ void AssetRequest::start() { _data = data; _totalReceived += data.size(); emit progress(_totalReceived, data.size()); - - saveToCache(getUrl(), data); + + if (!_byteRange.isSet()) { + saveToCache(getUrl(), data); + } } } From f3cc1c2d86d7bcb5e568ac3dca7bedd6544ab906 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 28 Apr 2017 11:30:06 -0700 Subject: [PATCH 096/134] 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 097/134] 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 086941356a842676ddb0ad05037df5f6f7edf6b5 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 28 Apr 2017 14:36:38 -0700 Subject: [PATCH 098/134] fix frozen bots --- libraries/recording/src/recording/Deck.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libraries/recording/src/recording/Deck.cpp b/libraries/recording/src/recording/Deck.cpp index 186516e01c..a4f154f85b 100644 --- a/libraries/recording/src/recording/Deck.cpp +++ b/libraries/recording/src/recording/Deck.cpp @@ -166,6 +166,12 @@ void Deck::processFrames() { if (!overLimit) { auto nextFrameTime = nextClip->positionFrameTime(); nextInterval = (int)Frame::frameTimeToMilliseconds(nextFrameTime - _position); + if (nextInterval < 0) { + qCWarning(recordingLog) << " Unexected nextInterval < 0 nextFrameTime:" << nextFrameTime + << "_position:" << _position << "-- setting nextInterval to 0"; + nextInterval = 0; + } + #ifdef WANT_RECORDING_DEBUG qCDebug(recordingLog) << "Now " << _position; qCDebug(recordingLog) << "Next frame time " << nextInterval; From bace47af7c8e7ace57ba2bd1e56c32bdcdfa0303 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 28 Apr 2017 14:46:27 -0700 Subject: [PATCH 099/134] CR review --- libraries/recording/src/recording/Deck.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/recording/src/recording/Deck.cpp b/libraries/recording/src/recording/Deck.cpp index a4f154f85b..c9ac0524ad 100644 --- a/libraries/recording/src/recording/Deck.cpp +++ b/libraries/recording/src/recording/Deck.cpp @@ -167,7 +167,7 @@ void Deck::processFrames() { auto nextFrameTime = nextClip->positionFrameTime(); nextInterval = (int)Frame::frameTimeToMilliseconds(nextFrameTime - _position); if (nextInterval < 0) { - qCWarning(recordingLog) << " Unexected nextInterval < 0 nextFrameTime:" << nextFrameTime + qCWarning(recordingLog) << "Unexpected nextInterval < 0 nextFrameTime:" << nextFrameTime << "_position:" << _position << "-- setting nextInterval to 0"; nextInterval = 0; } From a99c2426468faef0af60213d2f37bbfd763d0cff Mon Sep 17 00:00:00 2001 From: David Kelly Date: Fri, 28 Apr 2017 15:52:32 -0700 Subject: [PATCH 100/134] increase minimum grip pressure to initiate handshake --- scripts/system/makeUserConnection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 0ffea0c568..8d67e67700 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -13,7 +13,7 @@ var LABEL = "makeUserConnection"; var MAX_AVATAR_DISTANCE = 0.2; // m - var GRIP_MIN = 0.05; // goes from 0-1, so 5% pressed is pressed + var GRIP_MIN = 0.25; // goes from 0-1, so 25% pressed is pressed var MESSAGE_CHANNEL = "io.highfidelity.makeUserConnection"; var STATES = { INACTIVE: 0, From 765a06108621338507a0ea136b209e22f9029704 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 28 Apr 2017 16:26:03 -0700 Subject: [PATCH 101/134] Fix stuck ATP downloads Occasionally ATP requests would get stuck because of a race inside AssetClient::handleAssetReply. This was more likely to happen to small resources. --- libraries/networking/src/AssetClient.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 15e0b8c9b5..054557e920 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -348,18 +348,19 @@ void AssetClient::handleAssetGetReply(QSharedPointer message, S // Store message in case we need to disconnect from it later. callbacks.message = message; + + auto weakNode = senderNode.toWeakRef(); + connect(message.data(), &ReceivedMessage::progress, this, [this, weakNode, messageID, length](qint64 size) { + handleProgressCallback(weakNode, messageID, size, length); + }); + connect(message.data(), &ReceivedMessage::completed, this, [this, weakNode, messageID]() { + handleCompleteCallback(weakNode, messageID); + }); + if (message->isComplete()) { + disconnect(message.data(), nullptr, this, nullptr); callbacks.completeCallback(true, error, message->readAll()); messageCallbackMap.erase(requestIt); - } else { - auto weakNode = senderNode.toWeakRef(); - - connect(message.data(), &ReceivedMessage::progress, this, [this, weakNode, messageID, length](qint64 size) { - handleProgressCallback(weakNode, messageID, size, length); - }); - connect(message.data(), &ReceivedMessage::completed, this, [this, weakNode, messageID]() { - handleCompleteCallback(weakNode, messageID); - }); } } From 89fb0a917b469592ed0d4587dbe70d0a738e5b3e Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 28 Apr 2017 16:27:44 -0700 Subject: [PATCH 102/134] Fix poorly indented if in AssetRequest --- libraries/networking/src/AssetRequest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/AssetRequest.cpp b/libraries/networking/src/AssetRequest.cpp index 9c756b0060..920c7ae036 100644 --- a/libraries/networking/src/AssetRequest.cpp +++ b/libraries/networking/src/AssetRequest.cpp @@ -77,7 +77,7 @@ void AssetRequest::start() { _assetRequestID = assetClient->getAsset(_hash, _byteRange.fromInclusive, _byteRange.toExclusive, [this, that, hash](bool responseReceived, AssetServerError serverError, const QByteArray& data) { - if (!that) { + if (!that) { qCWarning(asset_client) << "Got reply for dead asset request " << hash << "- error code" << _error; // If the request is dead, return return; From 59f84a60c548233d4f7757a93fe2ae256816e93b Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Sat, 29 Apr 2017 00:31:09 +0100 Subject: [PATCH 103/134] 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 104/134] 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 168cfa9b5d68a3f085e4c57a96c643c866d82672 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Fri, 28 Apr 2017 16:39:58 -0700 Subject: [PATCH 105/134] upping polling time plus min grip --- scripts/system/makeUserConnection.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index 8d67e67700..7a3215ca89 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -13,7 +13,7 @@ var LABEL = "makeUserConnection"; var MAX_AVATAR_DISTANCE = 0.2; // m - var GRIP_MIN = 0.25; // goes from 0-1, so 25% pressed is pressed + var GRIP_MIN = 0.75; // goes from 0-1, so 75% pressed is pressed var MESSAGE_CHANNEL = "io.highfidelity.makeUserConnection"; var STATES = { INACTIVE: 0, @@ -592,7 +592,11 @@ Window.makeConnection(false, result.connection); UserActivityLogger.makeUserConnection(connectingId, false, result.connection); } - var POLL_INTERVAL_MS = 200, POLL_LIMIT = 5; + // This is a bit fragile - but to account for skew in when people actually create the + // connection request, I've upped this to 2 seconds (plus the round-trip times) + // TODO: keep track of when the person we are connecting with is done, and don't stop + // until say 1 second after that. + var POLL_INTERVAL_MS = 200, POLL_LIMIT = 10; function handleConnectionResponseAndMaybeRepeat(error, response) { // If response is 'pending', set a short timeout to try again. // If we fail other than pending, set result and immediately call connectionRequestCompleted. From f9debf13886f451eea64173f36b3c2dc17741971 Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 28 Apr 2017 16:53:08 -0700 Subject: [PATCH 106/134] 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 107/134] 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 953a3f7a6f07140396cbeb08d8ab5936e35188f5 Mon Sep 17 00:00:00 2001 From: 1st-BrainStormer <1st-BrainStormer@users.noreply.github.com> Date: Fri, 28 Apr 2017 20:14:01 -0700 Subject: [PATCH 108/134] Update EntityDynamicInterface.cpp Grammar correction. --- libraries/entities/src/EntityDynamicInterface.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/entities/src/EntityDynamicInterface.cpp b/libraries/entities/src/EntityDynamicInterface.cpp index bed3185b8f..2ab9a60397 100644 --- a/libraries/entities/src/EntityDynamicInterface.cpp +++ b/libraries/entities/src/EntityDynamicInterface.cpp @@ -49,20 +49,20 @@ -An dynamic is a callback which is registered with bullet. An dynamic is called-back every physics +A dynamic is a callback which is registered with bullet. A dynamic is called-back every physics simulation step and can do whatever it wants with the various datastructures it has available. An dynamic, for example, can pull an EntityItem toward a point as if that EntityItem were connected to that point by a spring. -In this system, an dynamic is a property of an EntityItem (rather, an EntityItem has a property which +In this system, a dynamic is a property of an EntityItem (rather, an EntityItem has a property which encodes a list of dynamics). Each dynamic has a type and some arguments. Dynamics can be created by a script or when receiving information via an EntityTree data-stream (either over the network or from an svo file). In the interface, if an EntityItem has dynamics, this EntityItem will have pointers to ObjectDynamic -subclass (like ObjectDynamicSpring) instantiations. Code in the entities library affects an dynamic-object +subclass (like ObjectDynamicSpring) instantiations. Code in the entities library affects a dynamic-object via the EntityDynamicInterface (which knows nothing about bullet). When the ObjectDynamic subclass -instance is created, it is registered as an dynamic with bullet. Bullet will call into code in this +instance is created, it is registered as a dynamic with bullet. Bullet will call into code in this instance with the btDynamicInterface every physics-simulation step. Because the dynamic can exist next to the interface's EntityTree or the entity-server's EntityTree, From 2b43a1989f629662bb09f83b1644b7b88c543eab Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 28 Apr 2017 16:40:48 -0700 Subject: [PATCH 109/134] 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 110/134] 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 111/134] 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 112/134] 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 113/134] 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 114/134] 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 115/134] 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 116/134] 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 117/134] 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 118/134] 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 119/134] 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 120/134] 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 121/134] 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 122/134] 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 123/134] 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 124/134] 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 125/134] 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 126/134] 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 127/134] 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 128/134] 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 129/134] 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 130/134] 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 131/134] 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 132/134] 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 133/134] 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 134/134] 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;