From 2f887228cfa9ec7da9dbae36618f57b0dfd731f8 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Fri, 22 Nov 2019 14:51:37 -0700 Subject: [PATCH 01/29] Fix camera pitch by mouse pitch --- .../input-plugins/src/input-plugins/KeyboardMouseDevice.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index f17caf22db..ea948a929b 100755 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -146,7 +146,7 @@ bool KeyboardMouseDevice::isWheelByTouchPad(QWheelEvent* event) { } else { _wheelDeltaRepeatCount.setX(0); } - return deltaValueX != COMMON_WHEEL_DELTA_VALUE && _wheelDeltaRepeatCount.x() < MAX_WHEEL_DELTA_REPEAT; + return deltaValueX < COMMON_WHEEL_DELTA_VALUE && _wheelDeltaRepeatCount.x() < MAX_WHEEL_DELTA_REPEAT; } if (deltaValueY != 0) { if (abs(_lastWheelDelta.y()) == deltaValueY) { @@ -154,7 +154,7 @@ bool KeyboardMouseDevice::isWheelByTouchPad(QWheelEvent* event) { } else { _wheelDeltaRepeatCount.setY(0); } - return deltaValueY != COMMON_WHEEL_DELTA_VALUE && _wheelDeltaRepeatCount.y() < MAX_WHEEL_DELTA_REPEAT; + return deltaValueY < COMMON_WHEEL_DELTA_VALUE && _wheelDeltaRepeatCount.y() < MAX_WHEEL_DELTA_REPEAT; } return false; } From 320095f765e31bb51699caf01b0ecabccfe5751a Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Fri, 22 Nov 2019 15:40:44 -0800 Subject: [PATCH 02/29] Added animedit avatar-animation.json graph editor tool --- tools/animedit/.gitignore | 1 + tools/animedit/LICENCE | 4 + tools/animedit/README.md | 14 + tools/animedit/animedit.pro | 35 ++ tools/animedit/deploy.bat | 3 + tools/animedit/main.cpp | 37 ++ tools/animedit/qml.qrc | 26 ++ tools/animedit/qml/TreeDelegate.qml | 10 + tools/animedit/qml/fields/BooleanField.qml | 44 ++ tools/animedit/qml/fields/IdField.qml | 41 ++ tools/animedit/qml/fields/JSONField.qml | 52 +++ .../animedit/qml/fields/NumberArrayField.qml | 54 +++ tools/animedit/qml/fields/NumberField.qml | 45 ++ tools/animedit/qml/fields/StringField.qml | 44 ++ tools/animedit/qml/fields/TypeField.qml | 67 +++ tools/animedit/qml/main.qml | 262 +++++++++++ tools/animedit/qml/nodes/BlendDirectional.qml | 129 ++++++ tools/animedit/qml/nodes/BlendLinear.qml | 73 +++ tools/animedit/qml/nodes/BlendLinearMove.qml | 85 ++++ tools/animedit/qml/nodes/ClipData.qml | 148 +++++++ tools/animedit/qml/nodes/DefaultPose.qml | 53 +++ .../animedit/qml/nodes/InverseKinematics.qml | 71 +++ tools/animedit/qml/nodes/Manipulator.qml | 72 +++ tools/animedit/qml/nodes/Overlay.qml | 80 ++++ tools/animedit/qml/nodes/PoleVector.qml | 96 ++++ .../animedit/qml/nodes/RandomStateMachine.qml | 109 +++++ tools/animedit/qml/nodes/SplineIK.qml | 139 ++++++ tools/animedit/qml/nodes/StateMachine.qml | 65 +++ tools/animedit/qml/nodes/TwoBoneIK.qml | 120 +++++ tools/animedit/treeitem.cpp | 91 ++++ tools/animedit/treeitem.h | 45 ++ tools/animedit/treemodel.cpp | 418 ++++++++++++++++++ tools/animedit/treemodel.h | 79 ++++ 33 files changed, 2612 insertions(+) create mode 100644 tools/animedit/.gitignore create mode 100644 tools/animedit/LICENCE create mode 100644 tools/animedit/README.md create mode 100644 tools/animedit/animedit.pro create mode 100644 tools/animedit/deploy.bat create mode 100644 tools/animedit/main.cpp create mode 100644 tools/animedit/qml.qrc create mode 100644 tools/animedit/qml/TreeDelegate.qml create mode 100644 tools/animedit/qml/fields/BooleanField.qml create mode 100644 tools/animedit/qml/fields/IdField.qml create mode 100644 tools/animedit/qml/fields/JSONField.qml create mode 100644 tools/animedit/qml/fields/NumberArrayField.qml create mode 100644 tools/animedit/qml/fields/NumberField.qml create mode 100644 tools/animedit/qml/fields/StringField.qml create mode 100644 tools/animedit/qml/fields/TypeField.qml create mode 100644 tools/animedit/qml/main.qml create mode 100644 tools/animedit/qml/nodes/BlendDirectional.qml create mode 100644 tools/animedit/qml/nodes/BlendLinear.qml create mode 100644 tools/animedit/qml/nodes/BlendLinearMove.qml create mode 100644 tools/animedit/qml/nodes/ClipData.qml create mode 100644 tools/animedit/qml/nodes/DefaultPose.qml create mode 100644 tools/animedit/qml/nodes/InverseKinematics.qml create mode 100644 tools/animedit/qml/nodes/Manipulator.qml create mode 100644 tools/animedit/qml/nodes/Overlay.qml create mode 100644 tools/animedit/qml/nodes/PoleVector.qml create mode 100644 tools/animedit/qml/nodes/RandomStateMachine.qml create mode 100644 tools/animedit/qml/nodes/SplineIK.qml create mode 100644 tools/animedit/qml/nodes/StateMachine.qml create mode 100644 tools/animedit/qml/nodes/TwoBoneIK.qml create mode 100644 tools/animedit/treeitem.cpp create mode 100644 tools/animedit/treeitem.h create mode 100644 tools/animedit/treemodel.cpp create mode 100644 tools/animedit/treemodel.h diff --git a/tools/animedit/.gitignore b/tools/animedit/.gitignore new file mode 100644 index 0000000000..86a9657caa --- /dev/null +++ b/tools/animedit/.gitignore @@ -0,0 +1 @@ +*.pro.user \ No newline at end of file diff --git a/tools/animedit/LICENCE b/tools/animedit/LICENCE new file mode 100644 index 0000000000..fb888fd1b7 --- /dev/null +++ b/tools/animedit/LICENCE @@ -0,0 +1,4 @@ +Copyright (c) 2019 High Fidelity, Inc. All rights reserved. + +Distributed under the Apache License, Version 2.0. +See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html diff --git a/tools/animedit/README.md b/tools/animedit/README.md new file mode 100644 index 0000000000..f314b4e5b7 --- /dev/null +++ b/tools/animedit/README.md @@ -0,0 +1,14 @@ +animedit +------------- +avatar-animation.json editor for High Fidelity. + +Use QtCreator to build. + +Known issues: + +* When switching node types, clear old types fields & set new fields to default values. +* Name field does not change when it has focus and leftHandPane selection is changed. + + + + diff --git a/tools/animedit/animedit.pro b/tools/animedit/animedit.pro new file mode 100644 index 0000000000..0e55ac5d1e --- /dev/null +++ b/tools/animedit/animedit.pro @@ -0,0 +1,35 @@ +QT += quick +CONFIG += c++11 + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Refer to the documentation for the +# deprecated API to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + main.cpp \ + treeitem.cpp \ + treemodel.cpp + +RESOURCES += qml.qrc + +# Additional import path used to resolve QML modules in Qt Creator's code model +QML_IMPORT_PATH = + +# Additional import path used to resolve QML modules just for Qt Quick Designer +QML_DESIGNER_IMPORT_PATH = + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +HEADERS += \ + treeitem.h \ + treemodel.h diff --git a/tools/animedit/deploy.bat b/tools/animedit/deploy.bat new file mode 100644 index 0000000000..e788d9d204 --- /dev/null +++ b/tools/animedit/deploy.bat @@ -0,0 +1,3 @@ +mkdir C:\Users\ajthy\Documents\animedit\bin +copy C:\Users\ajthy\Documents\build-animedit-Desktop_Qt_5_12_3_MSVC2015_64bit-Release\release\animedit.exe C:\Users\ajthy\Documents\animedit\bin +C:\Qt\5.12.3\msvc2015_64\bin\windeployqt.exe -qmldir C:\Users\ajthy\Documents\animedit\qml C:\Users\ajthy\Documents\animedit\bin\animedit.exe \ No newline at end of file diff --git a/tools/animedit/main.cpp b/tools/animedit/main.cpp new file mode 100644 index 0000000000..4c7ea4de68 --- /dev/null +++ b/tools/animedit/main.cpp @@ -0,0 +1,37 @@ +// +// Copyright (c) 2019 High Fidelity, Inc. All rights reserved. +// +// 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 "treemodel.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + + QGuiApplication app(argc, argv); + + QQmlApplicationEngine engine; + + // expose model + TreeModel model; + engine.rootContext()->setContextProperty("theModel", &model); + + const QUrl url(QStringLiteral("qrc:/qml/main.qml")); + QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { + if (!obj && url == objUrl) { + QCoreApplication::exit(-1); + } else { + ; + } + }, Qt::QueuedConnection); + engine.load(url); + + return app.exec(); +} diff --git a/tools/animedit/qml.qrc b/tools/animedit/qml.qrc new file mode 100644 index 0000000000..25fab138aa --- /dev/null +++ b/tools/animedit/qml.qrc @@ -0,0 +1,26 @@ + + + qml/main.qml + qml/TreeDelegate.qml + qml/fields/BooleanField.qml + qml/fields/IdField.qml + qml/fields/JSONField.qml + qml/fields/NumberArrayField.qml + qml/fields/NumberField.qml + qml/fields/StringField.qml + qml/fields/TypeField.qml + qml/nodes/BlendDirectional.qml + qml/nodes/BlendLinear.qml + qml/nodes/BlendLinearMove.qml + qml/nodes/ClipData.qml + qml/nodes/DefaultPose.qml + qml/nodes/InverseKinematics.qml + qml/nodes/Manipulator.qml + qml/nodes/Overlay.qml + qml/nodes/PoleVector.qml + qml/nodes/RandomStateMachine.qml + qml/nodes/StateMachine.qml + qml/nodes/SplineIK.qml + qml/nodes/TwoBoneIK.qml + + diff --git a/tools/animedit/qml/TreeDelegate.qml b/tools/animedit/qml/TreeDelegate.qml new file mode 100644 index 0000000000..f4f21591c9 --- /dev/null +++ b/tools/animedit/qml/TreeDelegate.qml @@ -0,0 +1,10 @@ +import QtQuick 2.0 + +Item { + Text { + anchors.fill: parent + color: styleData.textColor + elide: styleData.elideMode + text: styleData.value + } +} diff --git a/tools/animedit/qml/fields/BooleanField.qml b/tools/animedit/qml/fields/BooleanField.qml new file mode 100644 index 0000000000..8ba2b93855 --- /dev/null +++ b/tools/animedit/qml/fields/BooleanField.qml @@ -0,0 +1,44 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 + + +Row { + id: row + height: 20 + width: 300 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + + property string key + property bool value + property bool optional + + function setValue(newValue) { + parent.fieldChanged(key, newValue); + } + + Text { + id: keyText + y: 5 + width: 100 + text: key + ":" + font.pixelSize: 12 + color: optional ? "blue" : "black" + } + + CheckBox { + id: valueCheckBox + x: 100 + y: 5 + width: 200 + checked: value ? Qt.Checked : Qt.Unchecked + onCheckedChanged: { + setValue(checked); + } + } +} diff --git a/tools/animedit/qml/fields/IdField.qml b/tools/animedit/qml/fields/IdField.qml new file mode 100644 index 0000000000..b3ee5b6ac9 --- /dev/null +++ b/tools/animedit/qml/fields/IdField.qml @@ -0,0 +1,41 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 + +Row { + id: row + x: 0 + y: 0 + height: 20 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + + property string theValue + + function setValue(newValue) { + var ROLE_NAME = 0x0101; + theModel.setData(leftHandPane.currentIndex, newValue, ROLE_NAME); + } + + Text { + id: element + y: 5 + width: 100 + text: qsTr("Id:") + font.pixelSize: 12 + } + + TextField { + id: textField + x: 100 + width: 200 + text: theValue + onEditingFinished: { + setValue(text); + } + } +} diff --git a/tools/animedit/qml/fields/JSONField.qml b/tools/animedit/qml/fields/JSONField.qml new file mode 100644 index 0000000000..f7b16b82cd --- /dev/null +++ b/tools/animedit/qml/fields/JSONField.qml @@ -0,0 +1,52 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 + + +Column { + id: row + width: parent.width + height: parent.height + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + + property string key + property var value + property bool optional + + spacing: 5 + + function setValue(newValue) { + parent.fieldChanged(key, newValue); + } + + Text { + id: keyText + y: 5 + width: row.width + text: key + ":" + font.pixelSize: 12 + color: optional ? "blue" : "black" + } + + TextArea { + id: valueTextField + x: 0 + width: (keyText.width - 20) + height: (parent.height - 120) + wrapMode: TextEdit.NoWrap + + // TODO: validate + + text: JSON.stringify(value, null, 4) + + onEditingFinished: { + value = JSON.parse(text); + setValue(value); + } + } +} diff --git a/tools/animedit/qml/fields/NumberArrayField.qml b/tools/animedit/qml/fields/NumberArrayField.qml new file mode 100644 index 0000000000..dd393dc259 --- /dev/null +++ b/tools/animedit/qml/fields/NumberArrayField.qml @@ -0,0 +1,54 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 + + +Row { + id: row + height: 20 + width: 300 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + + property string key + property var value + property bool optional + + function setValue(newValue) { + parent.fieldChanged(key, newValue); + } + + Text { + id: keyText + y: 5 + width: 100 + text: key + ":" + font.pixelSize: 12 + color: optional ? "blue" : "black" + } + + TextField { + id: valueTextField + x: 100 + width: 200 + + // first start with a regex for an array of numbers + // ^\[\s*(\d+)(\s*,\s*(\d+))*\]$|\[\s*\] + // then a regex for a floating point number + // \d+\.?\d* + // then substitue the second into the \d+ of the first, yeilding this monstrocity. + // ^\[\s*(\d+\.?\d*)(\s*,\s*(\d+\.?\d*))*\]$|\[\s*\] + + //validator: RegExpValidator { regExp: /^\[\s*(\d+\.?\d*)(\s*,\s*(\d+\.?\d*))*\]$|\[\s*\]/ } + + text: JSON.stringify(value) + onEditingFinished: { + value = JSON.parse(text); + setValue(value); + } + } +} diff --git a/tools/animedit/qml/fields/NumberField.qml b/tools/animedit/qml/fields/NumberField.qml new file mode 100644 index 0000000000..88c952d205 --- /dev/null +++ b/tools/animedit/qml/fields/NumberField.qml @@ -0,0 +1,45 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 + + +Row { + id: row + height: 20 + width: 300 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + + property string key + property real value + property bool optional + + function setValue(newValue) { + parent.fieldChanged(key, newValue); + } + + Text { + id: keyText + y: 5 + width: 100 + text: key + ":" + font.pixelSize: 12 + color: optional ? "blue" : "black" + } + + TextField { + id: valueTextField + x: 100 + width: 200 + inputMethodHints: Qt.ImhFormattedNumbersOnly + text: value + onEditingFinished: { + value = text; + setValue(parseInt(text, 10)); + } + } +} diff --git a/tools/animedit/qml/fields/StringField.qml b/tools/animedit/qml/fields/StringField.qml new file mode 100644 index 0000000000..7ce5cd4c6c --- /dev/null +++ b/tools/animedit/qml/fields/StringField.qml @@ -0,0 +1,44 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 + + +Row { + id: row + height: 20 + width: 300 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + + property string key + property string value + property bool optional + + function setValue(newValue) { + parent.fieldChanged(key, newValue); + } + + Text { + id: keyText + y: 5 + width: 100 + text: key + ":" + font.pixelSize: 12 + color: optional ? "blue" : "black" + } + + TextField { + id: valueTextField + x: 100 + width: 200 + text: value + onEditingFinished: { + value = text; + setValue(text); + } + } +} diff --git a/tools/animedit/qml/fields/TypeField.qml b/tools/animedit/qml/fields/TypeField.qml new file mode 100644 index 0000000000..0a9a711074 --- /dev/null +++ b/tools/animedit/qml/fields/TypeField.qml @@ -0,0 +1,67 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 + +Row { + id: row + + function getTypes() { + return [ + "clip", + "blendDirectional", + "blendLinear", + "overlay", + "stateMachine", + "randomSwitchStateMachine", + "manipulator", + "inverseKinematics", + "defaultPose", + "twoBoneIK", + "splineIK", + "poleVectorConstraint" + ]; + } + + function indexFromString(str) { + var index = getTypes().indexOf(str); + return (index === -1) ? 0 : index; + } + + x: 0 + y: 0 + height: 20 + anchors.left: parent.left + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + + property string theValue: "clip" + property int theIndex: indexFromString(theValue) + + function setValue(newValue) { + var ROLE_TYPE = 0x0102; + theModel.setData(leftHandPane.currentIndex, newValue, ROLE_TYPE); + rightHandPane.reset(); + } + + Text { + id: element + y: 5 + width: 100 + text: qsTr("Type:") + font.pixelSize: 12 + } + + ComboBox { + id: comboBox + x: 100 + width: 200 + model: getTypes() + currentIndex: theIndex + onActivated: { + setValue(currentText); + } + } +} diff --git a/tools/animedit/qml/main.qml b/tools/animedit/qml/main.qml new file mode 100644 index 0000000000..2d0c277132 --- /dev/null +++ b/tools/animedit/qml/main.qml @@ -0,0 +1,262 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "fields" +import "nodes" + +ApplicationWindow { + id: root + visible: true + width: 1600 + height: 1000 + color: "#ffffff" + opacity: 1 + title: qsTr("AnimEdit") + menuBar: appMenuBar + + SplitView { + id: splitView + anchors.fill: parent + + TreeView { + id: leftHandPane + width: 1000 + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.top: parent.top + model: theModel + itemDelegate: TreeDelegate {} + + TableViewColumn { + role: "name" + title: "Name" + width: 500 + } + + TableViewColumn { + role: "type" + title: "Type" + } + + onClicked: { + rightHandPane.setIndex(index); + } + + function expandTreeView() { + function expandAll(index) { + leftHandPane.expand(index); + var children = theModel.getChildrenModelIndices(index); + for (var i = 0; i < children.length; i++) { + leftHandPane.expand(children[i]); + expandAll(children[i]); + } + } + + var index = theModel.index(0, 0); + expandAll(index); + } + } + + Rectangle { + id: rightHandPane + color: "#adadad" + height: parent.height + width: 500 + anchors.left: leftHandPane.right + anchors.leftMargin: 0 + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.top: parent.top + anchors.topMargin: 0 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + + function createCustomData(qml, index) { + var component = Qt.createComponent(qml, Component.PreferSynchronous); + if (component.status === Component.Ready) { + var obj = component.createObject(rightHandPaneColumn); + obj.setIndex(index); + } else if (component.status === Component.Error) { + console.log("ERROR: " + component.errorString()); + } else if (component.status === Component.Loading) { + console.log("ERROR: NOT READY"); + } + } + + function reset() { + setIndex(leftHandPane.currentIndex); + } + + function setIndex(index) { + var ROLE_NAME = 0x0101; + var ROLE_TYPE = 0x0102; + var ROLE_DATA = 0x0103; + + var idValue = theModel.data(index, ROLE_NAME); + var typeValue = theModel.data(index, ROLE_TYPE); + + idField.theValue = idValue; + typeField.theValue = typeValue; + + // delete previous custom data obj, if present + var orig = rightHandPaneColumn.children[2]; + if (orig) { + orig.destroy(); + } + + if (typeValue === "clip") { + createCustomData("nodes/ClipData.qml", index); + } else if (typeValue === "blendDirectional") { + createCustomData("nodes/BlendDirectional.qml", index); + } else if (typeValue === "blendLinear") { + createCustomData("nodes/BlendLinear.qml", index); + } else if (typeValue === "blendLinearMove") { + createCustomData("nodes/BlendLinearMove.qml", index); + } else if (typeValue === "overlay") { + createCustomData("nodes/Overlay.qml", index); + } else if (typeValue === "stateMachine") { + createCustomData("nodes/StateMachine.qml", index); + } else if (typeValue === "randomSwitchStateMachine") { + createCustomData("nodes/RandomStateMachine.qml", index); + } else if (typeValue === "inverseKinematics") { + createCustomData("nodes/InverseKinematics.qml", index); + } else if (typeValue === "twoBoneIK") { + createCustomData("nodes/TwoBoneIK.qml", index); + } else if (typeValue === "defaultPose") { + createCustomData("nodes/DefaultPose.qml", index); + } else if (typeValue === "manipulator") { + createCustomData("nodes/Manipulator.qml", index); + } else if (typeValue === "splineIK") { + createCustomData("nodes/SplineIK.qml", index); + } else if (typeValue === "poleVectorConstraint") { + createCustomData("nodes/PoleVector.qml", index); + } + } + + Column { + id: rightHandPaneColumn + + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.bottom: parent.bottom + anchors.bottomMargin: 0 + anchors.top: parent.top + anchors.topMargin: 0 + + spacing: 6 + + IdField { + id: idField + } + + TypeField { + id: typeField + } + } + } + } + + MenuBar { + id: appMenuBar + Menu { + title: "File" + MenuItem { + text: "Open..." + onTriggered: openFileDialog.open() + } + MenuItem { + text: "Save As..." + onTriggered: saveAsFileDialog.open() + } + } + Menu { + title: "Edit" + MenuItem { + text: "Add New Node as Child" + onTriggered: { + theModel.newNode(leftHandPane.currentIndex); + } + } + MenuItem { + text: "Delete Selected" + onTriggered: { + theModel.deleteNode(leftHandPane.currentIndex); + } + } + MenuItem { + text: "Insert New Node Above" + onTriggered: { + theModel.insertNodeAbove(leftHandPane.currentIndex); + } + } + MenuItem { + text: "Copy Node Only" + onTriggered: { + theModel.copyNode(leftHandPane.currentIndex); + } + } + MenuItem { + text: "Copy Node And Children" + onTriggered: { + theModel.copyNodeAndChildren(leftHandPane.currentIndex); + } + } + MenuItem { + text: "Paste Over" + onTriggered: { + theModel.pasteOver(leftHandPane.currentIndex); + } + } + MenuItem { + text: "Paste As Child" + onTriggered: { + theModel.pasteAsChild(leftHandPane.currentIndex); + } + } + } + } + + FileDialog { + id: openFileDialog + title: "Open an animation json file" + folder: shortcuts.home + nameFilters: ["Json files (*.json)"] + onAccepted: { + var path = openFileDialog.fileUrl.toString(); + // remove prefixed "file:///" + path = path.replace(/^(file:\/{3})/,""); + // unescape html codes like '%23' for '#' + var cleanPath = decodeURIComponent(path); + console.log("You chose: " + cleanPath); + theModel.loadFromFile(cleanPath); + leftHandPane.expandTreeView(); + } + onRejected: { + console.log("Canceled"); + } + } + + FileDialog { + id: saveAsFileDialog + title: "Save an animation json file" + folder: shortcuts.home + nameFilters: ["Json files (*.json)"] + selectExisting: false + onAccepted: { + var path = saveAsFileDialog.fileUrl.toString(); + // remove prefixed "file:///" + path = path.replace(/^(file:\/{3})/,""); + // unescape html codes like '%23' for '#' + var cleanPath = decodeURIComponent(path); + console.log("You chose: " + cleanPath); + theModel.saveToFile(cleanPath); + } + onRejected: { + console.log("Canceled"); + } + } +} diff --git a/tools/animedit/qml/nodes/BlendDirectional.qml b/tools/animedit/qml/nodes/BlendDirectional.qml new file mode 100644 index 0000000000..efdea74662 --- /dev/null +++ b/tools/animedit/qml/nodes/BlendDirectional.qml @@ -0,0 +1,129 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: 50 + width: 200 + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["alpha", "alphaVar", "centerId", "upId", "downId", "leftId", "rightId", "upLeftId", "upRightId", "downLeftId", "downRightId"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + NumberArrayField { + id: alphaField + key: "alpha" + value: [0, 0, 0] + } + + StringField { + id: alphaVarField + key: "alphaVar" + value: "" + optional: true + } + + StringField { + id: centerIdField + key: "centerId" + value: "" + optional: true + } + + StringField { + id: upIdField + key: "upId" + value: "" + optional: true + } + + StringField { + id: downIdField + key: "downId" + value: "" + optional: true + } + + StringField { + id: leftIdField + key: "leftId" + value: "" + optional: true + } + + StringField { + id: rightIdField + key: "rightId" + value: "" + optional: true + } + + StringField { + id: upLeftIdField + key: "upLeftId" + value: "" + optional: true + } + + StringField { + id: upRightIdField + key: "upRightId" + value: "" + optional: true + } + + StringField { + id: downLeftIdField + key: "downLeftId" + value: "" + optional: true + } + + StringField { + id: downRightIdField + key: "downRightId" + value: "" + optional: true + } +} + diff --git a/tools/animedit/qml/nodes/BlendLinear.qml b/tools/animedit/qml/nodes/BlendLinear.qml new file mode 100644 index 0000000000..5ed15d9ea7 --- /dev/null +++ b/tools/animedit/qml/nodes/BlendLinear.qml @@ -0,0 +1,73 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: 50 + width: 200 + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["alpha", "blendType", "alphaVar"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + NumberField { + id: alphaField + key: "alpha" + value: 0.0 + } + + StringField { + id: blendTypeField + key: "blendType" + value: "" + optional: true + } + + StringField { + id: alphaVarField + key: "alphaVar" + value: "" + optional: true + } +} + diff --git a/tools/animedit/qml/nodes/BlendLinearMove.qml b/tools/animedit/qml/nodes/BlendLinearMove.qml new file mode 100644 index 0000000000..cde0c83fd7 --- /dev/null +++ b/tools/animedit/qml/nodes/BlendLinearMove.qml @@ -0,0 +1,85 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: 50 + width: 200 + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["alpha", "desiredSpeed", "characteristicSpeeds", "alphaVar", "desiredSpeedVar"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + NumberField { + id: alphaField + key: "alpha" + value: 0.0 + } + + NumberField { + id: desiredSpeedField + key: "desiredSpeed" + value: 0.0 + } + + NumberArrayField { + id: characteristicSpeedsField + key: "characteristicSpeeds" + value: [] + } + + StringField { + id: alphaVarField + key: "alphaVar" + value: "" + optional: true + } + + StringField { + id: desiredSpeedVarField + key: "desiredSpeedVar" + value: "" + optional: true + } +} + diff --git a/tools/animedit/qml/nodes/ClipData.qml b/tools/animedit/qml/nodes/ClipData.qml new file mode 100644 index 0000000000..9949064393 --- /dev/null +++ b/tools/animedit/qml/nodes/ClipData.qml @@ -0,0 +1,148 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: 50 + width: 200 + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["url", "startFrame", "endFrame", "timeScale", "loopFlag", "mirrorFlag", + "blendType", "baseURL", "baseFrame", "startFrameVar", "endFrameVar", + "timeScaleVar", "loopFlagVar", "mirrorFlagVar"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + StringField { + id: urlField + key: "url" + value: "qrc:///avatar/animations/idle.fbx" + } + + NumberField { + id: startFrameField + key: "startFrame" + value: 0.0 + } + + NumberField { + id: endFrameField + key: "endFrame" + value: 0.0 + } + + NumberField { + id: timeScaleField + key: "timeScale" + value: 0.0 + } + + BooleanField { + id: loopFlagField + key: "loopFlag" + value: false + } + + BooleanField { + id: mirrorFlagField + key: "mirrorFlag" + value: false + optional: true + } + + StringField { + id: blendTypeField + key: "blendType" + value: "" + optional: true + } + + StringField { + id: baseURLField + key: "baseURL" + value: "" + optional: true + } + + NumberField { + id: baseFrameField + key: "baseFrame" + value: 0.0 + optional: true + } + + StringField { + id: startFrameVarField + key: "startFrameVar" + value: "" + optional: true + } + + StringField { + id: endFrameVarField + key: "endFrameVar" + value: "" + optional: true + } + + StringField { + id: timeScaleVarField + key: "timeScaleVar" + value: "" + optional: true + } + + StringField { + id: loopFlagVarField + key: "loopFlagVar" + value: "" + optional: true + } + + StringField { + id: mirrorFlagVarField + key: "mirrorFlagVar" + value: "" + optional: true + } +} + diff --git a/tools/animedit/qml/nodes/DefaultPose.qml b/tools/animedit/qml/nodes/DefaultPose.qml new file mode 100644 index 0000000000..eb16dfb0c0 --- /dev/null +++ b/tools/animedit/qml/nodes/DefaultPose.qml @@ -0,0 +1,53 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: 50 + width: 200 + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = []; // This node has no data values + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } +} + diff --git a/tools/animedit/qml/nodes/InverseKinematics.qml b/tools/animedit/qml/nodes/InverseKinematics.qml new file mode 100644 index 0000000000..b401869c32 --- /dev/null +++ b/tools/animedit/qml/nodes/InverseKinematics.qml @@ -0,0 +1,71 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + width: parent.width + height: (parent.height - 50) + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["solutionSource", "solutionSourceVar", "targets"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + StringField { + id: solutionSourceField + key: "solutionSource" + value: "relaxToUnderPoses" + } + + StringField { + id: solutionSourceVarField + key: "solutionSourceVar" + value: "solutionSource" + } + + JSONField { + id: statesField + key: "targets" + value: {} + } +} + diff --git a/tools/animedit/qml/nodes/Manipulator.qml b/tools/animedit/qml/nodes/Manipulator.qml new file mode 100644 index 0000000000..6a651ae73b --- /dev/null +++ b/tools/animedit/qml/nodes/Manipulator.qml @@ -0,0 +1,72 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + width: parent.width + height: parent.height + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["alpha", "joints", "alphaVar"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + NumberField { + id: alphaField + key: "alpha" + value: 1.0 + } + + JSONField { + id: statesField + key: "joints" + value: {} + } + + StringField { + optional: true + id: alphaVarField + key: "alphaVar" + value: "" + } +} + diff --git a/tools/animedit/qml/nodes/Overlay.qml b/tools/animedit/qml/nodes/Overlay.qml new file mode 100644 index 0000000000..96c974d6e5 --- /dev/null +++ b/tools/animedit/qml/nodes/Overlay.qml @@ -0,0 +1,80 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: 50 + width: 200 + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["boneSet", "alpha", "boneSetVar", "alphaVar"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + StringField { + id: boneSetField + key: "boneSet" + value: "fullBody" + optional: true + } + + NumberField { + id: alphaField + key: "alpha" + value: 0.0 + } + + StringField { + id: boneSetVarField + key: "boneSetVar" + value: "" + optional: true + } + + StringField { + id: alphaVarField + key: "alphaVar" + value: "" + optional: true + } +} + diff --git a/tools/animedit/qml/nodes/PoleVector.qml b/tools/animedit/qml/nodes/PoleVector.qml new file mode 100644 index 0000000000..bbf5e96a2f --- /dev/null +++ b/tools/animedit/qml/nodes/PoleVector.qml @@ -0,0 +1,96 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: 50 + width: 200 + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["referenceVector", "enabled", "baseJointName", "midJointName", "tipJointName", + "enabledVar", "poleVectorVar"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + NumberArrayField { + id: referenceVectorField + key: "referenceVector" + value: [1, 0, 0] + } + + BooleanField { + id: enabledField + key: "enabled" + value: false + } + + StringField { + id: baseJointNameField + key: "baseJointName" + value: "" + } + + StringField { + id: midJointNameField + key: "midJointName" + value: "" + } + + StringField { + id: tipJointNameField + key: "tipJointName" + value: "" + } + + StringField { + id: enabledVarField + key: "enabledVar" + value: "" + } + + StringField { + id: poleVectorVarField + key: "poleVectorVar" + value: "" + } +} + diff --git a/tools/animedit/qml/nodes/RandomStateMachine.qml b/tools/animedit/qml/nodes/RandomStateMachine.qml new file mode 100644 index 0000000000..357fc4b2bf --- /dev/null +++ b/tools/animedit/qml/nodes/RandomStateMachine.qml @@ -0,0 +1,109 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + width: parent.width + height: (parent.height - 150) + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["currentState", "randomSwitchTimeMin", "randomSwitchTimeMax", "triggerRandomSwitch", + "triggerTimeMin", "triggerTimeMax", "transitionVar", "states"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + StringField { + id: currentStateField + key: "currentState" + value: "" + } + + NumberField { + id: randomSwitchTimeMinField + key: "randomSwitchTimeMin" + value: 1.0 + optional: true + } + + NumberField { + id: randomSwitchTimeMaxField + key: "randomSwitchTimeMax" + value: 10.0 + optional: true + } + + StringField { + id: triggerRandomSwitchField + key: "triggerRandomSwitch" + value: "" + optional: true + } + + NumberField { + id: triggerTimeMinField + key: "triggerTimeMin" + value: 1.0 + optional: true + } + + NumberField { + id: triggerTimeMaxField + key: "triggerTimeMax" + value: 10.0 + optional: true + } + + StringField { + id: transitionVarField + key: "transitionVar" + value: "" + optional: true + } + + JSONField { + id: statesField + key: "states" + value: {} + } +} + diff --git a/tools/animedit/qml/nodes/SplineIK.qml b/tools/animedit/qml/nodes/SplineIK.qml new file mode 100644 index 0000000000..df1c8931a3 --- /dev/null +++ b/tools/animedit/qml/nodes/SplineIK.qml @@ -0,0 +1,139 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: 50 + width: 200 + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["alpha", "enabled", "interpDuration", "baseJointName", "midJointName", "tipJointName", + "basePositionVar", "baseRotationVar", "midPositionVar", "midRotationVar", "tipPositionVar", "tipRotationVar", + "alphaVar", "enabledVar"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + NumberField { + id: alphaField + key: "alpha" + value: 1.0 + } + + BooleanField { + id: enabledField + key: "enabled" + value: false + } + + NumberField { + id: interpDurationField + key: "interpDuration" + value: 15.0 + } + + StringField { + id: baseJointNameField + key: "baseJointName" + value: "" + } + + StringField { + id: midJointNameField + key: "midJointName" + value: "" + } + + StringField { + id: tipJointNameField + key: "tipJointName" + value: "" + } + + StringField { + id: basePositionVarField + key: "basePositionVar" + value: "" + } + + StringField { + id: baseRotationVarField + key: "baseRotationVar" + value: "" + } + + StringField { + id: midPositionVarField + key: "midPositionVar" + value: "" + } + + StringField { + id: midRotationVarField + key: "midRotationVar" + value: "" + } + + StringField { + id: tipPositionVarField + key: "tipPositionVar" + value: "" + } + + StringField { + id: tipRotationVarField + key: "tipRotationVar" + value: "" + } + + StringField { + id: alphaVarField + key: "alphaVar" + value: "" + } + + StringField { + id: enabledVarField + key: "enabledVar" + value: "" + } +} + diff --git a/tools/animedit/qml/nodes/StateMachine.qml b/tools/animedit/qml/nodes/StateMachine.qml new file mode 100644 index 0000000000..aec4c30d27 --- /dev/null +++ b/tools/animedit/qml/nodes/StateMachine.qml @@ -0,0 +1,65 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: parent.height + width: parent.width + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["currentState", "states"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + StringField { + id: currentStateField + key: "currentState" + value: "default" + } + + JSONField { + id: statesField + key: "states" + value: {} + } +} + diff --git a/tools/animedit/qml/nodes/TwoBoneIK.qml b/tools/animedit/qml/nodes/TwoBoneIK.qml new file mode 100644 index 0000000000..6bdae5c1be --- /dev/null +++ b/tools/animedit/qml/nodes/TwoBoneIK.qml @@ -0,0 +1,120 @@ +import QtQuick 2.12 +import QtQuick.Window 2.12 +import QtQuick.Controls 1.6 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import "../fields" + +Column { + id: column + x: 0 + y: 0 + height: 50 + width: 200 + spacing: 6 + property var modelIndex + + signal fieldChanged(string key, var newValue) + + // called by each field when its value is changed. + function fieldChangedMethod(key, newValue) { + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + dataValue[key] = newValue; + + // copy the new value into the model. + theModel.setData(modelIndex, dataValue, ROLE_DATA); + } + + // called when a new model is loaded + function setIndex(index) { + modelIndex = index; + + var ROLE_DATA = 0x0103; + var dataValue = theModel.data(modelIndex, ROLE_DATA); + + var fields = ["alpha", "enabled", "interpDuration", "baseJointName", "midJointName", "tipJointName", "midHingeAxis", + "alphaVar", "enabledVar", "endEffectorRotationVarVar", "endEffectorPositionVarVar"]; + + // copy data from theModel into each field. + var l = fields.length; + for (var i = 0; i < l; i++) { + var val = dataValue[fields[i]]; + if (val) { + column.children[i].value = val; + } + } + } + + Component.onCompleted: { + // this signal is fired from each data field when values are changed. + column.fieldChanged.connect(fieldChangedMethod) + } + + NumberField { + id: alphaField + key: "alpha" + value: 1.0 + } + + BooleanField { + id: enabledField + key: "enabled" + value: false + } + + NumberField { + id: interpDurationField + key: "interpDuration" + value: 15.0 + } + + StringField { + id: baseJointNameField + key: "baseJointName" + value: "" + } + + StringField { + id: midJointNameField + key: "midJointName" + value: "" + } + + StringField { + id: tipJointNameField + key: "tipJointName" + value: "" + } + + NumberArrayField { + id: midHingeAxisField + key: "midHingeAxis" + value: [1, 0, 0] + } + + StringField { + id: alphaVarField + key: "alphaVar" + value: "" + } + + StringField { + id: enabledVarField + key: "enabledVar" + value: "" + } + + StringField { + id: endEffectorRotationVarVarField + key: "endEffectorRotationVarVar" + value: "" + } + + StringField { + id: endEffectorPositionVarVarField + key: "endEffectorPositionVarVar" + value: "" + } +} + diff --git a/tools/animedit/treeitem.cpp b/tools/animedit/treeitem.cpp new file mode 100644 index 0000000000..172f78a280 --- /dev/null +++ b/tools/animedit/treeitem.cpp @@ -0,0 +1,91 @@ +// +// TreeItem +// +// Created by Anthony Thibault on 6/5/2019 +// Copyright 2019 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 "treeitem.h" +#include +#include + +TreeItem::TreeItem(const QList& data) { + m_parentItem = nullptr; + m_itemData = data; +} + +TreeItem::~TreeItem() { + qDeleteAll(m_childItems); +} + +void TreeItem::appendChild(TreeItem *item) { + item->m_parentItem = this; + m_childItems.append(item); +} + +int TreeItem::findChild(TreeItem* child) { + for (int i = 0; i < m_childItems.size(); i++) { + if (m_childItems[i] == child) { + return i; + } + } + return -1; +} + +void TreeItem::removeChild(int index) { + // TODO: delete TreeItem + m_childItems.removeAt(index); +} + +void TreeItem::insertChild(int index, TreeItem* child) { + child->m_parentItem = this; + m_childItems.insert(index, child); +} + +TreeItem* TreeItem::child(int row) { + return m_childItems.value(row); +} + +int TreeItem::childCount() const { + return m_childItems.count(); +} + +int TreeItem::columnCount() const { + return m_itemData.count(); +} + +QVariant TreeItem::data(int column) const { + return m_itemData.value(column); +} + +bool TreeItem::setData(int column, const QVariant& value) { + m_itemData[column] = QVariant(value); + return true; +} + +TreeItem* TreeItem::parentItem() { + return m_parentItem; +} + +int TreeItem::row() const { + if (m_parentItem) { + return m_parentItem->m_childItems.indexOf(const_cast(this)); + } + + return 0; +} + +TreeItem* TreeItem::cloneNode() const { + return new TreeItem(m_itemData); +} + +TreeItem* TreeItem::cloneNodeAndChildren() const { + TreeItem* newNode = new TreeItem(m_itemData); + for (int i = 0; i < m_childItems.size(); ++i) { + newNode->appendChild(m_childItems[i]->cloneNodeAndChildren()); + } + return newNode; +} diff --git a/tools/animedit/treeitem.h b/tools/animedit/treeitem.h new file mode 100644 index 0000000000..bc7afb455a --- /dev/null +++ b/tools/animedit/treeitem.h @@ -0,0 +1,45 @@ +// +// TreeItem +// +// Created by Anthony Thibault on 6/5/2019 +// Copyright 2019 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_TreeItem_h +#define hifi_TreeItem_h + +#include +#include + +class TreeItem +{ +public: + explicit TreeItem(const QList& data); + ~TreeItem(); + + void appendChild(TreeItem* child); + int findChild(TreeItem* child); + void removeChild(int index); + void insertChild(int index, TreeItem* child); + + TreeItem* child(int row); + int childCount() const; + int columnCount() const; + QVariant data(int column) const; + bool setData(int column, const QVariant& value); + int row() const; + TreeItem* parentItem(); + + TreeItem* cloneNode() const; + TreeItem* cloneNodeAndChildren() const; + +private: + QList m_childItems; + QList m_itemData; + TreeItem* m_parentItem; +}; + +#endif diff --git a/tools/animedit/treemodel.cpp b/tools/animedit/treemodel.cpp new file mode 100644 index 0000000000..28c6101f78 --- /dev/null +++ b/tools/animedit/treemodel.cpp @@ -0,0 +1,418 @@ +// +// TreeModel +// +// Created by Anthony Thibault on 6/5/2019 +// Copyright 2019 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 "treeitem.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "treemodel.h" + +static TreeItem* newBlankTreeItem() { + QList columnData; + columnData << "newNode"; + columnData << "clip"; + columnData << QJsonObject(); // blank + return new TreeItem(columnData); +} + +TreeModel::TreeModel(QObject* parent) : QAbstractItemModel(parent) { + _roleNameMapping[TreeModelRoleName] = "name"; + _roleNameMapping[TreeModelRoleType] = "type"; + _roleNameMapping[TreeModelRoleData] = "data"; + + QList rootData; + rootData << "Name" << "Type" << "Data"; + _rootItem = new TreeItem(rootData); + _clipboard = nullptr; +} + +TreeModel::~TreeModel() { + delete _rootItem; +} + +QHash TreeModel::roleNames() const { + return _roleNameMapping; +} + +Qt::ItemFlags TreeModel::flags(const QModelIndex& index) const { + if (!index.isValid()) { + return Qt::NoItemFlags; + } + + return QAbstractItemModel::flags(index); +} + +QVariant TreeModel::data(const QModelIndex& index, int role) const { + TreeItem* item = getItem(index); + return item->data(role - Qt::UserRole - 1); +} + +QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + return _rootItem->data(section); + } + + return QVariant(); +} + +QModelIndex TreeModel::index(int row, int column, const QModelIndex& parent) const { + if (!hasIndex(row, column, parent)) { + return QModelIndex(); + } + + TreeItem *parentItem; + + if (!parent.isValid()) { + parentItem = _rootItem; + } else { + parentItem = static_cast(parent.internalPointer()); + } + + TreeItem *childItem = parentItem->child(row); + if (childItem) { + return createIndex(row, column, childItem); + } else { + return QModelIndex(); + } +} + +QModelIndex TreeModel::parent(const QModelIndex& index) const { + if (!index.isValid()) { + return QModelIndex(); + } + + TreeItem *childItem = static_cast(index.internalPointer()); + TreeItem *parentItem = childItem->parentItem(); + + if (parentItem == _rootItem) { + return QModelIndex(); + } + + return createIndex(parentItem->row(), 0, parentItem); +} + +int TreeModel::rowCount(const QModelIndex& parent) const { + TreeItem* parentItem = getItem(parent); + return parentItem->childCount(); +} + +int TreeModel::columnCount(const QModelIndex& parent) const { + return _rootItem->columnCount(); +} + +bool TreeModel::setData(const QModelIndex& index, const QVariant& value, int role) { + TreeItem* item = getItem(index); + + bool returnValue = item->setData(role - Qt::UserRole - 1, value); + + emit dataChanged(index, index); + + return returnValue; +} + +bool TreeModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, int role) { + return false; +} + +bool TreeModel::insertColumns(int position, int columns, const QModelIndex& parent) { + return false; +} + +bool TreeModel::removeColumns(int position, int columns, const QModelIndex& parent) { + return false; +} + +bool TreeModel::insertRows(int position, int rows, const QModelIndex& parent) { + return false; +} + +bool TreeModel::removeRows(int position, int rows, const QModelIndex& parent) { + return false; +} + +void TreeModel::loadFromFile(const QString& filename) { + + beginResetModel(); + + QFile file(filename); + if (!file.exists()) { + qCritical() << "TreeModel::loadFromFile, failed to open file" << filename; + } else if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "TreeModel::loadFromFile, failed to open file" << filename; + } else { + qDebug() << "TreeModel::loadFromFile, success opening file" << filename; + QByteArray contents = file.readAll(); + QJsonParseError error; + auto doc = QJsonDocument::fromJson(contents, &error); + if (error.error != QJsonParseError::NoError) { + qCritical() << "TreeModel::loadFromFile, failed to parse json, error" << error.errorString(); + } else { + QJsonObject obj = doc.object(); + + // version + QJsonValue versionVal = obj.value("version"); + if (!versionVal.isString()) { + qCritical() << "TreeModel::loadFromFile, bad string \"version\""; + return; + } + QString version = versionVal.toString(); + + // check version + if (version != "1.0" && version != "1.1") { + qCritical() << "TreeModel::loadFromFile, bad version number" << version << "expected \"1.0\" or \"1.1\""; + return; + } + + // root + QJsonValue rootVal = obj.value("root"); + if (!rootVal.isObject()) { + qCritical() << "TreeModel::loadFromFile, bad object \"root\""; + return; + } + + QList columnData; + columnData << QString("root"); + columnData << QString("root"); + columnData << QString("root"); + + // create root item + _rootItem = new TreeItem(columnData); + _rootItem->appendChild(loadNode(rootVal.toObject())); + } + } + + endResetModel(); +} + +void TreeModel::saveToFile(const QString& filename) { + + QJsonObject obj; + obj.insert("version", "1.1"); + + const int FIRST_CHILD = 0; + obj.insert("root", jsonFromItem(_rootItem->child(FIRST_CHILD))); + + QJsonDocument doc(obj); + QByteArray byteArray = doc.toJson(QJsonDocument::Indented); + + QFile file(filename); + if (!file.open(QIODevice::WriteOnly)) { + qCritical() << "TreeModel::safeToFile, failed to open file" << filename; + } else { + file.write(byteArray); + } +} + +void TreeModel::newNode(const QModelIndex& parent) { + TreeItem* parentItem = _rootItem; + if (parent.isValid()) { + parentItem = static_cast(parent.internalPointer()); + } + + beginInsertRows(parent, parentItem->childCount(), parentItem->childCount()); + TreeItem* childItem = newBlankTreeItem(); + parentItem->appendChild(childItem); + + endInsertRows(); +} + +void TreeModel::deleteNode(const QModelIndex& index) { + TreeItem* item = static_cast(index.internalPointer()); + TreeItem* parentItem = item->parentItem(); + int childNum = parentItem->findChild(item); + + if (childNum >= 0) { + beginRemoveRows(createIndex(0, 0, reinterpret_cast(parentItem)), childNum, childNum); + parentItem->removeChild(childNum); + endRemoveRows(); + } +} + +void TreeModel::insertNodeAbove(const QModelIndex& index) { + TreeItem* item = static_cast(index.internalPointer()); + TreeItem* parentItem = item->parentItem(); + int childNum = parentItem->findChild(item); + + if (childNum >= 0) { + TreeItem* newItem = newBlankTreeItem(); + QModelIndex parentIndex = createIndex(0, 0, reinterpret_cast(parentItem)); + + // remove item + beginRemoveRows(parentIndex, childNum, childNum); + parentItem->removeChild(childNum); + endRemoveRows(); + + // append item to newItem + newItem->appendChild(item); + + // then insert newItem + beginInsertRows(parentIndex, childNum, childNum); + parentItem->insertChild(childNum, newItem); + endInsertRows(); + } +} + +QVariantList TreeModel::getChildrenModelIndices(const QModelIndex& index) { + QVariantList indices; + + TreeItem* parent = static_cast(index.internalPointer()); + for (int i = 0; i < parent->childCount(); ++i) { + TreeItem* child = parent->child(i); + indices.push_back(createIndex(i, 0, reinterpret_cast(child))); + } + return indices; +} + +void TreeModel::copyNode(const QModelIndex& index) { + TreeItem* item = static_cast(index.internalPointer()); + // TODO: delete previous clipboard + _clipboard = item->cloneNode(); +} + +void TreeModel::copyNodeAndChildren(const QModelIndex& index) { + TreeItem* item = static_cast(index.internalPointer()); + // TODO: delete previous clipboard + _clipboard = item->cloneNodeAndChildren(); +} + +void TreeModel::pasteOver(const QModelIndex& index) { + if (_clipboard) { + TreeItem* item = static_cast(index.internalPointer()); + TreeItem* parentItem = item->parentItem(); + int childNum = parentItem->findChild(item); + + if (childNum >= 0) { + QModelIndex parentIndex = createIndex(0, 0, reinterpret_cast(parentItem)); + + // remove item + beginRemoveRows(parentIndex, childNum, childNum); + parentItem->removeChild(childNum); + endRemoveRows(); + + // then insert clone of _clipboard + beginInsertRows(parentIndex, childNum, childNum); + parentItem->insertChild(childNum, _clipboard->cloneNodeAndChildren()); + endInsertRows(); + } + } +} + +void TreeModel::pasteAsChild(const QModelIndex& index) { + if (_clipboard) { + TreeItem* parentItem = _rootItem; + if (index.isValid()) { + parentItem = static_cast(index.internalPointer()); + } + + beginInsertRows(index, parentItem->childCount(), parentItem->childCount()); + parentItem->appendChild(_clipboard->cloneNodeAndChildren()); + + endInsertRows(); + } +} + +TreeItem* TreeModel::loadNode(const QJsonObject& jsonObj) { + + // id + auto idVal = jsonObj.value("id"); + if (!idVal.isString()) { + qCritical() << "loadNode, bad string \"id\""; + return nullptr; + } + QString id = idVal.toString(); + + // type + auto typeVal = jsonObj.value("type"); + if (!typeVal.isString()) { + qCritical() << "loadNode, bad object \"type\", id =" << id; + return nullptr; + } + QString typeStr = typeVal.toString(); + + // data + auto dataValue = jsonObj.value("data"); + if (!dataValue.isObject()) { + qCritical() << "AnimNodeLoader, bad string \"data\", id =" << id; + return nullptr; + } + + QList columnData; + columnData << id; + columnData << typeStr; + columnData << dataValue.toVariant(); + + // create node + TreeItem* node = new TreeItem(columnData); + + // children + auto childrenValue = jsonObj.value("children"); + if (!childrenValue.isArray()) { + qCritical() << "AnimNodeLoader, bad array \"children\", id =" << id; + return nullptr; + } + auto childrenArray = childrenValue.toArray(); + for (const auto& childValue : childrenArray) { + if (!childValue.isObject()) { + qCritical() << "AnimNodeLoader, bad object in \"children\", id =" << id; + return nullptr; + } + TreeItem* child = loadNode(childValue.toObject()); + if (child) { + node->appendChild(child); + } else { + return nullptr; + } + } + + return node; +} + +TreeItem* TreeModel::getItem(const QModelIndex& index) const { + if (index.isValid()) { + TreeItem *item = static_cast(index.internalPointer()); + if (item) { + return item; + } + } + return _rootItem; +} + +QJsonObject TreeModel::jsonFromItem(TreeItem* treeItem) { + QJsonObject obj; + + const int ID_COLUMN = 0; + obj.insert("id", treeItem->data(ID_COLUMN).toJsonValue()); + + const int TYPE_COLUMN = 1; + obj.insert("type", treeItem->data(TYPE_COLUMN).toJsonValue()); + + const int DATA_COLUMN = 2; + QVariant data = treeItem->data(DATA_COLUMN); + if (data.canConvert()) { + obj.insert("data", data.value().toVariant().toJsonValue()); + } else { + obj.insert("data", data.toJsonValue()); + } + + QJsonArray children; + for (int i = 0; i < treeItem->childCount(); i++) { + children.push_back(jsonFromItem(treeItem->child(i))); + } + obj.insert("children", children); + + return obj; +} diff --git a/tools/animedit/treemodel.h b/tools/animedit/treemodel.h new file mode 100644 index 0000000000..3e18f24395 --- /dev/null +++ b/tools/animedit/treemodel.h @@ -0,0 +1,79 @@ +// +// TreeModel +// +// Created by Anthony Thibault on 6/5/2019 +// Copyright 2019 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_TreeModel_h +#define hifi_TreeModel_h + +#include +#include +#include +#include +#include +#include + +class TreeItem; + +class TreeModel : public QAbstractItemModel { + Q_OBJECT + +public: + enum TreeModelRoles + { + TreeModelRoleName = Qt::UserRole + 1, + TreeModelRoleType, + TreeModelRoleData + }; + + explicit TreeModel(QObject* parent = nullptr); + ~TreeModel() override; + + // QAbstractItemModel interface + QHash roleNames() const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + + // read methods + QVariant data(const QModelIndex& index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = TreeModelRoleName) const override; + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& index) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + + // write methods + bool setData(const QModelIndex& index, const QVariant& value, int role = TreeModelRoleName) override; + bool setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, int role = TreeModelRoleName) override; + bool insertColumns(int position, int columns, const QModelIndex& parent = QModelIndex()) override; + bool removeColumns(int position, int columns, const QModelIndex& parent = QModelIndex()) override; + bool insertRows(int position, int rows, const QModelIndex& parent = QModelIndex()) override; + bool removeRows(int position, int rows, const QModelIndex& parent = QModelIndex()) override; + + // invokable from qml + Q_INVOKABLE void loadFromFile(const QString& filename); + Q_INVOKABLE void saveToFile(const QString& filename); + Q_INVOKABLE void newNode(const QModelIndex& parent); + Q_INVOKABLE void deleteNode(const QModelIndex& index); + Q_INVOKABLE void insertNodeAbove(const QModelIndex& index); + Q_INVOKABLE QVariantList getChildrenModelIndices(const QModelIndex& index); + Q_INVOKABLE void copyNode(const QModelIndex& index); + Q_INVOKABLE void copyNodeAndChildren(const QModelIndex& index); + Q_INVOKABLE void pasteOver(const QModelIndex& index); + Q_INVOKABLE void pasteAsChild(const QModelIndex& index); + +private: + TreeItem* loadNode(const QJsonObject& jsonObj); + TreeItem* getItem(const QModelIndex& index) const; + QJsonObject jsonFromItem(TreeItem* treeItem); + + TreeItem* _rootItem; + QHash _roleNameMapping; + TreeItem* _clipboard; +}; + +#endif From 69a063a7008811140ddd2cbda7e57d0d96c9cbfe Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Mon, 25 Nov 2019 10:19:54 -0700 Subject: [PATCH 03/29] Better mouse wheel detection and attenuated touchpad X axis --- .../src/input-plugins/KeyboardMouseDevice.cpp | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index ea948a929b..b1b8875804 100755 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -138,23 +138,28 @@ bool KeyboardMouseDevice::isWheelByTouchPad(QWheelEvent* event) { QPoint delta = event->angleDelta(); int deltaValueX = abs(delta.x()); int deltaValueY = abs(delta.y()); - const int MAX_WHEEL_DELTA_REPEAT = 20; const int COMMON_WHEEL_DELTA_VALUE = 120; - if (deltaValueX != 0) { - if (abs(_lastWheelDelta.x()) == deltaValueX) { - _wheelDeltaRepeatCount.setX(_wheelDeltaRepeatCount.x() + 1); - } else { - _wheelDeltaRepeatCount.setX(0); + // If deltaValueX or deltaValueY are multiple of 120 they are triggered by a mouse wheel + bool isMouseWheel = (deltaValueX + deltaValueY) % COMMON_WHEEL_DELTA_VALUE == 0; + if (!isMouseWheel) { + // We track repetition in wheel values to detect non-standard mouse wheels + const int MAX_WHEEL_DELTA_REPEAT = 10; + if (deltaValueX != 0) { + if (abs(_lastWheelDelta.x()) == deltaValueX) { + _wheelDeltaRepeatCount.setX(_wheelDeltaRepeatCount.x() + 1); + } else { + _wheelDeltaRepeatCount.setX(0); + } + return _wheelDeltaRepeatCount.x() < MAX_WHEEL_DELTA_REPEAT; } - return deltaValueX < COMMON_WHEEL_DELTA_VALUE && _wheelDeltaRepeatCount.x() < MAX_WHEEL_DELTA_REPEAT; - } - if (deltaValueY != 0) { - if (abs(_lastWheelDelta.y()) == deltaValueY) { - _wheelDeltaRepeatCount.setY(_wheelDeltaRepeatCount.y() + 1); - } else { - _wheelDeltaRepeatCount.setY(0); + if (deltaValueY != 0) { + if (abs(_lastWheelDelta.y()) == deltaValueY) { + _wheelDeltaRepeatCount.setY(_wheelDeltaRepeatCount.y() + 1); + } else { + _wheelDeltaRepeatCount.setY(0); + } + return _wheelDeltaRepeatCount.y() < MAX_WHEEL_DELTA_REPEAT; } - return deltaValueY < COMMON_WHEEL_DELTA_VALUE && _wheelDeltaRepeatCount.y() < MAX_WHEEL_DELTA_REPEAT; } return false; } @@ -166,8 +171,9 @@ void KeyboardMouseDevice::wheelEvent(QWheelEvent* event) { QPoint delta = event->angleDelta(); float deltaX = (float)delta.x(); float deltaY = (float)delta.y(); - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()].value = (deltaX > 0 ? deltaX : 0.0f); - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()].value = (deltaX < 0 ? -deltaX : 0.0f); + const float WHEEL_X_ATTENUATION = 0.3f; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()].value = (deltaX > 0 ? WHEEL_X_ATTENUATION * deltaX : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()].value = (deltaX < 0 ? -WHEEL_X_ATTENUATION * deltaX : 0.0f); // Y mouse is inverted positive is pointing up the screen const float WHEEL_Y_ATTENUATION = 0.02f; _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()].value = (deltaY < 0 ? -WHEEL_Y_ATTENUATION * deltaY : 0.0f); From 6e23924ed9e4ae536685d57325a80eb0959ecd1a Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 25 Nov 2019 13:22:35 -0800 Subject: [PATCH 04/29] Add screenshare zone-property - WIP --- .../entities/src/EntityItemProperties.cpp | 23 +++++++++++++++++++ libraries/entities/src/EntityItemProperties.h | 3 +++ libraries/entities/src/EntityPropertyFlags.h | 3 +++ libraries/entities/src/ZoneEntityItem.cpp | 5 ++++ libraries/entities/src/ZoneEntityItem.h | 6 +++++ libraries/networking/src/udt/PacketHeaders.h | 1 + .../create/assets/data/createAppTooltips.json | 3 +++ scripts/system/create/edit.js | 3 ++- .../html/js/entityProperties.js | 6 +++++ 9 files changed, 52 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 319dfc922f..a3bb72fa51 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -299,6 +299,16 @@ void EntityItemProperties::setAvatarPriorityFromString(const QString& mode) { } } +QString EntityItemProperties::getScreenshareAsString() const { return getComponentModeAsString(_screenshare); } +void EntityItemProperties::setScreenshareFromString(const QString& mode) { + auto modeItr = stringToComponentMode.find(mode.toLower()); + if (modeItr != stringToComponentMode.end()) { + _screenshare = modeItr.value(); + _screenshareChanged = true; + } +} + + inline void addTextEffect(QHash& lookup, TextEffect effect) { lookup[TextEffectHelpers::getNameForTextEffect(effect)] = effect; } const QHash stringToTextEffectLookup = [] { QHash toReturn; @@ -566,6 +576,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_HAZE_MODE, hazeMode); CHECK_PROPERTY_CHANGE(PROP_BLOOM_MODE, bloomMode); CHECK_PROPERTY_CHANGE(PROP_AVATAR_PRIORITY, avatarPriority); + CHECK_PROPERTY_CHANGE(PROP_SCREENSHARE, screenshare); // Polyvox CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize); @@ -1429,6 +1440,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {Entities.AvatarPriorityMode} avatarPriority="inherit" - Configures the priority of updates from avatars in the * zone to other clients. * + * @property {Entities.ScreenshareMode} screenshare="inherit" - Configures a zone for screen-sharing. + * * @example Create a zone that casts a red key light along the x-axis. * var zone = Entities.addEntity({ * type: "Zone", @@ -1779,6 +1792,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_HAZE_MODE, hazeMode, getHazeModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BLOOM_MODE, bloomMode, getBloomModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_AVATAR_PRIORITY, avatarPriority, getAvatarPriorityAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SCREENSHARE, screenshare, getScreenshareAsString()); } // Web only @@ -2150,6 +2164,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(hazeMode, HazeMode); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(bloomMode, BloomMode); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(avatarPriority, AvatarPriority); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(screenshare, Screenshare); // Polyvox COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, vec3, setVoxelVolumeSize); @@ -2438,6 +2453,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(hazeMode); COPY_PROPERTY_IF_CHANGED(bloomMode); COPY_PROPERTY_IF_CHANGED(avatarPriority); + COPY_PROPERTY_IF_CHANGED(screenshare); // Polyvox COPY_PROPERTY_IF_CHANGED(voxelVolumeSize); @@ -2834,6 +2850,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_PROPERTY_TO_MAP(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t); ADD_PROPERTY_TO_MAP(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t); ADD_PROPERTY_TO_MAP(PROP_AVATAR_PRIORITY, AvatarPriority, avatarPriority, uint32_t); + ADD_PROPERTY_TO_MAP(PROP_SCREENSHARE, Screenshare, screenshare, uint32_t); // Polyvox ADD_PROPERTY_TO_MAP(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, vec3); @@ -3252,6 +3269,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)properties.getHazeMode()); APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)properties.getBloomMode()); APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, (uint32_t)properties.getAvatarPriority()); + APPEND_ENTITY_PROPERTY(PROP_SCREENSHARE, (uint32_t)properties.getScreenshare()); } if (properties.getType() == EntityTypes::PolyVox) { @@ -3726,6 +3744,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HAZE_MODE, uint32_t, setHazeMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BLOOM_MODE, uint32_t, setBloomMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_AVATAR_PRIORITY, uint32_t, setAvatarPriority); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SCREENSHARE, uint32_t, setScreenshare); } if (properties.getType() == EntityTypes::PolyVox) { @@ -4117,6 +4136,7 @@ void EntityItemProperties::markAllChanged() { _hazeModeChanged = true; _bloomModeChanged = true; _avatarPriorityChanged = true; + _screenshareChanged = true; // Polyvox _voxelVolumeSizeChanged = true; @@ -4739,6 +4759,9 @@ QList EntityItemProperties::listChangedProperties() { if (avatarPriorityChanged()) { out += "avatarPriority"; } + if (screenshareChanged()) { + out += "screenshare"; + } // Polyvox if (voxelVolumeSizeChanged()) { diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 63d8183899..fbd941bd85 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -337,6 +337,7 @@ public: DEFINE_PROPERTY_REF_ENUM(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); DEFINE_PROPERTY_REF_ENUM(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); DEFINE_PROPERTY_REF_ENUM(PROP_AVATAR_PRIORITY, AvatarPriority, avatarPriority, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); + DEFINE_PROPERTY_REF_ENUM(PROP_SCREENSHARE, Screenshare, screenshare, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); // Polyvox DEFINE_PROPERTY_REF(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3, PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE); @@ -699,6 +700,8 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, AvatarPriority, avatarPriority, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, Screenshare, screenshare, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, EntityHostTypeAsString, entityHostType, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, OwningAvatarID, owningAvatarID, ""); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index d5af337a7d..1ffd8fdc3c 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -161,6 +161,7 @@ enum EntityPropertyList { PROP_DERIVED_31, PROP_DERIVED_32, PROP_DERIVED_33, + PROP_DERIVED_34, PROP_AFTER_LAST_ITEM, @@ -290,6 +291,8 @@ enum EntityPropertyList { PROP_BLOOM_MODE = PROP_DERIVED_32, // Avatar priority PROP_AVATAR_PRIORITY = PROP_DERIVED_33, + // Screen-sharing + PROP_SCREENSHARE = PROP_DERIVED_34, // Polyvox PROP_VOXEL_VOLUME_SIZE = PROP_DERIVED_0, diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index d54998e74f..4030291a44 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -71,6 +71,7 @@ EntityItemProperties ZoneEntityItem::getProperties(const EntityPropertyFlags& de COPY_ENTITY_PROPERTY_TO_PROPERTIES(hazeMode, getHazeMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(bloomMode, getBloomMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(avatarPriority, getAvatarPriority); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(screenshare, getScreenshare); return properties; } @@ -118,6 +119,7 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie SET_ENTITY_PROPERTY_FROM_PROPERTIES(hazeMode, setHazeMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(bloomMode, setBloomMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(avatarPriority, setAvatarPriority); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(screenshare, setScreenshare); somethingChanged = somethingChanged || _keyLightPropertiesChanged || _ambientLightPropertiesChanged || _skyboxPropertiesChanged || _hazePropertiesChanged || _bloomPropertiesChanged; @@ -194,6 +196,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_HAZE_MODE, uint32_t, setHazeMode); READ_ENTITY_PROPERTY(PROP_BLOOM_MODE, uint32_t, setBloomMode); READ_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, uint32_t, setAvatarPriority); + READ_ENTITY_PROPERTY(PROP_SCREENSHARE, uint32_t, setScreenshare); return bytesRead; } @@ -214,6 +217,7 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p requestedProperties += PROP_GHOSTING_ALLOWED; requestedProperties += PROP_FILTER_URL; requestedProperties += PROP_AVATAR_PRIORITY; + requestedProperties += PROP_SCREENSHARE; requestedProperties += PROP_KEY_LIGHT_MODE; requestedProperties += PROP_AMBIENT_LIGHT_MODE; @@ -260,6 +264,7 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)getHazeMode()); APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)getBloomMode()); APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, getAvatarPriority()); + APPEND_ENTITY_PROPERTY(PROP_SCREENSHARE, getScreenshare()); } void ZoneEntityItem::debugDump() const { diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index d6647e701e..295727d657 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -102,6 +102,9 @@ public: uint32_t getAvatarPriority() const { return _avatarPriority; } void setAvatarPriority(uint32_t value) { _avatarPriority = value; } + uint32_t getScreenshare() const { return _screenshare; } + void setScreenshare(uint32_t value) { _screenshare = value; } + bool keyLightPropertiesChanged() const { return _keyLightPropertiesChanged; } bool ambientLightPropertiesChanged() const { return _ambientLightPropertiesChanged; } bool skyboxPropertiesChanged() const { return _skyboxPropertiesChanged; } @@ -156,6 +159,9 @@ protected: // Avatar-updates priority uint32_t _avatarPriority { COMPONENT_MODE_INHERIT }; + // Screen-sharing zone + uint32_t _screenshare { COMPONENT_MODE_INHERIT }; + // Dirty flags turn true when either keylight properties is changing values. bool _keyLightPropertiesChanged { false }; bool _ambientLightPropertiesChanged { false }; diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 75904d8122..805e5d3a09 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -277,6 +277,7 @@ enum class EntityVersion : PacketVersion { ShadowBiasAndDistance, TextEntityFonts, ScriptServerKinematicMotion, + ScreenshareZone, // Add new versions above here NUM_PACKET_TYPE, diff --git a/scripts/system/create/assets/data/createAppTooltips.json b/scripts/system/create/assets/data/createAppTooltips.json index 8b160e0630..da12d2d503 100644 --- a/scripts/system/create/assets/data/createAppTooltips.json +++ b/scripts/system/create/assets/data/createAppTooltips.json @@ -147,6 +147,9 @@ "avatarPriority": { "tooltip": "Alter Avatars' update priorities." }, + "screenshare": { + "tooltip": "Enable screen-sharing within this zone" + }, "modelURL": { "tooltip": "A mesh model from an FBX or OBJ file." }, diff --git a/scripts/system/create/edit.js b/scripts/system/create/edit.js index 828fd70e6f..ee4a11f91a 100644 --- a/scripts/system/create/edit.js +++ b/scripts/system/create/edit.js @@ -393,7 +393,8 @@ const DEFAULT_ENTITY_PROPERTIES = { }, shapeType: "box", bloomMode: "inherit", - avatarPriority: "inherit" + avatarPriority: "inherit", + screenshare: "inherit", }, Model: { collisionShape: "none", diff --git a/scripts/system/create/entityProperties/html/js/entityProperties.js b/scripts/system/create/entityProperties/html/js/entityProperties.js index b5a9b77bf4..64d44cc270 100644 --- a/scripts/system/create/entityProperties/html/js/entityProperties.js +++ b/scripts/system/create/entityProperties/html/js/entityProperties.js @@ -497,6 +497,12 @@ const GROUPS = [ options: { inherit: "Inherit", crowd: "Crowd", hero: "Hero" }, propertyID: "avatarPriority", }, + { + label: "Screen-share", + type: "dropdown", + options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, + propertyID: "screenshare", + } ] }, From d8ba0564b528ae252391b1e5cb033c42e1769b02 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 22 Nov 2019 15:55:57 -0800 Subject: [PATCH 05/29] Adding QT Launcher Documention --- launchers/qt/BUILD.md | 11 +++++++++++ launchers/qt/readme.md | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 launchers/qt/BUILD.md create mode 100644 launchers/qt/readme.md diff --git a/launchers/qt/BUILD.md b/launchers/qt/BUILD.md new file mode 100644 index 0000000000..de6b91458c --- /dev/null +++ b/launchers/qt/BUILD.md @@ -0,0 +1,11 @@ +# Dependencies +- [cmake](https://cmake.org/download/): 3.9 + +# Windows +cmake -G "Visual Studio 16 2019" .. + +# OSX +cmake -G Xcode .. + + +if you wish to not use the compiled qml files pass the `-DLAUNCHER_SOURCE_TREE_RESOURCES=On` argument to cmake. \ No newline at end of file diff --git a/launchers/qt/readme.md b/launchers/qt/readme.md new file mode 100644 index 0000000000..fda076340a --- /dev/null +++ b/launchers/qt/readme.md @@ -0,0 +1,20 @@ +# HQ Launcher +Behavior of the HQ Launcher is as follows: +* Launching the user in the current HQ domain +* Update the Interface client to the current version. +* Update the HQ Launcher to the current version + +# directory structure + +## src/ - contains the c++ and objective-c. +* LauncherState - hold majority of the logic of the launcher (signin, config file, updating, running launcher) +* LauncherInstaller_windows - logic of how to install/uninstall HQ Launcher on windows +* Helper - helper functions +* UserSettings - getting the users setting (home location) from metaverse +* BuildsRequest - getting / parsing the build info from thunder api +* LoginRequest - checks the login credentials the user typed in. +* Unzipper - helper class for extracting zip files + +## resources/ +* image/ - Holds the images and icon that are used by the launcher +* qml/ - UI elements - `QML_FILE_FOR_UI_STATE` varible in LauchherState defines what qml files are used by the laucnher. \ No newline at end of file From c08a18c517c0e23c34112f4bb1e0bcded92d4c62 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Mon, 25 Nov 2019 14:50:58 -0800 Subject: [PATCH 06/29] making requested changes --- launchers/qt/BUILD.md | 10 ++++++---- launchers/qt/readme.md | 36 +++++++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/launchers/qt/BUILD.md b/launchers/qt/BUILD.md index de6b91458c..d08aa6bfa0 100644 --- a/launchers/qt/BUILD.md +++ b/launchers/qt/BUILD.md @@ -2,10 +2,12 @@ - [cmake](https://cmake.org/download/): 3.9 # Windows -cmake -G "Visual Studio 16 2019" .. +* Download `Visual Studio 2019` +`cmake -G "Visual Studio 16 2019" ..` -# OSX -cmake -G Xcode .. +# MacOS +* Install `Xcode` +`cmake -G Xcode ..` -if you wish to not use the compiled qml files pass the `-DLAUNCHER_SOURCE_TREE_RESOURCES=On` argument to cmake. \ No newline at end of file +If you wish to not use the compiled qml files, pass the `-DLAUNCHER_SOURCE_TREE_RESOURCES=On` argument to cmake. \ No newline at end of file diff --git a/launchers/qt/readme.md b/launchers/qt/readme.md index fda076340a..beaa54eebe 100644 --- a/launchers/qt/readme.md +++ b/launchers/qt/readme.md @@ -1,20 +1,34 @@ # HQ Launcher Behavior of the HQ Launcher is as follows: +* Update the HQ Launcher to the latest version +* Sign up and sign in is the user is not +* Download the latest Interface client * Launching the user in the current HQ domain -* Update the Interface client to the current version. -* Update the HQ Launcher to the current version # directory structure ## src/ - contains the c++ and objective-c. -* LauncherState - hold majority of the logic of the launcher (signin, config file, updating, running launcher) -* LauncherInstaller_windows - logic of how to install/uninstall HQ Launcher on windows -* Helper - helper functions -* UserSettings - getting the users setting (home location) from metaverse -* BuildsRequest - getting / parsing the build info from thunder api -* LoginRequest - checks the login credentials the user typed in. -* Unzipper - helper class for extracting zip files +* `BuildsRequest` - getting / parsing the build info from thunder api +* `CommandlineOptions` - parses and stores commandline arguments +* `Helper` - helper functions +* `Helper_darwin` - objective-c implemention of helper funcions +* `Helper_windows` - helper function that depend on windows api +* `Launcher` - initialized the Launcher Application and resources +* `LauncherInstaller_windows` - logic of how to install/uninstall HQ Launcher on windows +* `LauncherState` - hold majority of the logic of the launcher (signin, config file, updating, running launcher) + * config files hold the following saved data + * logged in + * home location +* `LauncherWindows` - wrapper for `QQuickWindow` that implements drag feature +* `LoginRequest` - checks the login credentials the user typed in. +* `NSTask+NSTaskExecveAddtions` - Extension of NSTask for replacing Launcher process with interface client process +* `PathUtils` - Helper class for getting relative paths for HQ Launcher +* `SignupRequest` - Determines if the users request to signup for a new account succeeded based on the entered credentials +* `Unzipper` - helper class for extracting zip files +* `UserSettingsRequest` - getting the users setting (home location) from metaverse ## resources/ -* image/ - Holds the images and icon that are used by the launcher -* qml/ - UI elements - `QML_FILE_FOR_UI_STATE` varible in LauchherState defines what qml files are used by the laucnher. \ No newline at end of file +* `images/`- Holds the images and icon that are used by the launcher +* `qml/` + * UI elements + * `QML_FILE_FOR_UI_STATE` varible in LauchherState defines what qml files are used by the laucnher. \ No newline at end of file From e0c5edfccd6a4356f80ca0bd1450bf053169052c Mon Sep 17 00:00:00 2001 From: danteruiz Date: Mon, 25 Nov 2019 14:58:40 -0800 Subject: [PATCH 07/29] fix typos --- launchers/qt/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launchers/qt/readme.md b/launchers/qt/readme.md index beaa54eebe..917c647511 100644 --- a/launchers/qt/readme.md +++ b/launchers/qt/readme.md @@ -21,7 +21,7 @@ Behavior of the HQ Launcher is as follows: * home location * `LauncherWindows` - wrapper for `QQuickWindow` that implements drag feature * `LoginRequest` - checks the login credentials the user typed in. -* `NSTask+NSTaskExecveAddtions` - Extension of NSTask for replacing Launcher process with interface client process +* `NSTask+NSTaskExecveAdditions` - Extension of NSTask for replacing Launcher process with interface client process * `PathUtils` - Helper class for getting relative paths for HQ Launcher * `SignupRequest` - Determines if the users request to signup for a new account succeeded based on the entered credentials * `Unzipper` - helper class for extracting zip files @@ -31,4 +31,4 @@ Behavior of the HQ Launcher is as follows: * `images/`- Holds the images and icon that are used by the launcher * `qml/` * UI elements - * `QML_FILE_FOR_UI_STATE` varible in LauchherState defines what qml files are used by the laucnher. \ No newline at end of file + * `QML_FILE_FOR_UI_STATE` variable in `LauchherState` defines what QML files are used by the Laucnher. \ No newline at end of file From 60140a3aca0fe68e5a493a50e49cbc9dde5ad341 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Mon, 25 Nov 2019 15:22:47 -0800 Subject: [PATCH 08/29] fix more typos --- launchers/qt/readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/launchers/qt/readme.md b/launchers/qt/readme.md index 917c647511..ce4e846172 100644 --- a/launchers/qt/readme.md +++ b/launchers/qt/readme.md @@ -1,9 +1,9 @@ # HQ Launcher Behavior of the HQ Launcher is as follows: * Update the HQ Launcher to the latest version -* Sign up and sign in is the user is not +* Sign up or sign in if is the user is not already signed in * Download the latest Interface client -* Launching the user in the current HQ domain +* Launch the user in the current HQ domain # directory structure @@ -31,4 +31,4 @@ Behavior of the HQ Launcher is as follows: * `images/`- Holds the images and icon that are used by the launcher * `qml/` * UI elements - * `QML_FILE_FOR_UI_STATE` variable in `LauchherState` defines what QML files are used by the Laucnher. \ No newline at end of file + * `QML_FILE_FOR_UI_STATE` variable in `LauncherState` defines what QML files are used by the Launcher. \ No newline at end of file From d797e3312382947e557877ad257dc5f78e36423a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 26 Nov 2019 13:37:04 +1300 Subject: [PATCH 09/29] Stats JSDoc --- interface/src/RefreshRateManager.cpp | 52 + interface/src/ui/Stats.h | 1343 +++++++++++++++++--------- 2 files changed, 915 insertions(+), 480 deletions(-) diff --git a/interface/src/RefreshRateManager.cpp b/interface/src/RefreshRateManager.cpp index e1da1699d7..beb1363aac 100644 --- a/interface/src/RefreshRateManager.cpp +++ b/interface/src/RefreshRateManager.cpp @@ -17,12 +17,64 @@ static const int VR_TARGET_RATE = 90; +/**jsdoc + *

Refresh rate profile.

+ * + * + * + * + * + * + * + * + * + *
Refresh Rate ProfileDescription
"Eco"Low refresh rate, which is reduced when Interface doesn't have focus or is + * minimized.
"Interactive"Medium refresh rate, which is reduced when Interface doesn't have focus or is + * minimized.
"Realtime"High refresh rate, even when Interface doesn't have focus or is minimized. + *
+ * + * @typedef {string} RefreshRateProfile + */ static const std::array REFRESH_RATE_PROFILE_TO_STRING = { { "Eco", "Interactive", "Realtime" } }; +/**jsdoc + *

Interface states that affect the refresh rate.

+ * + * + * + * + * + * + * + * + * + * + * + * + * + *
Refresh Rate RegimeDescription
"FocusActive"Interface has focus and the user is active or is in VR.
"FocusInactive"Interface has focus and the user is inactive.
"Unfocus"Interface doesn't have focus.
"Minimized"Interface is minimized.
"StartUp"Interface is starting up.
"ShutDown"Interface is shutting down.
+ * + * @typedef {string} RefreshRateRegime + */ static const std::array REFRESH_RATE_REGIME_TO_STRING = { { "FocusActive", "FocusInactive", "Unfocus", "Minimized", "StartUp", "ShutDown" } }; +/**jsdoc + *

Interface operates in different modes to provide different user experiences (UX).

+ * + * + * + * + * + * + * + * + * + *
UX ModeDescription
"Desktop"Desktop user experience mode.
"VR"VR user experience mode.
+ * + * @typedef {string} UXMode + */ static const std::array UX_MODE_TO_STRING = { { "Desktop", "VR" } }; diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index b87e3a3dbc..f9d53dc29e 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -24,6 +24,8 @@ private: \ type _##name{ initialValue }; /**jsdoc + * The Stats API provides and statistics on Interface and domain operation, per the statistics overlay. + * * @namespace Stats * * @hifi-interface @@ -32,157 +34,432 @@ private: \ * @hifi-server-entity * @hifi-assignment-client * - * @property {boolean} expanded - * @property {boolean} timingExpanded - Read-only. - * @property {string} monospaceFont - Read-only. + * @property {boolean} expanded - true if the statistics overlay should be in expanded form when the overlay is + * displayed, false if it shouldn't be expanded. + * @property {boolean} timingExpanded - true if timing details should be displayed when the statistics overlay is + * displayed in expanded form, false if timing details should not be displayed. Set by the menu item, + * Developer > Timing > Performance Timer > Display Timing Details. + * Read-only. + * @property {string} monospaceFont - The name of the monospace font used in the statistics overlay. + * Read-only. * - * @property {number} serverCount - Read-only. - * @property {number} renderrate - How often the app is creating new gpu::Frames. Read-only. - * @property {number} presentrate - How often the display plugin is presenting to the device. Read-only. - * @property {number} stutterrate - How often the display device is reprojecting old frames. Read-only. + * @property {number} serverCount - The number of servers that Interface is connected to. + * Read-only. + * @property {number} renderrate - The rate at which new GPU frames are being created, in Hz. + * Read-only. + * @property {number} presentrate - The rate at which the display plugin is presenting to the display device, in Hz. + * Read-only. + * @property {number} stutterrate - The rate at which the display plugin is reprojecting old GPU frames, in Hz. + * Read-only. * - * @property {number} appdropped - Read-only. - * @property {number} longsubmits - Read-only. - * @property {number} longrenders - Read-only. - * @property {number} longframes - Read-only. + * @property {number} appdropped - The number of times a frame has not been provided to the display device in time. + * Read-only. + * @property {number} longsubmits - The number of times the display devices takes longer than 11ms to return after being given + * frame. + * Read-only. + * @property {number} longrenders - The number of times it has taken longer than 11ms to submit a new frame to the display + * device. + * Read-only. + * @property {number} longframes - The number of times longsubmits + longrenders has taken longer than 15ms. + * Read-only. * - * @property {number} presentnewrate - Read-only. - * @property {number} presentdroprate - Read-only. - * @property {number} gameLoopRate - Read-only. - * @property {number} avatarCount - Read-only. - * @property {number} heroAvatarCount - Read-only. - * @property {number} physicsObjectCount - Read-only. - * @property {number} updatedAvatarCount - Read-only. - * @property {number} updatedHeroAvatarCount - Read-only. - * @property {number} notUpdatedAvatarCount - Read-only. - * @property {number} packetInCount - Read-only. - * @property {number} packetOutCount - Read-only. - * @property {number} mbpsIn - Read-only. - * @property {number} mbpsOut - Read-only. - * @property {number} assetMbpsIn - Read-only. - * @property {number} assetMbpsOut - Read-only. - * @property {number} audioPing - Read-only. - * @property {number} avatarPing - Read-only. - * @property {number} entitiesPing - Read-only. - * @property {number} assetPing - Read-only. - * @property {number} messagePing - Read-only. - * @property {Vec3} position - Read-only. - * @property {number} speed - Read-only. - * @property {number} yaw - Read-only. - * @property {number} avatarMixerInKbps - Read-only. - * @property {number} avatarMixerInPps - Read-only. - * @property {number} avatarMixerOutKbps - Read-only. - * @property {number} avatarMixerOutPps - Read-only. - * @property {number} myAvatarSendRate - Read-only. - * - * @property {number} audioMixerInKbps - Read-only. - * @property {number} audioMixerInPps - Read-only. - * @property {number} audioMixerOutKbps - Read-only. - * @property {number} audioMixerOutPps - Read-only. - * @property {number} audioMixerKbps - Read-only. - * @property {number} audioMixerPps - Read-only. - * @property {number} audioOutboundPPS - Read-only. - * @property {number} audioSilentOutboundPPS - Read-only. - * @property {number} audioAudioInboundPPS - Read-only. - * @property {number} audioSilentInboundPPS - Read-only. - * @property {number} audioPacketLoss - Read-only. - * @property {string} audioCodec - Read-only. - * @property {string} audioNoiseGate - Read-only. - * @property {Vec2} audioInjectors - Read-only. - * @property {number} entityPacketsInKbps - Read-only. - * - * @property {number} downloads - Read-only. - * @property {number} downloadLimit - Read-only. - * @property {number} downloadsPending - Read-only. - * @property {string[]} downloadUrls - Read-only. - * @property {number} processing - Read-only. - * @property {number} processingPending - Read-only. - * @property {number} triangles - Read-only. - * @property {number} materialSwitches - Read-only. - * @property {number} itemConsidered - Read-only. - * @property {number} itemOutOfView - Read-only. - * @property {number} itemTooSmall - Read-only. - * @property {number} itemRendered - Read-only. - * @property {number} shadowConsidered - Read-only. - * @property {number} shadowOutOfView - Read-only. - * @property {number} shadowTooSmall - Read-only. - * @property {number} shadowRendered - Read-only. - * @property {string} sendingMode - Read-only. - * @property {string} packetStats - Read-only. - * @property {number} lodAngle - Read-only. - * @property {number} lodTargetFramerate - Read-only. - * @property {string} lodStatus - Read-only. - * @property {string} timingStats - Read-only. - * @property {string} gameUpdateStats - Read-only. - * @property {number} serverElements - Read-only. - * @property {number} serverInternal - Read-only. - * @property {number} serverLeaves - Read-only. - * @property {number} localElements - Read-only. - * @property {number} localInternal - Read-only. - * @property {number} localLeaves - Read-only. - * @property {number} rectifiedTextureCount - Read-only. - * @property {number} decimatedTextureCount - Read-only. - * @property {number} gpuBuffers - Read-only. - * @property {number} gpuBufferMemory - Read-only. - * @property {number} gpuTextures - Read-only. - * @property {number} glContextSwapchainMemory - Read-only. - * @property {number} qmlTextureMemory - Read-only. - * @property {number} texturePendingTransfers - Read-only. - * @property {number} gpuTextureMemory - Read-only. - * @property {number} gpuTextureResidentMemory - Read-only. - * @property {number} gpuTextureFramebufferMemory - Read-only. - * @property {number} gpuTextureResourceMemory - Read-only. - * @property {number} gpuTextureResourceIdealMemory - Read-only. - * @property {number} gpuTextureResourcePopulatedMemory - Read-only. - * @property {number} gpuTextureExternalMemory - Read-only. - * @property {string} gpuTextureMemoryPressureState - Read-only. - * @property {number} gpuFreeMemory - Read-only. - * @property {number} gpuFrameTime - Read-only. - * @property {number} batchFrameTime - Read-only. - * @property {number} engineFrameTime - Read-only. - * @property {number} avatarSimulationTime - Read-only. - * - * - * @property {number} x - * @property {number} y - * @property {number} z - * @property {number} width - * @property {number} height - * - * @property {number} opacity - * @property {boolean} enabled - * @property {boolean} visible - * - * @property {string} state - * @property {object} anchors - Read-only. - * @property {number} baselineOffset - * - * @property {boolean} clip - * - * @property {boolean} focus - * @property {boolean} activeFocus - Read-only. - * @property {boolean} activeFocusOnTab - * - * @property {number} rotation - * @property {number} scale - * @property {number} transformOrigin - * - * @property {boolean} smooth - * @property {boolean} antialiasing - * @property {number} implicitWidth - * @property {number} implicitHeight - * - * @property {object} layer - Read-only. + * @property {number} presentnewrate - The rate at which the display plugin is presenting new GPU frames, in Hz. + * Read-only. + * @property {number} presentdroprate - The rate at which the display plugin is dropping GPU frames, in Hz. + * Read-only. - * @property {number} stylusPicksCount - Read-only. - * @property {number} rayPicksCount - Read-only. - * @property {number} parabolaPicksCount - Read-only. - * @property {number} collisionPicksCount - Read-only. - * @property {Vec3} stylusPicksUpdated - Read-only. - * @property {Vec3} rayPicksUpdated - Read-only. - * @property {Vec3} parabolaPicksUpdated - Read-only. - * @property {Vec3} collisionPicksUpdated - Read-only. - * @property {bool} eventQueueDebuggingOn - Read-only. + * @property {number} gameLoopRate - The rate at which the game loop is running, in Hz. + * Read-only. + * @property {number} refreshRateTarget - The current refresh rate target per the current refreshRateMode and + * refreshRateRegime if in desktop mode; a higher rate if in VR mode. + * Read-only. + + * @property {RefreshRateProfile} refreshRateMode - The current refresh rate profile. + * Read-only. + * @property {RefreshRateRegime} refreshRateRegime - The current refresh rate regime. + * Read-only. + * @property {UXMode} uxMode - The user experience (UX) mode that Interface is running in. + * Read-only. + + * @property {number} avatarCount - The number of avatars in the domain other than the client's. + * Read-only. + * @property {number} heroAvatarCount - The number avatars in a "hero" zone in the domain, other than the client's. + * Read-only. + * @property {number} physicsObjectCount - The number of objects that have collisions enabled. + * Read-only. + * @property {number} updatedAvatarCount - The number of avatars in the domain, other than the client's, that were updated in + * the most recent game loop. + * Read-only. + * @property {number} updatedHeroAvatarCount - The number of avatars in a "hero" zone in the domain, other than the client's, + * that were updated in the most recent game loop. + * Read-only. + * @property {number} notUpdatedAvatarCount - The number of avatars in the domain, other than the client's, that weren't able + * to be updated in the most recent game loop because there wasn't enough time. + * Read-only. + + * @property {number} packetInCount - The number of packets being received from the domain server, in packets per second. + * Read-only. + * @property {number} packetOutCount - The number of packets being sent to the domain server, in packets per second. + * Read-only. + * @property {number} mbpsIn - The amount of data being received from the domain server, in megabits per second. + * Read-only. + * @property {number} mbpsOut - The amount of data being sent to the domain server, in megabits per second. + * Read-only. + + * @property {number} assetMbpsIn - The amount of data being received from the asset server, in megabits per second. + * 0.0 if not connected to an avatar mixer. + * Read-only. + * @property {number} assetMbpsOut - The amount of data being sent to the asset server, in megabits per second. + * 0.0 if not connected to an avatar mixer. + * Read-only. + + * @property {number} audioPing - The ping time to the audio mixer, in ms. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} avatarPing - The ping time to the avatar mixer, in ms. + * -1 if not connected to an avatar mixer. + * Read-only. + * @property {number} entitiesPing - The average ping time to the entity servers, in ms. + * -1 if not connected to an entity server. + * Read-only. + * @property {number} assetPing - The ping time to the asset server, in ms. + * -1 if not connected to an asset server. + * Read-only. + * @property {number} messagePing - The ping time to the message mixer, in ms. + * -1 if not connected to a message mixer. + * Read-only. + + * @property {Vec3} position - The position of the user's avatar. + * Read-only. + *

Note: Property not available in the API.

+ * @property {number} speed - The speed of the user's avatar, in m/s. + * Read-only. + * @property {number} yaw - The yaw of the user's avatar body, in degrees. + * Read-only. + + * @property {number} avatarMixerInKbps - The amount of data being received from the avatar mixer, in kilobits per second. + * -1 if not connected to an avatar mixer. + * Read-only. + * @property {number} avatarMixerInPps - The number of packets being received from the avatar mixer, in packets per second. + * -1 if not connected to an avatar mixer. + * Read-only. + * @property {number} avatarMixerOutKbps - The amount of data being sent to the avatar mixer, in kilobits per second. + * -1 if not connected to an avatar mixer. + * Read-only. + * @property {number} avatarMixerOutPps - The number of packets being sent to the avatar mixer, in packets per second. + * -1 if not connected to an avatar mixer. + * Read-only. + + * @property {number} myAvatarSendRate - The number of avatar packets being sent by the user's avatar, in packets per second. + * Read-only. + * + * @property {number} audioMixerInKbps - The amount of data being received from the audio mixer, in kilobits per second. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} audioMixerInPps - The number of packets being received from the audio mixer, in packets per second. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} audioMixerOutKbps - The amount of data being sent to the audio mixer, in kilobits per second. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} audioMixerOutPps - The number of packets being sent to the audio mixer, in packets per second. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} audioMixerKbps - The total amount of data being sent to and received from the audio mixer, in kilobits + * per second. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} audioMixerPps - The total number of packets being sent to received from the audio mixer, in packets per + * second. + * -1 if not connected to an audio mixer. + * Read-only. + + * @property {number} audioOutboundPPS - The number of non-silent audio packets being sent by the user, in packets per second. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} audioSilentOutboundPPS - The number of silent audio packets being sent by the user, in packets per + * second. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} audioAudioInboundPPS - The number of non-silent audio packets being received by the user, in packets per + * second. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} audioSilentInboundPPS - The number of silent audio packets being received by the user, in packets per + * second. + * -1 if not connected to an audio mixer. + * Read-only. + * @property {number} audioPacketLoss - The number of audio packets being lost being sent to or received from the audio mixer, + * in %. + * -1 if not connected to an audio mixer. + * Read-only. + + * @property {string} audioCodec - The name of the audio codec. + * Read-only. + * @property {string} audioNoiseGate - The status of the audio noise gate: "Open" or "Closed". + * Read-only. + * @property {Vec2} audioInjectors - The number of audio injectors, local and non-local. + * Read-only. + *

Note: Property not available in the API.

+ + * @property {number} entityPacketsInKbps - The average amount of data being received from entity servers, in kilobits per + * second. (Multiply by the number of entity servers to get the total amount of data being received.) + * -1 if not connected to an entity server. + * Read-only. + * + * @property {number} downloads - The number of downloads in progress. + * Read-only. + * @property {number} downloadLimit - The maximum number of concurrent downloads. + * Read-only. + * @property {number} downloadsPending - The number of downloads pending. + * Read-only. + * @property {string[]} downloadUrls - The download URLs. + * Read-only. + *

Note: Property not available in the API.

+ + * @property {number} processing - The number of completed downloads being processed. + * Read-only. + * @property {number} processingPending - The number of completed downloads waiting to be processed. + * Read-only. + + * @property {number} triangles - The number of triangles in the rendered scene. + * Read-only. + * @property {number} drawcalls - The number of draw calls made for the rendered scene. + * Read-only. + * @property {number} materialSwitches - The number of material switches performed for the rendered scene. + * Read-only. + * @property {number} itemConsidered - The number of item considerations made for rendering. + * Read-only. + * @property {number} itemOutOfView - The number of items out of view. + * Read-only. + * @property {number} itemTooSmall - The number of items too small to render. + * Read-only. + * @property {number} itemRendered - The number of items rendered. + * Read-only. + * @property {number} shadowConsidered - The number of shadow considerations made for rendering. + * Read-only. + * @property {number} shadowOutOfView - The number of shadows out of view. + * Read-only. + * @property {number} shadowTooSmall - The number of shadows too small to render. + * Read-only. + * @property {number} shadowRendered - The number of shadows rendered. + * Read-only. + * @property {string} sendingMode - Description of the octree sending mode. + * Read-only. + * @property {string} packetStats - Description of the octree packet processing state. + * Read-only. + * @property {number} lodAngle - The target LOD angle, in degrees. + * Read-only. + * @property {number} lodTargetFramerate - The target LOD frame rate, in Hz. + * Read-only. + * @property {string} lodStatus - Description of the current LOD. + * Read-only. + + * @property {string} timingStats - Details of the average time (ms) spent in and number of calls made to different parts of + * the code. Provided only if timingExpanded is true. Only the top 10 items are provided if + * Developer > Timing > Performance Timer > Only Display Top 10 is enabled. + * Read-only. + * @property {string} gameUpdateStats - Details of the average time (ms) spent in different parts of the game loop. + * Read-only. + + * @property {number} serverElements - The total number of elements in the server octree. + * Read-only. + * @property {number} serverInternal - The number of internal elements in the server octree. + * Read-only. + * @property {number} serverLeaves - The number of leaf elements in the server octree. + * Read-only. + * @property {number} localElements - The total number of elements in the client octree. + * Read-only. + * @property {number} localInternal - The number of internal elements in the client octree. + * Read-only. + * @property {number} localLeaves - The number of leaf elements in the client octree. + * Read-only. + + * @property {number} rectifiedTextureCount - The number of textures that have been resized so that their size is a power of 2 + * if smaller than 128, or a multiple of 128 if greater than 128. + * Read-only. + * @property {number} decimatedTextureCount - The number of textures that have been reduced in size because they were over the + * maximum allowed size of 4096 on desktop or 2048 on mobile. + * Read-only. + + * @property {number} gpuBuffers - The number of OpenGL buffer objects managed by the GPU back-end. + * Read-only. + * @property {number} gpuBufferMemory - The total memory size of the gpuBuffers, in MB. + * Read-only. + * @property {number} gpuTextures - The number of OpenGL textures managed by the GPU back-end. This is the sum of the number of + * textures managed for gpuTextureResidentMemory, gpuTextureResourceMemory, and + * gpuTextureFramebufferMemory. + * Read-only. + * @property {number} gpuTextureMemory - The total memory size of the gpuTextures, in MB. This is the sum of + * gpuTextureResidentMemory, gpuTextureResourceMemory, and + * gpuTextureFramebufferMemory. + * Read-only. + * @property {number} glContextSwapchainMemory - The estimated memory used by the default OpenGL frame buffer, in MB. + * Read-only. + * @property {number} qmlTextureMemory - The memory size of textures managed by the offscreen QML surface, in MB. + * Read-only. + * @property {number} texturePendingTransfers - The memory size of textures pending transfer to the GPU, in MB. + * Read-only. + * @property {number} gpuTextureResidentMemory - The memory size of the "strict" textures that always have their full + * resolution in GPU memory, in MB. + * Read-only. + * @property {number} gpuTextureFramebufferMemory - The memory size of the frame buffer on the GPU, in MB. + * Read-only. + * @property {number} gpuTextureResourceMemory - The amount of GPU memory that has been allocated for "variable" textures, in + * MB. + * Read-only. + * @property {number} gpuTextureResourceIdealMemory - The amount of memory that "variable" textures would take up if they were + * all completely loaded, in MB. + * Read-only. + * @property {number} gpuTextureResourcePopulatedMemory - How much of the GPU memory allocated has actually been populated, in +* MB. + * Read-only. + * @property {string} gpuTextureMemoryPressureState - The stats of the texture transfer engine. + *
    + *
  • "Undersubscribed": There is texture data that can fit in memory but that isn't on the GPU, so more + * GPU texture memory should be allocated.
  • + *
  • "Transfer": More GPU texture memory has been allocated and texture data is being transferred.
  • + *
  • "Idle": Either all texture data has been transferred to the GPU or there is nor more space + * available.
  • + *
+ * Read-only. + * @property {number} gpuFreeMemory - The amount of GPU memory available after all allocations, in MB. + * Read-only. + *

Note: This is not a reliable number because OpenGL doesn't have an official method of getting this + * information.

+ * @property {number} gpuTextureExternalMemory - The estimated amount of memory consumed by textures being used but that are + * not managed by the GPU library, in MB. + * Read-only. + + * @property {Vec2} gpuFrameSize - The dimensions of the frames being rendered. + * Read-only. + *

Note: Property not available in the API.

+ * @property {number} gpuFrameTime - The time the GPU is spending on a frame, in ms. + * Read-only. + * @property {number} gpuFrameTimePerPixel - The time the GPU is spending on a pixel, in ns. + * Read-only. + * @property {number} batchFrameTime - The time being spent batch processing each frame, in ms. + * Read-only. + * @property {number} engineFrameTime - The time being spent in the render engine each frame, in ms. + * Read-only. + * @property {number} avatarSimulationTime - The time being spent simulating avatars each frame, in ms. + * Read-only. + * + * @property {number} stylusPicksCount - The number of stylus picks currently in effect. + * Read-only. + * @property {number} rayPicksCount - The number of ray picks currently in effect. + * Read-only. + * @property {number} parabolaPicksCount - The number of parabola picks currently in effect. + * Read-only. + * @property {number} collisionPicksCount - The number of collision picks currently in effect. + * Read-only. + * @property {Vec3} stylusPicksUpdated - The number of stylus pick intersection that were found in the most recent game loop: + *
    + *
  • x = entity intersections.
  • + *
  • y = avatar intersections.
  • + *
  • z = HUD intersections.
  • + *
+ * Read-only. + *

Note: Property not available in the API.

+ * @property {Vec3} rayPicksUpdated - The number of ray pick intersections that were found in the most recent game loop: + *
    + *
  • x = entity intersections.
  • + *
  • y = avatar intersections.
  • + *
  • z = HUD intersections.
  • + *
+ * Read-only. + *

Note: Property not available in the API.

+ * @property {Vec3} parabolaPicksUpdated - The number of parabola pick intersections that were found in the most recent game + * loop: + *
    + *
  • x = entity intersections.
  • + *
  • y = avatar intersections.
  • + *
  • z = HUD intersections.
  • + *
+ * Read-only. + *

Note: Property not available in the API.

+ * @property {Vec3} collisionPicksUpdated - The number of collision pick intersections that were found in the most recent game + * loop: + *
    + *
  • x = entity intersections.
  • + *
  • y = avatar intersections.
  • + *
  • z = HUD intersections.
  • + *
+ * Read-only. + *

Note: Property not available in the API.

+ * + * @property {boolean} eventQueueDebuggingOn - true if event queue statistics are provided, false if + * they're not. + * Read-only. + * @property {number} mainThreadQueueDepth - The number of events in the main thread's event queue. + * Only provided if eventQueueDebuggingOn is true. + * Read-only. + * @property {number} nodeListThreadQueueDepth - The number of events in the node list thread's event queue. + * Only provided if eventQueueDebuggingOn is true. + * Read-only. + * + * + * @comment The following property is from Stats.qml. It shouldn't be in the API. + * @property {string} bgColor + * Read-only. + *

Deprecated: This property is deprecated and will be removed.

+ * + * @comment The following properties are from QQuickItem. They shouldn't be in the API. + * @property {boolean} activeFocus + * Read-only. + *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} activeFocusOnTab + *

Deprecated: This property is deprecated and will be removed.

+ * @property {object} anchors + * Read-only. + *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} antialiasing + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} baselineOffset + *

Deprecated: This property is deprecated and will be removed.

+ * @property {object[]} children + * Read-only. + *

Note: Property not available in the API.

+ *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} clip + *

Deprecated: This property is deprecated and will be removed.

+ * @property {object} containmentMask + *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} enabled + *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} focus + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} height + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} implicitHeight + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} implicitWidth + *

Deprecated: This property is deprecated and will be removed.

+ * @property {object} layer + * Read-only. + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} opacity + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} rotation + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} scale + *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} smooth + *

Deprecated: This property is deprecated and will be removed.

+ * @property {string} state + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} transformOrigin + *

Deprecated: This property is deprecated and will be removed.

+ * @property {boolean} visible + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} width + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} x + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} y + *

Deprecated: This property is deprecated and will be removed.

+ * @property {number} z + *

Deprecated: This property is deprecated and will be removed.

*/ // Properties from x onwards are QQuickItem properties. @@ -287,6 +564,7 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, localLeaves, 0) STATS_PROPERTY(int, rectifiedTextureCount, 0) STATS_PROPERTY(int, decimatedTextureCount, 0) + STATS_PROPERTY(int, gpuBuffers, 0) STATS_PROPERTY(int, gpuBufferMemory, 0) STATS_PROPERTY(int, gpuTextures, 0) @@ -352,37 +630,35 @@ public: QStringList downloadUrls () { return _downloadUrls; } public slots: + + /**jsdoc + * Updates statistics to make current values available to scripts even though the statistics overlay may not be displayed. + * (Many statistics values are normally updated only if the statistics overlay is displayed.) + *

Note: Not all statistics values are updated when the statistics overlay isn't displayed or + * expanded.

+ * @function Stats.forceUpdateStats + * @example Report avatar mixer data and packet rates. + * // The statistics to report. + * var stats = [ + * "avatarMixerInKbps", + * "avatarMixerInPps", + * "avatarMixerOutKbps", + * "avatarMixerOutPps" + * ]; + * + * // update the statistics for the script. + * Stats.forceUpdateStats(); + * + * // Report the statistics. + * for (var i = 0; i < stats.length; i++) { + * print(stats[i], "=", Stats[stats[i]]); + * } + */ void forceUpdateStats() { updateStats(true); } signals: - /**jsdoc - * Triggered when the value of the longsubmits property changes. - * @function Stats.longsubmitsChanged - * @returns {Signal} - */ - void longsubmitsChanged(); - - /**jsdoc - * Triggered when the value of the longrenders property changes. - * @function Stats.longrendersChanged - * @returns {Signal} - */ - void longrendersChanged(); - - /**jsdoc - * Triggered when the value of the longframes property changes. - * @function Stats.longframesChanged - * @returns {Signal} - */ - void longframesChanged(); - - /**jsdoc - * Triggered when the value of the appdropped property changes. - * @function Stats.appdroppedChanged - * @returns {Signal} - */ - void appdroppedChanged(); + // Signals for properties... /**jsdoc * Triggered when the value of the expanded property changes. @@ -419,6 +695,41 @@ signals: */ void presentrateChanged(); + /**jsdoc + * Triggered when the value of the stutterrate property changes. + * @function Stats.stutterrateChanged + * @returns {Signal} + */ + void stutterrateChanged(); + + /**jsdoc + * Triggered when the value of the appdropped property changes. + * @function Stats.appdroppedChanged + * @returns {Signal} + */ + void appdroppedChanged(); + + /**jsdoc + * Triggered when the value of the longsubmits property changes. + * @function Stats.longsubmitsChanged + * @returns {Signal} + */ + void longsubmitsChanged(); + + /**jsdoc + * Triggered when the value of the longrenders property changes. + * @function Stats.longrendersChanged + * @returns {Signal} + */ + void longrendersChanged(); + + /**jsdoc + * Triggered when the value of the longframes property changes. + * @function Stats.longframesChanged + * @returns {Signal} + */ + void longframesChanged(); + /**jsdoc * Triggered when the value of the presentnewrate property changes. * @function Stats.presentnewrateChanged @@ -433,13 +744,6 @@ signals: */ void presentdroprateChanged(); - /**jsdoc - * Triggered when the value of the stutterrate property changes. - * @function Stats.stutterrateChanged - * @returns {Signal} - */ - void stutterrateChanged(); - /**jsdoc * Triggered when the value of the gameLoopRate property changes. * @function Stats.gameLoopRateChanged @@ -447,13 +751,6 @@ signals: */ void gameLoopRateChanged(); - /**jsdoc - * Trigered when - * @function Stats.numPhysicsBodiesChanged - * @returns {Signal} - */ - void physicsObjectCountChanged(); - /**jsdoc * Triggered when the value of the avatarCount property changes. * @function Stats.avatarCountChanged @@ -461,6 +758,34 @@ signals: */ void avatarCountChanged(); + /**jsdoc + * Triggered when the value of the refreshRateTarget property changes. + * @function Stats.refreshRateTargetChanged + * @returns {Signal} + */ + void refreshRateTargetChanged(); + + /**jsdoc + * Triggered when the value of the refreshRateMode property changes. + * @function Stats.refreshRateModeChanged + * @returns {Signal} + */ + void refreshRateModeChanged(); + + /**jsdoc + * Triggered when the value of the refreshRateRegime property changes. + * @function Stats.refreshRateRegimeChanged + * @returns {Signal} + */ + void refreshRateRegimeChanged(); + + /**jsdoc + * Triggered when the value of the uxMode property changes. + * @function Stats.uxModeChanged + * @returns {Signal} + */ + void uxModeChanged(); + /**jsdoc * Triggered when the value of the heroAvatarCount property changes. * @function Stats.heroAvatarCountChanged @@ -468,6 +793,13 @@ signals: */ void heroAvatarCountChanged(); + /**jsdoc + * Triggered when the value of the physicsObjectCount property changes. + * @function Stats.physicsObjectCountChanged + * @returns {Signal} + */ + void physicsObjectCountChanged(); + /**jsdoc * Triggered when the value of the updatedAvatarCount property changes. * @function Stats.updatedAvatarCountChanged @@ -727,7 +1059,6 @@ signals: */ void entityPacketsInKbpsChanged(); - /**jsdoc * Triggered when the value of the downloads property changes. * @function Stats.downloadsChanged @@ -778,11 +1109,10 @@ signals: void trianglesChanged(); /**jsdoc - * Triggered when the value of the drawcalls property changes. - * This - * @function Stats.drawcallsChanged - * @returns {Signal} - */ + * Triggered when the value of the drawcalls property changes. + * @function Stats.drawcallsChanged + * @returns {Signal} + */ void drawcallsChanged(); /**jsdoc @@ -883,6 +1213,20 @@ signals: */ void lodStatusChanged(); + /**jsdoc + * Triggered when the value of the timingStats property changes. + * @function Stats.timingStatsChanged + * @returns {Signal} + */ + void timingStatsChanged(); + + /**jsdoc + * Triggered when the value of the gameUpdateStats property changes. + * @function Stats.gameUpdateStatsChanged + * @returns {Signal} + */ + void gameUpdateStatsChanged(); + /**jsdoc * Triggered when the value of the serverElements property changes. * @function Stats.serverElementsChanged @@ -926,39 +1270,18 @@ signals: void localLeavesChanged(); /**jsdoc - * Triggered when the value of the timingStats property changes. - * @function Stats.timingStatsChanged + * Triggered when the value of the rectifiedTextureCount property changes. + * @function Stats.rectifiedTextureCountChanged * @returns {Signal} */ - void timingStatsChanged(); + void rectifiedTextureCountChanged(); /**jsdoc - * Triggered when the value of the gameUpdateStats property changes. - * @function Stats.gameUpdateStatsChanged + * Triggered when the value of the decimatedTextureCount property changes. + * @function Stats.decimatedTextureCountChanged * @returns {Signal} */ - void gameUpdateStatsChanged(); - - /**jsdoc - * Triggered when the value of the glContextSwapchainMemory property changes. - * @function Stats.glContextSwapchainMemoryChanged - * @returns {Signal} - */ - void glContextSwapchainMemoryChanged(); - - /**jsdoc - * Triggered when the value of the qmlTextureMemory property changes. - * @function Stats.qmlTextureMemoryChanged - * @returns {Signal} - */ - void qmlTextureMemoryChanged(); - - /**jsdoc - * Triggered when the value of the texturePendingTransfers property changes. - * @function Stats.texturePendingTransfersChanged - * @returns {Signal} - */ - void texturePendingTransfersChanged(); + void decimatedTextureCountChanged(); /**jsdoc * Triggered when the value of the gpuBuffers property changes. @@ -981,6 +1304,27 @@ signals: */ void gpuTexturesChanged(); + /**jsdoc + * Triggered when the value of the glContextSwapchainMemory property changes. + * @function Stats.glContextSwapchainMemoryChanged + * @returns {Signal} + */ + void glContextSwapchainMemoryChanged(); + + /**jsdoc + * Triggered when the value of the qmlTextureMemory property changes. + * @function Stats.qmlTextureMemoryChanged + * @returns {Signal} + */ + void qmlTextureMemoryChanged(); + + /**jsdoc + * Triggered when the value of the texturePendingTransfers property changes. + * @function Stats.texturePendingTransfersChanged + * @returns {Signal} + */ + void texturePendingTransfersChanged(); + /**jsdoc * Triggered when the value of the gpuTextureMemory property changes. * @function Stats.gpuTextureMemoryChanged @@ -1045,15 +1389,8 @@ signals: void gpuFreeMemoryChanged(); /**jsdoc - * Triggered when the value of the gpuFrameTime property changes. - * @function Stats.gpuFrameTimeChanged - * @returns {Signal} - */ - void gpuFrameTimeChanged(); - - /**jsdoc - * Triggered when the value of the gpuFrameTime property changes. - * @function Stats.gpuFrameTimeChanged + * Triggered when the value of the gpuFrameSize property changes. + * @function Stats.gpuFrameSizeChanged * @returns {Signal} */ void gpuFrameSizeChanged(); @@ -1063,6 +1400,13 @@ signals: * @function Stats.gpuFrameTimeChanged * @returns {Signal} */ + void gpuFrameTimeChanged(); + + /**jsdoc + * Triggered when the value of the gpuFrameTimePerPixel property changes. + * @function Stats.gpuFrameTimePerPixelChanged + * @returns {Signal} + */ void gpuFrameTimePerPixelChanged(); /**jsdoc @@ -1086,250 +1430,6 @@ signals: */ void avatarSimulationTimeChanged(); - /**jsdoc - * Triggered when the value of the rectifiedTextureCount property changes. - * @function Stats.rectifiedTextureCountChanged - * @returns {Signal} - */ - void rectifiedTextureCountChanged(); - - /**jsdoc - * Triggered when the value of the decimatedTextureCount property changes. - * @function Stats.decimatedTextureCountChanged - * @returns {Signal} - */ - void decimatedTextureCountChanged(); - - - void refreshRateTargetChanged(); - - void refreshRateModeChanged(); - - void refreshRateRegimeChanged(); - - void uxModeChanged(); - - // QQuickItem signals. - - /**jsdoc - * Triggered when the parent item changes. - * @function Stats.parentChanged - * @param {object} parent - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the x property changes. - * @function Stats.xChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the y property changes. - * @function Stats.yChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the z property changes. - * @function Stats.zChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the width property changes. - * @function Stats.widthChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the height property changes. - * @function Stats.heightChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the opacity property changes. - * @function Stats.opacityChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the enabled property changes. - * @function Stats.enabledChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the visibleChanged property changes. - * @function Stats.visibleChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the list of visible children changes. - * @function Stats.visibleChildrenChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the state property changes. - * @function Stats.stateChanged - * @paramm {string} state - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the position and size of the rectangle containing the children changes. - * @function Stats.childrenRectChanged - * @param {Rect} childrenRect - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the baselineOffset property changes. - * @function Stats.baselineOffsetChanged - * @param {number} baselineOffset - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the clip property changes. - * @function Stats.clipChanged - * @param {boolean} clip - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the focus property changes. - * @function Stats.focusChanged - * @param {boolean} focus - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the activeFocus property changes. - * @function Stats.activeFocusChanged - * @param {boolean} activeFocus - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the activeFocusOnTab property changes. - * @function Stats.activeFocusOnTabChanged - * @param {boolean} activeFocusOnTab - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the rotation property changes. - * @function Stats.rotationChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the scaleChanged property changes. - * @function Stats.scaleChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the transformOrigin property changes. - * @function Stats.transformOriginChanged - * @param {number} transformOrigin - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the smooth property changes. - * @function Stats.smoothChanged - * @param {boolean} smooth - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the antialiasing property changes. - * @function Stats.antialiasingChanged - * @param {boolean} antialiasing - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the implicitWidth property changes. - * @function Stats.implicitWidthChanged - * @returns {Signal} - */ - - /**jsdoc - * Triggered when the value of the implicitHeight property changes. - * @function Stats.implicitHeightChanged - * @returns {Signal} - */ - - /**jsdoc - * @function Stats.windowChanged - * @param {object} window - * @returns {Signal} - */ - - - // QQuickItem functions. - - /**jsdoc - * @function Stats.grabToImage - * @param {object} callback - * @param {Size} [targetSize=0,0] - * @returns {boolean} - */ - - /**jsdoc - * @function Stats.contains - * @param {Vec2} point - * @returns {boolean} - */ - - /**jsdoc - * @function Stats.mapFromItem - * @param {object} item - */ - - /**jsdoc - * @function Stats.mapToItem - * @param {object} item - */ - - /**jsdoc - * @function Stats.mapFromGlobal - * @param {object} global - */ - - /**jsdoc - * @function Stats.mapToGlobal - * @param {object} global - */ - - /**jsdoc - * @function Stats.forceActiveFocus - * @param {number} [reason=7] - */ - - /**jsdoc - * @function Stats.nextItemInFocusChain - * @param {boolean} [forward=true] - * @returns {object} - */ - - /**jsdoc - * @function Stats.childAt - * @param {number} x - * @param {number} y - * @returns {object} - */ - - /**jsdoc - * @function Stats.update - */ - /**jsdoc * Triggered when the value of the stylusPicksCount property changes. * @function Stats.stylusPicksCountChanged @@ -1387,11 +1487,11 @@ signals: void collisionPicksUpdatedChanged(); /**jsdoc - * Triggered when the value of the eventQueueDebuggingOn property changes. - * @function Stats.eventQueueDebuggingOn + * Triggered when the value of the mainThreadQueueDepth property changes. + * @function Stats.mainThreadQueueDepthChanged * @returns {Signal} */ - void eventQueueDebuggingOnChanged(); + void mainThreadQueueDepthChanged(); /**jsdoc * Triggered when the value of the nodeListThreadQueueDepth property changes. @@ -1401,11 +1501,294 @@ signals: void nodeListThreadQueueDepthChanged(); /**jsdoc - * Triggered when the value of the nodeListThreadQueueDepth property changes. - * @function Stats.nodeListThreadQueueDepth + * Triggered when the value of the eventQueueDebuggingOn property changes. + * @function Stats.eventQueueDebuggingOnChanged * @returns {Signal} */ - void mainThreadQueueDepthChanged(); + void eventQueueDebuggingOnChanged(); + + + // Stats.qml signals: shouldn't be in the API. + + /**jsdoc + * Triggered when the value of the bgColor property changes. + * @function Stats.bgColorChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + + // QQuickItem signals: shouldn't be in the API. + + /**jsdoc + * Triggered when the value of the activeFocus property changes. + * @function Stats.activeFocusChanged + * @param {boolean} activeFocus - Active focus. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the activeFocusOnTab property changes. + * @function Stats.activeFocusOnTabChanged + * @param {boolean} activeFocusOnTab - Active focus on tab. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the antialiasing property changes. + * @function Stats.antialiasingChanged + * @param {boolean} antialiasing - Antialiasing. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the baselineOffset property changes. + * @function Stats.baselineOffsetChanged + * @param {number} baselineOffset - Baseline offset. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the children property changes. + * @function Stats.childrenChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the position and size of the rectangle containing the children changes. + * @function Stats.childrenRectChanged + * @param {Rect} childrenRect - Children rect. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + + /**jsdoc + * Triggered when the value of the clip property changes. + * @function Stats.clipChanged + * @param {boolean} clip - Clip. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the containmentMask property changes. + * @function Stats.containmentMaskChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the enabled property changes. + * @function Stats.enabledChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the focus property changes. + * @function Stats.focusChanged + * @param {boolean} focus - Focus. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the height property changes. + * @function Stats.heightChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the implicitHeight property changes. + * @function Stats.implicitHeightChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the implicitWidth property changes. + * @function Stats.implicitWidthChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the opacity property changes. + * @function Stats.opacityChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the parent item changes. + * @function Stats.parentChanged + * @param {object} parent - Parent. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the rotation property changes. + * @function Stats.rotationChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the scale property changes. + * @function Stats.scaleChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the smooth property changes. + * @function Stats.smoothChanged + * @param {boolean} smooth - Smooth. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the state property changes. + * @function Stats.stateChanged + * @paramm {string} state - State. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the transformOrigin property changes. + * @function Stats.transformOriginChanged + * @param {number} transformOrigin - Transformm origin. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the visibleChanged property changes. + * @function Stats.visibleChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the list of visible children changes. + * @function Stats.visibleChildrenChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the width property changes. + * @function Stats.widthChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the stats window changes. + * @function Stats.windowChanged + * @param {object} window - Window. + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the x property changes. + * @function Stats.xChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the y property changes. + * @function Stats.yChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + /**jsdoc + * Triggered when the value of the z property changes. + * @function Stats.zChanged + * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. + */ + + + // QQuickItem methods: shouldn't be in the API. + + /**jsdoc + * @function Stats.childAt + * @param {number} x - X. + * @param {number} y - Y. + * @returns {object} + * @deprecated This method is deprecated and will be removed. + */ + + /**jsdoc + * @function Stats.contains + * @param {Vec2} point - Point + * @returns {boolean} + * @deprecated This method is deprecated and will be removed. + */ + + /**jsdoc + * @function Stats.forceActiveFocus + * @param {number} [reason=7] - Reason + * @deprecated This method is deprecated and will be removed. + */ + + /**jsdoc + * @function Stats.grabToImage + * @param {object} callback - Callback. + * @param {Size} [targetSize=0,0] - Target size. + * @returns {boolean} + * @deprecated This method is deprecated and will be removed. + */ + + /**jsdoc + * @function Stats.mapFromGlobal + * @param {object} global - Global. + * @deprecated This method is deprecated and will be removed. + */ + + /**jsdoc + * @function Stats.mapFromItem + * @param {object} item - Item. + * @deprecated This method is deprecated and will be removed. + */ + + /**jsdoc + * @function Stats.mapToGlobal + * @param {object} global - Global. + * @deprecated This method is deprecated and will be removed. + */ + + /**jsdoc + * @function Stats.mapToItem + * @param {object} item - Item + * @deprecated This method is deprecated and will be removed. + */ + + /**jsdoc + * @function Stats.nextItemInFocusChain + * @param {boolean} [forward=true] - Forward. + * @returns {object} + * @deprecated This method is deprecated and will be removed. + */ + + /**jsdoc + * @function Stats.update + * @deprecated This method is deprecated and will be removed. + */ private: int _recentMaxPackets{ 0 } ; // recent max incoming voxel packets to process From 46c389f064b0c485ecf0dfec8c3b5fe123c70b2a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 26 Nov 2019 15:19:54 +1300 Subject: [PATCH 10/29] Tidying --- interface/src/ui/Stats.h | 48 ++++++++++++---------------------------- 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index f9d53dc29e..00b699c3e6 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -54,8 +54,8 @@ private: \ * * @property {number} appdropped - The number of times a frame has not been provided to the display device in time. * Read-only. - * @property {number} longsubmits - The number of times the display devices takes longer than 11ms to return after being given - * frame. + * @property {number} longsubmits - The number of times the display device has taken longer than 11ms to return after being + * given a frame. * Read-only. * @property {number} longrenders - The number of times it has taken longer than 11ms to submit a new frame to the display * device. @@ -73,14 +73,12 @@ private: \ * @property {number} refreshRateTarget - The current refresh rate target per the current refreshRateMode and * refreshRateRegime if in desktop mode; a higher rate if in VR mode. * Read-only. - * @property {RefreshRateProfile} refreshRateMode - The current refresh rate profile. * Read-only. * @property {RefreshRateRegime} refreshRateRegime - The current refresh rate regime. * Read-only. * @property {UXMode} uxMode - The user experience (UX) mode that Interface is running in. * Read-only. - * @property {number} avatarCount - The number of avatars in the domain other than the client's. * Read-only. * @property {number} heroAvatarCount - The number avatars in a "hero" zone in the domain, other than the client's. @@ -94,9 +92,8 @@ private: \ * that were updated in the most recent game loop. * Read-only. * @property {number} notUpdatedAvatarCount - The number of avatars in the domain, other than the client's, that weren't able - * to be updated in the most recent game loop because there wasn't enough time. + * to be updated in the most recent game loop because there wasn't enough time to. * Read-only. - * @property {number} packetInCount - The number of packets being received from the domain server, in packets per second. * Read-only. * @property {number} packetOutCount - The number of packets being sent to the domain server, in packets per second. @@ -105,14 +102,12 @@ private: \ * Read-only. * @property {number} mbpsOut - The amount of data being sent to the domain server, in megabits per second. * Read-only. - - * @property {number} assetMbpsIn - The amount of data being received from the asset server, in megabits per second. + @property {number} assetMbpsIn - The amount of data being received from the asset server, in megabits per second. * 0.0 if not connected to an avatar mixer. * Read-only. * @property {number} assetMbpsOut - The amount of data being sent to the asset server, in megabits per second. * 0.0 if not connected to an avatar mixer. * Read-only. - * @property {number} audioPing - The ping time to the audio mixer, in ms. * -1 if not connected to an audio mixer. * Read-only. @@ -128,7 +123,6 @@ private: \ * @property {number} messagePing - The ping time to the message mixer, in ms. * -1 if not connected to a message mixer. * Read-only. - * @property {Vec3} position - The position of the user's avatar. * Read-only. *

Note: Property not available in the API.

@@ -136,7 +130,6 @@ private: \ * Read-only. * @property {number} yaw - The yaw of the user's avatar body, in degrees. * Read-only. - * @property {number} avatarMixerInKbps - The amount of data being received from the avatar mixer, in kilobits per second. * -1 if not connected to an avatar mixer. * Read-only. @@ -149,7 +142,6 @@ private: \ * @property {number} avatarMixerOutPps - The number of packets being sent to the avatar mixer, in packets per second. * -1 if not connected to an avatar mixer. * Read-only. - * @property {number} myAvatarSendRate - The number of avatar packets being sent by the user's avatar, in packets per second. * Read-only. * @@ -169,11 +161,10 @@ private: \ * per second. * -1 if not connected to an audio mixer. * Read-only. - * @property {number} audioMixerPps - The total number of packets being sent to received from the audio mixer, in packets per - * second. + * @property {number} audioMixerPps - The total number of packets being sent to and received from the audio mixer, in packets + * per second. * -1 if not connected to an audio mixer. * Read-only. - * @property {number} audioOutboundPPS - The number of non-silent audio packets being sent by the user, in packets per second. * -1 if not connected to an audio mixer. * Read-only. @@ -189,11 +180,9 @@ private: \ * second. * -1 if not connected to an audio mixer. * Read-only. - * @property {number} audioPacketLoss - The number of audio packets being lost being sent to or received from the audio mixer, - * in %. + * @property {number} audioPacketLoss - The number of audio packets being lost, sent to or received from the audio mixer, in %. * -1 if not connected to an audio mixer. * Read-only. - * @property {string} audioCodec - The name of the audio codec. * Read-only. * @property {string} audioNoiseGate - The status of the audio noise gate: "Open" or "Closed". @@ -201,7 +190,6 @@ private: \ * @property {Vec2} audioInjectors - The number of audio injectors, local and non-local. * Read-only. *

Note: Property not available in the API.

- * @property {number} entityPacketsInKbps - The average amount of data being received from entity servers, in kilobits per * second. (Multiply by the number of entity servers to get the total amount of data being received.) * -1 if not connected to an entity server. @@ -216,12 +204,10 @@ private: \ * @property {string[]} downloadUrls - The download URLs. * Read-only. *

Note: Property not available in the API.

- * @property {number} processing - The number of completed downloads being processed. * Read-only. * @property {number} processingPending - The number of completed downloads waiting to be processed. * Read-only. - * @property {number} triangles - The number of triangles in the rendered scene. * Read-only. * @property {number} drawcalls - The number of draw calls made for the rendered scene. @@ -254,14 +240,12 @@ private: \ * Read-only. * @property {string} lodStatus - Description of the current LOD. * Read-only. - * @property {string} timingStats - Details of the average time (ms) spent in and number of calls made to different parts of * the code. Provided only if timingExpanded is true. Only the top 10 items are provided if * Developer > Timing > Performance Timer > Only Display Top 10 is enabled. * Read-only. * @property {string} gameUpdateStats - Details of the average time (ms) spent in different parts of the game loop. * Read-only. - * @property {number} serverElements - The total number of elements in the server octree. * Read-only. * @property {number} serverInternal - The number of internal elements in the server octree. @@ -274,14 +258,12 @@ private: \ * Read-only. * @property {number} localLeaves - The number of leaf elements in the client octree. * Read-only. - - * @property {number} rectifiedTextureCount - The number of textures that have been resized so that their size is a power of 2 - * if smaller than 128, or a multiple of 128 if greater than 128. + * @property {number} rectifiedTextureCount - The number of textures that have been resized so that their dimensions is a power + * of 2 if smaller than 128 pixels, or a multiple of 128 if greater than 128 pixels. * Read-only. * @property {number} decimatedTextureCount - The number of textures that have been reduced in size because they were over the - * maximum allowed size of 4096 on desktop or 2048 on mobile. + * maximum allowed dimensions of 4096 pixels on desktop or 2048 pixels on mobile. * Read-only. - * @property {number} gpuBuffers - The number of OpenGL buffer objects managed by the GPU back-end. * Read-only. * @property {number} gpuBufferMemory - The total memory size of the gpuBuffers, in MB. @@ -305,8 +287,8 @@ private: \ * Read-only. * @property {number} gpuTextureFramebufferMemory - The memory size of the frame buffer on the GPU, in MB. * Read-only. - * @property {number} gpuTextureResourceMemory - The amount of GPU memory that has been allocated for "variable" textures, in - * MB. + * @property {number} gpuTextureResourceMemory - The amount of GPU memory that has been allocated for "variable" textures that + * don't necessarily always have their full resolution in GPU memory, in MB. * Read-only. * @property {number} gpuTextureResourceIdealMemory - The amount of memory that "variable" textures would take up if they were * all completely loaded, in MB. @@ -317,7 +299,7 @@ private: \ * @property {string} gpuTextureMemoryPressureState - The stats of the texture transfer engine. *
    *
  • "Undersubscribed": There is texture data that can fit in memory but that isn't on the GPU, so more - * GPU texture memory should be allocated.
  • + * GPU texture memory should be allocated if possible. *
  • "Transfer": More GPU texture memory has been allocated and texture data is being transferred.
  • *
  • "Idle": Either all texture data has been transferred to the GPU or there is nor more space * available.
  • @@ -330,8 +312,7 @@ private: \ * @property {number} gpuTextureExternalMemory - The estimated amount of memory consumed by textures being used but that are * not managed by the GPU library, in MB. * Read-only. - - * @property {Vec2} gpuFrameSize - The dimensions of the frames being rendered. + * @property {Vec2} gpuFrameSize - The dimensions of the frames being rendered, in pixels. * Read-only. *

    Note: Property not available in the API.

    * @property {number} gpuFrameTime - The time the GPU is spending on a frame, in ms. @@ -398,7 +379,6 @@ private: \ * Only provided if eventQueueDebuggingOn is true. * Read-only. * - * * @comment The following property is from Stats.qml. It shouldn't be in the API. * @property {string} bgColor * Read-only. From 6b371ec38824c615908b8ad18ab03eecddbda045 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 25 Nov 2019 18:30:53 -0800 Subject: [PATCH 11/29] Changes to avatar mixer to use screenshare zones --- .../src/avatars/AvatarMixerClientData.cpp | 96 ++++++++++--------- libraries/entities/src/ZoneEntityItem.cpp | 4 +- 2 files changed, 52 insertions(+), 48 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index ea5246c59a..aeac279a95 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -23,8 +23,7 @@ #include "AvatarMixerSlave.h" -AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) : - NodeData(nodeID, nodeLocalID) { +AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) : NodeData(nodeID, nodeLocalID) { // in case somebody calls getSessionUUID on the AvatarData instance, make sure it has the right ID _avatar->setID(nodeID); } @@ -92,41 +91,46 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData } namespace { - using std::static_pointer_cast; +using std::static_pointer_cast; - // Operator to find if a point is within an avatar-priority (hero) Zone Entity. - struct FindPriorityZone { - glm::vec3 position; - bool isInPriorityZone { false }; - float zoneVolume { std::numeric_limits::max() }; - EntityItemID id {}; +// Operator to find if a point is within an avatar-priority (hero) Zone Entity. +struct FindContainingZone { + glm::vec3 position; + bool isInPriorityZone { false }; + bool isInScreenshareZone { false }; + float priorityZoneVolume { std::numeric_limits::max() }; + float screenshareZoneVolume { priorityZoneVolume }; + EntityItemID id{}; - static bool operation(const OctreeElementPointer& element, void* extraData) { - auto findPriorityZone = static_cast(extraData); - if (element->getAACube().contains(findPriorityZone->position)) { - const EntityTreeElementPointer entityTreeElement = static_pointer_cast(element); - entityTreeElement->forEachEntity([&findPriorityZone](EntityItemPointer item) { - if (item->getType() == EntityTypes::Zone - && item->contains(findPriorityZone->position)) { - auto zoneItem = static_pointer_cast(item); - if (zoneItem->getAvatarPriority() != COMPONENT_MODE_INHERIT) { - float volume = zoneItem->getVolumeEstimate(); - if (volume < findPriorityZone->zoneVolume) { // Smaller volume wins - findPriorityZone->isInPriorityZone = zoneItem->getAvatarPriority() == COMPONENT_MODE_ENABLED; - findPriorityZone->zoneVolume = volume; - findPriorityZone->id = zoneItem->getEntityItemID(); - } + static bool operation(const OctreeElementPointer& element, void* extraData) { + auto findContainingZone = static_cast(extraData); + if (element->getAACube().contains(findContainingZone->position)) { + const EntityTreeElementPointer entityTreeElement = static_pointer_cast(element); + entityTreeElement->forEachEntity([&findContainingZone](EntityItemPointer item) { + if (item->getType() == EntityTypes::Zone && item->contains(findContainingZone->position)) { + auto zoneItem = static_pointer_cast(item); + if (zoneItem->getAvatarPriority() != COMPONENT_MODE_INHERIT) { + float volume = zoneItem->getVolumeEstimate(); + if (volume < findContainingZone->priorityZoneVolume) { // Smaller volume wins + findContainingZone->isInPriorityZone = zoneItem->getAvatarPriority() == COMPONENT_MODE_ENABLED; + findContainingZone->priorityZoneVolume = volume; + } + if (volume < findContainingZone->screenshareZoneVolume) { + findContainingZone->isInScreenshareZone = zoneItem->getScreenshare() == COMPONENT_MODE_ENABLED; + findContainingZone->screenshareZoneVolume = volume; + findContainingZone->id = zoneItem->getEntityItemID(); } } - }); - return true; // Keep recursing - } else { // Position isn't within this subspace, so end recursion. - return false; - } + } + }); + return true; // Keep recursing + } else { // Position isn't within this subspace, so end recursion. + return false; } - }; + } +}; -} // Close anonymous namespace. +} // namespace int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveSharedData& slaveSharedData) { // pull the sequence number from the data first @@ -152,15 +156,17 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared auto newPosition = _avatar->getClientGlobalPosition(); if (newPosition != oldPosition || _avatar->getNeedsHeroCheck()) { EntityTree& entityTree = *slaveSharedData.entityTree; - FindPriorityZone findPriorityZone { newPosition } ; - entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone); - bool currentlyHasPriority = findPriorityZone.isInPriorityZone; + FindContainingZone findContainingZone{ newPosition }; + entityTree.recurseTreeWithOperation(&FindContainingZone::operation, &findContainingZone); + bool currentlyHasPriority = findContainingZone.isInPriorityZone; if (currentlyHasPriority != _avatar->getHasPriority()) { _avatar->setHasPriority(currentlyHasPriority); + } + if (findContainingZone.isInScreenshareZone) { auto nodeList = DependencyManager::get(); auto packet = NLPacket::create(PacketType::AvatarZonePresence, 2 * NUM_BYTES_RFC4122_UUID, true); packet->write(_avatar->getSessionUUID().toRfc4122()); - packet->write(findPriorityZone.id.toRfc4122()); + packet->write(findContainingZone.id.toRfc4122()); nodeList->sendPacket(std::move(packet), nodeList->getDomainSockAddr()); } _avatar->setNeedsHeroCheck(false); @@ -227,8 +233,7 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, } } else { // Trying to read more bytes than available, bail - if (message.getBytesLeftToRead() < qint64(NUM_BYTES_RFC4122_UUID + - sizeof(AvatarTraits::TraitWireSize))) { + if (message.getBytesLeftToRead() < qint64(NUM_BYTES_RFC4122_UUID + sizeof(AvatarTraits::TraitWireSize))) { qWarning() << "Refusing to process malformed traits packet from" << message.getSenderSockAddr(); return; } @@ -244,8 +249,7 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, break; } - if (traitType == AvatarTraits::AvatarEntity || - traitType == AvatarTraits::Grab) { + if (traitType == AvatarTraits::AvatarEntity || traitType == AvatarTraits::Grab) { auto& instanceVersionRef = _lastReceivedTraitVersions.getInstanceValueRef(traitType, instanceID); if (packetTraitVersion > instanceVersionRef) { @@ -303,7 +307,8 @@ void AvatarMixerClientData::processBulkAvatarTraitsAckMessage(ReceivedMessage& m auto simpleReceivedIt = traitVersions.simpleCBegin(); while (simpleReceivedIt != traitVersions.simpleCEnd()) { if (*simpleReceivedIt != AvatarTraits::DEFAULT_TRAIT_VERSION) { - auto traitType = static_cast(std::distance(traitVersions.simpleCBegin(), simpleReceivedIt)); + auto traitType = + static_cast(std::distance(traitVersions.simpleCBegin(), simpleReceivedIt)); _perNodeAckedTraitVersions[nodeId][traitType] = *simpleReceivedIt; } simpleReceivedIt++; @@ -361,8 +366,8 @@ void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedDa // make sure we're not unecessarily overriding the default avatar with the default avatar if (_avatar->getWireSafeSkeletonModelURL() != slaveSharedData.skeletonReplacementURL) { // we need to change this avatar's skeleton URL, and send them a traits packet informing them of the change - qDebug() << "Overwriting avatar URL" << _avatar->getWireSafeSkeletonModelURL() - << "to replacement" << slaveSharedData.skeletonReplacementURL << "for" << sendingNode.getUUID(); + qDebug() << "Overwriting avatar URL" << _avatar->getWireSafeSkeletonModelURL() << "to replacement" + << slaveSharedData.skeletonReplacementURL << "for" << sendingNode.getUUID(); _avatar->setSkeletonModelURL(slaveSharedData.skeletonReplacementURL); auto packet = NLPacket::create(PacketType::SetAvatarTraits, -1, true); @@ -463,9 +468,7 @@ void AvatarMixerClientData::readViewFrustumPacket(const QByteArray& message) { bool AvatarMixerClientData::otherAvatarInView(const AABox& otherAvatarBox) { return std::any_of(std::begin(_currentViewFrustums), std::end(_currentViewFrustums), - [&](const ConicalViewFrustum& viewFrustum) { - return viewFrustum.intersects(otherAvatarBox); - }); + [&](const ConicalViewFrustum& viewFrustum) { return viewFrustum.intersects(otherAvatarBox); }); } void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const { @@ -484,7 +487,8 @@ void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const { jsonObject["recent_other_av_out_of_view"] = _recentOtherAvatarsOutOfView; } -AvatarMixerClientData::TraitsCheckTimestamp AvatarMixerClientData::getLastOtherAvatarTraitsSendPoint(Node::LocalID otherAvatar) const { +AvatarMixerClientData::TraitsCheckTimestamp AvatarMixerClientData::getLastOtherAvatarTraitsSendPoint( + Node::LocalID otherAvatar) const { auto it = _lastSentTraitsTimestamps.find(otherAvatar); if (it != _lastSentTraitsTimestamps.end()) { diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 4030291a44..1ddc972029 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -476,9 +476,9 @@ bool ZoneEntityItem::matchesJSONFilters(const QJsonObject& jsonFilters) const { static const QString AVATAR_PRIORITY_PROPERTY = "avatarPriority"; - // If set ignore only priority-inherit zones: + // If set match zones of interest to avatar mixer: if (jsonFilters.contains(AVATAR_PRIORITY_PROPERTY) && jsonFilters[AVATAR_PRIORITY_PROPERTY].toBool() - && _avatarPriority != COMPONENT_MODE_INHERIT) { + && (_avatarPriority != COMPONENT_MODE_INHERIT || _screenshare != COMPONENT_MODE_INHERIT)) { return true; } From 5eb6e9dadd5ea993654405a28fdc66b662788f69 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 26 Nov 2019 17:00:13 +1300 Subject: [PATCH 12/29] Add recently added properties to caches' JSDoc --- .../animation/src/AnimationCacheScriptingInterface.h | 4 ++++ libraries/audio/src/SoundCacheScriptingInterface.h | 4 ++++ .../material-networking/TextureCacheScriptingInterface.h | 4 ++++ .../src/model-networking/ModelCacheScriptingInterface.h | 4 ++++ libraries/networking/src/ResourceCache.h | 8 +++++--- .../src/procedural/MaterialCacheScriptingInterface.h | 4 ++++ 6 files changed, 25 insertions(+), 3 deletions(-) diff --git a/libraries/animation/src/AnimationCacheScriptingInterface.h b/libraries/animation/src/AnimationCacheScriptingInterface.h index 0ceb302913..fc31ecaa2b 100644 --- a/libraries/animation/src/AnimationCacheScriptingInterface.h +++ b/libraries/animation/src/AnimationCacheScriptingInterface.h @@ -38,6 +38,10 @@ class AnimationCacheScriptingInterface : public ScriptableResourceCache, public * @property {number} numCached - Total number of cached resource. Read-only. * @property {number} sizeTotal - Size in bytes of all resources. Read-only. * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + * @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers). + * Read-only. + * @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers). + * Read-only. * * @borrows ResourceCache.getResourceList as getResourceList * @borrows ResourceCache.updateTotalSize as updateTotalSize diff --git a/libraries/audio/src/SoundCacheScriptingInterface.h b/libraries/audio/src/SoundCacheScriptingInterface.h index ea767e00b4..28425f0406 100644 --- a/libraries/audio/src/SoundCacheScriptingInterface.h +++ b/libraries/audio/src/SoundCacheScriptingInterface.h @@ -39,6 +39,10 @@ class SoundCacheScriptingInterface : public ScriptableResourceCache, public Depe * @property {number} numCached - Total number of cached resource. Read-only. * @property {number} sizeTotal - Size in bytes of all resources. Read-only. * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + * @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers). + * Read-only. + * @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers). + * Read-only. * * @borrows ResourceCache.getResourceList as getResourceList * @borrows ResourceCache.updateTotalSize as updateTotalSize diff --git a/libraries/material-networking/src/material-networking/TextureCacheScriptingInterface.h b/libraries/material-networking/src/material-networking/TextureCacheScriptingInterface.h index 58d2784855..5ff15c03d9 100644 --- a/libraries/material-networking/src/material-networking/TextureCacheScriptingInterface.h +++ b/libraries/material-networking/src/material-networking/TextureCacheScriptingInterface.h @@ -37,6 +37,10 @@ class TextureCacheScriptingInterface : public ScriptableResourceCache, public De * @property {number} numCached - Total number of cached resource. Read-only. * @property {number} sizeTotal - Size in bytes of all resources. Read-only. * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + * @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers). + * Read-only. + * @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers). + * Read-only. * * @borrows ResourceCache.getResourceList as getResourceList * @borrows ResourceCache.updateTotalSize as updateTotalSize diff --git a/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.h b/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.h index cea2a6cd40..d83b853b01 100644 --- a/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.h +++ b/libraries/model-networking/src/model-networking/ModelCacheScriptingInterface.h @@ -37,6 +37,10 @@ class ModelCacheScriptingInterface : public ScriptableResourceCache, public Depe * @property {number} numCached - Total number of cached resource. Read-only. * @property {number} sizeTotal - Size in bytes of all resources. Read-only. * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + * @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers). + * Read-only. + * @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers). + * Read-only. * * @borrows ResourceCache.getResourceList as getResourceList * @borrows ResourceCache.updateTotalSize as updateTotalSize diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 4213d92fc0..9d08fd415a 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -318,9 +318,11 @@ class ScriptableResourceCache : public QObject { Q_PROPERTY(size_t sizeCached READ getSizeCachedResources NOTIFY dirty) /**jsdoc - * @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource managers). Read-only. - * @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource managers). Read-only. - */ + * @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers). + * Read-only. + * @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers). + * Read-only. + */ Q_PROPERTY(size_t numGlobalQueriesPending READ getNumGlobalQueriesPending NOTIFY dirty) Q_PROPERTY(size_t numGlobalQueriesLoading READ getNumGlobalQueriesLoading NOTIFY dirty) diff --git a/libraries/procedural/src/procedural/MaterialCacheScriptingInterface.h b/libraries/procedural/src/procedural/MaterialCacheScriptingInterface.h index 3a13652aec..a1b2b1e01e 100644 --- a/libraries/procedural/src/procedural/MaterialCacheScriptingInterface.h +++ b/libraries/procedural/src/procedural/MaterialCacheScriptingInterface.h @@ -36,6 +36,10 @@ class MaterialCacheScriptingInterface : public ScriptableResourceCache, public D * @property {number} numCached - Total number of cached resource. Read-only. * @property {number} sizeTotal - Size in bytes of all resources. Read-only. * @property {number} sizeCached - Size in bytes of all cached resources. Read-only. + * @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers). + * Read-only. + * @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers). + * Read-only. * * @borrows ResourceCache.getResourceList as getResourceList * @borrows ResourceCache.updateTotalSize as updateTotalSize From fafb3dd30bddadd3119ca01baa4132638c8b0f14 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 26 Nov 2019 17:43:23 +1300 Subject: [PATCH 13/29] MaterialCache JSDoc --- libraries/networking/src/ResourceCache.h | 8 ++++---- .../src/procedural/MaterialCacheScriptingInterface.h | 2 +- tools/jsdoc/plugins/hifi.js | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 9d08fd415a..ddc750664f 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -91,8 +91,8 @@ private: class ScriptableResource : public QObject { /**jsdoc - * Information about a cached resource. Created by {@link AnimationCache.prefetch}, {@link ModelCache.prefetch}, - * {@link SoundCache.prefetch}, or {@link TextureCache.prefetch}. + * Information about a cached resource. Created by {@link AnimationCache.prefetch}, {@link MaterialCache.prefetch}, + * {@link ModelCache.prefetch}, {@link SoundCache.prefetch}, or {@link TextureCache.prefetch}. * * @class ResourceObject * @@ -334,7 +334,7 @@ public: * @function ResourceCache.getResourceList * @returns {string[]} The URLs of all resources in the cache. * @example Report cached resources. - * // Replace AnimationCache with ModelCache, SoundCache, or TextureCache as appropriate. + * // Replace AnimationCache with MaterialCache, ModelCache, SoundCache, or TextureCache as appropriate. * * var cachedResources = AnimationCache.getResourceList(); * print("Cached resources: " + JSON.stringify(cachedResources)); @@ -354,7 +354,7 @@ public: * @param {string} url - The URL of the resource to prefetch. * @returns {ResourceObject} A resource object. * @example Prefetch a resource and wait until it has loaded. - * // Replace AnimationCache with ModelCache, SoundCache, or TextureCache as appropriate. + * // Replace AnimationCache with MaterialCache, ModelCache, SoundCache, or TextureCache as appropriate. * // TextureCache has its own version of this function. * * var resourceURL = "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/animations/sitting_idle.fbx"; diff --git a/libraries/procedural/src/procedural/MaterialCacheScriptingInterface.h b/libraries/procedural/src/procedural/MaterialCacheScriptingInterface.h index a1b2b1e01e..2e0d319b7a 100644 --- a/libraries/procedural/src/procedural/MaterialCacheScriptingInterface.h +++ b/libraries/procedural/src/procedural/MaterialCacheScriptingInterface.h @@ -24,7 +24,7 @@ class MaterialCacheScriptingInterface : public ScriptableResourceCache, public D // Properties are copied over from ResourceCache (see ResourceCache.h for reason). /**jsdoc - * The TextureCache API manages texture cache resources. + * The MaterialCache API manages material cache resources. * * @namespace MaterialCache * diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index aa2b81c0a8..0da2a34c48 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -57,6 +57,7 @@ exports.handlers = { '../../libraries/physics/src', '../../libraries/platform/src/platform/backend', '../../libraries/plugins/src/plugins', + '../../libraries/procedural/src/procedural', '../../libraries/pointers/src', '../../libraries/render-utils/src', '../../libraries/script-engine/src', From c998148589ee7c550a896aa217bc191820445cd2 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 26 Nov 2019 17:43:41 +1300 Subject: [PATCH 14/29] Revise recent changes to Entities.Material JSDoc --- .../procedural/ProceduralMaterialCache.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp b/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp index b9611358e7..fcdfdfbea8 100644 --- a/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp +++ b/libraries/procedural/src/procedural/ProceduralMaterialCache.cpp @@ -113,9 +113,9 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater /**jsdoc * A material used in a {@link Entities.MaterialResource|MaterialResource}. * @typedef {object} Entities.Material + * @property {string} name="" - A name for the material. Supported by all material models. * @property {string} model="hifi_pbr" - Different material models support different properties and rendering modes. * Supported models are: "hifi_pbr", "hifi_shader_simple". - * @property {string} name="" - A name for the material. Supported by all material models. * @property {ColorFloat|RGBS|string} emissive - The emissive color, i.e., the color that the material emits. A * {@link ColorFloat} value is treated as sRGB and must have component values in the range 0.0 – * 1.0. A {@link RGBS} value can be either RGB or sRGB. @@ -144,12 +144,17 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater * value for transparency. * "hifi_pbr" model only. * @property {number|string} opacityMapMode - The mode defining the interpretation of the opacity map. Values can be: - * "OPACITY_MAP_OPAQUE" for ignoring the opacity map information. - * "OPACITY_MAP_MASK" for using the opacity map as a mask, where only the texel greater than opacityCutoff are visible and rendered opaque. - * "OPACITY_MAP_BLEND" for using the opacity map for alpha blending the material surface with the background. + *
      + *
    • "OPACITY_MAP_OPAQUE" for ignoring the opacityMap information.
    • + *
    • "OPACITY_MAP_MASK" for using the opacityMap as a mask, where only the texel greater + * than opacityCutoff are visible and rendered opaque.
    • + *
    • "OPACITY_MAP_BLEND" for using the opacityMap for alpha blending the material surface + * with the background.
    • + *
    * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. - * @property {number|string} opacityCutoff - The opacity cutoff threshold used to determine the opaque texels of the Opacity map - * when opacityMapMode is "OPACITY_MAP_MASK", range 0.01.0. + * @property {number|string} opacityCutoff - The opacity cutoff threshold used to determine the opaque texels of the + * opacityMap when opacityMapMode is "OPACITY_MAP_MASK", range 0.0 + * – 1.0. * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. * @property {string} roughnessMap - The URL of the roughness texture image. You can use this or glossMap, but not * both. @@ -179,7 +184,7 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. * @property {Mat4|string} texCoordTransform1 - The transform to use for occlusionMap and lightMap. * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. - * @property {string} lightmapParams - Parameters for controlling how lightMap is used. + * @property {string} lightmapParams - Parameters for controlling how lightMap is used. * Set to "fallthrough" to fall through to the material below. "hifi_pbr" model only. *

    Currently not used.

    * @property {string} materialParams - Parameters for controlling the material projection and repetition. From 764b6a9adf56756ab0dddec6b4f8fe0316475707 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 26 Nov 2019 10:52:21 -0800 Subject: [PATCH 15/29] code review feedback --- tools/animedit/deploy.bat | 3 --- tools/animedit/main.cpp | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 tools/animedit/deploy.bat diff --git a/tools/animedit/deploy.bat b/tools/animedit/deploy.bat deleted file mode 100644 index e788d9d204..0000000000 --- a/tools/animedit/deploy.bat +++ /dev/null @@ -1,3 +0,0 @@ -mkdir C:\Users\ajthy\Documents\animedit\bin -copy C:\Users\ajthy\Documents\build-animedit-Desktop_Qt_5_12_3_MSVC2015_64bit-Release\release\animedit.exe C:\Users\ajthy\Documents\animedit\bin -C:\Qt\5.12.3\msvc2015_64\bin\windeployqt.exe -qmldir C:\Users\ajthy\Documents\animedit\qml C:\Users\ajthy\Documents\animedit\bin\animedit.exe \ No newline at end of file diff --git a/tools/animedit/main.cpp b/tools/animedit/main.cpp index 4c7ea4de68..ae592b5c02 100644 --- a/tools/animedit/main.cpp +++ b/tools/animedit/main.cpp @@ -26,9 +26,8 @@ int main(int argc, char *argv[]) const QUrl url(QStringLiteral("qrc:/qml/main.qml")); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) { + // failure loading main.qml QCoreApplication::exit(-1); - } else { - ; } }, Qt::QueuedConnection); engine.load(url); From 7b49542a4d61030cdd216bceb3ec6b8fe12762f8 Mon Sep 17 00:00:00 2001 From: Zach Date: Thu, 21 Nov 2019 15:38:28 -0800 Subject: [PATCH 16/29] Fix screenshare on MacOS DEBUG builds; fix z-fighting on smartboard in screenshare mode --- interface/src/scripting/ScreenshareScriptingInterface.cpp | 4 ++-- interface/src/scripting/ScreenshareScriptingInterface.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/scripting/ScreenshareScriptingInterface.cpp b/interface/src/scripting/ScreenshareScriptingInterface.cpp index 89f5ee6970..5cf4d1939a 100644 --- a/interface/src/scripting/ScreenshareScriptingInterface.cpp +++ b/interface/src/scripting/ScreenshareScriptingInterface.cpp @@ -90,7 +90,7 @@ static const EntityTypes::EntityType LOCAL_SCREENSHARE_WEB_ENTITY_TYPE = EntityT static const uint8_t LOCAL_SCREENSHARE_WEB_ENTITY_FPS = 30; // This is going to be a good amount of work to make this work dynamically for any screensize. // V1 will have only hardcoded values. -static const glm::vec3 LOCAL_SCREENSHARE_WEB_ENTITY_LOCAL_POSITION(0.0128f, -0.0918f, 0.0771f); +static const glm::vec3 LOCAL_SCREENSHARE_WEB_ENTITY_LOCAL_POSITION(0.0128f, -0.0918f, 0.0711f); static const glm::vec3 LOCAL_SCREENSHARE_WEB_ENTITY_DIMENSIONS(3.6790f, 2.0990f, 0.0100f); static const QString LOCAL_SCREENSHARE_WEB_ENTITY_URL = "https://content.highfidelity.com/Experiences/Releases/usefulUtilities/smartBoard/screenshareViewer/screenshareClient.html"; @@ -124,7 +124,7 @@ void ScreenshareScriptingInterface::startScreenshare(const QUuid& screenshareZon // Ensure that the screenshare executable exists where we expect it to. // Error out and reset the screen share state machine if the executable doesn't exist. QFileInfo screenshareExecutable(SCREENSHARE_EXE_PATH); - if (!screenshareExecutable.exists() || !screenshareExecutable.isFile()) { + if (!screenshareExecutable.exists() || !(screenshareExecutable.isFile() || screenshareExecutable.isBundle())) { qDebug() << "Screenshare executable doesn't exist at" << SCREENSHARE_EXE_PATH; stopScreenshare(); emit screenshareError(); diff --git a/interface/src/scripting/ScreenshareScriptingInterface.h b/interface/src/scripting/ScreenshareScriptingInterface.h index 22a65e169e..9dc828c13b 100644 --- a/interface/src/scripting/ScreenshareScriptingInterface.h +++ b/interface/src/scripting/ScreenshareScriptingInterface.h @@ -43,10 +43,10 @@ private: #ifdef Q_OS_WIN const QString SCREENSHARE_EXE_PATH{ PathUtils::projectRootPath() + "/screenshare/hifi-screenshare-win32-x64/hifi-screenshare.exe" }; #elif defined(Q_OS_MAC) - const QString SCREENSHARE_EXE_PATH{ PathUtils::projectRootPath() + "/screenshare/screenshare-darwin-x64/hifi-screenshare.app" }; + const QString SCREENSHARE_EXE_PATH{ PathUtils::projectRootPath() + "/build/screenshare/hifi-screenshare-darwin-x64/hifi-screenshare.app" }; #else // This path won't exist on other platforms, so the Screenshare Scripting Interface will exit early when invoked. - const QString SCREENSHARE_EXE_PATH{ PathUtils::projectRootPath() + "/screenshare/screenshare-other-os/hifi-screenshare" }; + const QString SCREENSHARE_EXE_PATH{ PathUtils::projectRootPath() + "/screenshare/hifi-screenshare-other-os/hifi-screenshare" }; #endif #else #ifdef Q_OS_WIN From 0de812b7b52df114c66fafd5e919962044550490 Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Tue, 26 Nov 2019 11:41:41 -0800 Subject: [PATCH 17/29] waiting on local injector prep --- libraries/audio-client/src/AudioClient.cpp | 7 ++++++- libraries/audio-client/src/AudioClient.h | 4 +++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 5502986cff..4080019822 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -2064,6 +2064,11 @@ bool AudioClient::switchOutputToAudioDevice(const HifiAudioDeviceInfo outputDevi Lock localAudioLock(_localAudioMutex); _localSamplesAvailable.exchange(0, std::memory_order_release); + //wait on local injectors prep to finish running + if (_localPrepInjectorFuture.isStarted() || _localPrepInjectorFuture.isRunning()) { + _localPrepInjectorFuture.waitForFinished(); + } + // cleanup any previously initialized device if (_audioOutput) { _audioOutputIODevice.close(); @@ -2344,7 +2349,7 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { } // prepare injectors for the next callback - QtConcurrent::run(QThreadPool::globalInstance(), [this] { + _audio->_localPrepInjectorFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] { _audio->prepareLocalAudioInjectors(); }); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index e31b4789ce..09abb2c356 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -506,7 +507,8 @@ private: #endif AudioSolo _solo; - + + QFuture _localPrepInjectorFuture; QReadWriteLock _hmdNameLock; Mutex _checkDevicesMutex; QTimer* _checkDevicesTimer { nullptr }; From 323007eb2c2fcbb2155a880a4f440f8539c15a70 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Tue, 26 Nov 2019 12:11:38 -0800 Subject: [PATCH 18/29] ignore anonymous screenshare zone entry --- domain-server/src/DomainServer.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 9eec7df86e..de41927fe2 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -3630,6 +3630,9 @@ void DomainServer::processAvatarZonePresencePacket(QSharedPointergetPermissions().getVerifiedUserName(); + if (verifiedUsername.isEmpty()) { // Silently bail for users who are not logged in. + return; + } static const int SCREENSHARE_EXPIRATION_SECONDS = 24 * 60 * 60; screensharePresence(zone.isNull() ? "" : zone.toString(), verifiedUsername, SCREENSHARE_EXPIRATION_SECONDS); } From f17caf48ca55c97d01a8076c03ed9f273b27eaab Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 26 Nov 2019 13:42:01 -0700 Subject: [PATCH 19/29] Add automaticLookAt.js to the developer scripts --- scripts/developer/automaticLookAt.js | 1344 ++++++++++++++++++++++++++ 1 file changed, 1344 insertions(+) create mode 100644 scripts/developer/automaticLookAt.js diff --git a/scripts/developer/automaticLookAt.js b/scripts/developer/automaticLookAt.js new file mode 100644 index 0000000000..542d0df0d8 --- /dev/null +++ b/scripts/developer/automaticLookAt.js @@ -0,0 +1,1344 @@ +// +// automaticLookAt.js +// This script controls the avatar's look-at-target for the head and eyes, according to other avatar's actions +// It tries to simulate human interaction during group conversations +// +// Created by Luis Cuenca on 11/11/19 +// Copyright 2019 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 +// + +(function() { + + ////////////////////////////////////////////// + // debugger.js /////// + ////////////////////////////////////////////// + + var TEXT_BOX_WIDTH = 350; + var TEXT_BOX_MIN_HEIGHT = 40; + var TEXT_BOX_TOP_MARGIN = 100; + var TEXT_CAPTION_HEIGHT = 30; + var TEXT_CAPTION_COLOR = { red: 0, green: 0, blue: 0 }; + var TEXT_CAPTION_SIZE = 18; + var TEXT_CAPTION_MARGIN = 6; + var CHECKBOX_MARK_MARGIN = 3; + + var DEGREE_TO_RADIAN = 0.0174533; + var ENGAGED_AVATARS_DEBUG_COLOR = { red: 0, green: 255, blue: 255 }; + var ENGAGED_AVATARS_DEBUG_ALPHA = 0.3; + var FOCUS_AVATAR_DEBUG_COLOR = { red: 255, green: 0, blue: 0 }; + var FOCUS_AVATAR_DEBUG_ALPHA = 1.0; + var TALKER_AVATAR_DEBUG_COLOR = { red: 0, green: 0, blue: 0 }; + var TALKER_AVATAR_DEBUG_ALPHA = 0.8; + var DEFAULT_OUTLINE_COLOR = { red: 155, green: 155, blue: 255 }; + var DEFAULT_OUTLINE_WIDTH = 2; + + var LookAtDebugger = function() { + var self = this; + var IMAGE_DIMENSIONS = {x: 0.2, y: 0.2, z:0.2}; + var TARGET_ICON_PATH = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/LookAtApp/eyeFocus.png"; + var INFINITY_ICON_PATH = "https://hifi-content.s3.amazonaws.com/luis/test_scripts/LookAtApp/noFocus.png"; + this.items = {}; + this.active = false; + + // UI elements + this.textBox; + this.activeCheckBox; + this.activeCheckBoxMark; + this.activeCaption; + this.textBoxHeight = 0.0; + this.logs = []; + + this.getStyleProps = function(color, alpha) { + return { + fillUnoccludedColor: color, + fillUnoccludedAlpha: alpha, + fillOccludedColor: color, + fillOccludedAlpha: alpha, + outlineUnoccludedColor: DEFAULT_OUTLINE_COLOR, + outlineUnoccludedAlpha: alpha, + outlineOccludedColor: DEFAULT_OUTLINE_COLOR, + outlineOccludedAlpha: alpha, + outlineWidth: DEFAULT_OUTLINE_WIDTH, + isOutlineSmooth: false + } + } + this.Styles = { + "engaged" : { + "style": self.getStyleProps(ENGAGED_AVATARS_DEBUG_COLOR, ENGAGED_AVATARS_DEBUG_ALPHA), + "name" : "engagedSel" + }, + "focus" : { + "style": self.getStyleProps(FOCUS_AVATAR_DEBUG_COLOR, FOCUS_AVATAR_DEBUG_ALPHA), + "name" : "focusSel" + }, + "talker" : { + "style": self.getStyleProps(TALKER_AVATAR_DEBUG_COLOR, TALKER_AVATAR_DEBUG_ALPHA), + "name" : "talkerSel" + } + }; + + this.eyeTarget; + this.eyesTargetProps = { + name: "Eyes-Target-Image", + position: { x: 0.0, y: 0.0, z: 0.0 }, + color: {red: 255, green: 0, blue: 255}, + url: TARGET_ICON_PATH, + dimensions: IMAGE_DIMENSIONS, + alpha: 1.0, + visible: false, + emissive: true, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + isFacingAvatar: true + }; + this.headTarget; + this.headTargetProps = { + name: "Head-Target-Image", + position: { x: 0.0, y: 0.0, z: 0.0 }, + color: {red: 0, green: 255, blue: 255}, + url: TARGET_ICON_PATH, + dimensions: IMAGE_DIMENSIONS, + alpha: 1.0, + visible: false, + emissive: true, + ignoreRayIntersection: true, + drawInFront: true, + grabbable: false, + isFacingAvatar: true + }; + + + this.textBoxProps = { + x: Window.innerWidth - TEXT_BOX_WIDTH, + y: TEXT_BOX_TOP_MARGIN, + width: TEXT_BOX_WIDTH, + height: TEXT_BOX_MIN_HEIGHT, + alpha: 0.7, + color: { red: 255, green: 255, blue: 255 } + }; + + this.activeCheckBoxProps = { + x: TEXT_CAPTION_MARGIN + self.textBoxProps.x, + y: 2 * TEXT_CAPTION_MARGIN + self.textBoxProps.y, + width: TEXT_CAPTION_SIZE, + height: TEXT_CAPTION_SIZE, + alpha: 0.0, + borderWidth: 2, + borderColor: TEXT_CAPTION_COLOR + }; + + this.activeCheckBoxMarkProps = { + x: self.activeCheckBoxProps.x + CHECKBOX_MARK_MARGIN, + y: self.activeCheckBoxProps.y + CHECKBOX_MARK_MARGIN, + width: TEXT_CAPTION_SIZE - 2.0 * CHECKBOX_MARK_MARGIN, + height: TEXT_CAPTION_SIZE - 2.0 * CHECKBOX_MARK_MARGIN, + visible: false, + alpha: 1.0, + color: TEXT_CAPTION_COLOR + } + + this.captionProps = { + x: 2 * TEXT_CAPTION_SIZE + self.textBoxProps.x, + y: TEXT_CAPTION_MARGIN + self.textBoxProps.y, + width: self.textBoxProps.width, + height: 30, + alpha: 1.0, + backgroundAlpha: 0.0, + visible: true, + text: "Debug Auto Look At", + font: { + size: TEXT_CAPTION_SIZE + }, + color: TEXT_CAPTION_COLOR, + topMargin: 0.5 * TEXT_CAPTION_MARGIN + }; + + this.log = function(txt) { + if (self.active) { + self.logs.push(Overlays.addOverlay("text", self.captionProps)); + var y = self.textBoxProps.y + self.captionProps.height * (self.logs.length); + Overlays.editOverlay(self.logs[self.logs.length - 1], {y: y, text: txt}); + var height = (TEXT_CAPTION_SIZE + 2.0 * TEXT_CAPTION_MARGIN) * (self.logs.length + 2); + if (this.textBoxHeight !== height) { + this.textBoxHeight = height + Overlays.editOverlay(self.textBox, {height: height}); + } + } + } + + this.clearLog = function() { + for (var n = 0; n < self.logs.length; n++) { + Overlays.deleteOverlay(self.logs[n]); + } + Overlays.editOverlay(self.textBox, self.textBoxProps); + self.logs = []; + self.log("____________________________"); + } + + this.setActive = function(isActive) { + self.active = isActive; + if (!self.active) { + self.turnOff(); + } else { + self.turnOn(); + } + }; + + + this.onClick = function(event) { + if (event.x > self.activeCheckBoxProps.x && event.x < (self.activeCheckBoxProps.x + self.activeCheckBoxProps.width) && + event.y > self.activeCheckBoxProps.y && event.y < (self.activeCheckBoxProps.y + self.activeCheckBoxProps.height)) { + self.setActive(!self.active); + Overlays.editOverlay(self.activeCheckBoxMark, {visible: self.active}); + } + } + + this.turnOn = function() { + for (var key in self.Styles) { + var selStyle = self.Styles[key]; + Selection.enableListHighlight(selStyle.name, selStyle.style); + } + if (!self.eyeTarget) { + self.eyeTarget = Overlays.addOverlay("image3d", self.eyesTargetProps); + } + if (!self.headTarget) { + self.headTarget = Overlays.addOverlay("image3d", self.headTargetProps); + } + } + + this.init = function() { + if (!self.textBox) { + self.textBox = Overlays.addOverlay("rectangle", self.textBoxProps); + } + if (!self.activeCheckBox) { + self.activeCheckBox = Overlays.addOverlay("rectangle", self.activeCheckBoxProps); + } + if (!self.activeCheckBoxMark) { + self.activeCheckBoxMark = Overlays.addOverlay("rectangle", self.activeCheckBoxMarkProps); + } + if (!self.activeCaption) { + self.activeCaption = Overlays.addOverlay("text", self.captionProps); + } + }; + + this.highLightAvatars = function(engagedIDs, focusID, talkerID) { + if (self.active) { + self.clearSelection(); + engagedIDs = !engagedIDs ? [] : engagedIDs; + var focusIDs = !focusID ? [] : [focusID]; + var talkerIDs = !talkerID ? [] : [talkerID]; + self.highLightIDs(self.Styles.engaged.name, engagedIDs); + self.highLightIDs(self.Styles.focus.name, focusIDs); + self.highLightIDs(self.Styles.talker.name, talkerIDs); + return true; + } else { + return false; + } + }; + + this.getTargetProps = function(target) { + var distance = Vec3.length(Vec3.subtract(target, Camera.getPosition())); + RETARGET_MAX_DISTANCE = 100.0; + DIMENSION_SCALE = 0.05; + var isInfinite = false; + if (distance > RETARGET_MAX_DISTANCE) { + isInfinite = true; + var eyesToTarget = Vec3.multiply(RETARGET_MAX_DISTANCE, Vec3.normalize(Vec3.subtract(target, MyAvatar.getDefaultEyePosition()))); + var newTarget = Vec3.sum(MyAvatar.getDefaultEyePosition(), eyesToTarget); + var cameraToTarget = Vec3.normalize(Vec3.subtract(newTarget, Camera.getPosition())); + target = Vec3.sum(Camera.getPosition(), cameraToTarget); + distance = Vec3.length(cameraToTarget); + } + // Scale the target to appear always with the same size on screen + var fov = DEGREE_TO_RADIAN * Camera.frustum.fieldOfView; + var scale = (Camera.frustum.aspectRatio < 1.0 ? Camera.frustum.aspectRatio : 1.0) * DIMENSION_SCALE; + var dimensionRatio = scale * (distance / (0.5 * Math.tan(0.5 * fov))); + var dimensions = Vec3.multiply(dimensionRatio, IMAGE_DIMENSIONS); + return {"dimensions": dimensions, "visible": true, "position": target, "url": isInfinite ? INFINITY_ICON_PATH : TARGET_ICON_PATH}; + } + + this.showTarget = function(headTarget, eyeTarget) { + if (!self.active) { + return; + } + var targetProps = self.getTargetProps(eyeTarget); + Overlays.editOverlay(self.eyeTarget, targetProps); + var headTargetProps = self.getTargetProps(headTarget); + Overlays.editOverlay(self.headTarget, headTargetProps); + } + + this.hideEyeTarget = function() { + Overlays.editOverlay(self.eyeTarget, {"visible": false}); + } + + this.highLightIDs = function(selectionName, ids) { + self.items[selectionName] = ids; + for (var idx in ids) { + Selection.addToSelectedItemsList(selectionName, "avatar", ids[idx]); + } + } + this.clearSelection = function() { + for (key in self.Styles) { + var selStyle = self.Styles[key]; + Selection.clearSelectedItemsList(selStyle.name); + } + } + + this.turnOff = function() { + self.clearLog(); + for (var key in self.Styles) { + var selStyle = self.Styles[key]; + Selection.disableListHighlight(selStyle.name); + } + Overlays.deleteOverlay(self.headTarget); + self.headTarget = undefined; + Overlays.deleteOverlay(self.eyeTarget); + self.eyeTarget = undefined; + } + + this.finish = function() { + if (self.active) { + self.setActive(false); + } + Overlays.deleteOverlay(self.textBox); + self.textBox = undefined; + Overlays.deleteOverlay(self.activeCheckBox); + self.activeCheckBox = undefined; + Overlays.deleteOverlay(self.activeCaption); + self.activeCaption = undefined; + Overlays.deleteOverlay(self.activeCheckBoxMark); + self.activeCheckBoxMark = undefined; + } + + this.init(); + } + + ////////////////////////////////////////////// + // randomHelper.js ////////// + ////////////////////////////////////////////// + + var RandomHelper = function() { + var self = this; + + this.createRandomIndexes = function(count) { + var indexes = []; + for (var n = 0; n < count; n++) { + indexes.push(n); + } + var randomIndexes = []; + for (var n = 0; n < count; n++) { + var indexesCount = indexes.length; + var randomIndex = 0; + if (indexesCount > 1) { + var randFactor = Math.random(); + randomIndex = randFactor !== 1.0 ? Math.floor(randFactor * indexes.length) : indexes.length - 1; + } + randomIndexes.push(indexes[randomIndex]); + indexes.splice(randomIndex, 1); + } + return randomIndexes; + } + + this.getRandomKey = function(keypool) { + // keypool can be an object. {key1: percentage1, key2: percentage2} or + // keypool can be an array. [key1, key2] Equal percentage each component + + var equalChance = Array.isArray(keypool); + var totalPercentage = 0.0; + var percentages = {}; + var normalizedPercentages = {}; + var keys = equalChance ? keypool : Object.keys(keypool); + for (var n = 0; n < keys.length; n++) { + var key = keys[n]; + percentages[key] = equalChance ? 1.0 / keys.length : keypool[key].chance; + totalPercentage += equalChance ? percentages[key] : keypool[key].chance; + } + var accumulatedVal = 0.0; + for (var n = 0; n < keys.length; n++) { + var key = keys[n]; + var val = accumulatedVal + (percentages[key] / totalPercentage); + normalizedPercentages[key] = val; + accumulatedVal = val; + } + var dice = Math.random(); + var floor = 0.0; + var hit = normalizedPercentages[keys[0]]; + for (var n = 0; n < keys.length; n++) { + var key = keys[n]; + if (dice > floor && dice < normalizedPercentages[key]) { + hit = key; + break; + } + floor = normalizedPercentages[key]; + } + return { randomKey: hit, chance: percentages[hit]}; + } + } + + + ////////////////////////////////////////////// + // automaticMachine.js ////////// + ////////////////////////////////////////////// + var MIN_LOOKAT_HEAD_MIX_ALPHA = 0.04; + var MAX_LOOKAT_HEAD_MIX_ALPHA = 0.08; + var CAMERA_HEAD_MIX_ALPHA = 0.06; + + var TargetType = { + "unknown" : 0, + "avatar" : 1, + "entity" : 2 + } + + var TargetOffsetMode = { + "noOffset" : 0, + "onlyHead" : 1, + "onlyEyes" : 2, + "headAndEyes" : 3, + "print" : function(sta) { + return ("OffsetMode: " + (Object.keys(TargetOffsetMode))[sta]); + } + } + + var TargetMode = { + "noTarget" : 0, + "leftEye" : 1, + "rightEye" : 2, + "mouth" : 3, + "leftHand" : 4, + "rightHand" : 5, + "random" : 6, + "print" : function(sta) { + return ("TargetMode: " + (Object.keys(TargetMode))[sta]); + } + } + + var ACTION_CONFIGURATION = { + "TargetMode.mouth": { + "joint": "Head", + "stareTimeRange" : {"min": 0.2, "max": 2.0}, + "headSpeedRange" : {"min": MAX_LOOKAT_HEAD_MIX_ALPHA, "max": MIN_LOOKAT_HEAD_MIX_ALPHA}, + "offsetChances" : { + "TargetOffsetMode.noOffset": {"chance": 0.7}, + "TargetOffsetMode.onlyHead": {"chance": 0.3}, + "TargetOffsetMode.onlyEyes": {"chance": 0.0}, + "TargetOffsetMode.headAndEyes": {"chance": 0.0} + }, + "offsetAngleRange" : {"min": 1.0, "max": 5.0}, + "chanceWhileListening" : 0.45, + "chanceWhileTalking" : 0.25 + }, + "TargetMode.leftEye": { + "joint": "LeftEye", + "stareTimeRange" : {"min": 0.2, "max": 2.0}, + "headSpeedRange" : {"min": MAX_LOOKAT_HEAD_MIX_ALPHA, "max": MIN_LOOKAT_HEAD_MIX_ALPHA}, + "offsetChances" : { + "TargetOffsetMode.noOffset": {"chance": 0.5}, + "TargetOffsetMode.onlyHead": {"chance": 0.3}, + "TargetOffsetMode.onlyEyes": {"chance": 0.1}, + "TargetOffsetMode.headAndEyes": {"chance": 0.1} + }, + "offsetAngleRange" : {"min": 1.0, "max": 5.0}, + "chanceWhileListening" : 0.20, + "chanceWhileTalking" : 0.30 + }, + "TargetMode.rightEye": { + "joint": "RightEye", + "stareTimeRange" : {"min": 0.2, "max": 2.0}, + "headSpeedRange" : {"min": MAX_LOOKAT_HEAD_MIX_ALPHA, "max": MIN_LOOKAT_HEAD_MIX_ALPHA}, + "offsetChances" : { + "TargetOffsetMode.noOffset": {"chance": 0.5}, + "TargetOffsetMode.onlyHead": {"chance": 0.3}, + "TargetOffsetMode.onlyEyes": {"chance": 0.1}, + "TargetOffsetMode.headAndEyes": {"chance": 0.1} + }, + "offsetAngleRange" : {"min": 1.0, "max": 5.0}, + "chanceWhileListening" : 0.20, + "chanceWhileTalking" : 0.30 + }, + "TargetMode.leftHand": { + "joint": "LeftHand", + "stareTimeRange" : {"min": 0.2, "max": 2.0}, + "headSpeedRange" : {"min": MAX_LOOKAT_HEAD_MIX_ALPHA, "max": MIN_LOOKAT_HEAD_MIX_ALPHA}, + "offsetChances" : { + "TargetOffsetMode.noOffset": {"chance": 0.9}, + "TargetOffsetMode.onlyHead": {"chance": 0.1}, + "TargetOffsetMode.onlyEyes": {"chance": 0.0}, + "TargetOffsetMode.headAndEyes": {"chance": 0.0} + }, + "offsetAngleRange" : {"min": 1.0, "max": 10.0}, + "chanceWhileListening" : 0.05, + "chanceWhileTalking" : 0.05 + }, + "TargetMode.rightHand": { + "joint": "RightHand", + "stareTimeRange" : {"min": 0.2, "max": 2.0}, + "headSpeedRange" : {"min": MAX_LOOKAT_HEAD_MIX_ALPHA, "max": MIN_LOOKAT_HEAD_MIX_ALPHA}, + "offsetChances" : { + "TargetOffsetMode.noOffset": {"chance": 0.9}, + "TargetOffsetMode.onlyHead": {"chance": 0.1}, + "TargetOffsetMode.onlyEyes": {"chance": 0.0}, + "TargetOffsetMode.headAndEyes": {"chance": 0.0} + }, + "offsetAngleRange" : {"min": 1.0, "max": 10.0}, + "chanceWhileListening" : 0.05, + "chanceWhileTalking" : 0.05 + }, + "TargetMode.random": { + "joint": "Head", + "stareTimeRange" : {"min": 0.2, "max": 1.0}, + "headSpeedRange" : {"min": MAX_LOOKAT_HEAD_MIX_ALPHA, "max": MIN_LOOKAT_HEAD_MIX_ALPHA}, + "offsetChances" : { + "TargetOffsetMode.noOffset": {"chance": 0.0}, + "TargetOffsetMode.onlyHead": {"chance": 0.0}, + "TargetOffsetMode.onlyEyes": {"chance": 0.4}, + "TargetOffsetMode.headAndEyes": {"chance": 0.6} + }, + "offsetAngleRange" : {"min": 5.0, "max": 12.0}, + "chanceWhileListening" : 0.05, + "chanceWhileTalking" : 0.05 + }, + "TargetMode.noTarget": { + "joint": undefined, + "stareTimeRange" : {"min": 0.1, "max": 2.0}, + "headSpeedRange" : {"min": MAX_LOOKAT_HEAD_MIX_ALPHA, "max": MIN_LOOKAT_HEAD_MIX_ALPHA}, + "offsetChances" : { + "TargetOffsetMode.noOffset": {"chance": 0.5}, + "TargetOffsetMode.onlyHead": {"chance": 0.0}, + "TargetOffsetMode.onlyEyes": {"chance": 0.5}, + "TargetOffsetMode.headAndEyes": {"chance": 0.0} + }, + "offsetAngleRange" : {"min": 1.0, "max": 15.0}, + "chanceWhileListening" : 0.0, + "chanceWhileTalking" : 0.0 + } + } + + var FOCUS_MODE_CHANCES = { + "idle" : { + "TargetMode.mouth": {"chance": 0.2}, + "TargetMode.rightEye": {"chance": 0.4}, + "TargetMode.leftEye": {"chance": 0.4} + }, + "talking" : ["TargetMode.mouth", "TargetMode.rightEye", "TargetMode.leftEye"] // Equal chances (33.33% each) + } + + var LookAction = function() { + var self = this; + this.targetType = TargetType.unknown; + this.speed = MIN_LOOKAT_HEAD_MIX_ALPHA; + this.id = ""; + this.focusName = "None"; + this.focusChance = 0.0; + this.config = undefined; + this.targetMode = undefined; + this.lookAtJoint = undefined; + this.targetPoint = undefined; + this.elapseTime = 0.0; + this.totalTime = 1.0; + this.eyesHeadOffset = Vec3.ZERO; + this.eyesForward = false; + this.offsetEyes = false; + this.offsetHead = false; + this.offsetModeName = ""; + this.offsetChance = 0.0; + this.confortAngle = 0.0; + this.printChance = function(chance) { + return "" + Math.floor(100 * chance) + "%"; + } + this.print = function() { + var lines = []; + lines.push(TargetMode.print(eval(self.targetMode)) + " P: " + self.printChance(self.focusChance)); + lines.push(TargetOffsetMode.print(eval(self.offsetModeName)) + " P: " + self.printChance(self.offsetChance)); + lines.push("Action time: " + self.totalTime.toFixed(2) + " seconds"); + return lines; + } + } + + var AudienceAvatar = function(id) { + var self = this; + this.id = id; + this.name = ""; + this.engaged = true; + this.moved = true; + this.isTalking = false; + this.isListening = false; + this.position = Vec3.ZERO; + this.headPosition = Vec3.ZERO; + this.leftPalmPosition = Vec3.ZERO; + this.rightPalmPosition = Vec3.ZERO; + this.leftHandSpeed = 0.0; + this.rightHandSpeed = 0.0; + this.velocity = 0.0; + this.reactionTime = 0.0; + this.distance = 0.0; + this.loudness = 0.0; + this.talkingTime = 0.0; + } + + + var SmartLookMachine = function() { + var self = this; + this.myAvatarID = MyAvatar.sessionUUID; + + this.nearAvatarList = {}; + this.dice = new RandomHelper(); + + var LOOK_FOR_AVATARS_MAX_DISTANCE = 15.0; + var MIN_FOCUS_TO_LISTENER_TIME = 3.0; + var MAX_FOCUS_TO_LISTENER_TIME = 5.0; + var MIN_FOCUS_TO_TALKER_TIME = 0.5; + var MAX_FOCUS_TO_TALKER_RANGE = 1.5; + var TRIGGER_FOCUS_WHILE_IDLE_CHANCE = 0.1; + + + + this.currentAvatarFocusID = undefined; + this.currentTalker; + this.currentAction = new LookAction(); + + this.eyesTargetPoint = Vec3.ZERO; + this.headTargetPoint = Vec3.ZERO; + + + this.timeScale = 1.0; + this.lookAtDebugger = new LookAtDebugger(); + + this.active = true; + this.headTargetSpeed = 0.0; + + this.avatarFocusTotalTime = 0.0; + this.avatarFocusMax = 0.0; + this.lockedFocusID = undefined; + + this.visibilityCount = 0; + + this.shouldUpdateDebug = false; + + var TalkingState = { + "noTalking" : 0, + "meTalkingFirst" : 1, + "meTalkingAgain" : 2, + "otherTalkingFirst" : 3, + "otherTalkingAgain" : 4, + "othersTalking": 5, + "print" : function(sta) { + return ("TalkingState: " + (Object.keys(TalkingState))[sta]); + } + } + this.talkingState = TalkingState.noTalking; + var FocusState = { + "onNobody" : 0, + "onTalker" : 1, + "onRandomAudience" : 2, + "onLastTalker" : 3, + "onRandomLastTalker" : 4, + "onLastFocus" : 5, + "onSelected" : 6, + "onMovement" : 7, + "print" : function(sta) { + return ("FocusState: " + (Object.keys(FocusState))[sta]); + } + } + + this.focusState = FocusState.onNobody; + + var LockFocusType = { + "none" : 0, + "click" : 1, + "movement" : 2 + } + self.lockFocusType = LockFocusType.none; + + this.wasMeTalking = false; + this.nearAvatarIDs = []; + + this.updateAvatarVisibility = function() { + if (self.nearAvatarIDs.length > 0) { + if (self.nearAvatarList[self.myAvatarID] && self.nearAvatarList[self.myAvatarID].moved) { + for (id in self.nearAvatarList) { + self.nearAvatarList[id].moved = true; + } + self.nearAvatarList[self.myAvatarID].moved = false; + } + self.visibilityCount = ((self.visibilityCount + 1) >= self.nearAvatarIDs.length) ? 0 : self.visibilityCount + 1; + var id = self.nearAvatarIDs[self.visibilityCount]; + var avatar = self.nearAvatarList[id]; + if (id !== self.myAvatarID && avatar !== undefined && avatar.moved) { + self.nearAvatarList[id].moved = false; + var eyePos = MyAvatar.getDefaultEyePosition(); + var avatarSight = Vec3.subtract(avatar.headPosition, eyePos); + var intersection = Entities.findRayIntersection({origin: eyePos, direction: Vec3.normalize(avatarSight)}, true); + self.nearAvatarList[avatar.id].engaged = !intersection.intersects || intersection.distance > Vec3.length(avatarSight); + } + } + } + + this.getEngagedAvatars = function() { + var engagedAvatarIDs = []; + for (var id in self.nearAvatarList) { + if (self.nearAvatarList[id].engaged) { + engagedAvatarIDs.push(id); + } + } + return engagedAvatarIDs; + } + + this.updateAvatarList = function(deltaTime) { + var TALKING_LOUDNESS_THRESHOLD = 50.0; + var SILENCE_TALK_ATTENUATION = 0.5; + var ATTENTION_HANDS_SPEED = 5.0; + var ATTENTION_AVATAR_SPEED = 2.0; + var MAX_TALKING_TIME = 5.0; + var talkingAvatarID; + var maxLoudness = 0.0; + var count = 0; + var previousTalkers = []; + var fastHands = []; + var fastMovers = []; + var lookupCenter = Vec3.sum(MyAvatar.position, Vec3.multiply(0.8 * LOOK_FOR_AVATARS_MAX_DISTANCE, Quat.getFront(MyAvatar.orientation))); + var nearbyAvatars = AvatarManager.getAvatarsInRange(lookupCenter, LOOK_FOR_AVATARS_MAX_DISTANCE); + for (var n = 0; n < nearbyAvatars.length; n++) { + var avatar = AvatarManager.getAvatar(nearbyAvatars[n]); + var distance = Vec3.distance(MyAvatar.position, avatar.position); + var loudness = avatar.audioLoudness; + loudness = avatar.audioLoudness > 30.0 ? 100.0 : 0.0; + var TALKING_TAU = 0.01; + if (self.nearAvatarList[avatar.sessionUUID] === undefined) { + self.nearAvatarList[avatar.sessionUUID] = new AudienceAvatar(avatar.sessionUUID); + self.nearAvatarList[avatar.sessionUUID].name = avatar.displayName; + } else { + if (Vec3.distance(self.nearAvatarList[avatar.sessionUUID].position, avatar.position) > 0.0) { + self.nearAvatarList[avatar.sessionUUID].moved = true; + } else { + self.nearAvatarList[avatar.sessionUUID].velocity = Vec3.length(avatar.velocity); + if (self.nearAvatarList[avatar.sessionUUID].velocity > 0.0) { + self.nearAvatarList[avatar.sessionUUID].moved = true; + } + } + self.nearAvatarList[avatar.sessionUUID].position = avatar.position; + self.nearAvatarList[avatar.sessionUUID].headPosition = avatar.getJointPosition("Head"); + if (self.nearAvatarList[avatar.sessionUUID].engaged) { + self.nearAvatarList[avatar.sessionUUID].loudness = self.nearAvatarList[avatar.sessionUUID].loudness + TALKING_TAU * (loudness - self.nearAvatarList[avatar.sessionUUID].loudness); + self.nearAvatarList[avatar.sessionUUID].orientation = avatar.orientation; + var leftPalmPos = avatar.getJointPosition("LeftHand"); + var rightPalmPos = avatar.getJointPosition("RightHand"); + + var distanceAttenuation = (distance > 1.0) ? (1.0 / distance) : 1.0; + var leftPalmSpeed = distanceAttenuation * Vec3.distance(self.nearAvatarList[avatar.sessionUUID].leftPalmPosition, leftPalmPos) / deltaTime; + var rightPalmSpeed = distanceAttenuation * Vec3.distance(self.nearAvatarList[avatar.sessionUUID].rightPalmPosition, rightPalmPos) / deltaTime; + self.nearAvatarList[avatar.sessionUUID].leftPalmSpeed = leftPalmSpeed; + self.nearAvatarList[avatar.sessionUUID].rightPalmSpeed = rightPalmSpeed; + + self.nearAvatarList[avatar.sessionUUID].leftPalmPosition = leftPalmPos; + self.nearAvatarList[avatar.sessionUUID].rightPalmPosition = rightPalmPos; + if (self.nearAvatarList[avatar.sessionUUID] && self.nearAvatarList[avatar.sessionUUID].loudness) { + self.nearAvatarList[avatar.sessionUUID].loudness += 0.1; + } + self.nearAvatarList[avatar.sessionUUID].isTalking = false; + self.nearAvatarList[avatar.sessionUUID].isTalker = false; + if (self.nearAvatarList[avatar.sessionUUID].loudness > TALKING_LOUDNESS_THRESHOLD) { + if (self.nearAvatarList[avatar.sessionUUID].talkingTime < MAX_TALKING_TIME) { + self.nearAvatarList[avatar.sessionUUID].talkingTime += deltaTime; + } + self.nearAvatarList[avatar.sessionUUID].isTalking = true; + count++; + if (maxLoudness < self.nearAvatarList[avatar.sessionUUID].loudness) { + maxLoudness = self.nearAvatarList[avatar.sessionUUID].loudness; + talkingAvatarID = avatar.sessionUUID; + } + } else if (self.nearAvatarList[avatar.sessionUUID].talkingTime > 0.0){ + self.nearAvatarList[avatar.sessionUUID].talkingTime -= SILENCE_TALK_ATTENUATION * deltaTime; + if (self.nearAvatarList[avatar.sessionUUID].talkingTime < 0.0) { + self.nearAvatarList[avatar.sessionUUID].talkingTime = 0.0; + } + } + if (!self.nearAvatarList[avatar.sessionUUID].isTalking && self.nearAvatarList[avatar.sessionUUID].talkingTime > 0.0){ + previousTalkers.push(avatar.sessionUUID); + } + if ((leftPalmSpeed > ATTENTION_AVATAR_SPEED || rightPalmSpeed > ATTENTION_AVATAR_SPEED) && avatar.sessionUUID !== self.myAvatarID) { + if (!self.nearAvatarList[avatar.sessionUUID].isTalking) { + fastHands.push(avatar.sessionUUID); + } else if (self.nearAvatarList[avatar.sessionUUID].isTalking) { + var headPos = avatar.getJointPosition("Neck"); + var raisedHand = Math.max(leftPalmPos.y, rightPalmPos.y) > headPos.y; + if (raisedHand) { + // If the talker raise the hands it will trigger attention + fastHands.push(avatar.sessionUUID); + } + } + } + } + } + } + + for (var id in self.nearAvatarList) { + if (nearbyAvatars.indexOf(id) == -1) { + delete self.nearAvatarList[id]; + } + } + self.nearAvatarIDs = Object.keys(self.nearAvatarList); + if (self.nearAvatarList[self.myAvatarID] === undefined) { + self.myAvatarID = MyAvatar.sessionUUID; + } + if (talkingAvatarID !== undefined) { + self.nearAvatarList[talkingAvatarID].isTalker = true; + } + self.updateAvatarVisibility(); + return { talker: talkingAvatarID, talkingCount: count, previousTalkers: previousTalkers, fastHands: fastHands, fastMovers: fastMovers }; + } + + this.getHeadConfortAngle = function(point) { + var eyesToPoint = Vec3.subtract(point, MyAvatar.getDefaultEyePosition()); + var angle = Vec3.getAngle(eyesToPoint, Quat.getFront(MyAvatar.orientation)) / DEGREE_TO_RADIAN; + angle = Math.min(angle, 90.0); + var MAX_OFFSET_DEGREES = 20.0; + var offsetMultiplier = MAX_OFFSET_DEGREES / 90.0; + var facingRight = Vec3.dot(eyesToPoint, Quat.getRight(MyAvatar.orientation)) > 0.0; + angle = (facingRight ? 1.0 : -1.0) * offsetMultiplier * angle; + return angle; + } + + this.updateCurrentAction = function(deltaTime) { + if (self.currentAction.lookAtJoint !== undefined) { + var avatar = AvatarList.getAvatar(self.currentAction.id); + self.currentAction.targetPoint = avatar.getJointPosition(self.currentAction.lookAtJoint); + } + self.currentAction.elapseTime += deltaTime; + } + + this.requestNewAction = function(targetType, id) { + + var HAND_ATTENTION_TRIGGER_SPEED = 0.2; + var action = new LookAction(); + action.targetType = targetType; + action.id = id; + var sortStare = false; + action.targetMode = "TargetMode.noTarget"; + if (targetType == TargetType.avatar && id !== undefined && self.nearAvatarList[id] !== undefined) { + var avatar = AvatarList.getAvatar(id); + action.focusName = self.nearAvatarList[id].name; + if (self.nearAvatarList[id].rightPalmSpeed > HAND_ATTENTION_TRIGGER_SPEED || self.nearAvatarList[id].leftPalmSpeed > HAND_ATTENTION_TRIGGER_SPEED) { + if (self.nearAvatarList[id].rightPalmSpeed < self.nearAvatarList[id].leftPalmSpeed) { + action.targetMode = "TargetMode.leftHand"; + } else { + action.targetMode = "TargetMode.rightHand"; + } + action.focusChance = 1.0; + } else { + var faceChances = self.nearAvatarList[id].isTalking ? FOCUS_MODE_CHANCES.talking : FOCUS_MODE_CHANCES.idle; + var randomFaceTargetMode = self.dice.getRandomKey(faceChances); + action.targetMode = randomFaceTargetMode.randomKey; + action.focusChance = randomFaceTargetMode.chance; + } + + } else if (targetType == TargetType.entity) { + // TODO + // Randomize around the entity size + } + var actionConfig = ACTION_CONFIGURATION[action.targetMode]; + action.config = actionConfig; + action.lookAtJoint = actionConfig.joint; + action.targetPoint = action.lookAtJoint !== undefined ? avatar.getJointPosition(action.lookAtJoint) : action.targetPoint = MyAvatar.getHeadLookAt(); + var randomKeyResult = self.dice.getRandomKey(actionConfig.offsetChances); + action.offsetModeName = randomKeyResult.randomKey; + action.offsetChance = randomKeyResult.chance; + var offsetMode = eval(action.offsetModeName); + if (offsetMode !== TargetOffsetMode.noOffset) { + var headPosition = MyAvatar.getJointPosition("Head"); + var headToTarget = Vec3.subtract(action.targetPoint, headPosition); + var randAngle = actionConfig.offsetAngleRange.min + Math.random() * (actionConfig.offsetAngleRange.max - actionConfig.offsetAngleRange.min); + var randAngle = Math.random() < 0.5 ? -randAngle : randAngle; + if (self.nearAvatarList[self.myAvatarID]) { + var randOffsetRotation = Quat.angleAxis(randAngle, Vec3.UNIT_Y); + action.eyesHeadOffset = Vec3.subtract(Vec3.sum(headPosition, Vec3.multiplyQbyV(randOffsetRotation, headToTarget)), action.targetPoint); + } + action.offsetEyes = offsetMode === TargetOffsetMode.onlyEyes || offsetMode === TargetOffsetMode.headAndEyes; + action.offsetHead = offsetMode === TargetOffsetMode.onlyHead || offsetMode === TargetOffsetMode.headAndEyes; + } + action.totalTime = actionConfig.stareTimeRange.min + Math.random() * (actionConfig.stareTimeRange.max - actionConfig.stareTimeRange.min); + action.confortAngle = self.getHeadConfortAngle(action.targetPoint); + action.speed = actionConfig.headSpeedRange.min + Math.random() * (actionConfig.headSpeedRange.max - actionConfig.headSpeedRange.min); + + return action; + } + + this.findAudienceAvatar = function(avatarIDs) { + // We look for avatars on the avatarIDs array if provided + // If not avatarIDs becomes the array with all the engaged avatars nearAvatarList + if (avatarIDs === undefined) { + avatarIDs = self.nearAvatarIDs; + } + var randAvatarID; + var MAX_AUDIENCE_DISTANCE = 8; + var firstAnyOther = undefined; + var firstNearOther = undefined; + if (avatarIDs.length > 1) { + var randomIndexes = self.dice.createRandomIndexes(avatarIDs.length); + for (var n = 0; n < randomIndexes.length; n++) { + var avatarID = avatarIDs[randomIndexes[n]]; + var avatar = self.nearAvatarList[avatarID]; + if (avatarID != self.myAvatarID && avatar.engaged) { + firstAnyOther = !firstAnyOther ? avatarID : firstAnyOther; + + if (avatar.distance < MAX_AUDIENCE_DISTANCE) { + firstNearOther = !firstNearOther ? avatarID : firstNearOther; + var otherToMe = Vec3.normalize(Vec3.subtract(self.nearAvatarList[self.myAvatarID].position, avatar.position)); + var myFront = Quat.getFront(self.nearAvatarList[self.myAvatarID].orientation); + var otherFront = Quat.getFront(avatar.orientation); + if (Vec3.dot(otherToMe, otherFront) > 0.0 && Vec3.dot(myFront, otherToMe) < 0.0) { + randAvatarID = avatarID; + break; + } + } + if (n === randomIndexes.length - 1) { + // We have not found a valid candidate facing us + // return the first id different from out avatar's id + randAvatarID = firstNearOther !== undefined ? firstNearOther : firstAnyOther; + } + } + } + } else if (avatarIDs.length > 0 && avatarIDs[0] != self.myAvatarID && self.nearAvatarList[avatarIDs[0]].engaged){ + // If the array provided only has one ID + randAvatarID = avatarIDs[0]; + } + return randAvatarID; + } + + this.applyHeadOffset = function(point, angle) { + var eyesToPoint = Vec3.subtract(point, MyAvatar.getDefaultEyePosition()); + var offsetRot = Quat.angleAxis(angle, Quat.getUp(MyAvatar.orientation)); + var offsetPoint = Vec3.sum(MyAvatar.getDefaultEyePosition(), Vec3.multiplyQbyV(offsetRot, eyesToPoint)); + return offsetPoint; + } + + this.computeTalkingState = function(sceneData, myAvatarID, currentTalker, currentFocus) { + var talkingState = TalkingState.noTalking; + if (sceneData.talker === myAvatarID) { + if (currentTalker != myAvatarID) { + talkingState = TalkingState.meTalkingFirst; + } else { + talkingState = TalkingState.meTalkingAgain; + } + } else if (sceneData.talkingCount > 1) { + talkingState = TalkingState.othersTalking; + } else if (sceneData.talkingCount > 0) { + if (sceneData.talker !== currentFocus) { + talkingState = TalkingState.otherTalkingFirst; + } else { + talkingState = TalkingState.otherTalkingAgain; + } + } + return talkingState; + } + + this.computeFocusState = function(sceneData, talkingState, currentFocus, lockedFocus, lockType) { + var focusState = FocusState.onNobody; + switch (talkingState) { + case TalkingState.noTalking : { + if (sceneData.previousTalkers.length > 0) { + focusState = FocusState.onLastTalker; + } else if (Math.random() < TRIGGER_FOCUS_WHILE_IDLE_CHANCE) { + // There is chance of triggering a random focus when nobody is talking + focusState = FocusState.onRandomAudience; + } else { + focusState = FocusState.onNobody; + } + break; + } + case TalkingState.meTalkingFirst : { + if (currentFocus !== undefined) { + // Look at the last focused avatar + focusState = FocusState.onLastFocus; + } else if (sceneData.previousTalkers.length > 0) { + // Look at one of the previous talkers + focusState = FocusState.onRandomLastTalker; + } else { + focusState = FocusState.onRandomAudience; + } + break; + } + case TalkingState.meTalkingAgain : { + // Look at any random avatar + focusState = FocusState.onRandomAudience; + break; + } + case TalkingState.otherTalkingAgain : { + // If we were focused already on the talker we have a 15% chance to look at somebody else + // randomly giving preference to the previous talkers + if (Math.random() < 0.15) { + focusState = FocusState.onRandomLastTalker; + } else { + focusState = FocusState.onTalker; + } + break; + } + case TalkingState.otherTalkingFirst : { + // Focus on the new talker + focusState = FocusState.onTalker; + break; + } + case TalkingState.othersTalking : { + // When multiple people talk at the same time we have a 50% chance of not changing focus + if (Math.random() < 0.5) { + focusState = FocusState.onLastFocus; + } else { + focusState = FocusState.onTalker; + } + break; + } + } + if (lockedFocus !== undefined) { + if (lockType === LockFocusType.click) { + focusState = FocusState.onSelected; + } else if (lockType === LockFocusType.movement) { + focusState = FocusState.onMovement; + } + } + return focusState; + } + + this.computeAvatarFocus = function(sceneData, focusState, currentFocus, lockedFocus) { + var avatarFocusID = undefined; + switch (focusState) { + case FocusState.onTalker: { + avatarFocusID = sceneData.talker; + break; + } + case FocusState.onRandomAudience: { + avatarFocusID = self.findAudienceAvatar(); + break; + } + case FocusState.onLastTalker: + case FocusState.onRandomLastTalker: { + if (sceneData.previousTalkers.length > 0) { + avatarFocusID = self.findAudienceAvatar(sceneData.previousTalkers); + } + if (avatarFocusID === undefined) { + // Guarantee a 20% chance of looking at somebody + if (focusState === FocusState.onRandomLastTalker || Math.random() < 0.2) { + avatarFocusID = self.findAudienceAvatar(); + } + } + break; + } + case FocusState.onLastFocus: { + avatarFocusID = currentFocus; + break; + } + case FocusState.onMovement: + case FocusState.onSelected: { + avatarFocusID = lockedFocus; + break; + } + } + return avatarFocusID; + } + + this.forceFocus = function(avatarID) { + if (self.nearAvatarList[avatarID] !== undefined) { + self.lockedFocusID = avatarID; + self.lockFocusType = LockFocusType.click; + } + } + + this.logAction = function(action) { + self.lookAtDebugger.clearLog(); + self.lookAtDebugger.log(TalkingState.print(self.talkingState)); + self.lookAtDebugger.log("________________________Focus"); + self.lookAtDebugger.log("On avatar: " + action.focusName); + self.lookAtDebugger.log(FocusState.print(self.focusState)); + self.lookAtDebugger.log("Focus time: " + self.avatarFocusMax.toFixed(2) + " seconds"); + self.lookAtDebugger.log("________________________Action"); + var extraLogs = action.print(); + for (var n = 0; n < extraLogs.length; n++) { + self.lookAtDebugger.log(extraLogs[n]); + } + } + + this.update = function(deltaTime) { + var FPS = 60.0; + var CLICKED_AVATAR_MAX_FOCUS_TIME = 10.0; + self.timeScale = deltaTime * FPS; + + var sceneData = self.updateAvatarList(deltaTime); + if (self.nearAvatarIDs.length === 0) { + return; + } + + var abortAction = self.lockFocusType === LockFocusType.click; + // Focus on any avatar moving their hands + if (sceneData.fastHands.length > 0 && self.lockFocusType === LockFocusType.none) { + var randomFastHands = self.findAudienceAvatar(sceneData.fastHands); + if (self.nearAvatarList[randomFastHands] && (!self.nearAvatarList[randomFastHands].isTalking || self.nearAvatarList[randomFastHands].isTalker)) { + abortAction = Math.random() < 0.3; + self.lockedFocusID = randomFastHands; + self.lockFocusType = LockFocusType.movement; + } + } else if (self.avatarFocusTotalTime >= self.avatarFocusMax) { + self.lockFocusType = LockFocusType.none; + } + + // Set the talking status + self.talkingState = self.computeTalkingState(sceneData, self.myAvatarID, self.currentTalker, self.currentAvatarFocusID); + + // If the talker change, we have a 50% chance to focus on them once the next action is completed. + var otherTalkerTriggerRefocus = self.talkingState === TalkingState.otherTalkingFirst && Math.random() < 0.5; + if (self.lockedFocusID !== undefined || otherTalkerTriggerRefocus) { + // Force a new focus + self.avatarFocusTotalTime = self.avatarFocusMax; + if (abortAction) { + // Force a new action + self.currentAction.elapseTime = self.currentAction.totalTime; + } + } + var needsNewFocus = self.avatarFocusTotalTime >= self.avatarFocusMax; + var needsNewAction = self.currentAction.elapseTime >= self.currentAction.totalTime; + var newAvatarFocusID = self.currentAvatarFocusID; + + if (needsNewAction && needsNewFocus) { + self.focusState = self.computeFocusState(sceneData, self.talkingState, self.currentAvatarFocusID, self.lockedFocusID, self.lockFocusType); + newAvatarFocusID = self.computeAvatarFocus(sceneData, self.focusState, self.currentAvatarFocusID, self.lockedFocusID); + self.lockedFocusID = undefined; + if (self.lockFocusType !== LockFocusType.click) { + if (self.talkingState === TalkingState.meTalkingAgain) { + self.avatarFocusMax = MIN_FOCUS_TO_TALKER_TIME + Math.random() * MAX_FOCUS_TO_TALKER_RANGE; + } else { + self.avatarFocusMax = MIN_FOCUS_TO_LISTENER_TIME + Math.random() * MAX_FOCUS_TO_LISTENER_TIME; + } + } else { + self.avatarFocusMax = CLICKED_AVATAR_MAX_FOCUS_TIME; + } + self.avatarFocusTotalTime = 0.0; + self.currentTalker = sceneData.talker; + self.shouldUpdateDebug = true; + } else { + self.avatarFocusTotalTime += deltaTime; + } + if (needsNewAction) { + var currentFocus = newAvatarFocusID; + if (otherTalkerTriggerRefocus) { + // Reset the last action on the previous focus to provide a random delay + currentFocus = self.currentAction.id; + self.currentAction.elapseTime = 0.0; + } else { + // Create a new action + self.currentAction = self.requestNewAction(TargetType.avatar, newAvatarFocusID); + } + + if (self.currentAvatarFocusID !== newAvatarFocusID) { + self.currentAvatarFocusID = newAvatarFocusID; + } + + if (self.currentAvatarFocusID === undefined || + self.talkingState === TalkingState.meTalkingAgain || + self.talkingState === TalkingState.meTalkingFirst) { + // Minimize the head speed when we are talking or looking nowhere + self.currentAction.speed = MIN_LOOKAT_HEAD_MIX_ALPHA; + } + + self.logAction(self.currentAction); + } else { + self.updateCurrentAction(deltaTime); + } + + self.headTargetPoint = self.currentAction.targetPoint; + self.eyesTargetPoint = self.currentAction.targetPoint; + if (self.currentAction.offsetHead) { + self.headTargetPoint = Vec3.sum(self.headTargetPoint, self.currentAction.eyesHeadOffset); + } + self.headTargetPoint = self.applyHeadOffset(self.headTargetPoint, self.currentAction.confortAngle); + if (self.currentAction.offsetEyes) { + self.eyesTargetPoint = Vec3.sum(self.eyesTargetPoint, self.currentAction.eyesHeadOffset); + } + self.headTargetSpeed = Math.min(1.0, self.currentAction.speed * self.timeScale); + } + + this.getResults = function() { + return { + "eyesTarget" : self.eyesTargetPoint, + "headTarget" : self.headTargetPoint, + "headSpeed" : self.headTargetSpeed + } + } + + } + + ////////////////////////////////////////////// + // autoLook.js /////// + ////////////////////////////////////////////// + + var LookAtController = function() { + var CAMERA_HEAD_MIX_ALPHA = 0.06; + var LookState = { + "CameraLookActivating": 0, + "CameraLookActive":1, + "ClickToLookDeactivating":2, + "ClickToLookActive":3, + "AutomaticLook": 4 + } + + var self = this; + this.smartLookAt = new SmartLookMachine(); + this.currentState = LookState.AutomaticLook; + this.lookAtTarget = undefined; + this.lookingAtAvatarID = undefined; + this.lookingAvatarJointIndex = undefined; + + this.interpolatedHeadLookAt = MyAvatar.getHeadLookAt(); + + var CLICK_TO_LOOK_TOTAL_TIME = 5.0; + this.clickToLookTimer = 0; + + var CAMERA_LOOK_TOTAL_TIME = 5.0; + this.cameraLookTimer = 0; + + this.timeScale = 1.0; + this.eyesTarget = Vec3.ZERO; + this.headTarget = Vec3.ZERO; + + this.mousePressEvent = function(event) { + if (event.isLeftButton) { + self.smartLookAt.lookAtDebugger.onClick(event); + if (self.currentState === LookState.AutomaticLook) { + var pickRay = Camera.computePickRay(event.x, event.y); + var intersection = AvatarManager.findRayIntersection({origin: pickRay.origin, direction: pickRay.direction}, [], [self.smartLookAt.myAvatarID], false); + self.lookingAtAvatarID = intersection.intersects ? intersection.avatarID : undefined; + if (self.lookingAtAvatarID) { + self.smartLookAt.forceFocus(self.lookingAtAvatarID); + } + } + } + } + + this.mouseMoveEvent = function(event) { + if (event.isRightButton && self.currentState === LookState.AutomaticLook) { + self.currentState = LookState.CameraLookActivating; + self.cameraLookTimer = 0.0; + } + } + + this.updateHeadLookAtTarget = function(target, interpolatedTarget, speed, noPitch) { + var eyesPosition = MyAvatar.getDefaultEyePosition(); + var targetRot = Quat.lookAt(eyesPosition, target, Quat.getUp(MyAvatar.orientation)); + var interpolatedRot = Quat.lookAt(eyesPosition, interpolatedTarget, Quat.getUp(MyAvatar.orientation)); + var newInterpolatedRot = Quat.mix(interpolatedRot, targetRot, speed); + var newInterpolatedTarget = Vec3.sum(eyesPosition, Vec3.multiply(Vec3.distance(eyesPosition, target), Quat.getFront(newInterpolatedRot))); + // avoid pitch + if (noPitch) { + newInterpolatedTarget.y = eyesPosition.y; + } + MyAvatar.setHeadLookAt(newInterpolatedTarget); + return newInterpolatedTarget; + } + + this.retargetHeadTo = function(target, speed, noPitch, tolerance) { + var eyePos = MyAvatar.getDefaultEyePosition(); + var localTarget = Vec3.normalize(Vec3.subtract(target, eyePos)); + self.interpolatedHeadLookAt = self.updateHeadLookAtTarget(target, self.interpolatedHeadLookAt, speed, noPitch); + return (Vec3.dot(Vec3.normalize(Vec3.subtract(self.interpolatedHeadLookAt, eyePos)), Vec3.normalize(localTarget)) > tolerance); + } + + var MAX_INTERPOLING_STEPS = 100; + this.interpolatingSteps = 0.0; + this.automaticResults = {}; + + this.update = function(deltaTime) { + // Update timeScale + var FPS = 60.0; + self.timeScale = deltaTime * FPS; + var stateTransitSpeed = Math.min(1.0, CAMERA_HEAD_MIX_ALPHA * self.timeScale); + + var CLICK_RETARGET_TOLERANCE = 0.98; + var CAMERA_RETARGET_TOLERANCE = 0.98; + + var headTarget = MyAvatar.getHeadLookAt(); + var eyesTarget = MyAvatar.getEyesLookAt(); + + var isTargetValid = (self.lookAtTarget || self.lookingAtAvatarID); + if (isTargetValid && self.currentState === LookState.ClickToLookActive) { + if (self.lookingAtAvatarID) { + self.lookAtTarget = AvatarList.getAvatar(self.lookingAtAvatarID).getJointPosition(self.lookingAvatarJointIndex); + } + self.interpolatedHeadLookAt = self.updateHeadLookAtTarget(self.lookAtTarget, self.interpolatedHeadLookAt, stateTransitSpeed); + MyAvatar.setEyesLookAt(self.lookAtTarget); + if (self.clickToLookTimer > CLICK_TO_LOOK_TOTAL_TIME) { + self.currentState = LookState.ClickToLookDeactivating; + } + self.clickToLookTimer += deltaTime; + } else if (self.currentState === LookState.ClickToLookDeactivating) { + var breakInterpolation = self.interpolatingSteps > MAX_INTERPOLING_STEPS; + if (breakInterpolation || self.retargetHeadTo(self.automaticResults.headTarget, stateTransitSpeed, false, CLICK_RETARGET_TOLERANCE)) { + self.currentState = LookState.AutomaticLook; + self.interpolatingSteps = 0.0; + } + if (breakInterpolation) { + console.log("Breaking look at interpolation"); + } else if (self.interpolatingSteps++ === 0.0){ + console.log("Interpolating click"); + } + } else if (self.currentState === LookState.CameraLookActivating) { + var cameraFront = Quat.getFront(Camera.getOrientation()); + if (Camera.mode === "selfie") { + cameraFront = Vec3.multiply(-1, cameraFront); + } + var breakInterpolation = self.interpolatingSteps > MAX_INTERPOLING_STEPS; + if (breakInterpolation || self.retargetHeadTo(Vec3.sum(MyAvatar.getDefaultEyePosition(), cameraFront), stateTransitSpeed, true, CAMERA_RETARGET_TOLERANCE)) { + self.currentState = LookState.CameraLookActive; + self.interpolatingSteps = 0.0; + MyAvatar.releaseHeadLookAtControl(); + MyAvatar.releaseEyesLookAtControl(); + } + if (breakInterpolation) { + console.log("Breaking camera interpolation"); + } else if (self.interpolatingSteps++ === 0.0){ + console.log("Interpolating camera"); + } + } else if (self.currentState === LookState.CameraLookActive) { + if (self.cameraLookTimer > CAMERA_LOOK_TOTAL_TIME) { + // Set as initial target the current head look at + self.interpolatedHeadLookAt = MyAvatar.getHeadLookAt(); + self.currentState = LookState.AutomaticLook; + } + self.cameraLookTimer += deltaTime; + } else if (self.currentState === LookState.AutomaticLook) { + var updateLookat = Vec3.length(MyAvatar.velocity) < 1.0; + self.smartLookAt.update(deltaTime); + if (updateLookat) { + self.automaticResults = self.smartLookAt.getResults(); + self.headTarget = self.automaticResults.headTarget; + self.eyesTarget = self.automaticResults.eyesTarget; + self.interpolatedHeadLookAt = self.updateHeadLookAtTarget(self.headTarget, self.interpolatedHeadLookAt, self.automaticResults.headSpeed, true); + MyAvatar.setEyesLookAt(self.eyesTarget); + headTarget = self.interpolatedHeadLookAt; + eyesTarget = self.eyesTarget; + } else { + // Too fast. Stand by automatic look + MyAvatar.setEyesLookAt(MyAvatar.getEyesLookAt()); + MyAvatar.setHeadLookAt(MyAvatar.getHeadLookAt()); + } + } + if (self.smartLookAt.shouldUpdateDebug) { + // Update engaged avatars debugging when a new action is created + var engagedAvatars = self.smartLookAt.getEngagedAvatars(); + self.smartLookAt.lookAtDebugger.highLightAvatars(engagedAvatars, + self.smartLookAt.currentAction.id, + self.smartLookAt.currentTalker !== self.smartLookAt.currentAction.id ? self.smartLookAt.currentTalker : undefined); + } + self.smartLookAt.lookAtDebugger.showTarget(headTarget, eyesTarget); + } + + this.finish = function() { + self.smartLookAt.lookAtDebugger.finish(); + } + } + var lookAtController = new LookAtController(); + Controller.mousePressEvent.connect(lookAtController.mousePressEvent); + Controller.mouseMoveEvent.connect(lookAtController.mouseMoveEvent); + Script.update.connect(lookAtController.update); + Script.scriptEnding.connect(lookAtController.finish); +})(); \ No newline at end of file From 8b2fc968f63c67edf013f27e5ce00ab0fb8fd2b3 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 26 Nov 2019 13:32:50 -0800 Subject: [PATCH 20/29] Fix logic in zone-checking code, other clean-up --- .../src/avatars/AvatarMixerClientData.cpp | 40 +++++++++++-------- assignment-client/src/avatars/MixerAvatar.h | 4 ++ 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index aeac279a95..8f62fe6bc9 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -100,7 +100,7 @@ struct FindContainingZone { bool isInScreenshareZone { false }; float priorityZoneVolume { std::numeric_limits::max() }; float screenshareZoneVolume { priorityZoneVolume }; - EntityItemID id{}; + EntityItemID screenshareZoneid{}; static bool operation(const OctreeElementPointer& element, void* extraData) { auto findContainingZone = static_cast(extraData); @@ -109,17 +109,19 @@ struct FindContainingZone { entityTreeElement->forEachEntity([&findContainingZone](EntityItemPointer item) { if (item->getType() == EntityTypes::Zone && item->contains(findContainingZone->position)) { auto zoneItem = static_pointer_cast(item); - if (zoneItem->getAvatarPriority() != COMPONENT_MODE_INHERIT) { - float volume = zoneItem->getVolumeEstimate(); - if (volume < findContainingZone->priorityZoneVolume) { // Smaller volume wins - findContainingZone->isInPriorityZone = zoneItem->getAvatarPriority() == COMPONENT_MODE_ENABLED; - findContainingZone->priorityZoneVolume = volume; - } - if (volume < findContainingZone->screenshareZoneVolume) { - findContainingZone->isInScreenshareZone = zoneItem->getScreenshare() == COMPONENT_MODE_ENABLED; + auto avatarPriorityProperty = zoneItem->getAvatarPriority(); + auto screenshareProperty = zoneItem->getScreenshare(); + float volume = zoneItem->getVolumeEstimate(); + if (avatarPriorityProperty != COMPONENT_MODE_INHERIT + && volume < findContainingZone->priorityZoneVolume) { // Smaller volume wins + findContainingZone->isInPriorityZone = avatarPriorityProperty == COMPONENT_MODE_ENABLED; + findContainingZone->priorityZoneVolume = volume; + } + if (screenshareProperty != COMPONENT_MODE_INHERIT + && volume < findContainingZone->screenshareZoneVolume) { + findContainingZone->isInScreenshareZone = screenshareProperty == COMPONENT_MODE_ENABLED; findContainingZone->screenshareZoneVolume = volume; - findContainingZone->id = zoneItem->getEntityItemID(); - } + findContainingZone->screenshareZoneid = zoneItem->getEntityItemID(); } } }); @@ -162,12 +164,16 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared if (currentlyHasPriority != _avatar->getHasPriority()) { _avatar->setHasPriority(currentlyHasPriority); } - if (findContainingZone.isInScreenshareZone) { - auto nodeList = DependencyManager::get(); - auto packet = NLPacket::create(PacketType::AvatarZonePresence, 2 * NUM_BYTES_RFC4122_UUID, true); - packet->write(_avatar->getSessionUUID().toRfc4122()); - packet->write(findContainingZone.id.toRfc4122()); - nodeList->sendPacket(std::move(packet), nodeList->getDomainSockAddr()); + bool isInScreenshareZone = findContainingZone.isInScreenshareZone; + if (isInScreenshareZone != _avatar->isInScreenshareZone()) { + _avatar->setInScreenshareZone(isInScreenshareZone); + if (isInScreenshareZone) { + auto nodeList = DependencyManager::get(); + auto packet = NLPacket::create(PacketType::AvatarZonePresence, 2 * NUM_BYTES_RFC4122_UUID, true); + packet->write(_avatar->getSessionUUID().toRfc4122()); + packet->write(findContainingZone.screenshareZoneid.toRfc4122()); + nodeList->sendPacket(std::move(packet), nodeList->getDomainSockAddr()); + } } _avatar->setNeedsHeroCheck(false); } diff --git a/assignment-client/src/avatars/MixerAvatar.h b/assignment-client/src/avatars/MixerAvatar.h index ec24d4e6bc..57daef021d 100644 --- a/assignment-client/src/avatars/MixerAvatar.h +++ b/assignment-client/src/avatars/MixerAvatar.h @@ -43,6 +43,9 @@ public: }; Q_ENUM(VerifyState) + bool isInScreenshareZone() const { return _inScreenshareZone; } + void setInScreenshareZone(bool value = true) { _inScreenshareZone = value; } + private: bool _needsHeroCheck { false }; static const char* stateToName(VerifyState state); @@ -65,6 +68,7 @@ private: int _numberChallenges { 0 }; bool _certifyFailed { false }; bool _needsIdentityUpdate { false }; + bool _inScreenshareZone { false }; bool generateFSTHash(); bool validateFSTHash(const QString& publicKey) const; From 65fcf898bee3579cdc536836edba3870f0733f52 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 26 Nov 2019 13:40:37 -0800 Subject: [PATCH 21/29] Use null zone id for leaving a screen zone --- .../src/avatars/AvatarMixerClientData.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 8f62fe6bc9..5fdbd4cc9a 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -167,13 +167,12 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared bool isInScreenshareZone = findContainingZone.isInScreenshareZone; if (isInScreenshareZone != _avatar->isInScreenshareZone()) { _avatar->setInScreenshareZone(isInScreenshareZone); - if (isInScreenshareZone) { - auto nodeList = DependencyManager::get(); - auto packet = NLPacket::create(PacketType::AvatarZonePresence, 2 * NUM_BYTES_RFC4122_UUID, true); - packet->write(_avatar->getSessionUUID().toRfc4122()); - packet->write(findContainingZone.screenshareZoneid.toRfc4122()); - nodeList->sendPacket(std::move(packet), nodeList->getDomainSockAddr()); - } + const QUuid& zoneId = isInScreenshareZone ? findContainingZone.screenshareZoneid : QUuid(); + auto nodeList = DependencyManager::get(); + auto packet = NLPacket::create(PacketType::AvatarZonePresence, 2 * NUM_BYTES_RFC4122_UUID, true); + packet->write(_avatar->getSessionUUID().toRfc4122()); + packet->write(zoneId.toRfc4122()); + nodeList->sendPacket(std::move(packet), nodeList->getDomainSockAddr()); } _avatar->setNeedsHeroCheck(false); } From 8be866ebbb1c7b0b120e8502e85ddd04b0b5d9a0 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 26 Nov 2019 18:10:21 -0500 Subject: [PATCH 22/29] DEV-2840: Fix z-fighting issues in Smartboard and enable quicker positional iteration in the future --- .../src/scripting/ScreenshareScriptingInterface.cpp | 7 +++++-- .../src/scripting/ScreenshareScriptingInterface.h | 11 +++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/interface/src/scripting/ScreenshareScriptingInterface.cpp b/interface/src/scripting/ScreenshareScriptingInterface.cpp index 5cf4d1939a..37365c1b89 100644 --- a/interface/src/scripting/ScreenshareScriptingInterface.cpp +++ b/interface/src/scripting/ScreenshareScriptingInterface.cpp @@ -90,7 +90,8 @@ static const EntityTypes::EntityType LOCAL_SCREENSHARE_WEB_ENTITY_TYPE = EntityT static const uint8_t LOCAL_SCREENSHARE_WEB_ENTITY_FPS = 30; // This is going to be a good amount of work to make this work dynamically for any screensize. // V1 will have only hardcoded values. -static const glm::vec3 LOCAL_SCREENSHARE_WEB_ENTITY_LOCAL_POSITION(0.0128f, -0.0918f, 0.0711f); +// The `z` value here is dynamic. +static const glm::vec3 LOCAL_SCREENSHARE_WEB_ENTITY_LOCAL_POSITION(0.0128f, -0.0918f, 0.0f); static const glm::vec3 LOCAL_SCREENSHARE_WEB_ENTITY_DIMENSIONS(3.6790f, 2.0990f, 0.0100f); static const QString LOCAL_SCREENSHARE_WEB_ENTITY_URL = "https://content.highfidelity.com/Experiences/Releases/usefulUtilities/smartBoard/screenshareViewer/screenshareClient.html"; @@ -241,7 +242,9 @@ void ScreenshareScriptingInterface::handleSuccessfulScreenshareInfoGet(QNetworkR EntityItemProperties localScreenshareWebEntityProps; localScreenshareWebEntityProps.setType(LOCAL_SCREENSHARE_WEB_ENTITY_TYPE); localScreenshareWebEntityProps.setMaxFPS(LOCAL_SCREENSHARE_WEB_ENTITY_FPS); - localScreenshareWebEntityProps.setLocalPosition(LOCAL_SCREENSHARE_WEB_ENTITY_LOCAL_POSITION); + glm::vec3 localPosition(LOCAL_SCREENSHARE_WEB_ENTITY_LOCAL_POSITION); + localPosition.z = _localWebEntityZOffset; + localScreenshareWebEntityProps.setLocalPosition(localPosition); localScreenshareWebEntityProps.setSourceUrl(LOCAL_SCREENSHARE_WEB_ENTITY_URL); localScreenshareWebEntityProps.setParentID(_smartboardEntityID); localScreenshareWebEntityProps.setDimensions(LOCAL_SCREENSHARE_WEB_ENTITY_DIMENSIONS); diff --git a/interface/src/scripting/ScreenshareScriptingInterface.h b/interface/src/scripting/ScreenshareScriptingInterface.h index 9dc828c13b..10d203d529 100644 --- a/interface/src/scripting/ScreenshareScriptingInterface.h +++ b/interface/src/scripting/ScreenshareScriptingInterface.h @@ -21,6 +21,7 @@ class ScreenshareScriptingInterface : public QObject, public Dependency { Q_OBJECT + Q_PROPERTY(float localWebEntityZOffset MEMBER _localWebEntityZOffset NOTIFY localWebEntityZOffsetChanged) public: ScreenshareScriptingInterface(); ~ScreenshareScriptingInterface(); @@ -32,6 +33,7 @@ signals: void screenshareError(); void screenshareProcessTerminated(); void startScreenshareViewer(); + void localWebEntityZOffsetChanged(const float& newZOffset); private slots: void onWebEventReceived(const QUuid& entityID, const QVariant& message); @@ -63,6 +65,15 @@ private: int _requestScreenshareInfoRetries{ 0 }; void requestScreenshareInfo(); + // Empirically determined. The default value here can be changed in Screenshare scripts, which enables faster iteration when we discover + // positional issues with various Smartboard entities. + // The following four values are closely linked: + // 1. The z-offset of whiteboard polylines (`STROKE_FORWARD_OFFSET_M` in `drawSphereClient.js`). + // 2. The z-offset of the screenshare local web entity (`LOCAL_WEB_ENTITY_Z_OFFSET` in `smartboardZoneClient.js`). + // 3. The z-offset of the screenshare "glass bezel" (`DEFAULT_SMARTBOARD_SCREENSHARE_GLASS_PROPS` in `smartboardZoneClient.js`). + // 4. The z-offset of the screenshare "status icon" (handled in the screenshare JSON file). + float _localWebEntityZOffset{ 0.0375f }; + std::unique_ptr _screenshareProcess{ nullptr }; QUuid _screenshareViewerLocalWebEntityUUID; QString _token{ "" }; From 168e85b26a8b422258680c4394f0b7bf47d20b8c Mon Sep 17 00:00:00 2001 From: amer cerkic Date: Tue, 26 Nov 2019 15:27:40 -0800 Subject: [PATCH 23/29] addressing comment --- libraries/audio-client/src/AudioClient.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 4080019822..25924bf0ea 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -2065,7 +2065,7 @@ bool AudioClient::switchOutputToAudioDevice(const HifiAudioDeviceInfo outputDevi _localSamplesAvailable.exchange(0, std::memory_order_release); //wait on local injectors prep to finish running - if (_localPrepInjectorFuture.isStarted() || _localPrepInjectorFuture.isRunning()) { + if ( !_localPrepInjectorFuture.isFinished()) { _localPrepInjectorFuture.waitForFinished(); } @@ -2347,9 +2347,9 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { qCDebug(audiostream, "Read %d samples from injectors (%d available, %d requested)", injectorSamplesPopped, _localInjectorsStream.samplesAvailable(), samplesRequested); } } - + // prepare injectors for the next callback - _audio->_localPrepInjectorFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] { + _audio->_localPrepInjectorFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] { _audio->prepareLocalAudioInjectors(); }); From e3ae92ec84034ae13dd66702ee1e5d2aa03d4457 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 26 Nov 2019 16:06:25 -0800 Subject: [PATCH 24/29] Send notification whenever screen-share zone changes --- assignment-client/src/avatars/AvatarMixerClientData.cpp | 4 +++- assignment-client/src/avatars/MixerAvatar.h | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 5fdbd4cc9a..f86dc7f766 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -165,8 +165,10 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared _avatar->setHasPriority(currentlyHasPriority); } bool isInScreenshareZone = findContainingZone.isInScreenshareZone; - if (isInScreenshareZone != _avatar->isInScreenshareZone()) { + if (isInScreenshareZone != _avatar->isInScreenshareZone() + || findContainingZone.screenshareZoneid != _avatar->getScreenshareZone()) { _avatar->setInScreenshareZone(isInScreenshareZone); + _avatar->setScreenshareZone(findContainingZone.screenshareZoneid); const QUuid& zoneId = isInScreenshareZone ? findContainingZone.screenshareZoneid : QUuid(); auto nodeList = DependencyManager::get(); auto packet = NLPacket::create(PacketType::AvatarZonePresence, 2 * NUM_BYTES_RFC4122_UUID, true); diff --git a/assignment-client/src/avatars/MixerAvatar.h b/assignment-client/src/avatars/MixerAvatar.h index 57daef021d..09a3522067 100644 --- a/assignment-client/src/avatars/MixerAvatar.h +++ b/assignment-client/src/avatars/MixerAvatar.h @@ -45,6 +45,8 @@ public: bool isInScreenshareZone() const { return _inScreenshareZone; } void setInScreenshareZone(bool value = true) { _inScreenshareZone = value; } + const QUuid& getScreenshareZone() const { return _screenshareZone; } + void setScreenshareZone(QUuid zone) { _screenshareZone = zone; } private: bool _needsHeroCheck { false }; @@ -69,6 +71,7 @@ private: bool _certifyFailed { false }; bool _needsIdentityUpdate { false }; bool _inScreenshareZone { false }; + QUuid _screenshareZone; bool generateFSTHash(); bool validateFSTHash(const QString& publicKey) const; From d4b50523c2f82ce98b65d763a478956938bc8bc9 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 27 Nov 2019 12:55:44 -0500 Subject: [PATCH 25/29] DEV-2832: Automatically minimize Screen Share app when sharing window is confirmed --- screenshare/src/screenshareApp.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/screenshare/src/screenshareApp.js b/screenshare/src/screenshareApp.js index 2abc151988..4de36639dc 100644 --- a/screenshare/src/screenshareApp.js +++ b/screenshare/src/screenshareApp.js @@ -7,6 +7,8 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +const { remote } = require('electron'); + // Helpers function handleError(error) { if (error) { @@ -221,6 +223,7 @@ function onAccessApproved(desktop_id) { } } }, gotStream, handleError); + remote.getCurrentWindow().minimize(); } From 5eae6711e6a5d9f5fc20b5a9b6e0736706be2821 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 27 Nov 2019 13:01:41 -0500 Subject: [PATCH 26/29] Resolve DEV-2816 while I'm at it - allow users to switch between screens --- screenshare/src/screenshareApp.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/screenshare/src/screenshareApp.js b/screenshare/src/screenshareApp.js index 4de36639dc..1cdc392fb6 100644 --- a/screenshare/src/screenshareApp.js +++ b/screenshare/src/screenshareApp.js @@ -190,6 +190,10 @@ function stopSharing() { // Callback to start publishing after we have setup the chromium stream function gotStream(stream) { + if (localStream) { + stopSharing(); + } + localStream = stream; startTokboxPublisher(localStream); @@ -208,6 +212,9 @@ function onAccessApproved(desktop_id) { console.log('Desktop Capture access rejected.'); return; } + + + document.getElementById('screenshare').style.visibility = "block"; desktopSharing = true; navigator.webkitGetUserMedia({ From 327508aa0d51ca2509613e94450740acca4bdde6 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 27 Nov 2019 13:56:55 -0500 Subject: [PATCH 27/29] Prevent video dropout with audioFallbackEnabled set to false --- screenshare/src/screenshareApp.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/screenshare/src/screenshareApp.js b/screenshare/src/screenshareApp.js index 1cdc392fb6..b16f5ca993 100644 --- a/screenshare/src/screenshareApp.js +++ b/screenshare/src/screenshareApp.js @@ -258,11 +258,15 @@ var publisher; function startTokboxPublisher(stream) { publisher = document.createElement("div"); var publisherOptions = { - videoSource: stream.getVideoTracks()[0], + audioFallbackEnabled: false, audioSource: null, + fitMode: 'contain', + frameRate: 30, + height: 720, insertMode: 'append', - width: 1280, - height: 720 + publishAudio: false, + videoSource: stream.getVideoTracks()[0], + width: 1280 }; publisher = OT.initPublisher(publisher, publisherOptions, function(error){ From e02725e418ae931c05c22ff902c95e450bb3f528 Mon Sep 17 00:00:00 2001 From: amerhifi Date: Wed, 27 Nov 2019 15:30:47 -0800 Subject: [PATCH 28/29] input init to dummy to prevent starve and airpods thread lock --- libraries/audio-client/src/AudioClient.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 5502986cff..1eb8403465 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -335,9 +335,9 @@ AudioClient::AudioClient() { connect(&_receivedAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioClient::handleMismatchAudioFormat); // initialize wasapi; if getAvailableDevices is called from the CheckDevicesThread before this, it will crash - getAvailableDevices(QAudio::AudioInput, QString()); - getAvailableDevices(QAudio::AudioOutput, QString()); - + defaultAudioDeviceName(QAudio::AudioInput); + defaultAudioDeviceName(QAudio::AudioOutput); + // start a thread to detect any device changes _checkDevicesTimer = new QTimer(this); const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000; @@ -787,8 +787,11 @@ void AudioClient::start() { inputName = _hmdInputName; outputName = _hmdOutputName; } + + //initialize input to the dummy device to prevent starves + switchInputToAudioDevice(HifiAudioDeviceInfo()); + switchOutputToAudioDevice(defaultAudioDeviceForMode(QAudio::AudioOutput, QString())); - #if defined(Q_OS_ANDROID) connect(&_checkInputTimer, &QTimer::timeout, this, &AudioClient::checkInputTimeout); _checkInputTimer.start(CHECK_INPUT_READS_MSECS); From 3ebceff428ed8ad76ead0cb1b312eecfc6912e35 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Thu, 28 Nov 2019 12:42:35 +1300 Subject: [PATCH 29/29] Doc review --- interface/src/ui/Stats.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 00b699c3e6..794d1c8eb3 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -24,7 +24,9 @@ private: \ type _##name{ initialValue }; /**jsdoc - * The Stats API provides and statistics on Interface and domain operation, per the statistics overlay. + * The Stats API provides statistics on Interface and domain operation, per the statistics overlay. + * + *

    Note: This API is primarily an internal diagnostics tool and is provided "as is".

    * * @namespace Stats *