diff --git a/assignment-client/src/AssignmentClientApp.cpp b/assignment-client/src/AssignmentClientApp.cpp index bd656ceb09..dd3050ba4e 100644 --- a/assignment-client/src/AssignmentClientApp.cpp +++ b/assignment-client/src/AssignmentClientApp.cpp @@ -17,8 +17,8 @@ #include #include -#include #include +#include #include #include "Assignment.h" diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index bc67a31c02..c1eff76d78 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -50,6 +50,7 @@ { "label": "Places / Paths", "html_id": "places_paths", + "restart": false, "settings": [ { "name": "paths", @@ -75,6 +76,7 @@ { "name": "descriptors", "label": "Description", + "restart": false, "help": "This data will be queryable from your server. It may be collected by High Fidelity and used to share your domain with others.", "settings": [ { diff --git a/domain-server/resources/web/js/tables.js b/domain-server/resources/web/js/tables.js index 09f85a7047..600e5a5f8e 100644 --- a/domain-server/resources/web/js/tables.js +++ b/domain-server/resources/web/js/tables.js @@ -2,11 +2,11 @@ $(document).ready(function(){ // setup the underscore templates var nodeTemplate = _.template($('#nodes-template').html()); var queuedTemplate = _.template($('#queued-template').html()); - + // setup a function to grab the assignments function getNodesAndAssignments() { $.getJSON("nodes.json", function(json){ - + json.nodes.sort(function(a, b){ if (a.type === b.type) { if (a.uptime < b.uptime) { @@ -16,36 +16,50 @@ $(document).ready(function(){ } else { return 0; } - } - + } + if (a.type === "agent" && b.type !== "agent") { return 1; } else if (b.type === "agent" && a.type !== "agent") { return -1; } - + if (a.type > b.type) { return 1; } - + if (a.type < b.type) { return -1; - } + } }); - + $('#nodes-table tbody').html(nodeTemplate(json)); + }).fail(function(jqXHR, textStatus, errorThrown) { + // we assume a 401 means the DS has restarted + // and no longer has our OAuth produced uuid + // so just reload and re-auth + if (jqXHR.status == 401) { + location.reload(); + } }); - - $.getJSON("assignments.json", function(json){ + + $.getJSON("assignments.json", function(json){ $('#assignments-table tbody').html(queuedTemplate(json)); + }).fail(function(jqXHR, textStatus, errorThrown) { + // we assume a 401 means the DS has restarted + // and no longer has our OAuth produced uuid + // so just reload and re-auth + if (jqXHR.status == 401) { + location.reload(); + } }); } - + // do the first GET on page load getNodesAndAssignments(); // grab the new assignments JSON every two seconds var getNodesAndAssignmentsInterval = setInterval(getNodesAndAssignments, 2000); - + // hook the node delete to the X button $(document.body).on('click', '.glyphicon-remove', function(){ // fire off a delete for this node @@ -57,10 +71,10 @@ $(document).ready(function(){ } }); }); - + $(document.body).on('click', '#kill-all-btn', function() { var confirmed_kill = confirm("Are you sure?"); - + if (confirmed_kill == true) { $.ajax({ url: "/nodes/", diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 095613a473..c5171620de 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -40,11 +40,11 @@ #include #include #include +#include +#include #include "DomainServerNodeData.h" #include "NodeConnectionData.h" -#include -#include int const DomainServer::EXIT_CODE_REBOOT = 234923; @@ -162,8 +162,10 @@ DomainServer::DomainServer(int argc, char* argv[]) : _gatekeeper.preloadAllowedUserPublicKeys(); // so they can connect on first request + //send signal to DomainMetadata when descriptors changed _metadata = new DomainMetadata(this); - + connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated, + _metadata, &DomainMetadata::descriptorsChanged); qDebug() << "domain-server is running"; static const QString AC_SUBNET_WHITELIST_SETTING_PATH = "security.ac_subnet_whitelist"; @@ -1972,7 +1974,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return _settingsManager.handleAuthenticatedHTTPRequest(connection, url); } -const QString HIFI_SESSION_COOKIE_KEY = "DS_WEB_SESSION_UUID"; +static const QString HIFI_SESSION_COOKIE_KEY = "DS_WEB_SESSION_UUID"; +static const QString STATE_QUERY_KEY = "state"; bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &url, bool skipSubHandler) { qDebug() << "HTTPS request received at" << url.toString(); @@ -1983,10 +1986,9 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u const QString CODE_QUERY_KEY = "code"; QString authorizationCode = codeURLQuery.queryItemValue(CODE_QUERY_KEY); - const QString STATE_QUERY_KEY = "state"; QUuid stateUUID = QUuid(codeURLQuery.queryItemValue(STATE_QUERY_KEY)); - if (!authorizationCode.isEmpty() && !stateUUID.isNull()) { + if (!authorizationCode.isEmpty() && !stateUUID.isNull() && _webAuthenticationStateSet.remove(stateUUID)) { // fire off a request with this code and state to get an access token for the user const QString OAUTH_TOKEN_REQUEST_PATH = "/oauth/token"; @@ -2004,47 +2006,83 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); QNetworkReply* tokenReply = NetworkAccessManager::getInstance().post(tokenRequest, tokenPostBody.toLocal8Bit()); + connect(tokenReply, &QNetworkReply::finished, this, &DomainServer::tokenGrantFinished); - if (_webAuthenticationStateSet.remove(stateUUID)) { - // this is a web user who wants to auth to access web interface - // we hold the response back to them until we get their profile information - // and can decide if they are let in or not + // add this connection to our list of pending connections so that we can hold the response + _pendingOAuthConnections.insert(stateUUID, connection); - QEventLoop loop; - connect(tokenReply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + // set the state UUID on the reply so that we can associate the response with the connection later + tokenReply->setProperty(STATE_QUERY_KEY.toLocal8Bit(), stateUUID); - // start the loop for the token request - loop.exec(); + return true; + } else { + connection->respond(HTTPConnection::StatusCode400); - QNetworkReply* profileReply = profileRequestGivenTokenReply(tokenReply); + return true; + } + } else { + return false; + } +} - // stop the loop once the profileReply is complete - connect(profileReply, &QNetworkReply::finished, &loop, &QEventLoop::quit); +HTTPSConnection* DomainServer::connectionFromReplyWithState(QNetworkReply* reply) { + // grab the UUID state property from the reply + QUuid stateUUID = reply->property(STATE_QUERY_KEY.toLocal8Bit()).toUuid(); - // restart the loop for the profile request - loop.exec(); + if (!stateUUID.isNull()) { + return _pendingOAuthConnections.take(stateUUID); + } else { + return nullptr; + } +} +void DomainServer::tokenGrantFinished() { + auto tokenReply = qobject_cast(sender()); + + if (tokenReply) { + if (tokenReply->error() == QNetworkReply::NoError) { + // now that we have a token for this profile, send off a profile request + QNetworkReply* profileReply = profileRequestGivenTokenReply(tokenReply); + + // forward along the state UUID that we kept with the token request + profileReply->setProperty(STATE_QUERY_KEY.toLocal8Bit(), tokenReply->property(STATE_QUERY_KEY.toLocal8Bit())); + + connect(profileReply, &QNetworkReply::finished, this, &DomainServer::profileRequestFinished); + } else { + // the token grant failed, send back a 500 (assuming the connection is still around) + auto connection = connectionFromReplyWithState(tokenReply); + + if (connection) { + connection->respond(HTTPConnection::StatusCode500); + } + } + + tokenReply->deleteLater(); + } +} + +void DomainServer::profileRequestFinished() { + + auto profileReply = qobject_cast(sender()); + + if (profileReply) { + auto connection = connectionFromReplyWithState(profileReply); + + if (connection) { + if (profileReply->error() == QNetworkReply::NoError) { // call helper method to get cookieHeaders Headers cookieHeaders = setupCookieHeadersFromProfileReply(profileReply); connection->respond(HTTPConnection::StatusCode302, QByteArray(), HTTPConnection::DefaultContentType, cookieHeaders); - delete tokenReply; - delete profileReply; - - // we've redirected the user back to our homepage - return true; - + } else { + // the profile request failed, send back a 500 (assuming the connection is still around) + connection->respond(HTTPConnection::StatusCode500); } } - // respond with a 200 code indicating that login is complete - connection->respond(HTTPConnection::StatusCode200); - - return true; - } else { - return false; + profileReply->deleteLater(); } } @@ -2104,22 +2142,31 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl // the user does not have allowed username or role, return 401 return false; } else { - // re-direct this user to OAuth page + static const QByteArray REQUESTED_WITH_HEADER = "X-Requested-With"; + static const QString XML_REQUESTED_WITH = "XMLHttpRequest"; - // generate a random state UUID to use - QUuid stateUUID = QUuid::createUuid(); + if (connection->requestHeaders().value(REQUESTED_WITH_HEADER) == XML_REQUESTED_WITH) { + // unauthorized XHR requests get a 401 and not a 302, since there isn't an XHR + // path to OAuth authorize + connection->respond(HTTPConnection::StatusCode401, UNAUTHENTICATED_BODY); + } else { + // re-direct this user to OAuth page - // add it to the set so we can handle the callback from the OAuth provider - _webAuthenticationStateSet.insert(stateUUID); + // generate a random state UUID to use + QUuid stateUUID = QUuid::createUuid(); - QUrl authURL = oauthAuthorizationURL(stateUUID); + // add it to the set so we can handle the callback from the OAuth provider + _webAuthenticationStateSet.insert(stateUUID); - Headers redirectHeaders; + QUrl authURL = oauthAuthorizationURL(stateUUID); - redirectHeaders.insert("Location", authURL.toEncoded()); + Headers redirectHeaders; - connection->respond(HTTPConnection::StatusCode302, - QByteArray(), HTTPConnection::DefaultContentType, redirectHeaders); + redirectHeaders.insert("Location", authURL.toEncoded()); + + connection->respond(HTTPConnection::StatusCode302, + QByteArray(), HTTPConnection::DefaultContentType, redirectHeaders); + } // we don't know about this user yet, so they are not yet authenticated return false; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 8851e3380b..4808297c89 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -111,6 +111,9 @@ private slots: void updateDownstreamNodes(); void updateUpstreamNodes(); + void tokenGrantFinished(); + void profileRequestFinished(); + signals: void iceServerChanged(); void userConnected(); @@ -178,6 +181,8 @@ private: void updateReplicationNodes(ReplicationServerDirection direction); + HTTPSConnection* connectionFromReplyWithState(QNetworkReply* reply); + SubnetList _acSubnetWhitelist; std::vector _replicatedUsernames; @@ -235,6 +240,8 @@ private: bool _sendICEServerAddressToMetaverseAPIInProgress { false }; bool _sendICEServerAddressToMetaverseAPIRedo { false }; + + QHash> _pendingOAuthConnections; }; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 9279648319..7a2cfa645a 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1198,6 +1198,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ static const QString SECURITY_ROOT_KEY = "security"; static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist"; static const QString BROADCASTING_KEY = "broadcasting"; + static const QString DESCRIPTION_ROOT_KEY = "descriptors"; auto& settingsVariant = _configMap.getConfig(); bool needRestart = false; @@ -1249,7 +1250,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ if (!matchingDescriptionObject.isEmpty()) { updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject); - if (rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY) { + if (rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY && rootKey != SETTINGS_PATHS_KEY ) { needRestart = true; } } else { @@ -1265,7 +1266,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ if (!matchingDescriptionObject.isEmpty()) { const QJsonValue& settingValue = rootValue.toObject()[settingKey]; updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject); - if ((rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY) + if ((rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY && rootKey != DESCRIPTION_ROOT_KEY) || settingKey == AC_SUBNET_WHITELIST_KEY) { needRestart = true; } diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index a0e9bd30d4..8f58ef3c98 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -34,36 +34,32 @@ { "from": "Vive.RSCenter", "to": "Standard.RightPrimaryThumb" }, { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" }, - { "from": "Vive.LeftHand", "to": "Standard.LeftHand", "when": [ "Application.InHMD" ] }, - { "from": "Vive.RightHand", "to": "Standard.RightHand", "when": [ "Application.InHMD" ] }, + { "from": "Vive.LeftHand", "to": "Standard.LeftHand"}, + { "from": "Vive.RightHand", "to": "Standard.RightHand"}, { "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot", - "filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}], - "when": [ "Application.InHMD" ] + "filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}] }, { "from": "Vive.RightFoot", "to" : "Standard.RightFoot", - "filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}], - "when": [ "Application.InHMD" ] + "filters" : [{"type" : "lowVelocity", "rotation" : 1.0, "translation": 1.0}] }, { "from": "Vive.Hips", "to" : "Standard.Hips", - "filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}], - "when": [ "Application.InHMD" ] + "filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}] }, { "from": "Vive.Spine2", "to" : "Standard.Spine2", - "filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}], - "when": [ "Application.InHMD" ] + "filters" : [{"type" : "lowVelocity", "rotation" : 0.01, "translation": 0.01}] }, - { "from": "Vive.Head", "to" : "Standard.Head", "when": [ "Application.InHMD" ] }, + { "from": "Vive.Head", "to" : "Standard.Head"}, - { "from": "Vive.RightArm", "to" : "Standard.RightArm", "when": [ "Application.InHMD" ] }, - { "from": "Vive.LeftArm", "to" : "Standard.LeftArm", "when": [ "Application.InHMD" ] } + { "from": "Vive.RightArm", "to" : "Standard.RightArm"}, + { "from": "Vive.LeftArm", "to" : "Standard.LeftArm"} ] } diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf index ddb0743bf8..8733349227 100644 Binary files a/interface/resources/fonts/hifi-glyphs.ttf and b/interface/resources/fonts/hifi-glyphs.ttf differ diff --git a/interface/resources/images/calibration-help.png b/interface/resources/images/calibration-help.png new file mode 100644 index 0000000000..e3734a7d9c Binary files /dev/null and b/interface/resources/images/calibration-help.png differ diff --git a/interface/resources/qml/controls-uit/ImageMessageBox.qml b/interface/resources/qml/controls-uit/ImageMessageBox.qml new file mode 100644 index 0000000000..95c753aab4 --- /dev/null +++ b/interface/resources/qml/controls-uit/ImageMessageBox.qml @@ -0,0 +1,64 @@ +// +// ImageMessageBox.qml +// +// Created by Dante Ruiz on 7/5/2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../styles-uit" + +Item { + id: imageBox + visible: false + anchors.fill: parent + property alias source: image.source + property alias imageWidth: image.width + property alias imageHeight: image.height + + Rectangle { + anchors.fill: parent + color: "black" + opacity: 0.3 + } + + Image { + id: image + anchors.centerIn: parent + + HiFiGlyphs { + id: closeGlyphButton + text: hifi.glyphs.close + size: 25 + + anchors { + top: parent.top + topMargin: 15 + right: parent.right + rightMargin: 15 + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + + onEntered: { + parent.text = hifi.glyphs.closeInverted; + } + + onExited: { + parent.text = hifi.glyphs.close; + } + + onClicked: { + imageBox.visible = false; + } + } + } + } + +} diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 8db04a0f5b..c98cfba1ba 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -1101,9 +1101,9 @@ Rectangle { case 'nearbyUsers': var data = message.params; var index = -1; + iAmAdmin = Users.canKick; index = findNearbySessionIndex('', data); if (index !== -1) { - iAmAdmin = Users.canKick; myData = data[index]; data.splice(index, 1); } else { diff --git a/interface/resources/qml/hifi/tablet/CalibratingScreen.qml b/interface/resources/qml/hifi/tablet/CalibratingScreen.qml index 8053673e9c..dd56bc96ab 100644 --- a/interface/resources/qml/hifi/tablet/CalibratingScreen.qml +++ b/interface/resources/qml/hifi/tablet/CalibratingScreen.qml @@ -65,7 +65,7 @@ Rectangle { HiFiGlyphs { id: image - text: hifi.glyphs.avatar1 + text: hifi.glyphs.avatarTPose size: 190 color: hifi.colors.white diff --git a/interface/resources/qml/hifi/tablet/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml index e1ba93a840..4814eaf01c 100644 --- a/interface/resources/qml/hifi/tablet/ControllerSettings.qml +++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml @@ -16,6 +16,7 @@ import "../../controls-uit" as HifiControls StackView { id: stack initialItem: inputConfiguration + property alias messageVisible: imageMessageBox.visible Rectangle { id: inputConfiguration anchors.fill: parent @@ -26,6 +27,15 @@ StackView { property var pluginSettings: null + HifiControls.ImageMessageBox { + id: imageMessageBox + anchors.fill: parent + z: 2000 + imageWidth: 442 + imageHeight: 670 + source: "../../../images/calibration-help.png" + } + Rectangle { width: inputConfiguration.width height: 1 @@ -167,7 +177,7 @@ StackView { loader.item.pluginName = box.currentText; } } - + if (loader.item.hasOwnProperty("displayInformation")) { loader.item.displayConfiguration(); } @@ -183,20 +193,20 @@ StackView { return InputConfiguration.activeInputPlugins(); } } - + function initialize() { changeSource(); } - + function changeSource() { loader.source = ""; var source = ""; if (box.currentText == "Vive") { source = InputConfiguration.configurationLayout("OpenVR"); - } else { + } else { source = InputConfiguration.configurationLayout(box.currentText); } - + loader.source = source; if (source === "") { box.label = "(not configurable)"; @@ -204,14 +214,14 @@ StackView { box.label = ""; } } - + Timer { id: timer repeat: false interval: 300 onTriggered: initialize() } - + Component.onCompleted: { timer.start(); } diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index 96413534c3..90d6ba7022 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -50,9 +50,12 @@ Rectangle { readonly property int apply: 1 readonly property int applyAndCalibrate: 2 readonly property int calibrate: 3 - + } - + + + + MouseArea { id: mouseArea @@ -64,6 +67,7 @@ Rectangle { mouse.accepted = false; } } + color: hifi.colors.baseGray RalewayBold { @@ -146,6 +150,7 @@ Rectangle { label: "Y: offset" minimumValue: -10 stepSize: 0.0254 + value: -0.05 colorScheme: hifi.colorSchemes.dark onEditingFinished: { @@ -161,15 +166,16 @@ Rectangle { minimumValue: -10 stepSize: 0.0254 decimals: 4 + value: -0.05 colorScheme: hifi.colorSchemes.dark - + onEditingFinished: { sendConfigurationSettings(); } } } - - + + RalewayBold { id: hands @@ -245,7 +251,7 @@ Rectangle { anchors.left: openVrConfiguration.left anchors.leftMargin: leftMargin + 10 spacing: 10 - + HifiControls.SpinBox { id: handYOffset decimals: 4 @@ -269,7 +275,7 @@ Rectangle { stepSize: 0.0254 decimals: 4 colorScheme: hifi.colorSchemes.dark - + onEditingFinished: { sendConfigurationSettings(); } @@ -290,6 +296,52 @@ Rectangle { anchors.leftMargin: leftMargin } + RalewayRegular { + id: info + + text: "See Recommended Tracker Placement" + color: hifi.colors.blueHighlight + size: 10 + anchors { + left: additional.right + leftMargin: 10 + verticalCenter: additional.verticalCenter + } + + Rectangle { + id: selected + color: hifi.colors.blueHighlight + + width: info.width + height: 1 + + anchors { + top: info.bottom + topMargin: 1 + left: info.left + right: info.right + } + + visible: false + } + + MouseArea { + anchors.fill: parent; + hoverEnabled: true + + onEntered: { + selected.visible = true; + } + + onExited: { + selected.visible = false; + } + onClicked: { + stack.messageVisible = true; + } + } + } + Row { id: feetConfig anchors.top: additional.bottom @@ -379,6 +431,7 @@ Rectangle { if (checked) { hipBox.checked = true; feetBox.checked = true; + shoulderBox.checked = false; } sendConfigurationSettings(); } @@ -416,6 +469,7 @@ Rectangle { if (checked) { hipBox.checked = true; feetBox.checked = true; + chestBox.checked = false; } sendConfigurationSettings(); } @@ -463,7 +517,7 @@ Rectangle { anchors.leftMargin: leftMargin radius: hifi.buttons.radius - + gradient: Gradient { GradientStop { position: 0.2 @@ -479,7 +533,7 @@ Rectangle { } } } - + GradientStop { position: 1.0 color: { @@ -495,10 +549,10 @@ Rectangle { } } } - - + + HiFiGlyphs { id: glyphButton color: enabled ? hifi.buttons.textColor[calibrationButton.color] @@ -512,7 +566,7 @@ Rectangle { bottomMargin: 1 } } - + RalewayBold { id: calibrationText font.capitalization: Font.AllUppercase @@ -527,7 +581,7 @@ Rectangle { topMargin: 7 } } - + MouseArea { anchors.fill: parent @@ -549,19 +603,19 @@ Rectangle { } } } - + onPressed: { calibrationButton.pressed = true; } - + onReleased: { calibrationButton.pressed = false; } - + onEntered: { calibrationButton.hovered = true; } - + onExited: { calibrationButton.hovered = false; } @@ -642,6 +696,57 @@ Rectangle { } } + Separator { + id: advanceSeperator + width: parent.width + anchors.top: timeToCalibrate.bottom + anchors.topMargin: 10 + } + + RalewayBold { + id: advanceSettings + + text: "Advanced Settings" + size: 12 + + color: hifi.colors.white + + anchors.top: advanceSeperator.bottom + anchors.topMargin: 10 + anchors.left: parent.left + anchors.leftMargin: leftMargin + } + + + HifiControls.CheckBox { + id: viveInDesktop + width: 15 + height: 15 + boxRadius: 7 + + anchors.top: advanceSettings.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + + onClicked: { + sendConfigurationSettings(); + } + } + + RalewayBold { + id: viveDesktopText + size: 10 + text: "Use Vive devices in desktop mode" + color: hifi.colors.white + + anchors { + left: viveInDesktop.right + leftMargin: 5 + verticalCenter: viveInDesktop.verticalCenter + } + } + NumberAnimation { id: numberAnimation target: openVrConfiguration @@ -667,14 +772,14 @@ Rectangle { calibratingScreen = screen.createObject(); stack.push(calibratingScreen); } - + if (status["calibrated"]) { calibrationScreen.success(); if (status["UI"]) { logAction("mocap_ui_success", status); } - + } else if (!status["calibrated"]) { calibrationScreen.failure(); @@ -728,6 +833,7 @@ Rectangle { var HmdHead = settings["HMDHead"]; var viveController = settings["handController"]; + var desktopMode = settings["desktopMode"]; if (HmdHead) { headBox.checked = true; @@ -745,6 +851,8 @@ Rectangle { handBox.checked = false; } + viveInDesktop.checked = desktopMode; + initializeButtonState(); updateCalibrationText(); @@ -786,11 +894,11 @@ Rectangle { var handOverride = handSetting["override"]; var settingsChanged = false; - + if (lastConfiguration["bodyConfiguration"] !== bodySetting) { settingsChanged = true; } - + var lastHead = lastConfiguration["headConfiguration"]; if (lastHead["override"] !== headOverride) { settingsChanged = true; @@ -800,13 +908,13 @@ Rectangle { if (lastHand["override"] !== handOverride) { settingsChanged = true; } - + if (settingsChanged) { if ((!handOverride) && (!headOverride) && (bodySetting === "None")) { state = buttonState.apply; } else { state = buttonState.applyAndCalibrate; - } + } } else { if (state == buttonState.apply) { state = buttonState.disabled; @@ -814,7 +922,7 @@ Rectangle { state = buttonState.calibrate; } } - + lastConfiguration = settings; } @@ -831,7 +939,7 @@ Rectangle { state = buttonState.disabled; } else { state = buttonState.calibrate; - } + } } function updateCalibrationButton() { @@ -897,11 +1005,12 @@ Rectangle { "Y": handYOffset.value, "Z": handZOffset.value } - + var settingsObject = { "bodyConfiguration": trackerConfiguration, "headConfiguration": headObject, - "handConfiguration": handObject + "handConfiguration": handObject, + "desktopMode": viveInDesktop.checked } return settingsObject; diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 073f143dbe..a02e79a5e2 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -94,10 +94,20 @@ StackView { property bool keyboardEnabled: false property bool keyboardRaised: false property bool punctuationMode: false - + width: parent.width height: parent.height + MouseArea { + anchors.fill: parent + propagateComposedEvents: true + onPressed: { + parent.forceActiveFocus(); + addressBarDialog.keyboardEnabled = false; + mouse.accepted = false; + } + } + anchors { right: parent.right left: parent.left @@ -227,9 +237,9 @@ StackView { MouseArea { anchors.fill: parent; onClicked: { - if (!addressLine.focus || !HMD.active) { - addressLine.focus = true; - addressLine.forceActiveFocus(); + addressLine.focus = true; + addressLine.forceActiveFocus(); + if (HMD.active) { addressBarDialog.keyboardEnabled = HMD.active; } tabletRoot.playButtonClickSound(); diff --git a/interface/resources/qml/hifi/tablet/WindowRoot.qml b/interface/resources/qml/hifi/tablet/WindowRoot.qml index ea0c2844a1..fdfcfcf806 100644 --- a/interface/resources/qml/hifi/tablet/WindowRoot.qml +++ b/interface/resources/qml/hifi/tablet/WindowRoot.qml @@ -52,8 +52,10 @@ Windows.ScrollingWindow { // used to receive messages from interface script function fromScript(message) { - if (loader.item.hasOwnProperty("fromScript")) { - loader.item.fromScript(message); + if (loader.item !== null) { + if (loader.item.hasOwnProperty("fromScript")) { + loader.item.fromScript(message); + } } } diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index 7bea72be91..1556a9c0c0 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -336,5 +336,6 @@ Item { readonly property string source: "\ue01c" readonly property string playback_play: "\ue01d" readonly property string stop_square: "\ue01e" + readonly property string avatarTPose: "\ue01f" } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c3c51fa069..68c13b0935 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -953,58 +953,68 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Make sure we don't time out during slow operations at startup updateHeartbeat(); - - // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. - // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. - static const QString TESTER = "HIFI_TESTER"; - auto gpuIdent = GPUIdent::getInstance(); - auto glContextData = getGLContextData(); - QJsonObject properties = { - { "version", applicationVersion() }, - { "tester", QProcessEnvironment::systemEnvironment().contains(TESTER) }, - { "previousSessionCrashed", _previousSessionCrashed }, - { "previousSessionRuntime", sessionRunTime.get() }, - { "cpu_architecture", QSysInfo::currentCpuArchitecture() }, - { "kernel_type", QSysInfo::kernelType() }, - { "kernel_version", QSysInfo::kernelVersion() }, - { "os_type", QSysInfo::productType() }, - { "os_version", QSysInfo::productVersion() }, - { "gpu_name", gpuIdent->getName() }, - { "gpu_driver", gpuIdent->getDriver() }, - { "gpu_memory", static_cast(gpuIdent->getMemory()) }, - { "gl_version_int", glVersionToInteger(glContextData.value("version").toString()) }, - { "gl_version", glContextData["version"] }, - { "gl_vender", glContextData["vendor"] }, - { "gl_sl_version", glContextData["sl_version"] }, - { "gl_renderer", glContextData["renderer"] }, - { "ideal_thread_count", QThread::idealThreadCount() } - }; - auto macVersion = QSysInfo::macVersion(); - if (macVersion != QSysInfo::MV_None) { - properties["os_osx_version"] = QSysInfo::macVersion(); - } - auto windowsVersion = QSysInfo::windowsVersion(); - if (windowsVersion != QSysInfo::WV_None) { - properties["os_win_version"] = QSysInfo::windowsVersion(); - } - - ProcessorInfo procInfo; - if (getProcessorInfo(procInfo)) { - properties["processor_core_count"] = procInfo.numProcessorCores; - properties["logical_processor_count"] = procInfo.numLogicalProcessors; - properties["processor_l1_cache_count"] = procInfo.numProcessorCachesL1; - properties["processor_l2_cache_count"] = procInfo.numProcessorCachesL2; - properties["processor_l3_cache_count"] = procInfo.numProcessorCachesL3; - } - - // add firstRun flag from settings to launch event Setting::Handle firstRun { Settings::firstRun, true }; - properties["first_run"] = firstRun.get(); - // add the user's machine ID to the launch event - properties["machine_fingerprint"] = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()); + // once the settings have been loaded, check if we need to flip the default for UserActivityLogger + auto& userActivityLogger = UserActivityLogger::getInstance(); + if (!userActivityLogger.isDisabledSettingSet()) { + // the user activity logger is opt-out for Interface + // but it's defaulted to disabled for other targets + // so we need to enable it here if it has never been disabled by the user + userActivityLogger.disable(false); + } - UserActivityLogger::getInstance().logAction("launch", properties); + if (userActivityLogger.isEnabled()) { + // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. + // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. + static const QString TESTER = "HIFI_TESTER"; + auto gpuIdent = GPUIdent::getInstance(); + auto glContextData = getGLContextData(); + QJsonObject properties = { + { "version", applicationVersion() }, + { "tester", QProcessEnvironment::systemEnvironment().contains(TESTER) }, + { "previousSessionCrashed", _previousSessionCrashed }, + { "previousSessionRuntime", sessionRunTime.get() }, + { "cpu_architecture", QSysInfo::currentCpuArchitecture() }, + { "kernel_type", QSysInfo::kernelType() }, + { "kernel_version", QSysInfo::kernelVersion() }, + { "os_type", QSysInfo::productType() }, + { "os_version", QSysInfo::productVersion() }, + { "gpu_name", gpuIdent->getName() }, + { "gpu_driver", gpuIdent->getDriver() }, + { "gpu_memory", static_cast(gpuIdent->getMemory()) }, + { "gl_version_int", glVersionToInteger(glContextData.value("version").toString()) }, + { "gl_version", glContextData["version"] }, + { "gl_vender", glContextData["vendor"] }, + { "gl_sl_version", glContextData["sl_version"] }, + { "gl_renderer", glContextData["renderer"] }, + { "ideal_thread_count", QThread::idealThreadCount() } + }; + auto macVersion = QSysInfo::macVersion(); + if (macVersion != QSysInfo::MV_None) { + properties["os_osx_version"] = QSysInfo::macVersion(); + } + auto windowsVersion = QSysInfo::windowsVersion(); + if (windowsVersion != QSysInfo::WV_None) { + properties["os_win_version"] = QSysInfo::windowsVersion(); + } + + ProcessorInfo procInfo; + if (getProcessorInfo(procInfo)) { + properties["processor_core_count"] = procInfo.numProcessorCores; + properties["logical_processor_count"] = procInfo.numLogicalProcessors; + properties["processor_l1_cache_count"] = procInfo.numProcessorCachesL1; + properties["processor_l2_cache_count"] = procInfo.numProcessorCachesL2; + properties["processor_l3_cache_count"] = procInfo.numProcessorCachesL3; + } + + properties["first_run"] = firstRun.get(); + + // add the user's machine ID to the launch event + properties["machine_fingerprint"] = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()); + + userActivityLogger.logAction("launch", properties); + } // Tell our entity edit sender about our known jurisdictions _entityEditSender.setServerJurisdictions(&_entityServerJurisdictions); @@ -3158,59 +3168,6 @@ void Application::keyPressEvent(QKeyEvent* event) { break; #endif - case Qt::Key_H: { - // whenever switching to/from full screen mirror from the keyboard, remember - // the state you were in before full screen mirror, and return to that. - auto previousMode = _myCamera.getMode(); - if (previousMode != CAMERA_MODE_MIRROR) { - switch (previousMode) { - case CAMERA_MODE_FIRST_PERSON: - _returnFromFullScreenMirrorTo = MenuOption::FirstPerson; - break; - case CAMERA_MODE_THIRD_PERSON: - _returnFromFullScreenMirrorTo = MenuOption::ThirdPerson; - break; - - // FIXME - it's not clear that these modes make sense to return to... - case CAMERA_MODE_INDEPENDENT: - _returnFromFullScreenMirrorTo = MenuOption::IndependentMode; - break; - case CAMERA_MODE_ENTITY: - _returnFromFullScreenMirrorTo = MenuOption::CameraEntityMode; - break; - - default: - _returnFromFullScreenMirrorTo = MenuOption::ThirdPerson; - break; - } - } - - bool isMirrorChecked = Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror); - Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, !isMirrorChecked); - if (isMirrorChecked) { - - // if we got here without coming in from a non-Full Screen mirror case, then our - // _returnFromFullScreenMirrorTo is unknown. In that case we'll go to the old - // behavior of returning to ThirdPerson - if (_returnFromFullScreenMirrorTo.isEmpty()) { - _returnFromFullScreenMirrorTo = MenuOption::ThirdPerson; - } - Menu::getInstance()->setIsOptionChecked(_returnFromFullScreenMirrorTo, true); - } - cameraMenuChanged(); - break; - } - - case Qt::Key_P: { - if (!(isShifted || isMeta || isOption)) { - bool isFirstPersonChecked = Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson); - Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, !isFirstPersonChecked); - Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, isFirstPersonChecked); - cameraMenuChanged(); - } - break; - } - case Qt::Key_Slash: Menu::getInstance()->triggerOption(MenuOption::Stats); break; @@ -7127,6 +7084,12 @@ void Application::updateDisplayMode() { // reset the avatar, to set head and hand palms back to a reasonable default pose. getMyAvatar()->reset(false); + // switch to first person if entering hmd and setting is checked + if (isHmd && menu->isOptionChecked(MenuOption::FirstPersonHMD)) { + menu->setIsOptionChecked(MenuOption::FirstPerson, true); + cameraMenuChanged(); + } + Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index a746d72d91..8b616c3029 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -223,7 +223,7 @@ Menu::Menu() { // View > First Person cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, - MenuOption::FirstPerson, 0, // QML Qt:: Key_P + MenuOption::FirstPerson, 0, true, qApp, SLOT(cameraMenuChanged()))); // View > Third Person @@ -233,7 +233,7 @@ Menu::Menu() { // View > Mirror cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, - MenuOption::FullscreenMirror, 0, // QML Qt::Key_H, + MenuOption::FullscreenMirror, 0, false, qApp, SLOT(cameraMenuChanged()))); // View > Independent [advanced] @@ -258,6 +258,9 @@ Menu::Menu() { // View > Overlays addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true); + // View > Enter First Person Mode in HMD + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPersonHMD, 0, true); + // Navigate menu ---------------------------------- MenuWrapper* navigateMenu = addMenu("Navigate"); @@ -319,7 +322,7 @@ Menu::Menu() { QString("../../hifi/tablet/TabletLodPreferences.qml"), "LodPreferencesDialog"); }); - action = addActionToQMenuAndActionHash(settingsMenu, "Controller Settings"); + action = addActionToQMenuAndActionHash(settingsMenu, "Controller Settings..."); connect(action, &QAction::triggered, [] { auto tablet = DependencyManager::get()->getTablet("com.highfidelity.interface.tablet.system"); auto hmd = DependencyManager::get(); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 252fbcc142..a81ef9ac86 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -105,6 +105,7 @@ namespace MenuOption { const QString ExpandPhysicsSimulationTiming = "Expand /physics"; const QString ExpandUpdateTiming = "Expand /update"; const QString FirstPerson = "First Person"; + const QString FirstPersonHMD = "Enter First Person Mode in HMD"; const QString FivePointCalibration = "5 Point Calibration"; const QString FixGaze = "Fix Gaze (no saccade)"; const QString Forward = "Forward"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 8cd39b298f..afbcd170e5 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1634,7 +1634,8 @@ void MyAvatar::prepareForPhysicsSimulation() { _characterController.setParentVelocity(parentVelocity); _characterController.setPositionAndOrientation(getPosition(), getOrientation()); - if (qApp->isHMDMode()) { + auto headPose = getHeadControllerPoseInAvatarFrame(); + if (headPose.isValid()) { _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput()); } else { _follow.deactivate(); diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 399c730dbd..bcf9897dd8 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -307,6 +307,7 @@ void Overlays::deleteOverlay(OverlayID id) { } #endif + _overlaysToDelete.push_back(overlayToDelete); emit overlayDeleted(id); } @@ -606,22 +607,16 @@ QSizeF Overlays::textSize(OverlayID id, const QString& text) { return result; } - Overlay::Pointer thisOverlay; - { - QMutexLocker locker(&_mutex); - thisOverlay = _overlaysHUD[id]; - } + Overlay::Pointer thisOverlay = getOverlay(id); if (thisOverlay) { - if (auto textOverlay = std::dynamic_pointer_cast(thisOverlay)) { - return textOverlay->textSize(text); - } - } else { - { - QMutexLocker locker(&_mutex); - thisOverlay = _overlaysWorld[id]; - } - if (auto text3dOverlay = std::dynamic_pointer_cast(thisOverlay)) { - return text3dOverlay->textSize(text); + if (thisOverlay->is3D()) { + if (auto text3dOverlay = std::dynamic_pointer_cast(thisOverlay)) { + return text3dOverlay->textSize(text); + } + } else { + if (auto textOverlay = std::dynamic_pointer_cast(thisOverlay)) { + return textOverlay->textSize(text); + } } } return QSizeF(0.0f, 0.0f); diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h index 179e8e6e66..b44c60eba7 100644 --- a/libraries/networking/src/UserActivityLogger.h +++ b/libraries/networking/src/UserActivityLogger.h @@ -33,6 +33,7 @@ public: public slots: bool isEnabled() { return !_disabled.get(); } + bool isDisabledSettingSet() const { return _disabled.isSet(); } void disable(bool disable); void logAction(QString action, QJsonObject details = QJsonObject(), JSONCallbackParameters params = JSONCallbackParameters()); @@ -53,7 +54,7 @@ private slots: private: UserActivityLogger(); - Setting::Handle _disabled { "UserActivityLoggerDisabled", false }; + Setting::Handle _disabled { "UserActivityLoggerDisabled", true }; QElapsedTimer _timer; }; diff --git a/libraries/physics/src/ShapeManager.cpp b/libraries/physics/src/ShapeManager.cpp index fd3e35d28a..77716f671b 100644 --- a/libraries/physics/src/ShapeManager.cpp +++ b/libraries/physics/src/ShapeManager.cpp @@ -32,13 +32,6 @@ const btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { if (info.getType() == SHAPE_TYPE_NONE) { return nullptr; } - const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube - if (4.0f * glm::length2(info.getHalfExtents()) < MIN_SHAPE_DIAGONAL_SQUARED) { - // tiny shapes are not supported - // qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal; - return nullptr; - } - DoubleHashKey key = info.getHash(); ShapeReference* shapeRef = _shapeMap.find(key); if (shapeRef) { diff --git a/libraries/shared/src/SettingHandle.h b/libraries/shared/src/SettingHandle.h index 258d1f8491..341a4cb101 100644 --- a/libraries/shared/src/SettingHandle.h +++ b/libraries/shared/src/SettingHandle.h @@ -107,6 +107,7 @@ namespace Setting { } bool isSet() const { + maybeInit(); return _isSet; } diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 583bceeaf2..496e94f8bd 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -15,6 +15,9 @@ #include "NumericalConstants.h" // for MILLIMETERS_PER_METER +// Bullet doesn't support arbitrarily small shapes +const float MIN_HALF_EXTENT = 0.005f; // 0.5 cm + void ShapeInfo::clear() { _url.clear(); _pointCollection.clear(); @@ -26,14 +29,20 @@ void ShapeInfo::clear() { } void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString url) { + _url = ""; _type = type; - _halfExtents = halfExtents; + setHalfExtents(halfExtents); switch(type) { case SHAPE_TYPE_NONE: _halfExtents = glm::vec3(0.0f); break; case SHAPE_TYPE_BOX: - case SHAPE_TYPE_SPHERE: + break; + case SHAPE_TYPE_SPHERE: { + float radius = glm::length(halfExtents) / SQUARE_ROOT_OF_3; + radius = glm::max(radius, MIN_HALF_EXTENT); + _halfExtents = glm::vec3(radius); + } break; case SHAPE_TYPE_COMPOUND: case SHAPE_TYPE_STATIC_MESH: @@ -48,14 +57,15 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString void ShapeInfo::setBox(const glm::vec3& halfExtents) { _url = ""; _type = SHAPE_TYPE_BOX; - _halfExtents = halfExtents; + setHalfExtents(halfExtents); _doubleHashKey.clear(); } void ShapeInfo::setSphere(float radius) { _url = ""; _type = SHAPE_TYPE_SPHERE; - _halfExtents = glm::vec3(radius, radius, radius); + radius = glm::max(radius, MIN_HALF_EXTENT); + _halfExtents = glm::vec3(radius); _doubleHashKey.clear(); } @@ -68,6 +78,8 @@ void ShapeInfo::setPointCollection(const ShapeInfo::PointCollection& pointCollec void ShapeInfo::setCapsuleY(float radius, float halfHeight) { _url = ""; _type = SHAPE_TYPE_CAPSULE_Y; + radius = glm::max(radius, MIN_HALF_EXTENT); + halfHeight = glm::max(halfHeight, 0.0f); _halfExtents = glm::vec3(radius, halfHeight, radius); _doubleHashKey.clear(); } @@ -239,3 +251,7 @@ const DoubleHashKey& ShapeInfo::getHash() const { } return _doubleHashKey; } + +void ShapeInfo::setHalfExtents(const glm::vec3& halfExtents) { + _halfExtents = glm::max(halfExtents, glm::vec3(MIN_HALF_EXTENT)); +} diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 17e4703fc2..0ffdf1310d 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -89,6 +89,8 @@ public: const DoubleHashKey& getHash() const; protected: + void setHalfExtents(const glm::vec3& halfExtents); + QUrl _url; // url for model of convex collision hulls PointCollection _pointCollection; TriangleIndices _triangleIndices; diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index f4253899a2..c54f2326c2 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -82,6 +82,12 @@ struct PoseData { angularVelocities[i] = transformVectorFast(resetMat, toGlm(vrPoses[i].vAngularVelocity)); } } + + void resetToInvalid() { + for (int i = 0; i < vr::k_unMaxTrackedDeviceCount; i++) { + vrPoses[i].bPoseIsValid = false; + } + } }; // FIXME remove once OpenVR header is updated diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 2f9aa4405a..07b3b2f73d 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include #include @@ -60,11 +62,6 @@ static const int SECOND_FOOT = 1; static const int HIP = 2; static const int CHEST = 3; -static float HEAD_PUCK_Y_OFFSET = -0.0254f; -static float HEAD_PUCK_Z_OFFSET = -0.152f; -static float HAND_PUCK_Y_OFFSET = -0.0508f; -static float HAND_PUCK_Z_OFFSET = 0.0254f; - const char* ViveControllerManager::NAME { "OpenVR" }; const std::map TRACKING_RESULT_TO_STRING = { @@ -121,6 +118,29 @@ static QString deviceTrackingResultToString(vr::ETrackingResult trackingResult) return result; } +static glm::mat4 calculateResetMat() { + auto chaperone = vr::VRChaperone(); + if (chaperone) { + float const UI_RADIUS = 1.0f; + float const UI_HEIGHT = 1.6f; + float const UI_Z_OFFSET = 0.5; + + float xSize, zSize; + chaperone->GetPlayAreaSize(&xSize, &zSize); + glm::vec3 uiPos(0.0f, UI_HEIGHT, UI_RADIUS - (0.5f * zSize) - UI_Z_OFFSET); + + return glm::inverse(createMatFromQuatAndPos(glm::quat(), uiPos)); + } + return glm::mat4(); +} + +bool ViveControllerManager::isDesktopMode() { + if (_container) { + return !_container->getActiveDisplayPlugin()->isHmd(); + } + return false; +} + void ViveControllerManager::calibrate() { if (isSupported()) { _inputDevice->calibrateNextFrame(); @@ -141,13 +161,21 @@ bool ViveControllerManager::isSupported() const { void ViveControllerManager::setConfigurationSettings(const QJsonObject configurationSettings) { if (isSupported()) { + if (configurationSettings.contains("desktopMode")) { + _desktopMode = configurationSettings["desktopMode"].toBool(); + if (!_desktopMode) { + _resetMatCalculated = false; + } + } _inputDevice->configureCalibrationSettings(configurationSettings); } } QJsonObject ViveControllerManager::configurationSettings() { if (isSupported()) { - return _inputDevice->configurationSettings(); + QJsonObject configurationSettings = _inputDevice->configurationSettings(); + configurationSettings["desktopMode"] = _desktopMode; + return configurationSettings; } return QJsonObject(); @@ -218,6 +246,18 @@ void ViveControllerManager::pluginUpdate(float deltaTime, const controller::Inpu return; } + if (isDesktopMode() && _desktopMode) { + if (!_resetMatCalculated) { + _resetMat = calculateResetMat(); + _resetMatCalculated = true; + } + + _system->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, 0, _nextSimPoseData.vrPoses, vr::k_unMaxTrackedDeviceCount); + _nextSimPoseData.update(_resetMat); + } else if (isDesktopMode()) { + _nextSimPoseData.resetToInvalid(); + } + auto userInputMapper = DependencyManager::get(); handleOpenVrEvents(); if (openVrQuitRequested()) { @@ -344,8 +384,8 @@ void ViveControllerManager::InputDevice::configureCalibrationSettings(const QJso bool overrideHead = headObject["override"].toBool(); if (overrideHead) { _headConfig = HeadConfig::Puck; - HEAD_PUCK_Y_OFFSET = headObject["Y"].toDouble(); - HEAD_PUCK_Z_OFFSET = headObject["Z"].toDouble(); + _headPuckYOffset = headObject["Y"].toDouble(); + _headPuckZOffset = headObject["Z"].toDouble(); } else { _headConfig = HeadConfig::HMD; } @@ -354,8 +394,8 @@ void ViveControllerManager::InputDevice::configureCalibrationSettings(const QJso bool overrideHands = handsObject["override"].toBool(); if (overrideHands) { _handConfig = HandConfig::Pucks; - HAND_PUCK_Y_OFFSET = handsObject["Y"].toDouble(); - HAND_PUCK_Z_OFFSET = handsObject["Z"].toDouble(); + _handPuckYOffset = handsObject["Y"].toDouble(); + _handPuckZOffset = handsObject["Z"].toDouble(); } else { _handConfig = HandConfig::HandController; } @@ -389,7 +429,7 @@ void ViveControllerManager::InputDevice::emitCalibrationStatus() { status["hand_pucks"] = (_handConfig == HandConfig::Pucks); status["puckCount"] = (int)_validTrackedObjects.size(); status["UI"] = _calibrate; - + emit inputConfiguration->calibrationStatus(status); } @@ -426,7 +466,9 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde // transform into avatar frame glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat; _poseStateMap[poseIndex] = pose.transform(controllerToAvatar); - _validTrackedObjects.push_back(std::make_pair(poseIndex, _poseStateMap[poseIndex])); + + // but _validTrackedObjects remain in sensor frame + _validTrackedObjects.push_back(std::make_pair(poseIndex, pose)); } else { controller::Pose invalidPose; _poseStateMap[poseIndex] = invalidPose; @@ -440,7 +482,7 @@ void ViveControllerManager::InputDevice::sendUserActivityData(QString activity) {"head_puck", (_headConfig == HeadConfig::Puck) ? true : false}, {"hand_pucks", (_handConfig == HandConfig::Pucks) ? true : false} }; - + UserActivityLogger::getInstance().logAction(activity, jsonData); } @@ -473,12 +515,13 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr glm::mat4 defaultToReferenceMat = glm::mat4(); if (_headConfig == HeadConfig::HMD) { defaultToReferenceMat = calculateDefaultToReferenceForHmd(inputCalibration); - } else if (_headConfig == HeadConfig::Puck) { + } else if (_headConfig == HeadConfig::Puck) { + std::sort(_validTrackedObjects.begin(), _validTrackedObjects.end(), sortPucksYPosition); defaultToReferenceMat = calculateDefaultToReferenceForHeadPuck(inputCalibration); } - + _config = _preferedConfig; - + bool headConfigured = configureHead(defaultToReferenceMat, inputCalibration); bool handsConfigured = configureHands(defaultToReferenceMat, inputCalibration); bool bodyConfigured = configureBody(defaultToReferenceMat, inputCalibration); @@ -668,63 +711,67 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u } } } + +// defaultToReferenceMat is an offset from avatar space to sensor space. +// it aligns the default center-eye in avatar space with the hmd in sensor space. +// +// * E_a is the the default center-of-the-eyes transform in avatar space. +// * E_s is the the hmd eye-center transform in sensor space, with roll and pitch removed. +// * D is the defaultReferenceMat. +// +// E_s = D * E_a => +// D = E_s * inverse(E_a) +// glm::mat4 ViveControllerManager::InputDevice::calculateDefaultToReferenceForHmd(const controller::InputCalibrationData& inputCalibration) { - // convert the hmd head from sensor space to avatar space - glm::mat4 hmdSensorFlippedMat = inputCalibration.hmdSensorMat * Matrices::Y_180; - glm::mat4 sensorToAvatarMat = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat; - glm::mat4 hmdAvatarMat = sensorToAvatarMat * hmdSensorFlippedMat; - // cancel the roll and pitch for the hmd head - glm::quat hmdRotation = cancelOutRollAndPitch(glmExtractRotation(hmdAvatarMat)); - glm::vec3 hmdTranslation = extractTranslation(hmdAvatarMat); - glm::mat4 currentHmd = createMatFromQuatAndPos(hmdRotation, hmdTranslation); + // the center-eye transform in avatar space. + glm::mat4 E_a = inputCalibration.defaultCenterEyeMat; - // calculate the offset from the centerOfEye to defaultHeadMat - glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; + // the center-eye transform in sensor space. + glm::mat4 E_s = inputCalibration.hmdSensorMat * Matrices::Y_180; // the Y_180 is to convert hmd from -z forward to z forward. - glm::mat4 currentHead = currentHmd * defaultHeadOffset; + // cancel out roll and pitch on E_s + glm::quat rot = cancelOutRollAndPitch(glmExtractRotation(E_s)); + glm::vec3 trans = extractTranslation(E_s); + E_s = createMatFromQuatAndPos(rot, trans); - // calculate the defaultToRefrenceXform - glm::mat4 defaultToReferenceMat = currentHead * glm::inverse(inputCalibration.defaultHeadMat); - - return defaultToReferenceMat; + return E_s * glm::inverse(E_a); } +// defaultToReferenceMat is an offset from avatar space to sensor space. +// It aligns the default center-of-the-eyes transform in avatar space with the head-puck in sensor space. +// The offset from the center-of-the-eyes to the head-puck can be configured via _headPuckYOffset and _headPuckZOffset, +// These values are exposed in the configuration UI. +// +// * E_a is the the default center-eye transform in avatar space. +// * E_s is the the head-puck center-eye transform in sensor space, with roll and pitch removed. +// * D is the defaultReferenceMat. +// +// E_s = D * E_a => +// D = E_s * inverse(E_a) +// glm::mat4 ViveControllerManager::InputDevice::calculateDefaultToReferenceForHeadPuck(const controller::InputCalibrationData& inputCalibration) { - glm::mat4 avatarToSensorMat = glm::inverse(inputCalibration.sensorToWorldMat) * inputCalibration.avatarMat; - glm::mat4 sensorToAvatarMat = glm::inverse(inputCalibration.avatarMat) * inputCalibration.sensorToWorldMat; + + // the center-eye transform in avatar space. + glm::mat4 E_a = inputCalibration.defaultCenterEyeMat; + + // calculate the center-eye transform in sensor space, via the head-puck size_t headPuckIndex = _validTrackedObjects.size() - 1; controller::Pose headPuckPose = _validTrackedObjects[headPuckIndex].second; - glm::mat4 headPuckAvatarMat = createMatFromQuatAndPos(headPuckPose.getRotation(), headPuckPose.getTranslation()) * Matrices::Y_180; - glm::vec3 headPuckTranslation = extractTranslation(headPuckAvatarMat); - glm::vec3 headPuckZAxis = cancelOutRollAndPitch(glmExtractRotation(headPuckAvatarMat)) * glm::vec3(0.0f, 0.0f, 1.0f); - glm::vec3 worldUp = glm::vec3(0.0f, 1.0f, 0.0f); - // check that the head puck z axis is not parrallel to the world up - const float EPSILON = 1.0e-4f; - glm::vec3 zAxis = glmExtractRotation(headPuckAvatarMat) * glm::vec3(0.0f, 0.0f, 1.0f); - if (fabsf(fabsf(glm::dot(glm::normalize(worldUp), glm::normalize(zAxis))) - 1.0f) < EPSILON) { - headPuckZAxis = glm::vec3(1.0f, 0.0f, 0.0f); - } + // AJT: TODO: handle case were forward is parallel with UNIT_Y. + glm::vec3 forward = headPuckPose.rotation * -Vectors::UNIT_Z; + glm::vec3 x = glm::normalize(glm::cross(Vectors::UNIT_Y, forward)); + glm::vec3 z = glm::normalize(glm::cross(x, Vectors::UNIT_Y)); + glm::mat3 centerEyeRotMat(x, Vectors::UNIT_Y, z); + glm::vec3 centerEyeTrans = headPuckPose.translation + centerEyeRotMat * glm::vec3(0.0f, _headPuckYOffset, _headPuckZOffset); - glm::vec3 yPrime = glm::vec3(0.0f, 1.0f, 0.0f); - glm::vec3 xPrime = glm::normalize(glm::cross(worldUp, headPuckZAxis)); - glm::vec3 zPrime = glm::normalize(glm::cross(xPrime, yPrime)); - glm::mat4 newHeadPuck = glm::mat4(glm::vec4(xPrime, 0.0f), glm::vec4(yPrime, 0.0f), - glm::vec4(zPrime, 0.0f), glm::vec4(headPuckTranslation, 1.0f)); + glm::mat4 E_s(glm::vec4(centerEyeRotMat[0], 0.0f), + glm::vec4(centerEyeRotMat[1], 0.0f), + glm::vec4(centerEyeRotMat[2], 0.0f), + glm::vec4(centerEyeTrans, 1.0f)); - glm::mat4 headPuckOffset = glm::mat4(glm::vec4(1.0f, 0.0f, 0.0f, 0.0f), glm::vec4(0.0f, 1.0f, 0.0f, 0.0f), - glm::vec4(0.0f, 0.0f, 1.0f, 0.0f), glm::vec4(0.0f, HEAD_PUCK_Y_OFFSET, HEAD_PUCK_Z_OFFSET, 1.0f)); - - glm::mat4 finalHeadPuck = newHeadPuck * headPuckOffset; - - glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; - - glm::mat4 currentHead = finalHeadPuck * defaultHeadOffset; - - // calculate the defaultToRefrenceXform - glm::mat4 defaultToReferenceMat = currentHead * glm::inverse(inputCalibration.defaultHeadMat); - return defaultToReferenceMat; + return E_s * glm::inverse(E_a); } void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxis, int yAxis, int centerPseudoButton, int xPseudoButton, int yPseudoButton) { @@ -912,12 +959,12 @@ void ViveControllerManager::InputDevice::calibrateLeftHand(glm::mat4& defaultToR glm::mat4 newHandMat = glm::mat4(glm::vec4(xPrime, 0.0f), glm::vec4(yPrime, 0.0f), glm::vec4(zPrime, 0.0f), glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); - - glm::vec3 translationOffset = glm::vec3(0.0f, HAND_PUCK_Y_OFFSET, HAND_PUCK_Z_OFFSET); + + glm::vec3 translationOffset = glm::vec3(0.0f, _handPuckYOffset, _handPuckZOffset); glm::quat initialRotation = glmExtractRotation(handPoseAvatarMat); glm::quat finalRotation = glmExtractRotation(newHandMat); - + glm::quat rotationOffset = glm::inverse(initialRotation) * finalRotation; glm::mat4 offsetMat = createMatFromQuatAndPos(rotationOffset, translationOffset); @@ -942,13 +989,13 @@ void ViveControllerManager::InputDevice::calibrateRightHand(glm::mat4& defaultTo glm::vec3 yPrime = glm::normalize(glm::cross(zPrime, xPrime)); glm::mat4 newHandMat = glm::mat4(glm::vec4(xPrime, 0.0f), glm::vec4(yPrime, 0.0f), glm::vec4(zPrime, 0.0f), glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); - - glm::vec3 translationOffset = glm::vec3(0.0f, HAND_PUCK_Y_OFFSET, HAND_PUCK_Z_OFFSET); + + glm::vec3 translationOffset = glm::vec3(0.0f, _handPuckYOffset, _handPuckZOffset); glm::quat initialRotation = glmExtractRotation(handPoseAvatarMat); glm::quat finalRotation = glmExtractRotation(newHandMat); - + glm::quat rotationOffset = glm::inverse(initialRotation) * finalRotation; glm::mat4 offsetMat = createMatFromQuatAndPos(rotationOffset, translationOffset); @@ -965,7 +1012,7 @@ void ViveControllerManager::InputDevice::calibrateFeet(glm::mat4& defaultToRefer auto& secondFoot = _validTrackedObjects[SECOND_FOOT]; controller::Pose& firstFootPose = firstFoot.second; controller::Pose& secondFootPose = secondFoot.second; - + if (determineLimbOrdering(firstFootPose, secondFootPose, headXAxis, headPosition)) { calibrateFoot(defaultToReferenceMat, inputCalibration, firstFoot, true); calibrateFoot(defaultToReferenceMat, inputCalibration, secondFoot, false); @@ -1030,13 +1077,8 @@ void ViveControllerManager::InputDevice::calibrateShoulders(glm::mat4& defaultTo void ViveControllerManager::InputDevice::calibrateHead(glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) { size_t headIndex = _validTrackedObjects.size() - 1; const PuckPosePair& head = _validTrackedObjects[headIndex]; - - // assume the person is wearing the head puck on his/her forehead - glm::mat4 defaultHeadOffset = glm::inverse(inputCalibration.defaultCenterEyeMat) * inputCalibration.defaultHeadMat; - controller::Pose newHead = head.second.postTransform(defaultHeadOffset); - _jointToPuckMap[controller::HEAD] = head.first; - _pucksOffset[head.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHeadMat, newHead); + _pucksOffset[head.first] = computeOffset(defaultToReferenceMat, inputCalibration.defaultHeadMat, head.second); } QString ViveControllerManager::InputDevice::configToString(Config config) { diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index 8397f8d804..a9bcc7e4e2 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -152,7 +152,7 @@ private: HandController, Pucks }; - + Config _config { Config::None }; Config _preferedConfig { Config::None }; HeadConfig _headConfig { HeadConfig::HMD }; @@ -177,6 +177,10 @@ private: float _leftHapticDuration { 0.0f }; float _rightHapticStrength { 0.0f }; float _rightHapticDuration { 0.0f }; + float _headPuckYOffset { -0.05f }; + float _headPuckZOffset { -0.05f }; + float _handPuckYOffset { 0.0f }; + float _handPuckZOffset { 0.0f }; bool _triggersPressedHandled { false }; bool _calibrated { false }; bool _timeTilCalibrationSet { false }; @@ -190,9 +194,12 @@ private: }; void renderHand(const controller::Pose& pose, gpu::Batch& batch, int sign); - + bool isDesktopMode(); bool _registeredWithInputMapper { false }; bool _modelLoaded { false }; + bool _resetMatCalculated { false }; + bool _desktopMode { false }; + glm::mat4 _resetMat { glm::mat4() }; model::Geometry _modelGeometry; gpu::TexturePointer _texture; diff --git a/scripts/system/controllers/godView.js b/scripts/system/controllers/godView.js new file mode 100644 index 0000000000..4b406399fd --- /dev/null +++ b/scripts/system/controllers/godView.js @@ -0,0 +1,116 @@ +"use strict"; +// +// godView.js +// scripts/system/ +// +// Created by Brad Hefta-Gaub on 1 Jun 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/* globals HMD, Script, Menu, Tablet, Camera */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ + +(function() { // BEGIN LOCAL_SCOPE + +var godView = false; + +var GOD_CAMERA_OFFSET = -1; // 1 meter below the avatar +var GOD_VIEW_HEIGHT = 300; // 300 meter above the ground +var ABOVE_GROUND_DROP = 2; +var MOVE_BY = 1; + +function moveTo(position) { + if (godView) { + MyAvatar.position = position; + Camera.position = Vec3.sum(MyAvatar.position, {x:0, y: GOD_CAMERA_OFFSET, z: 0}); + } else { + MyAvatar.position = position; + } +} + +function keyPressEvent(event) { + if (godView) { + switch(event.text) { + case "UP": + moveTo(Vec3.sum(MyAvatar.position, {x:0.0, y: 0, z: -1 * MOVE_BY})); + break; + case "DOWN": + moveTo(Vec3.sum(MyAvatar.position, {x:0, y: 0, z: MOVE_BY})); + break; + case "LEFT": + moveTo(Vec3.sum(MyAvatar.position, {x:-1 * MOVE_BY, y: 0, z: 0})); + break; + case "RIGHT": + moveTo(Vec3.sum(MyAvatar.position, {x:MOVE_BY, y: 0, z: 0})); + break; + } + } +} + +function mousePress(event) { + if (godView) { + var pickRay = Camera.computePickRay(event.x, event.y); + var pointingAt = Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction,300)); + var moveToPosition = { x: pointingAt.x, y: MyAvatar.position.y, z: pointingAt.z }; + moveTo(moveToPosition); + } +} + + +var oldCameraMode = Camera.mode; + +function startGodView() { + if (!godView) { + oldCameraMode = Camera.mode; + MyAvatar.position = Vec3.sum(MyAvatar.position, {x:0, y: GOD_VIEW_HEIGHT, z: 0}); + Camera.mode = "independent"; + Camera.position = Vec3.sum(MyAvatar.position, {x:0, y: GOD_CAMERA_OFFSET, z: 0}); + Camera.orientation = Quat.fromPitchYawRollDegrees(-90,0,0); + godView = true; + } +} + +function endGodView() { + if (godView) { + Camera.mode = oldCameraMode; + MyAvatar.position = Vec3.sum(MyAvatar.position, {x:0, y: (-1 * GOD_VIEW_HEIGHT) + ABOVE_GROUND_DROP, z: 0}); + godView = false; + } +} + +var button; +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + +function onClicked() { + if (godView) { + endGodView(); + } else { + startGodView(); + } +} + +button = tablet.addButton({ + icon: "icons/tablet-icons/switch-desk-i.svg", // FIXME - consider a better icon from Alan + text: "God View" +}); + +button.clicked.connect(onClicked); +Controller.keyPressEvent.connect(keyPressEvent); +Controller.mousePressEvent.connect(mousePress); + + +Script.scriptEnding.connect(function () { + if (godView) { + endGodView(); + } + button.clicked.disconnect(onClicked); + if (tablet) { + tablet.removeButton(button); + } + Controller.keyPressEvent.disconnect(keyPressEvent); + Controller.mousePressEvent.disconnect(mousePress); +}); + +}()); // END LOCAL_SCOPE diff --git a/scripts/system/edit.js b/scripts/system/edit.js index a83d2159bb..acea42237b 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1010,7 +1010,6 @@ function setupModelMenus() { Menu.addMenuItem({ menuName: "Edit", menuItemName: "Entity List...", - shortcutKey: "CTRL+META+L", afterItem: "Entities", grouping: "Advanced" }); @@ -1041,7 +1040,6 @@ function setupModelMenus() { Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Large Models", - shortcutKey: "CTRL+META+L", afterItem: GRABBABLE_ENTITIES_MENU_ITEM, isCheckable: true, isChecked: true, @@ -1050,7 +1048,6 @@ function setupModelMenus() { Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Small Models", - shortcutKey: "CTRL+META+S", afterItem: "Allow Selecting of Large Models", isCheckable: true, isChecked: true, @@ -1059,7 +1056,6 @@ function setupModelMenus() { Menu.addMenuItem({ menuName: "Edit", menuItemName: "Allow Selecting of Lights", - shortcutKey: "CTRL+SHIFT+META+L", afterItem: "Allow Selecting of Small Models", isCheckable: true, grouping: "Advanced" @@ -1067,14 +1063,12 @@ function setupModelMenus() { Menu.addMenuItem({ menuName: "Edit", menuItemName: "Select All Entities In Box", - shortcutKey: "CTRL+SHIFT+META+A", afterItem: "Allow Selecting of Lights", grouping: "Advanced" }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Select All Entities Touching Box", - shortcutKey: "CTRL+SHIFT+META+T", afterItem: "Select All Entities In Box", grouping: "Advanced" }); @@ -1082,21 +1076,18 @@ function setupModelMenus() { Menu.addMenuItem({ menuName: "Edit", menuItemName: "Export Entities", - shortcutKey: "CTRL+META+E", afterItem: "Entities", grouping: "Advanced" }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Import Entities", - shortcutKey: "CTRL+META+I", afterItem: "Export Entities", grouping: "Advanced" }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Import Entities from URL", - shortcutKey: "CTRL+META+U", afterItem: "Import Entities", grouping: "Advanced" }); diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 2c81622668..8ea22192fc 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -482,10 +482,10 @@ function populateNearbyUserList(selectData, oldAudioData) { isPresent: true, isReplicated: avatar.isReplicated }; + // Everyone needs to see admin status. Username and fingerprint returns default constructor output if the requesting user isn't an admin. + Users.requestUsernameFromID(id); if (id) { addAvatarNode(id); // No overlay for ourselves - // Everyone needs to see admin status. Username and fingerprint returns default constructor output if the requesting user isn't an admin. - Users.requestUsernameFromID(id); avatarsOfInterest[id] = true; } else { // Return our username from the Account API @@ -862,6 +862,9 @@ function avatarDisconnected(nodeID) { function clearLocalQMLDataAndClosePAL() { sendToQml({ method: 'clearLocalQMLData' }); + if (onPalScreen) { + tablet.gotoHomeScreen(); + } } function avatarAdded(avatarID) { diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 0561956709..5de44e8897 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -17,8 +17,8 @@ set_target_properties(ac-client PROPERTIES FOLDER "Tools") add_subdirectory(skeleton-dump) set_target_properties(skeleton-dump PROPERTIES FOLDER "Tools") -add_subdirectory(atp-get) -set_target_properties(atp-get PROPERTIES FOLDER "Tools") +add_subdirectory(atp-client) +set_target_properties(atp-client PROPERTIES FOLDER "Tools") add_subdirectory(oven) set_target_properties(oven PROPERTIES FOLDER "Tools") diff --git a/tools/ac-client/src/ACClientApp.cpp b/tools/ac-client/src/ACClientApp.cpp index 28f91e8121..e00560158f 100644 --- a/tools/ac-client/src/ACClientApp.cpp +++ b/tools/ac-client/src/ACClientApp.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -33,6 +34,9 @@ ACClientApp::ACClientApp(int argc, char* argv[]) : const QCommandLineOption verboseOutput("v", "verbose output"); parser.addOption(verboseOutput); + const QCommandLineOption authOption("u", "set usename and pass", "username:password"); + parser.addOption(authOption); + const QCommandLineOption domainAddressOption("d", "domain-server address", "127.0.0.1"); parser.addOption(domainAddressOption); @@ -42,7 +46,6 @@ ACClientApp::ACClientApp(int argc, char* argv[]) : const QCommandLineOption listenPortOption("listenPort", "listen port", QString::number(INVALID_PORT)); parser.addOption(listenPortOption); - if (!parser.parse(QCoreApplication::arguments())) { qCritical() << parser.errorText() << endl; parser.showHelp(); @@ -66,6 +69,7 @@ ACClientApp::ACClientApp(int argc, char* argv[]) : const_cast(&shared())->setEnabled(QtInfoMsg, false); const_cast(&shared())->setEnabled(QtWarningMsg, false); } + QString domainServerAddress = "127.0.0.1:40103"; if (parser.isSet(domainAddressOption)) { @@ -81,6 +85,18 @@ ACClientApp::ACClientApp(int argc, char* argv[]) : listenPort = parser.value(listenPortOption).toInt(); } + if (parser.isSet(authOption)) { + QStringList pieces = parser.value(authOption).split(":"); + if (pieces.size() != 2) { + qDebug() << "-u should be followed by username:password"; + parser.showHelp(); + Q_UNREACHABLE(); + } + + _username = pieces[0]; + _password = pieces[1]; + } + Setting::init(); DependencyManager::registerInheritance(); @@ -88,6 +104,9 @@ ACClientApp::ACClientApp(int argc, char* argv[]) : DependencyManager::set(); DependencyManager::set(NodeType::Agent, listenPort); + auto accountManager = DependencyManager::get(); + accountManager->setIsAgent(true); + accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL); auto nodeList = DependencyManager::get(); @@ -96,27 +115,45 @@ ACClientApp::ACClientApp(int argc, char* argv[]) : connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); - // start the nodeThread so its event loop is running + // start the nodeThread so its event loop is running // (must happen after the checkin timer is created with the nodelist as it's parent) nodeList->startThread(); const DomainHandler& domainHandler = nodeList->getDomainHandler(); - connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); - // connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain())); - // connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ACClientApp::domainConnectionRefused); connect(nodeList.data(), &NodeList::nodeAdded, this, &ACClientApp::nodeAdded); connect(nodeList.data(), &NodeList::nodeKilled, this, &ACClientApp::nodeKilled); connect(nodeList.data(), &NodeList::nodeActivated, this, &ACClientApp::nodeActivated); - // connect(nodeList.data(), &NodeList::uuidChanged, getMyAvatar(), &MyAvatar::setSessionUUID); - // connect(nodeList.data(), &NodeList::uuidChanged, this, &ACClientApp::setSessionUUID); connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &ACClientApp::notifyPacketVersionMismatch); - nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer << NodeType::EntityServer << NodeType::AssetServer << NodeType::MessagesMixer); + if (_verbose) { + QString username = accountManager->getAccountInfo().getUsername(); + qDebug() << "cached username is" << username << ", isLoggedIn =" << accountManager->isLoggedIn(); + } + + if (!_username.isEmpty()) { + + connect(accountManager.data(), &AccountManager::newKeypair, this, [&](){ + if (_verbose) { + qDebug() << "new keypair has been created."; + } + }); + + connect(accountManager.data(), &AccountManager::loginComplete, this, [&](){ + if (_verbose) { + qDebug() << "login successful"; + } + }); + connect(accountManager.data(), &AccountManager::loginFailed, this, [&](){ + qDebug() << "login failed."; + }); + accountManager->requestAccessToken(_username, _password); + } + DependencyManager::get()->handleLookupString(domainServerAddress, false); QTimer* doTimer = new QTimer(this); diff --git a/tools/ac-client/src/ACClientApp.h b/tools/ac-client/src/ACClientApp.h index 29d571688e..e295b17654 100644 --- a/tools/ac-client/src/ACClientApp.h +++ b/tools/ac-client/src/ACClientApp.h @@ -47,6 +47,9 @@ private: bool _sawAvatarMixer { false }; bool _sawAssetServer { false }; bool _sawMessagesMixer { false }; + + QString _username; + QString _password; }; #endif //hifi_ACClientApp_h diff --git a/tools/atp-get/CMakeLists.txt b/tools/atp-client/CMakeLists.txt similarity index 77% rename from tools/atp-get/CMakeLists.txt rename to tools/atp-client/CMakeLists.txt index 75f92b787d..4cee30bcc3 100644 --- a/tools/atp-get/CMakeLists.txt +++ b/tools/atp-client/CMakeLists.txt @@ -1,4 +1,4 @@ -set(TARGET_NAME atp-get) +set(TARGET_NAME atp-client) setup_hifi_project(Core Widgets) setup_memory_debugger() link_hifi_libraries(shared networking) diff --git a/tools/atp-client/src/ATPClientApp.cpp b/tools/atp-client/src/ATPClientApp.cpp new file mode 100644 index 0000000000..3e2f8ca71d --- /dev/null +++ b/tools/atp-client/src/ATPClientApp.cpp @@ -0,0 +1,406 @@ +// +// ATPClientApp.cpp +// tools/atp-client/src +// +// Created by Seth Alves on 2017-3-15 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ATPClientApp.h" + +#define HIGH_FIDELITY_ATP_CLIENT_USER_AGENT "Mozilla/5.0 (HighFidelityATPClient)" +#define TIMEOUT_MILLISECONDS 8000 + +ATPClientApp::ATPClientApp(int argc, char* argv[]) : + QCoreApplication(argc, argv) +{ + // parse command-line + QCommandLineParser parser; + parser.setApplicationDescription("High Fidelity ATP-Client"); + + const QCommandLineOption helpOption = parser.addHelpOption(); + + const QCommandLineOption verboseOutput("v", "verbose output"); + parser.addOption(verboseOutput); + + const QCommandLineOption uploadOption("T", "upload local file", "local-file-to-send"); + parser.addOption(uploadOption); + + const QCommandLineOption authOption("u", "set usename and pass", "username:password"); + parser.addOption(authOption); + + const QCommandLineOption outputFilenameOption("o", "output filename", "output-file-name"); + parser.addOption(outputFilenameOption); + + const QCommandLineOption domainAddressOption("d", "domain-server address", "127.0.0.1"); + parser.addOption(domainAddressOption); + + const QCommandLineOption listenPortOption("listenPort", "listen port", QString::number(INVALID_PORT)); + parser.addOption(listenPortOption); + + if (!parser.parse(QCoreApplication::arguments())) { + qCritical() << parser.errorText() << endl; + parser.showHelp(); + Q_UNREACHABLE(); + } + + if (parser.isSet(helpOption)) { + parser.showHelp(); + Q_UNREACHABLE(); + } + + _verbose = parser.isSet(verboseOutput); + if (!_verbose) { + QLoggingCategory::setFilterRules("qt.network.ssl.warning=false"); + + const_cast(&networking())->setEnabled(QtDebugMsg, false); + const_cast(&networking())->setEnabled(QtInfoMsg, false); + const_cast(&networking())->setEnabled(QtWarningMsg, false); + + const_cast(&shared())->setEnabled(QtDebugMsg, false); + const_cast(&shared())->setEnabled(QtInfoMsg, false); + const_cast(&shared())->setEnabled(QtWarningMsg, false); + } + + QStringList posArgs = parser.positionalArguments(); + if (posArgs.size() != 1) { + qDebug() << "give remote url argument"; + parser.showHelp(); + Q_UNREACHABLE(); + } + + _url = QUrl(posArgs[0]); + if (_url.scheme() != "atp") { + qDebug() << "url should start with atp:"; + parser.showHelp(); + Q_UNREACHABLE(); + } + + int domainPort = 40103; + if (_url.port() != -1) { + domainPort = _url.port(); + } + + if (parser.isSet(outputFilenameOption)) { + _localOutputFile = parser.value(outputFilenameOption); + } + + if (parser.isSet(uploadOption)) { + _localUploadFile = parser.value(uploadOption); + } + + if (parser.isSet(authOption)) { + QStringList pieces = parser.value(authOption).split(":"); + if (pieces.size() != 2) { + qDebug() << "-u should be followed by username:password"; + parser.showHelp(); + Q_UNREACHABLE(); + } + + _username = pieces[0]; + _password = pieces[1]; + _waitingForLogin = true; + } + + if (parser.isSet(listenPortOption)) { + _listenPort = parser.value(listenPortOption).toInt(); + } + + _domainServerAddress = QString("127.0.0.1") + ":" + QString::number(domainPort); + if (parser.isSet(domainAddressOption)) { + _domainServerAddress = parser.value(domainAddressOption); + } else if (!_url.host().isEmpty()) { + QUrl domainURL; + domainURL.setScheme("hifi"); + domainURL.setHost(_url.host()); + _domainServerAddress = domainURL.toString(); + } + + Setting::init(); + DependencyManager::registerInheritance(); + + DependencyManager::set([&]{ return QString(HIGH_FIDELITY_ATP_CLIENT_USER_AGENT); }); + DependencyManager::set(); + DependencyManager::set(NodeType::Agent, _listenPort); + + auto accountManager = DependencyManager::get(); + accountManager->setIsAgent(true); + accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL); + + auto nodeList = DependencyManager::get(); + + // setup a timer for domain-server check ins + _domainCheckInTimer = new QTimer(nodeList.data()); + connect(_domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); + _domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); + + // start the nodeThread so its event loop is running + // (must happen after the checkin timer is created with the nodelist as it's parent) + nodeList->startThread(); + + const DomainHandler& domainHandler = nodeList->getDomainHandler(); + connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); + connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ATPClientApp::domainConnectionRefused); + + connect(nodeList.data(), &NodeList::nodeAdded, this, &ATPClientApp::nodeAdded); + connect(nodeList.data(), &NodeList::nodeKilled, this, &ATPClientApp::nodeKilled); + connect(nodeList.data(), &NodeList::nodeActivated, this, &ATPClientApp::nodeActivated); + connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &ATPClientApp::notifyPacketVersionMismatch); + nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer + << NodeType::EntityServer << NodeType::AssetServer << NodeType::MessagesMixer); + + if (_verbose) { + QString username = accountManager->getAccountInfo().getUsername(); + qDebug() << "cached username is" << username << ", isLoggedIn =" << accountManager->isLoggedIn(); + } + + if (!_username.isEmpty()) { + + connect(accountManager.data(), &AccountManager::newKeypair, this, [&](){ + if (_verbose) { + qDebug() << "new keypair has been created."; + } + }); + + connect(accountManager.data(), &AccountManager::loginComplete, this, [&](){ + if (_verbose) { + qDebug() << "login successful"; + } + _waitingForLogin = false; + go(); + }); + connect(accountManager.data(), &AccountManager::loginFailed, this, [&](){ + qDebug() << "login failed."; + _waitingForLogin = false; + go(); + }); + accountManager->requestAccessToken(_username, _password); + } + + auto assetClient = DependencyManager::set(); + assetClient->init(); + + if (_verbose) { + qDebug() << "domain-server address is" << _domainServerAddress; + } + + DependencyManager::get()->handleLookupString(_domainServerAddress, false); + + QTimer* _timeoutTimer = new QTimer(this); + _timeoutTimer->setSingleShot(true); + connect(_timeoutTimer, &QTimer::timeout, this, &ATPClientApp::timedOut); + _timeoutTimer->start(TIMEOUT_MILLISECONDS); +} + +ATPClientApp::~ATPClientApp() { + if (_domainCheckInTimer) { + QMetaObject::invokeMethod(_domainCheckInTimer, "deleteLater", Qt::QueuedConnection); + } + if (_timeoutTimer) { + QMetaObject::invokeMethod(_timeoutTimer, "deleteLater", Qt::QueuedConnection); + } +} + +void ATPClientApp::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) { + // this is non-fatal if we are trying to get an authenticated connection -- it will be retried. + if (_verbose) { + qDebug() << "domainConnectionRefused"; + } +} + +void ATPClientApp::domainChanged(const QString& domainHostname) { + if (_verbose) { + qDebug() << "domainChanged"; + } +} + +void ATPClientApp::nodeAdded(SharedNodePointer node) { + if (_verbose) { + qDebug() << "node added: " << node->getType(); + } +} + +void ATPClientApp::nodeActivated(SharedNodePointer node) { + if (!_waitingForLogin && node->getType() == NodeType::AssetServer) { + _waitingForNode = false; + go(); + } +} + +void ATPClientApp::go() { + if (_waitingForLogin || _waitingForNode) { + return; + } + + auto path = _url.path(); + + if (_verbose) { + qDebug() << "path is " << path; + } + + if (!_localUploadFile.isEmpty()) { + uploadAsset(); + } else if (path == "/") { + listAssets(); + } else { + lookupAsset(); + } +} + +void ATPClientApp::nodeKilled(SharedNodePointer node) { + if (_verbose) { + qDebug() << "nodeKilled" << node->getType(); + } +} + +void ATPClientApp::timedOut() { + finish(1); +} + +void ATPClientApp::notifyPacketVersionMismatch() { + if (_verbose) { + qDebug() << "packet version mismatch"; + } + finish(1); +} + +void ATPClientApp::uploadAsset() { + auto path = _url.path(); + if (path == "/") { + qDebug() << "cannot upload to path of /"; + QCoreApplication::exit(1); + } + + auto upload = DependencyManager::get()->createUpload(_localUploadFile); + QObject::connect(upload, &AssetUpload::finished, this, [=](AssetUpload* upload, const QString& hash) mutable { + if (upload->getError() != AssetUpload::NoError) { + qDebug() << "upload failed: " << upload->getErrorString(); + } else { + setMapping(hash); + } + + upload->deleteLater(); + }); + + upload->start(); +} + +void ATPClientApp::setMapping(QString hash) { + auto path = _url.path(); + auto assetClient = DependencyManager::get(); + auto request = assetClient->createSetMappingRequest(path, hash); + + connect(request, &SetMappingRequest::finished, this, [=](SetMappingRequest* request) mutable { + if (request->getError() != SetMappingRequest::NoError) { + qDebug() << "upload succeeded, but couldn't set mapping: " << request->getErrorString(); + } else if (_verbose) { + qDebug() << "mapping set."; + } + request->deleteLater(); + finish(0); + }); + + request->start(); +} + +void ATPClientApp::listAssets() { + auto request = DependencyManager::get()->createGetAllMappingsRequest(); + QObject::connect(request, &GetAllMappingsRequest::finished, this, [=](GetAllMappingsRequest* request) mutable { + auto result = request->getError(); + if (result == GetAllMappingsRequest::NotFound) { + qDebug() << "not found: " << request->getErrorString(); + } else if (result == GetAllMappingsRequest::NoError) { + auto mappings = request->getMappings(); + for (auto& kv : mappings ) { + qDebug() << kv.first << kv.second; + } + } else { + qDebug() << "error -- " << request->getError() << " -- " << request->getErrorString(); + } + request->deleteLater(); + finish(0); + }); + request->start(); +} + +void ATPClientApp::lookupAsset() { + auto path = _url.path(); + auto request = DependencyManager::get()->createGetMappingRequest(path); + QObject::connect(request, &GetMappingRequest::finished, this, [=](GetMappingRequest* request) mutable { + auto result = request->getError(); + if (result == GetMappingRequest::NotFound) { + qDebug() << "not found: " << request->getErrorString(); + } else if (result == GetMappingRequest::NoError) { + qDebug() << "found, hash is " << request->getHash(); + download(request->getHash()); + } else { + qDebug() << "error -- " << request->getError() << " -- " << request->getErrorString(); + } + request->deleteLater(); + }); + request->start(); +} + +void ATPClientApp::download(AssetHash hash) { + auto assetClient = DependencyManager::get(); + auto assetRequest = new AssetRequest(hash); + + connect(assetRequest, &AssetRequest::finished, this, [this](AssetRequest* request) mutable { + Q_ASSERT(request->getState() == AssetRequest::Finished); + + if (request->getError() == AssetRequest::Error::NoError) { + QString data = QString::fromUtf8(request->getData()); + if (_localOutputFile == "" || _localOutputFile == "-") { + QTextStream cout(stdout); + cout << data; + } else { + QFile outputHandle(_localOutputFile); + if (outputHandle.open(QIODevice::ReadWrite)) { + QTextStream stream( &outputHandle ); + stream << data; + } else { + qDebug() << "couldn't open output file:" << _localOutputFile; + } + } + } + + request->deleteLater(); + finish(0); + }); + + assetRequest->start(); +} + +void ATPClientApp::finish(int exitCode) { + auto nodeList = DependencyManager::get(); + + // send the domain a disconnect packet, force stoppage of domain-server check-ins + nodeList->getDomainHandler().disconnect(); + nodeList->setIsShuttingDown(true); + + // tell the packet receiver we're shutting down, so it can drop packets + nodeList->getPacketReceiver().setShouldDropPackets(true); + + // remove the NodeList from the DependencyManager + DependencyManager::destroy(); + + QCoreApplication::exit(exitCode); +} diff --git a/tools/atp-get/src/ATPGetApp.h b/tools/atp-client/src/ATPClientApp.h similarity index 60% rename from tools/atp-get/src/ATPGetApp.h rename to tools/atp-client/src/ATPClientApp.h index 5507d2aa62..a3904d6e50 100644 --- a/tools/atp-get/src/ATPGetApp.h +++ b/tools/atp-client/src/ATPClientApp.h @@ -1,6 +1,6 @@ // -// ATPGetApp.h -// tools/atp-get/src +// ATPClientApp.h +// tools/atp-client/src // // Created by Seth Alves on 2017-3-15 // Copyright 2017 High Fidelity, Inc. @@ -10,8 +10,8 @@ // -#ifndef hifi_ATPGetApp_h -#define hifi_ATPGetApp_h +#ifndef hifi_ATPClientApp_h +#define hifi_ATPClientApp_h #include #include @@ -23,11 +23,11 @@ #include -class ATPGetApp : public QCoreApplication { +class ATPClientApp : public QCoreApplication { Q_OBJECT public: - ATPGetApp(int argc, char* argv[]); - ~ATPGetApp(); + ATPClientApp(int argc, char* argv[]); + ~ATPClientApp(); private slots: void domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo); @@ -38,15 +38,33 @@ private slots: void notifyPacketVersionMismatch(); private: + void go(); NodeList* _nodeList; void timedOut(); - void lookup(); + void uploadAsset(); + void setMapping(QString hash); + void lookupAsset(); + void listAssets(); void download(AssetHash hash); void finish(int exitCode); bool _verbose; QUrl _url; QString _localOutputFile; + QString _localUploadFile; + + int _listenPort { INVALID_PORT }; + + QString _domainServerAddress; + + QString _username; + QString _password; + + bool _waitingForLogin { false }; + bool _waitingForNode { true }; + + QTimer* _domainCheckInTimer { nullptr }; + QTimer* _timeoutTimer { nullptr }; }; -#endif // hifi_ATPGetApp_h +#endif // hifi_ATPClientApp_h diff --git a/tools/atp-get/src/main.cpp b/tools/atp-client/src/main.cpp similarity index 89% rename from tools/atp-get/src/main.cpp rename to tools/atp-client/src/main.cpp index bddf30c666..88119604cf 100644 --- a/tools/atp-get/src/main.cpp +++ b/tools/atp-client/src/main.cpp @@ -1,6 +1,6 @@ // // main.cpp -// tools/atp-get/src +// tools/atp-client/src // // Created by Seth Alves on 2017-3-15 // Copyright 2017 High Fidelity, Inc. @@ -15,7 +15,7 @@ #include -#include "ATPGetApp.h" +#include "ATPClientApp.h" using namespace std; @@ -25,7 +25,7 @@ int main(int argc, char * argv[]) { QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN); QCoreApplication::setApplicationVersion(BuildInfo::VERSION); - ATPGetApp app(argc, argv); + ATPClientApp app(argc, argv); return app.exec(); } diff --git a/tools/atp-get/src/ATPGetApp.cpp b/tools/atp-get/src/ATPGetApp.cpp deleted file mode 100644 index 4125582c21..0000000000 --- a/tools/atp-get/src/ATPGetApp.cpp +++ /dev/null @@ -1,255 +0,0 @@ -// -// ATPGetApp.cpp -// tools/atp-get/src -// -// Created by Seth Alves on 2017-3-15 -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ATPGetApp.h" - -ATPGetApp::ATPGetApp(int argc, char* argv[]) : - QCoreApplication(argc, argv) -{ - // parse command-line - QCommandLineParser parser; - parser.setApplicationDescription("High Fidelity ATP-Get"); - - const QCommandLineOption helpOption = parser.addHelpOption(); - - const QCommandLineOption verboseOutput("v", "verbose output"); - parser.addOption(verboseOutput); - - const QCommandLineOption domainAddressOption("d", "domain-server address", "127.0.0.1"); - parser.addOption(domainAddressOption); - - const QCommandLineOption cacheSTUNOption("s", "cache stun-server response"); - parser.addOption(cacheSTUNOption); - - const QCommandLineOption listenPortOption("listenPort", "listen port", QString::number(INVALID_PORT)); - parser.addOption(listenPortOption); - - - if (!parser.parse(QCoreApplication::arguments())) { - qCritical() << parser.errorText() << endl; - parser.showHelp(); - Q_UNREACHABLE(); - } - - if (parser.isSet(helpOption)) { - parser.showHelp(); - Q_UNREACHABLE(); - } - - _verbose = parser.isSet(verboseOutput); - if (!_verbose) { - QLoggingCategory::setFilterRules("qt.network.ssl.warning=false"); - - const_cast(&networking())->setEnabled(QtDebugMsg, false); - const_cast(&networking())->setEnabled(QtInfoMsg, false); - const_cast(&networking())->setEnabled(QtWarningMsg, false); - - const_cast(&shared())->setEnabled(QtDebugMsg, false); - const_cast(&shared())->setEnabled(QtInfoMsg, false); - const_cast(&shared())->setEnabled(QtWarningMsg, false); - } - - - QStringList filenames = parser.positionalArguments(); - if (filenames.empty() || filenames.size() > 2) { - qDebug() << "give remote url and optional local filename as arguments"; - parser.showHelp(); - Q_UNREACHABLE(); - } - - _url = QUrl(filenames[0]); - if (_url.scheme() != "atp") { - qDebug() << "url should start with atp:"; - parser.showHelp(); - Q_UNREACHABLE(); - } - - if (filenames.size() == 2) { - _localOutputFile = filenames[1]; - } - - QString domainServerAddress = "127.0.0.1:40103"; - if (parser.isSet(domainAddressOption)) { - domainServerAddress = parser.value(domainAddressOption); - } - - if (_verbose) { - qDebug() << "domain-server address is" << domainServerAddress; - } - - int listenPort = INVALID_PORT; - if (parser.isSet(listenPortOption)) { - listenPort = parser.value(listenPortOption).toInt(); - } - - Setting::init(); - DependencyManager::registerInheritance(); - - DependencyManager::set([&]{ return QString("Mozilla/5.0 (HighFidelityATPGet)"); }); - DependencyManager::set(); - DependencyManager::set(NodeType::Agent, listenPort); - - - auto nodeList = DependencyManager::get(); - nodeList->startThread(); - - // setup a timer for domain-server check ins - QTimer* domainCheckInTimer = new QTimer(nodeList.data()); - connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn); - domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); - - const DomainHandler& domainHandler = nodeList->getDomainHandler(); - - connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); - // connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain())); - // connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails())); - connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ATPGetApp::domainConnectionRefused); - - connect(nodeList.data(), &NodeList::nodeAdded, this, &ATPGetApp::nodeAdded); - connect(nodeList.data(), &NodeList::nodeKilled, this, &ATPGetApp::nodeKilled); - connect(nodeList.data(), &NodeList::nodeActivated, this, &ATPGetApp::nodeActivated); - // connect(nodeList.data(), &NodeList::uuidChanged, getMyAvatar(), &MyAvatar::setSessionUUID); - // connect(nodeList.data(), &NodeList::uuidChanged, this, &ATPGetApp::setSessionUUID); - connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &ATPGetApp::notifyPacketVersionMismatch); - - nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer - << NodeType::EntityServer << NodeType::AssetServer << NodeType::MessagesMixer); - - DependencyManager::get()->handleLookupString(domainServerAddress, false); - - auto assetClient = DependencyManager::set(); - assetClient->init(); - - QTimer* doTimer = new QTimer(this); - doTimer->setSingleShot(true); - connect(doTimer, &QTimer::timeout, this, &ATPGetApp::timedOut); - doTimer->start(4000); -} - -ATPGetApp::~ATPGetApp() { -} - - -void ATPGetApp::domainConnectionRefused(const QString& reasonMessage, int reasonCodeInt, const QString& extraInfo) { - qDebug() << "domainConnectionRefused"; -} - -void ATPGetApp::domainChanged(const QString& domainHostname) { - if (_verbose) { - qDebug() << "domainChanged"; - } -} - -void ATPGetApp::nodeAdded(SharedNodePointer node) { - if (_verbose) { - qDebug() << "node added: " << node->getType(); - } -} - -void ATPGetApp::nodeActivated(SharedNodePointer node) { - if (node->getType() == NodeType::AssetServer) { - lookup(); - } -} - -void ATPGetApp::nodeKilled(SharedNodePointer node) { - qDebug() << "nodeKilled"; -} - -void ATPGetApp::timedOut() { - finish(1); -} - -void ATPGetApp::notifyPacketVersionMismatch() { - if (_verbose) { - qDebug() << "packet version mismatch"; - } - finish(1); -} - -void ATPGetApp::lookup() { - - auto path = _url.path(); - qDebug() << "path is " << path; - - auto request = DependencyManager::get()->createGetMappingRequest(path); - QObject::connect(request, &GetMappingRequest::finished, this, [=](GetMappingRequest* request) mutable { - auto result = request->getError(); - if (result == GetMappingRequest::NotFound) { - qDebug() << "not found"; - } else if (result == GetMappingRequest::NoError) { - qDebug() << "found, hash is " << request->getHash(); - download(request->getHash()); - } else { - qDebug() << "error -- " << request->getError() << " -- " << request->getErrorString(); - } - request->deleteLater(); - }); - request->start(); -} - -void ATPGetApp::download(AssetHash hash) { - auto assetClient = DependencyManager::get(); - auto assetRequest = new AssetRequest(hash); - - connect(assetRequest, &AssetRequest::finished, this, [this](AssetRequest* request) mutable { - Q_ASSERT(request->getState() == AssetRequest::Finished); - - if (request->getError() == AssetRequest::Error::NoError) { - QString data = QString::fromUtf8(request->getData()); - if (_localOutputFile == "") { - QTextStream cout(stdout); - cout << data; - } else { - QFile outputHandle(_localOutputFile); - if (outputHandle.open(QIODevice::ReadWrite)) { - QTextStream stream( &outputHandle ); - stream << data; - } else { - qDebug() << "couldn't open output file:" << _localOutputFile; - } - } - } - - request->deleteLater(); - finish(0); - }); - - assetRequest->start(); -} - -void ATPGetApp::finish(int exitCode) { - auto nodeList = DependencyManager::get(); - - // send the domain a disconnect packet, force stoppage of domain-server check-ins - nodeList->getDomainHandler().disconnect(); - nodeList->setIsShuttingDown(true); - - // tell the packet receiver we're shutting down, so it can drop packets - nodeList->getPacketReceiver().setShouldDropPackets(true); - - // remove the NodeList from the DependencyManager - DependencyManager::destroy(); - - QCoreApplication::exit(exitCode); -} diff --git a/tools/oven/src/FBXBaker.cpp b/tools/oven/src/FBXBaker.cpp index 8a72784d7c..0259a6baf8 100644 --- a/tools/oven/src/FBXBaker.cpp +++ b/tools/oven/src/FBXBaker.cpp @@ -354,8 +354,8 @@ void FBXBaker::rewriteAndBakeSceneTextures() { FbxFileTexture* fileTexture = property.GetSrcObject(j); // use QFileInfo to easily split up the existing texture filename into its components - QString fbxFileName { fileTexture->GetFileName() }; - QFileInfo textureFileInfo { fbxFileName.replace("\\", "/") }; + QString fbxTextureFileName { fileTexture->GetFileName() }; + QFileInfo textureFileInfo { fbxTextureFileName.replace("\\", "/") }; // make sure this texture points to something and isn't one we've already re-mapped if (!textureFileInfo.filePath().isEmpty() @@ -372,6 +372,9 @@ void FBXBaker::rewriteAndBakeSceneTextures() { qCDebug(model_baking).noquote() << "Re-mapping" << fileTexture->GetFileName() << "to" << bakedTextureFilePath; + // figure out the URL to this texture, embedded or external + auto urlToTexture = getTextureURL(textureFileInfo, fileTexture); + // write the new filename into the FBX scene fileTexture->SetFileName(bakedTextureFilePath.toLocal8Bit()); @@ -379,9 +382,6 @@ void FBXBaker::rewriteAndBakeSceneTextures() { // be right beside the FBX fileTexture->SetRelativeFileName(bakedTextureFileName.toLocal8Bit().constData()); - // figure out the URL to this texture, embedded or external - auto urlToTexture = getTextureURL(textureFileInfo, fileTexture); - if (!_bakingTextures.contains(urlToTexture)) { // bake this texture asynchronously bakeTexture(urlToTexture, textureType, _uniqueOutputPath + BAKED_OUTPUT_SUBFOLDER);