From b969a9b1e02689ea692d587529462a9f0f6ee972 Mon Sep 17 00:00:00 2001 From: dante ruiz Date: Tue, 10 Sep 2019 16:50:50 -0700 Subject: [PATCH 01/40] fix tablet html loading errors --- .../ui/src/ui/TabletScriptingInterface.cpp | 60 ++++++++++++++++++- .../ui/src/ui/TabletScriptingInterface.h | 4 ++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index c54f63690d..6c57314367 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -795,11 +795,25 @@ void TabletProxy::loadWebScreenOnTop(const QVariant& url) { } void TabletProxy::loadWebScreenOnTop(const QVariant& url, const QString& injectJavaScriptUrl) { + bool localSafeContext = hifi::scripting::isLocalAccessSafeThread(); if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "loadWebScreenOnTop", Q_ARG(QVariant, url), Q_ARG(QString, injectJavaScriptUrl)); + QMetaObject::invokeMethod(this, "loadHTMLSourceImpl", Q_ARG(QVariant, url), Q_ARG(QString, injectJavaScriptUrl), Q_ARG(bool, localSafeContext)); return; } + loadHTMLSourceImpl(url, injectJavaScriptUrl, localSafeContext); +} + + + +void TabletProxy::loadHTMLSourceImpl(const QVariant& url, const QString& injectJavaScriptUrl, bool localSafeContext) { + if (QThread::currentThread() != thread()) { + qCWarning(uiLogging) << __FUNCTION__ << "may not be called directly by scripts"; + return; + + } + + QObject* root = nullptr; if (!_toolbarMode && _qmlTabletRoot) { root = _qmlTabletRoot; @@ -808,22 +822,59 @@ void TabletProxy::loadWebScreenOnTop(const QVariant& url, const QString& injectJ } if (root) { + if (localSafeContext) { + hifi::scripting::setLocalAccessSafeThread(true); + } QMetaObject::invokeMethod(root, "loadQMLOnTop", Q_ARG(const QVariant&, QVariant(WEB_VIEW_SOURCE_URL))); QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true))); if (_toolbarMode && _desktopWindow) { QMetaObject::invokeMethod(root, "setResizable", Q_ARG(const QVariant&, QVariant(false))); } QMetaObject::invokeMethod(root, "loadWebOnTop", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectJavaScriptUrl))); + hifi::scripting::setLocalAccessSafeThread(false); } _state = State::Web; + /*QObject* root = nullptr; + if (!_toolbarMode && _qmlTabletRoot) { + root = _qmlTabletRoot; + } else if (_toolbarMode && _desktopWindow) { + root = _desktopWindow->asQuickItem(); + } + + if (root) { + // BUGZ-1398: tablet access to local HTML files from client scripts + // Here we TEMPORARILY mark the main thread as allowed to load local file content, + // because the thread that originally made the call is so marked. + if (localSafeContext) { + hifi::scripting::setLocalAccessSafeThread(true); + } + QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, path)); + hifi::scripting::setLocalAccessSafeThread(false); + _state = State::QML; + _currentPathLoaded = path; + QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true))); + if (_toolbarMode && _desktopWindow) { + QMetaObject::invokeMethod(root, "setResizable", Q_ARG(const QVariant&, QVariant(resizable))); + } + + } else { + qCDebug(uiLogging) << "tablet cannot load QML because _qmlTabletRoot is null"; + }*/ } void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaScriptUrl, bool loadOtherBase) { + bool localSafeContext = hifi::scripting::isLocalAccessSafeThread(); if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "gotoWebScreen", Q_ARG(QString, url), Q_ARG(QString, injectedJavaScriptUrl), Q_ARG(bool, loadOtherBase)); + QMetaObject::invokeMethod(this, "loadHTMLSourceImpl", Q_ARG(QString, url), Q_ARG(QString, injectedJavaScriptUrl), Q_ARG(bool, loadOtherBase), Q_ARG(bool, localSafeContext)); return; } + + loadHTMLSourceImpl(url, injectedJavaScriptUrl, loadOtherBase, localSafeContext); +} + +void TabletProxy::loadHTMLSourceImpl(const QString& url, const QString& injectedJavaScriptUrl, bool loadOtherBase, bool localSafeContext) { + QObject* root = nullptr; if (!_toolbarMode && _qmlTabletRoot) { root = _qmlTabletRoot; @@ -832,6 +883,9 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS } if (root) { + if (localSafeContext) { + hifi::scripting::setLocalAccessSafeThread(true); + } if (loadOtherBase) { QMetaObject::invokeMethod(root, "loadTabletWebBase", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); } else { @@ -841,6 +895,8 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS if (_toolbarMode && _desktopWindow) { QMetaObject::invokeMethod(root, "setResizable", Q_ARG(const QVariant&, QVariant(false))); } + + hifi::scripting::setLocalAccessSafeThread(false); _state = State::Web; _currentPathLoaded = QVariant(url); } else { diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index ba02ba25b0..9a5ff9efac 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -298,6 +298,10 @@ public: */ Q_INVOKABLE void loadQMLSourceImpl(const QVariant& path, bool resizable, bool localSafeContext); + Q_INVOKABLE void loadHTMLSourceImpl(const QVariant& url, const QString& injectJavaScriptUrl, bool localSafeContext); + + Q_INVOKABLE void loadHTMLSourceImpl(const QString& url, const QString& injectedJavaScriptUrl, bool loadOtherBase, bool localSafeContext); + // FIXME: This currently relies on a script initializing the tablet (hence the bool denoting success); // it should be initialized internally so it cannot fail From a0ad1f3a68173f012e5f0b4e70a0d3266cde8c7c Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Tue, 10 Sep 2019 17:25:35 -0700 Subject: [PATCH 02/40] DEV-444 - OAuth administration improvements --- .../resources/describe-settings.json | 110 +++++++++++- .../resources/web/js/base-settings.js | 23 ++- .../resources/web/js/domain-server.js | 1 + .../resources/web/settings/js/settings.js | 119 +++++++++++- domain-server/src/DomainServer.cpp | 59 +++--- domain-server/src/DomainServer.h | 1 + .../src/DomainServerSettingsManager.cpp | 170 +++++++++++++++++- 7 files changed, 446 insertions(+), 37 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 9cb4c2cab9..b854955953 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1,5 +1,5 @@ { - "version": 2.3, + "version": 2.4, "settings": [ { "name": "metaverse", @@ -1705,6 +1705,114 @@ } ] }, + { + "name": "oauth", + "label": "OAuth", + "show_on_enable": true, + "settings": [ + { + "name": "enable", + "type": "checkbox", + "default": false, + "hidden": true + }, + { + "name": "admin-users", + "label": "Admin Users", + "type": "table", + "can_add_new_rows": true, + "help": "Any of these users can administer the domain.", + "numbered": false, + "backup": false, + "advanced": false, + "columns": [ + { + "name": "username", + "label": "Username", + "can_set": true + } + ] + }, + { + "name": "admin-roles", + "label": "Admin Roles", + "type": "table", + "can_add_new_rows": true, + "help": "Any user with any of these metaverse roles can administer the domain.", + "numbered": false, + "backup": false, + "advanced": true, + "columns": [ + { + "name": "role", + "label": "Role", + "can_set": true + } + ] + }, + { + "name": "client-id", + "label": "Client ID", + "help": "OAuth client ID.", + "default": "", + "advanced": true, + "backup": false + }, + { + "name": "client-secret", + "label": "Client Secret", + "help": "OAuth client secret.", + "type": "password", + "password_placeholder": "******", + "value-hidden": true, + "advanced": true, + "backup": false + }, + { + "name": "provider", + "label": "Provider", + "help": "OAuth provider URL.", + "default": "https://metaverse.highfidelity.com", + "advanced": true, + "backup": false + }, + { + "name": "hostname", + "label": "Hostname", + "help": "OAuth hostname.", + "default": "", + "advanced": true, + "backup": false + }, + { + "name": "key-passphrase", + "label": "SSL Private Key Passphrase", + "help": "SSL Private Key Passphrase", + "type": "password", + "password_placeholder": "******", + "value-hidden": true, + "advanced": true, + "backup": false + }, + { + "name": "cert-fingerprint", + "type": "hidden", + "readonly": true, + "advanced": true, + "backup": false + }, + { + "name": "cert", + "advanced": true, + "backup": false + }, + { + "name": "key", + "advanced": true, + "backup": false + } + ] + }, { "name": "automatic_content_archives", "label": "Automatic Content Archives", diff --git a/domain-server/resources/web/js/base-settings.js b/domain-server/resources/web/js/base-settings.js index bd96f636a8..295013878c 100644 --- a/domain-server/resources/web/js/base-settings.js +++ b/domain-server/resources/web/js/base-settings.js @@ -2,6 +2,9 @@ var DomainInfo = null; var viewHelpers = { getFormGroup: function(keypath, setting, values, isAdvanced) { + if (setting.hidden) { + return ""; + } form_group = "
" } - - form_group += "" + setting.help + "" + if (setting.help) { + form_group += "" + setting.help + "" + } } } @@ -114,12 +118,17 @@ function reloadSettings(callback) { data.descriptions.push(Settings.extraGroupsAtEnd[endGroupIndex]); } + data.descriptions = data.descriptions.map(function(x) { + x.hidden = x.hidden || (x.show_on_enable && data.values[x.name] && !data.values[x.name].enable); + return x; + }); + $('#panels').html(Settings.panelsTemplate(data)); Settings.data = data; Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true); - Settings.afterReloadActions(); + Settings.afterReloadActions(data); // setup any bootstrap switches $('.toggle-checkbox').bootstrapSwitch(); @@ -129,10 +138,14 @@ function reloadSettings(callback) { Settings.pendingChanges = 0; // call the callback now that settings are loaded - callback(true); + if (callback) { + callback(true); + } }).fail(function() { // call the failure object since settings load faild - callback(false) + if (callback) { + callback(false); + } }); } diff --git a/domain-server/resources/web/js/domain-server.js b/domain-server/resources/web/js/domain-server.js index a8b7267b88..9524b18caf 100644 --- a/domain-server/resources/web/js/domain-server.js +++ b/domain-server/resources/web/js/domain-server.js @@ -91,6 +91,7 @@ $(document).ready(function(){ // make a JSON request to get the dropdown menus for content and settings // we don't error handle here because the top level menu is still clickable and usables if this fails $.getJSON('/settings-menu-groups.json', function(data){ + function makeGroupDropdownElement(group, base) { var html_id = group.html_id ? group.html_id : group.name; return "
  • " + group.label + "
  • "; diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 08d0550841..fcf7700687 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -18,7 +18,19 @@ $(document).ready(function(){ Settings.extraGroupsAtIndex = Settings.extraDomainGroupsAtIndex; var METAVERSE_URL = URLs.METAVERSE_URL; - Settings.afterReloadActions = function() { + var SSL_PRIVATE_KEY_FILE_ID = 'ssl-private-key-file'; + var SSL_PRIVATE_KEY_CONTENTS_ID = 'key-contents'; + var SSL_PRIVATE_KEY_CONTENTS_NAME = 'oauth.key-contents'; + var SSL_CERT_UPLOAD_ID = 'ssl-cert-button'; + var SSL_CERT_FILE_ID = 'ssl-cert-file'; + var SSL_CERT_FINGERPRINT_ID = 'cert-fingerprint'; + var SSL_CERT_FINGERPRINT_SPAN_ID = 'cert-fingerprint-span-id'; + var SSL_CERT_CONTENTS_ID = 'cert-contents'; + var SSL_CERT_CONTENTS_NAME = 'oauth.cert-contents'; + var SSL_PRIVATE_KEY_PATH = 'oauth.key'; + var SSL_CERT_PATH = 'oauth.cert'; + + Settings.afterReloadActions = function(data) { getMetaverseUrl(function(metaverse_url) { METAVERSE_URL = metaverse_url; @@ -32,6 +44,8 @@ $(document).ready(function(){ setupDomainNetworkingSettings(); // setupDomainLabelSetting(); + setupSettingsOAuth(data); + setupSettingsBackup(); if (domainIDIsSet()) { @@ -124,6 +138,48 @@ $(document).ready(function(){ } } + if (formJSON["oauth"]) { + var private_key = formJSON["oauth"]["key-contents"]; + var cert = formJSON["oauth"]["cert-contents"]; + var oauthErrors = ""; + if (private_key != undefined) { + var pattern = /-+BEGIN PRIVATE KEY-+[A-Za-z0-9+/\n=]*-+END PRIVATE KEY-+/m; + if (!pattern.test(private_key)) { + oauthErrors = "Private key must be in PEM format"; + } + } + if (cert != undefined) { + var pattern = /-+BEGIN CERTIFICATE-+[A-Za-z0-9+/\n=]*-+END CERTIFICATE-+/m; + if (!pattern.test(cert)) { + oauthErrors = "Certificate must be in PEM format"; + } + } + if ($('#oauth.panel').length) { + if (!$('input[name="oauth.client-id"]').val()) { + oauthErrors += "OAuth requires a client Id.
    "; + } + if (!$('input[name="oauth.provider"]').val()) { + oauthErrors += "OAuth requires a provider.
    "; + } + if (!$('input[name="oauth.hostname"]').val()) { + oauthErrors += "OAuth requires a hostname.
    "; + } + if (!$('input[name="' + SSL_PRIVATE_KEY_PATH + '"]').val() && !$('input[name="' + SSL_PRIVATE_KEY_CONTENTS_NAME + '"]').val()) { + oauthErrors += "OAuth requires an SSL Private Key.
    "; + } + if (!$('input[name="' + SSL_CERT_PATH + '"]').val() && !$('input[name="' + SSL_CERT_CONTENTS_NAME + '"]').val()) { + oauthErrors += "OAuth requires an SSL Certificate.
    "; + } + if (!$("table[name='oauth.admin-users'] tr.value-row").length && + !$("table[name='oauth.admin-roles'] tr.value-row").length) { + oauthErrors += "OAuth must have at least one admin user or admin role.
    "; + } + } + if (oauthErrors) { + bootbox.alert({ "message": oauthErrors, "title": "OAuth Configuration Error" }); + return false; + } + } postSettings(formJSON); }; @@ -1035,6 +1091,67 @@ $(document).ready(function(){ }); } + function setupSettingsOAuth(data) { + // construct the HTML needed for the settings backup panel + var html = "
    "; + html += "
    "; + html += ""; + html += ""; + html += ""; + html += "
    "; + html += "
    "; + html += ""; + html += "
    Fingerprint:" + data.values.oauth["cert-fingerprint"] + "
    "; + html += ""; + html += ""; + html += ""; + html += "
    "; + + $('#oauth-advanced').append(html); + + $('#key-path-label').after($('[data-keypath="' + SSL_PRIVATE_KEY_PATH + '"]')); + $('#cert-path-label').after($('[data-keypath="' + SSL_CERT_PATH + '"]')); + $('[name="' + SSL_PRIVATE_KEY_PATH + '"]').val(data.values.oauth.key); + $('[name="' + SSL_CERT_PATH + '"]').val(data.values.oauth.cert); + + $('body').on('change input propertychange', '#' + SSL_PRIVATE_KEY_FILE_ID, function(e){ + var f = e.target.files[0]; + var reader = new FileReader(); + reader.onload = function(e) { + $('#' + SSL_PRIVATE_KEY_CONTENTS_ID).val(reader.result); + $('#' + SSL_PRIVATE_KEY_CONTENTS_ID).attr('data-changed', true); + $('[name="' + SSL_PRIVATE_KEY_PATH + '"]').val(''); + badgeForDifferences($('#' + SSL_PRIVATE_KEY_CONTENTS_ID)); + } + reader.readAsText(f); + }); + $('body').on('change input propertychange', '#' + SSL_CERT_FILE_ID, function(e){ + var f = e.target.files[0]; + var reader = new FileReader(); + reader.onload = function(e) { + $('#' + SSL_CERT_CONTENTS_ID).val(reader.result); + $('#' + SSL_CERT_CONTENTS_ID).attr('data-changed', true); + $('[name="' + SSL_CERT_PATH + '"]').val(''); + $('#' + SSL_CERT_FINGERPRINT_SPAN_ID).text(''); + badgeForDifferences($('#' + SSL_CERT_CONTENTS_ID)); + } + reader.readAsText(f); + }); + + $('body').on('change input propertychange', '[name="' + SSL_PRIVATE_KEY_PATH + '"]', function(e){ + $('#' + SSL_PRIVATE_KEY_FILE_ID).val(''); + $('#' + SSL_PRIVATE_KEY_CONTENTS_ID).val(''); + badgeForDifferences($('[name="' + SSL_PRIVATE_KEY_PATH + '"]').attr('data-changed', true)); + }); + + $('body').on('change input propertychange', '[name="' + SSL_CERT_PATH + '"]', function(e){ + $('#' + SSL_CERT_FILE_ID).val(''); + $('#' + SSL_CERT_CONTENTS_ID).val(''); + $('#' + SSL_CERT_FINGERPRINT_SPAN_ID).text(''); + badgeForDifferences($('[name="' + SSL_CERT_PATH + '"]').attr('data-changed', true)); + }); + } + var RESTORE_SETTINGS_UPLOAD_ID = 'restore-settings-button'; var RESTORE_SETTINGS_FILE_ID = 'restore-settings-file'; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 74ad014b53..7f6c366bc3 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -226,9 +226,10 @@ DomainServer::DomainServer(int argc, char* argv[]) : setupGroupCacheRefresh(); - // if we were given a certificate/private key or oauth credentials they must succeed - if (!(optionallyReadX509KeyAndCertificate() && optionallySetupOAuth())) { - return; + optionallySetupOAuth(); + + if (_oauthEnable) { + _oauthEnable = optionallyReadX509KeyAndCertificate(); } _settingsManager.apiRefreshGroupInformation(); @@ -447,8 +448,9 @@ QUuid DomainServer::getID() { } bool DomainServer::optionallyReadX509KeyAndCertificate() { - const QString X509_CERTIFICATE_OPTION = "cert"; - const QString X509_PRIVATE_KEY_OPTION = "key"; + const QString X509_CERTIFICATE_OPTION = "oauth.cert"; + const QString X509_PRIVATE_KEY_OPTION = "oauth.key"; + const QString X509_PRIVATE_KEY_PASSPHRASE_OPTION = "oauth.key-passphrase"; const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE"; QString certPath = _settingsManager.valueForKeyPath(X509_CERTIFICATE_OPTION).toString(); @@ -459,7 +461,12 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() { // this is used for Oauth callbacks when authorizing users against a data server // let's make sure we can load the key and certificate - QString keyPassphraseString = QProcessEnvironment::systemEnvironment().value(X509_KEY_PASSPHRASE_ENV); + QString keyPassphraseEnv = QProcessEnvironment::systemEnvironment().value(X509_KEY_PASSPHRASE_ENV); + QString keyPassphraseString = _settingsManager.valueForKeyPath(X509_PRIVATE_KEY_PASSPHRASE_OPTION).toString(); + + if (!keyPassphraseEnv.isEmpty()) { + keyPassphraseString = keyPassphraseEnv; + } qDebug() << "Reading certificate file at" << certPath << "for HTTPS."; qDebug() << "Reading key file at" << keyPath << "for HTTPS."; @@ -473,16 +480,15 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() { QSslCertificate sslCertificate(&certFile); QSslKey privateKey(&keyFile, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, keyPassphraseString.toUtf8()); + if (privateKey.isNull()) { + qCritical() << "SSL Private Key Not Loading. Bad password or key format?"; + } + _httpsManager.reset(new HTTPSManager(QHostAddress::AnyIPv4, DOMAIN_SERVER_HTTPS_PORT, sslCertificate, privateKey, QString(), this)); qDebug() << "TCP server listening for HTTPS connections on" << DOMAIN_SERVER_HTTPS_PORT; } else if (!certPath.isEmpty() || !keyPath.isEmpty()) { - static const QString MISSING_CERT_ERROR_MSG = "Missing certificate or private key. domain-server will now quit."; - static const int MISSING_CERT_ERROR_CODE = 3; - - QMetaObject::invokeMethod(this, "queuedQuit", Qt::QueuedConnection, - Q_ARG(QString, MISSING_CERT_ERROR_MSG), Q_ARG(int, MISSING_CERT_ERROR_CODE)); return false; } @@ -490,10 +496,12 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() { } bool DomainServer::optionallySetupOAuth() { - const QString OAUTH_PROVIDER_URL_OPTION = "oauth-provider"; - const QString OAUTH_CLIENT_ID_OPTION = "oauth-client-id"; + const QString OAUTH_ENABLE_OPTION = "oauth.enable"; + const QString OAUTH_PROVIDER_URL_OPTION = "oauth.provider"; + const QString OAUTH_CLIENT_ID_OPTION = "oauth.client-id"; const QString OAUTH_CLIENT_SECRET_ENV = "DOMAIN_SERVER_CLIENT_SECRET"; - const QString REDIRECT_HOSTNAME_OPTION = "hostname"; + const QString OAUTH_CLIENT_SECRET_OPTION = "oauth.client-secret"; + const QString REDIRECT_HOSTNAME_OPTION = "oauth.hostname"; _oauthProviderURL = QUrl(_settingsManager.valueForKeyPath(OAUTH_PROVIDER_URL_OPTION).toString()); @@ -502,22 +510,24 @@ bool DomainServer::optionallySetupOAuth() { _oauthProviderURL = NetworkingConstants::METAVERSE_SERVER_URL(); } + _oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV); + if (_oauthClientSecret.isEmpty()) { + _oauthClientSecret = _settingsManager.valueForKeyPath(OAUTH_CLIENT_SECRET_OPTION).toString(); + } auto accountManager = DependencyManager::get(); accountManager->setAuthURL(_oauthProviderURL); _oauthClientID = _settingsManager.valueForKeyPath(OAUTH_CLIENT_ID_OPTION).toString(); - _oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV); _hostname = _settingsManager.valueForKeyPath(REDIRECT_HOSTNAME_OPTION).toString(); - if (!_oauthClientID.isEmpty()) { + _oauthEnable = _settingsManager.valueForKeyPath(OAUTH_ENABLE_OPTION).toBool(); + + if (_oauthEnable) { if (_oauthProviderURL.isEmpty() || _hostname.isEmpty() || _oauthClientID.isEmpty() || _oauthClientSecret.isEmpty()) { - static const QString MISSING_OAUTH_INFO_MSG = "Missing OAuth provider URL, hostname, client ID, or client secret. domain-server will now quit."; - static const int MISSING_OAUTH_INFO_ERROR_CODE = 4; - QMetaObject::invokeMethod(this, "queuedQuit", Qt::QueuedConnection, - Q_ARG(QString, MISSING_OAUTH_INFO_MSG), Q_ARG(int, MISSING_OAUTH_INFO_ERROR_CODE)); + _oauthEnable = false; return false; } else { qDebug() << "OAuth will be used to identify clients using provider at" << _oauthProviderURL.toString(); @@ -2693,8 +2703,8 @@ void DomainServer::profileRequestFinished() { std::pair DomainServer::isAuthenticatedRequest(HTTPConnection* connection) { static const QByteArray HTTP_COOKIE_HEADER_KEY = "Cookie"; - static const QString ADMIN_USERS_CONFIG_KEY = "admin-users"; - static const QString ADMIN_ROLES_CONFIG_KEY = "admin-roles"; + static const QString ADMIN_USERS_CONFIG_KEY = "oauth.admin-users"; + static const QString ADMIN_ROLES_CONFIG_KEY = "oauth.admin-roles"; static const QString BASIC_AUTH_USERNAME_KEY_PATH = "security.http_username"; static const QString BASIC_AUTH_PASSWORD_KEY_PATH = "security.http_password"; const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)"; @@ -2704,12 +2714,11 @@ std::pair DomainServer::isAuthenticatedRequest(HTTPConnection* c QVariant adminUsersVariant = _settingsManager.valueForKeyPath(ADMIN_USERS_CONFIG_KEY); QVariant adminRolesVariant = _settingsManager.valueForKeyPath(ADMIN_ROLES_CONFIG_KEY); - if (!_oauthProviderURL.isEmpty() - && (adminUsersVariant.isValid() || adminRolesVariant.isValid())) { + if (_oauthEnable) { QString cookieString = connection->requestHeader(HTTP_COOKIE_HEADER_KEY); QRegExp cookieUUIDRegex(COOKIE_UUID_REGEX_STRING); - + QUuid cookieUUID; if (cookieString.indexOf(cookieUUIDRegex) != -1) { cookieUUID = cookieUUIDRegex.cap(1); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 02362abd7b..5e8eee53fe 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -236,6 +236,7 @@ private: bool _isUsingDTLS { false }; + bool _oauthEnable { false }; QUrl _oauthProviderURL; QString _oauthClientID; QString _oauthClientSecret; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 17d473f02c..2e6ccf8be2 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -22,7 +22,9 @@ #include #include #include +#include #include +#include #include #include @@ -46,10 +48,14 @@ const QString DESCRIPTION_SETTINGS_KEY = "settings"; const QString SETTING_DEFAULT_KEY = "default"; const QString DESCRIPTION_NAME_KEY = "name"; const QString DESCRIPTION_GROUP_LABEL_KEY = "label"; +const QString DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY = "show_on_enable"; +const QString DESCRIPTION_ENABLE_KEY = "enable"; const QString DESCRIPTION_BACKUP_FLAG_KEY = "backup"; const QString SETTING_DESCRIPTION_TYPE_KEY = "type"; const QString DESCRIPTION_COLUMNS_KEY = "columns"; const QString CONTENT_SETTING_FLAG_KEY = "content_setting"; +static const QString SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY = "domain_settings"; +static const QString SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY = "content_settings"; const QString SETTINGS_VIEWPOINT_KEY = "viewpoint"; @@ -136,6 +142,10 @@ void DomainServerSettingsManager::splitSettingsDescription() { settingsDropdownGroup[DESCRIPTION_GROUP_LABEL_KEY] = groupObject[DESCRIPTION_GROUP_LABEL_KEY]; + if (groupObject.contains(DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY)) { + settingsDropdownGroup[DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY] = groupObject[DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY]; + } + static const QString DESCRIPTION_GROUP_HTML_ID_KEY = "html_id"; if (groupObject.contains(DESCRIPTION_GROUP_HTML_ID_KEY)) { settingsDropdownGroup[DESCRIPTION_GROUP_HTML_ID_KEY] = groupObject[DESCRIPTION_GROUP_HTML_ID_KEY]; @@ -170,9 +180,6 @@ void DomainServerSettingsManager::splitSettingsDescription() { // populate the settings menu groups with what we've collected - static const QString SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY = "domain_settings"; - static const QString SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY = "content_settings"; - _settingsMenuGroups[SPLIT_MENU_GROUPS_DOMAIN_SETTINGS_KEY] = domainSettingsMenuGroups; _settingsMenuGroups[SPLIT_MENU_GROUPS_CONTENT_SETTINGS_KEY] = contentSettingsMenuGroups; } @@ -448,6 +455,77 @@ void DomainServerSettingsManager::setupConfigMap(const QString& userConfigFilena packPermissions(); } + if (oldVersion < 2.4) { + // migrate oauth settings to their own group + const QString ADMIN_USERS = "admin-users"; + const QString OAUTH_ADMIN_USERS = "oauth.admin-users"; + const QString OAUTH_CLIENT_ID = "oauth.client-id"; + const QString ALT_ADMIN_USERS = "admin.users"; + const QString ADMIN_ROLES = "admin-roles"; + const QString OAUTH_ADMIN_ROLES = "oauth.admin-roles"; + const QString OAUTH_ENABLE = "oauth.enable"; + + QVector > conversionMap = { + {"key", "oauth.key"}, + {"cert", "oauth.cert"}, + {"hostname", "oauth.hostname"}, + {"oauth-client-id", "oauth.client-id"}, + {"oauth-provider", "oauth.provider"} + }; + + for (auto & conversion : conversionMap) { + QVariant* prevValue = _configMap.valueForKeyPath(conversion.first); + if (prevValue) { + auto newValue = _configMap.valueForKeyPath(conversion.second, true); + *newValue = *prevValue; + } + } + + QVariant* client_id = _configMap.valueForKeyPath(OAUTH_CLIENT_ID); + if (client_id) { + QVariant* oauthEnable = _configMap.valueForKeyPath(OAUTH_ENABLE, true); + + *oauthEnable = QVariant(true); + } + + QVariant* oldAdminUsers = _configMap.valueForKeyPath(ADMIN_USERS); + QVariant* newAdminUsers = _configMap.valueForKeyPath(OAUTH_ADMIN_USERS, true); + QVariantList adminUsers(newAdminUsers->toList()); + if (oldAdminUsers) { + QStringList adminUsersList = oldAdminUsers->toStringList(); + for (auto & user : adminUsersList) { + if (!adminUsers.contains(user)) { + adminUsers.append(user); + } + } + } + QVariant* altAdminUsers = _configMap.valueForKeyPath(ALT_ADMIN_USERS); + if (altAdminUsers) { + QStringList adminUsersList = altAdminUsers->toStringList(); + for (auto & user : adminUsersList) { + if (!adminUsers.contains(user)) { + adminUsers.append(user); + } + } + } + + *newAdminUsers = adminUsers; + + QVariant* oldAdminRoles = _configMap.valueForKeyPath(ADMIN_ROLES); + QVariant* newAdminRoles = _configMap.valueForKeyPath(OAUTH_ADMIN_ROLES, true); + QVariantList adminRoles(newAdminRoles->toList()); + if (oldAdminRoles) { + QStringList adminRoleList = oldAdminRoles->toStringList(); + for (auto & role : adminRoleList) { + if (!adminRoles.contains(role)) { + adminRoles.append(role); + } + } + } + + *newAdminRoles = adminRoles; + } + // write the current description version to our settings *versionVariant = _descriptionVersion; @@ -1185,7 +1263,23 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection return true; } else if (url.path() == SETTINGS_MENU_GROUPS_PATH) { - connection->respond(HTTPConnection::StatusCode200, QJsonDocument(_settingsMenuGroups).toJson(), "application/json"); + + QJsonObject settings; + for (auto & key : _settingsMenuGroups.keys()) { + const QJsonArray& settingGroups = _settingsMenuGroups[key].toArray(); + QJsonArray groups; + foreach (const QJsonValue& group, settingGroups) { + QJsonObject groupObject = group.toObject(); + if (!groupObject.contains(DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY) + || (groupObject[DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY].toBool() + && _configMap.valueForKeyPath(groupObject[DESCRIPTION_NAME_KEY].toString() + "." + DESCRIPTION_ENABLE_KEY)->toBool() )) { + groups.append(groupObject); + } + } + settings[key] = groups; + } + + connection->respond(HTTPConnection::StatusCode200, QJsonDocument(settings).toJson(), "application/json"); return true; } else if (url.path() == SETTINGS_BACKUP_PATH) { @@ -1440,12 +1534,35 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt } if (!groupKey.isEmpty() && !groupResponseObject.isEmpty()) { + // set this group's object to the constructed object responseObject[groupKey] = groupResponseObject; } } } + // add 'derived' values used primarily for UI + + const QString X509_CERTIFICATE_OPTION = "oauth.cert"; + + QString certPath = valueForKeyPath(X509_CERTIFICATE_OPTION).toString(); + if (!certPath.isEmpty()) { + // the user wants to use the following cert and key for HTTPS + // this is used for Oauth callbacks when authorizing users against a data server + // let's make sure we can load the key and certificate + + qDebug() << "Reading certificate file at" << certPath << "for HTTPS."; + + QFile certFile(certPath); + certFile.open(QIODevice::ReadOnly); + + QSslCertificate sslCertificate(&certFile); + QString digest = sslCertificate.digest().toHex(':'); + auto groupObject = responseObject["oauth"].toObject(); + groupObject["cert-fingerprint"] = digest; + responseObject["oauth"] = groupObject; + } + return responseObject; } @@ -1551,23 +1668,66 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson return QJsonObject(); } -bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, +bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedSettingsObject, SettingsType settingsType) { // take a write lock since we're about to overwrite settings in the config map QWriteLocker locker(&_settingsLock); + QJsonObject postedObject(postedSettingsObject); + 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 WIZARD_KEY = "wizard"; static const QString DESCRIPTION_ROOT_KEY = "descriptors"; + static const QString OAUTH_ROOT_KEY = "oauth"; + static const QString OAUTH_KEY_CONTENTS = "key-contents"; + static const QString OAUTH_CERT_CONTENTS = "cert-contents"; + static const QString OAUTH_CERT_PATH = "cert"; + static const QString OAUTH_KEY_PASSPHRASE = "key-passphrase"; + static const QString OAUTH_KEY_PATH = "key"; auto& settingsVariant = _configMap.getConfig(); bool needRestart = false; auto& filteredDescriptionArray = settingsType == DomainSettings ? _domainSettingsDescription : _contentSettingsDescription; + auto oauthObject = postedObject[OAUTH_ROOT_KEY].toObject(); + if (oauthObject.contains(OAUTH_CERT_CONTENTS)) { + QSslCertificate cert(oauthObject[OAUTH_CERT_CONTENTS].toString().toUtf8()); + if (!cert.isNull()) { + static const QString CERT_FILE_NAME = "certificate.crt"; + auto certPath = PathUtils::getAppDataFilePath(CERT_FILE_NAME); + QFile file(certPath); + if (file.open(QFile::WriteOnly)) { + file.write(cert.toPem()); + file.close(); + } + oauthObject[OAUTH_CERT_PATH] = certPath; + } + oauthObject.remove(OAUTH_CERT_CONTENTS); + } + if (oauthObject.contains(OAUTH_KEY_CONTENTS)) { + QString keyPassphraseString = oauthObject[OAUTH_KEY_PASSPHRASE].toString(); + QSslKey key(oauthObject[OAUTH_KEY_CONTENTS].toString().toUtf8(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey, keyPassphraseString.toUtf8()); + if (!key.isNull()) { + static const QString KEY_FILE_NAME = "certificate.key"; + auto keyPath = PathUtils::getAppDataFilePath(KEY_FILE_NAME); + QFile file(keyPath); + if (file.open(QFile::WriteOnly)) { + file.write(key.toPem()); + file.close(); + file.setPermissions(QFile::ReadOwner | QFile::WriteOwner); + } + oauthObject[OAUTH_KEY_PATH] = keyPath; + } + oauthObject.remove(OAUTH_KEY_CONTENTS); + } + + postedObject[OAUTH_ROOT_KEY] = oauthObject; + + qDebug() << postedObject; // Iterate on the setting groups foreach(const QString& rootKey, postedObject.keys()) { const QJsonValue& rootValue = postedObject[rootKey]; From 0f242deaf2e5a03a0c1651e8eb4384fb3ec8e117 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Wed, 11 Sep 2019 14:11:43 -0700 Subject: [PATCH 03/40] fix crash and some UI issues --- domain-server/resources/web/settings/js/settings.js | 4 ++-- domain-server/src/DomainServerSettingsManager.cpp | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index fcf7700687..136d5b0ebc 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -145,13 +145,13 @@ $(document).ready(function(){ if (private_key != undefined) { var pattern = /-+BEGIN PRIVATE KEY-+[A-Za-z0-9+/\n=]*-+END PRIVATE KEY-+/m; if (!pattern.test(private_key)) { - oauthErrors = "Private key must be in PEM format"; + oauthErrors += "Private key must be in PEM format
    "; } } if (cert != undefined) { var pattern = /-+BEGIN CERTIFICATE-+[A-Za-z0-9+/\n=]*-+END CERTIFICATE-+/m; if (!pattern.test(cert)) { - oauthErrors = "Certificate must be in PEM format"; + oauthErrors += "Certificate must be in PEM format
    "; } } if ($('#oauth.panel').length) { diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 2e6ccf8be2..cc0f02ecda 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1270,9 +1270,10 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection QJsonArray groups; foreach (const QJsonValue& group, settingGroups) { QJsonObject groupObject = group.toObject(); + QVariant* enableKey = _configMap.valueForKeyPath(groupObject[DESCRIPTION_NAME_KEY].toString() + "." + DESCRIPTION_ENABLE_KEY); + if (!groupObject.contains(DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY) - || (groupObject[DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY].toBool() - && _configMap.valueForKeyPath(groupObject[DESCRIPTION_NAME_KEY].toString() + "." + DESCRIPTION_ENABLE_KEY)->toBool() )) { + || (groupObject[DESCRIPTION_GROUP_SHOW_ON_ENABLE_KEY].toBool() && enableKey && enableKey->toBool() )) { groups.append(groupObject); } } From 639beee6cbdb7b4222aab08b182c62be36eab24f Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Wed, 11 Sep 2019 15:21:49 -0700 Subject: [PATCH 04/40] Fix logic for reliable service address-change when new add already used --- libraries/networking/src/udt/Socket.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 4c01517346..67af69ae8f 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -546,7 +546,6 @@ void Socket::handleStateChanged(QAbstractSocket::SocketState socketState) { void Socket::handleRemoteAddressChange(HifiSockAddr previousAddress, HifiSockAddr currentAddress) { { Lock connectionsLock(_connectionsHashMutex); - _connectionsHash.erase(currentAddress); const auto connectionIter = _connectionsHash.find(previousAddress); if (connectionIter != _connectionsHash.end()) { @@ -554,18 +553,16 @@ void Socket::handleRemoteAddressChange(HifiSockAddr previousAddress, HifiSockAdd _connectionsHash.erase(connectionIter); connection->setDestinationAddress(currentAddress); _connectionsHash[currentAddress] = move(connection); - } - } + connectionsLock.release(); - { - Lock sequenceNumbersLock(_unreliableSequenceNumbersMutex); - _unreliableSequenceNumbers.erase(currentAddress); + Lock sequenceNumbersLock(_unreliableSequenceNumbersMutex); + const auto sequenceNumbersIter = _unreliableSequenceNumbers.find(previousAddress); + if (sequenceNumbersIter != _unreliableSequenceNumbers.end()) { + auto sequenceNumbers = sequenceNumbersIter->second; + _unreliableSequenceNumbers.erase(sequenceNumbersIter); + _unreliableSequenceNumbers[currentAddress] = sequenceNumbers; + } - const auto sequenceNumbersIter = _unreliableSequenceNumbers.find(previousAddress); - if (sequenceNumbersIter != _unreliableSequenceNumbers.end()) { - auto sequenceNumbers = sequenceNumbersIter->second; - _unreliableSequenceNumbers.erase(sequenceNumbersIter); - _unreliableSequenceNumbers[currentAddress] = sequenceNumbers; } } } From 9a7de106142fe2f449985374451a4029a0a850d1 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Wed, 11 Sep 2019 16:27:00 -0700 Subject: [PATCH 05/40] set file permissions on the config.json file for the domain server --- domain-server/resources/web/js/domain-server.js | 1 - domain-server/src/DomainServer.cpp | 2 +- domain-server/src/DomainServerSettingsManager.cpp | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/domain-server/resources/web/js/domain-server.js b/domain-server/resources/web/js/domain-server.js index 9524b18caf..a8b7267b88 100644 --- a/domain-server/resources/web/js/domain-server.js +++ b/domain-server/resources/web/js/domain-server.js @@ -91,7 +91,6 @@ $(document).ready(function(){ // make a JSON request to get the dropdown menus for content and settings // we don't error handle here because the top level menu is still clickable and usables if this fails $.getJSON('/settings-menu-groups.json', function(data){ - function makeGroupDropdownElement(group, base) { var html_id = group.html_id ? group.html_id : group.name; return "
  • " + group.label + "
  • "; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 7f6c366bc3..307a43ee88 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2718,7 +2718,7 @@ std::pair DomainServer::isAuthenticatedRequest(HTTPConnection* c QString cookieString = connection->requestHeader(HTTP_COOKIE_HEADER_KEY); QRegExp cookieUUIDRegex(COOKIE_UUID_REGEX_STRING); - + QUuid cookieUUID; if (cookieString.indexOf(cookieUUIDRegex) != -1) { cookieUUID = cookieUUIDRegex.cap(1); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index cc0f02ecda..73d78a5c70 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1535,7 +1535,6 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt } if (!groupKey.isEmpty() && !groupResponseObject.isEmpty()) { - // set this group's object to the constructed object responseObject[groupKey] = groupResponseObject; } @@ -1728,7 +1727,6 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ postedObject[OAUTH_ROOT_KEY] = oauthObject; - qDebug() << postedObject; // Iterate on the setting groups foreach(const QString& rootKey, postedObject.keys()) { const QJsonValue& rootValue = postedObject[rootKey]; @@ -1913,6 +1911,8 @@ void DomainServerSettingsManager::persistToFile() { _configMap.loadConfig(); return; // defend against future code } + + QFile(settingsFilename).setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner); } QStringList DomainServerSettingsManager::getAllKnownGroupNames() { From 12b980d99dafc779243c3c646247b5c437cba7a6 Mon Sep 17 00:00:00 2001 From: milad Date: Wed, 11 Sep 2019 17:08:35 -0700 Subject: [PATCH 06/40] about to test if this new right click disable is working --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index fd82567a94..0279963259 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -800,7 +800,7 @@ QUuid EntityTreeRenderer::mousePressEvent(QMouseEvent* event) { RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); EntityItemPointer entity; if (rayPickResult.intersects && (entity = getTree()->findEntityByID(rayPickResult.entityID))) { - if (!EntityTree::areEntityClicksCaptured()) { + if (!EntityTree::areEntityClicksCaptured() && event->button() == Qt::MouseButton::LeftButton) { auto properties = entity->getProperties(); QString urlString = properties.getHref(); QUrl url = QUrl(urlString, QUrl::StrictMode); From f7f0483320669d907cf84d618517e97901733416 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 12 Sep 2019 08:41:35 -0700 Subject: [PATCH 07/40] Don't disable HMD leaning while seated --- interface/src/avatar/MyAvatar.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4d1c20010c..ae84347d17 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -6257,7 +6257,6 @@ void MyAvatar::beginSit(const glm::vec3& position, const glm::quat& rotation) { _characterController.setSeated(true); setCollisionsEnabled(false); - setHMDLeanRecenterEnabled(false); // Disable movement setSitDriveKeysStatus(false); centerBody(); @@ -6276,7 +6275,6 @@ void MyAvatar::endSit(const glm::vec3& position, const glm::quat& rotation) { clearPinOnJoint(getJointIndex("Hips")); _characterController.setSeated(false); setCollisionsEnabled(true); - setHMDLeanRecenterEnabled(true); centerBody(); slamPosition(position); setWorldOrientation(rotation); From 2d68cfa5e7f6a3306d7a7ad64bef2d429fa53dcb Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 12 Sep 2019 12:04:24 -0700 Subject: [PATCH 08/40] Use unique_lock::unlock() instead of unique_lock::release() ... which actually has the opposite effect --- libraries/networking/src/udt/Socket.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 67af69ae8f..20cb30dbd8 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -553,7 +553,7 @@ void Socket::handleRemoteAddressChange(HifiSockAddr previousAddress, HifiSockAdd _connectionsHash.erase(connectionIter); connection->setDestinationAddress(currentAddress); _connectionsHash[currentAddress] = move(connection); - connectionsLock.release(); + connectionsLock.unlock(); Lock sequenceNumbersLock(_unreliableSequenceNumbersMutex); const auto sequenceNumbersIter = _unreliableSequenceNumbers.find(previousAddress); From 3c043309be8ea4dd160e61d038476c949baea2a0 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 12 Sep 2019 14:03:17 -0700 Subject: [PATCH 09/40] Detect HMD mode properly from rig, in order to enable head IK --- libraries/animation/src/Rig.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 561995cce4..5fe2cb33ff 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1958,8 +1958,7 @@ void Rig::updateReactions(const ControllerParameters& params) { bool isSeated = _state == RigRole::Seated; bool hipsEnabled = params.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Enabled; - bool hipsEstimated = params.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Estimated; - bool hmdMode = hipsEnabled && !hipsEstimated; + bool hmdMode = hipsEnabled; if ((reactionPlaying || isSeated) && !hmdMode) { // TODO: make this smooth. From 4aa85baf75c8894ddb3e25d525392627c1c00bca Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Thu, 12 Sep 2019 14:07:56 -0700 Subject: [PATCH 10/40] Restate disable leaning when seated --- interface/src/avatar/MyAvatar.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index ae84347d17..4d1c20010c 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -6257,6 +6257,7 @@ void MyAvatar::beginSit(const glm::vec3& position, const glm::quat& rotation) { _characterController.setSeated(true); setCollisionsEnabled(false); + setHMDLeanRecenterEnabled(false); // Disable movement setSitDriveKeysStatus(false); centerBody(); @@ -6275,6 +6276,7 @@ void MyAvatar::endSit(const glm::vec3& position, const glm::quat& rotation) { clearPinOnJoint(getJointIndex("Hips")); _characterController.setSeated(false); setCollisionsEnabled(true); + setHMDLeanRecenterEnabled(true); centerBody(); slamPosition(position); setWorldOrientation(rotation); From 5370e67507be6e2309fd51e8200899b0026dbc67 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 12 Sep 2019 14:48:21 -0700 Subject: [PATCH 11/40] Update logo on login popup --- .../resources/images/high-fidelity-banner.svg | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/interface/resources/images/high-fidelity-banner.svg b/interface/resources/images/high-fidelity-banner.svg index d5666be0fa..19127e729c 100644 --- a/interface/resources/images/high-fidelity-banner.svg +++ b/interface/resources/images/high-fidelity-banner.svg @@ -1,17 +1 @@ - - - - - - - - - - - - - - - - - +Artboard 1 \ No newline at end of file From bc119d6c850e62d2fc10f56d2c3440a6772fce5e Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 12 Sep 2019 17:02:19 -0700 Subject: [PATCH 12/40] don't expect final kinematic simulation update --- libraries/physics/src/EntityMotionState.cpp | 73 +++++++++++---------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 68c8266e9f..de82dd6ace 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -84,48 +84,51 @@ EntityMotionState::~EntityMotionState() { } void EntityMotionState::updateServerPhysicsVariables() { - if (_ownershipState != EntityMotionState::OwnershipState::LocallyOwned) { - // only slam these values if we are NOT the simulation owner - Transform localTransform; - _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); - _serverPosition = localTransform.getTranslation(); - _serverRotation = localTransform.getRotation(); - _serverAcceleration = _entity->getAcceleration(); - _serverActionData = _entity->getDynamicData(); - _lastStep = ObjectMotionState::getWorldSimulationStep(); - } + // only slam these values if we are NOT the simulation owner + Transform localTransform; + _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); + _serverPosition = localTransform.getTranslation(); + _serverRotation = localTransform.getRotation(); + _serverAcceleration = _entity->getAcceleration(); + _serverActionData = _entity->getDynamicData(); + _lastStep = ObjectMotionState::getWorldSimulationStep(); } void EntityMotionState::handleDeactivation() { - if (_entity->getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES)) { - // Some non-physical event (script-call or network-packet) has modified the entity's transform and/or velocities - // at the last minute before deactivation --> the values stored in _server* and _body are stale. - // We assume the EntityMotionState is the last to know, so we copy from EntityItem and let things sort themselves out. - Transform localTransform; - _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); - _serverPosition = localTransform.getTranslation(); - _serverRotation = localTransform.getRotation(); - _serverAcceleration = _entity->getAcceleration(); - _serverActionData = _entity->getDynamicData(); - _lastStep = ObjectMotionState::getWorldSimulationStep(); - } else { - // copy _server data to entity - Transform localTransform = _entity->getLocalTransform(); - localTransform.setTranslation(_serverPosition); - localTransform.setRotation(_serverRotation); - _entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3); - // and also to RigidBody - btTransform worldTrans; - worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition())); - worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation())); - _body->setWorldTransform(worldTrans); - // no need to update velocities... should already be zero - } + if (_entity->getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES)) { + // Some non-physical event (script-call or network-packet) has modified the entity's transform and/or + // velocities at the last minute before deactivation --> the values stored in _server* and _body are stale. + // We assume the EntityMotionState is the last to know, so we copy from EntityItem to _server* variables + // here but don't clear the flags --> the will body be set straight before next simulation step. + updateServerPhysicsVariables(); + } else if (_body->isStaticOrKinematicObject() && _ownershipState != EntityMotionState::OwnershipState::LocallyOwned) { + // To allow the ESS to move entities around in a kinematic way we had to remove the requirement that + // every moving+simulated entity has an authoritative simulation owner. As a result, we cannot rely + // on a final authoritative update of kinmatic objects prior to deactivation in the local simulation. + // For this case (unowned kinematic objects) we update the _server* variables for good measure but + // leave the entity and body alone. They should have been updated correctly in the last call to + // EntityMotionState::getWorldTransform(). + updateServerPhysicsVariables(); + } else { + // copy _server data to entity + Transform localTransform = _entity->getLocalTransform(); + localTransform.setTranslation(_serverPosition); + localTransform.setRotation(_serverRotation); + _entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3); + // and also to RigidBody + btTransform worldTrans; + worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition())); + worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation())); + _body->setWorldTransform(worldTrans); + // no need to update velocities... should already be zero + } } // virtual void EntityMotionState::handleEasyChanges(uint32_t& flags) { - updateServerPhysicsVariables(); + if (_ownershipState != EntityMotionState::OwnershipState::LocallyOwned) { + updateServerPhysicsVariables(); + } ObjectMotionState::handleEasyChanges(flags); if (flags & Simulation::DIRTY_SIMULATOR_ID) { From 8491a3792385a636298c527669a5ecbaec5ed4b6 Mon Sep 17 00:00:00 2001 From: danteruiz Date: Thu, 12 Sep 2019 17:12:50 -0700 Subject: [PATCH 13/40] fixing WebEntities html --- interface/resources/qml/Web3DSurface.qml | 33 ++++++++++++++----- .../src/RenderableWebEntityItem.cpp | 10 ++++++ libraries/entities/src/WebEntityItem.cpp | 10 ++++++ libraries/entities/src/WebEntityItem.h | 3 ++ .../ui/src/ui/TabletScriptingInterface.cpp | 26 --------------- 5 files changed, 47 insertions(+), 35 deletions(-) diff --git a/interface/resources/qml/Web3DSurface.qml b/interface/resources/qml/Web3DSurface.qml index 32c19daf14..ff574ceaa5 100644 --- a/interface/resources/qml/Web3DSurface.qml +++ b/interface/resources/qml/Web3DSurface.qml @@ -12,20 +12,35 @@ import QtQuick 2.5 import "controls" as Controls -Controls.WebView { +Item { + id: root + anchors.fill: parent + property string url: "" + property string scriptUrl: null - // This is for JS/QML communication, which is unused in a Web3DOverlay, - // but not having this here results in spurious warnings about a - // missing signal - signal sendToScript(var message); + onUrlChanged: { + load(root.url, root.scriptUrl); + } - function onWebEventReceived(event) { - if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") { - ApplicationInterface.addAssetToWorldFromURL(event.slice(18)); + onScriptUrlChanged: { + if (root.item) { + root.item.scriptUrl = root.scriptUrl; + } else { + load(root.url, root.scriptUrl); } } + property var item: null + + function load(url, scriptUrl) { + QmlSurface.load("./controls/WebView.qml", root, function(newItem) { + root.item = newItem + root.item.url = url + root.item.scriptUrl = scriptUrl + }) + } + Component.onCompleted: { - eventBridge.webEventReceived.connect(onWebEventReceived); + load(root.url, root.scriptUrl); } } diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index a1d24fe52e..b1feddfd47 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "EntitiesRendererLogging.h" #include @@ -180,14 +181,23 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene } // This work must be done on the main thread + bool localSafeContext = entity->getLocalSafeContext(); if (!_webSurface) { + if (localSafeContext) { + ::hifi::scripting::setLocalAccessSafeThread(true); + } buildWebSurface(entity, newSourceURL); + ::hifi::scripting::setLocalAccessSafeThread(false); } if (_webSurface) { if (_webSurface->getRootItem()) { if (_contentType == ContentType::HtmlContent && _sourceURL != newSourceURL) { + if (localSafeContext) { + ::hifi::scripting::setLocalAccessSafeThread(true); + } _webSurface->getRootItem()->setProperty(URL_PROPERTY, newSourceURL); + ::hifi::scripting::setLocalAccessSafeThread(false); _sourceURL = newSourceURL; } else if (_contentType != ContentType::HtmlContent) { _sourceURL = newSourceURL; diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index 186a8fa8b4..a62f599e4c 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -15,6 +15,7 @@ #include #include +#include #include "EntitiesLogging.h" #include "EntityItemProperties.h" @@ -31,6 +32,9 @@ EntityItemPointer WebEntityItem::factory(const EntityItemID& entityID, const Ent } WebEntityItem::WebEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { + // this initialzation of localSafeContext is reading a thread-local variable and that is depends on + // the ctor being executed on the same thread as the script, assuming it's being create by a script + _localSafeContext = hifi::scripting::isLocalAccessSafeThread(); _type = EntityTypes::Web; } @@ -241,6 +245,12 @@ glm::u8vec3 WebEntityItem::getColor() const { }); } +bool WebEntityItem::getLocalSafeContext() const { + return resultWithReadLock([&] { + return _localSafeContext; + }); +} + void WebEntityItem::setAlpha(float alpha) { withWriteLock([&] { _needsRenderUpdate |= _alpha != alpha; diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index bb1e527712..b61e2b124f 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -74,6 +74,8 @@ public: void setScriptURL(const QString& value); QString getScriptURL() const; + bool getLocalSafeContext() const; + static const uint8_t DEFAULT_MAX_FPS; void setMaxFPS(uint8_t value); uint8_t getMaxFPS() const; @@ -98,6 +100,7 @@ protected: uint8_t _maxFPS; WebInputMode _inputMode; bool _showKeyboardFocusHighlight; + bool _localSafeContext { false }; }; #endif // hifi_WebEntityItem_h diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 6c57314367..3465138e00 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -834,32 +834,6 @@ void TabletProxy::loadHTMLSourceImpl(const QVariant& url, const QString& injectJ hifi::scripting::setLocalAccessSafeThread(false); } _state = State::Web; - /*QObject* root = nullptr; - if (!_toolbarMode && _qmlTabletRoot) { - root = _qmlTabletRoot; - } else if (_toolbarMode && _desktopWindow) { - root = _desktopWindow->asQuickItem(); - } - - if (root) { - // BUGZ-1398: tablet access to local HTML files from client scripts - // Here we TEMPORARILY mark the main thread as allowed to load local file content, - // because the thread that originally made the call is so marked. - if (localSafeContext) { - hifi::scripting::setLocalAccessSafeThread(true); - } - QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, path)); - hifi::scripting::setLocalAccessSafeThread(false); - _state = State::QML; - _currentPathLoaded = path; - QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true))); - if (_toolbarMode && _desktopWindow) { - QMetaObject::invokeMethod(root, "setResizable", Q_ARG(const QVariant&, QVariant(resizable))); - } - - } else { - qCDebug(uiLogging) << "tablet cannot load QML because _qmlTabletRoot is null"; - }*/ } void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaScriptUrl, bool loadOtherBase) { From 60045f4783d11286dd4cf7dc56096d46f54aa0a3 Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Mon, 16 Sep 2019 17:48:30 -0700 Subject: [PATCH 14/40] Replacing key combination to trigger a frame capture --- interface/src/Application.cpp | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b719f26c68..33fdad913b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4347,14 +4347,14 @@ void Application::keyPressEvent(QKeyEvent* event) { _keyboardMouseDevice->keyReleaseEvent(event); } - bool isMeta = event->modifiers().testFlag(Qt::ControlModifier); + bool isControlOrCommand = event->modifiers().testFlag(Qt::ControlModifier); bool isOption = event->modifiers().testFlag(Qt::AltModifier); switch (event->key()) { case Qt::Key_4: case Qt::Key_5: case Qt::Key_6: case Qt::Key_7: - if (isMeta || isOption) { + if (isControlOrCommand || isOption) { unsigned int index = static_cast(event->key() - Qt::Key_1); auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins(); if (index < displayPlugins.size()) { @@ -4375,7 +4375,8 @@ void Application::keyPressEvent(QKeyEvent* event) { } bool isShifted = event->modifiers().testFlag(Qt::ShiftModifier); - bool isMeta = event->modifiers().testFlag(Qt::ControlModifier); + bool isControlOrCommand = event->modifiers().testFlag(Qt::ControlModifier); + bool isMetaOrMacControl = event->modifiers().testFlag(Qt::MetaModifier); bool isOption = event->modifiers().testFlag(Qt::AltModifier); switch (event->key()) { case Qt::Key_Enter: @@ -4408,7 +4409,7 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_5: case Qt::Key_6: case Qt::Key_7: - if (isMeta || isOption) { + if (isControlOrCommand || isOption) { unsigned int index = static_cast(event->key() - Qt::Key_1); auto displayPlugins = PluginManager::getInstance()->getDisplayPlugins(); if (index < displayPlugins.size()) { @@ -4424,7 +4425,7 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_G: - if (isShifted && isMeta && Menu::getInstance() && Menu::getInstance()->getMenu("Developer")->isVisible()) { + if (isShifted && isControlOrCommand && isOption && isMetaOrMacControl) { static const QString HIFI_FRAMES_FOLDER_VAR = "HIFI_FRAMES_FOLDER"; static const QString GPU_FRAME_FOLDER = QProcessEnvironment::systemEnvironment().contains(HIFI_FRAMES_FOLDER_VAR) ? QProcessEnvironment::systemEnvironment().value(HIFI_FRAMES_FOLDER_VAR) @@ -4437,7 +4438,7 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; case Qt::Key_X: - if (isShifted && isMeta) { + if (isShifted && isControlOrCommand) { auto offscreenUi = getOffscreenUI(); offscreenUi->togglePinned(); //offscreenUi->getSurfaceContext()->engine()->clearComponentCache(); @@ -4447,7 +4448,7 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_Y: - if (isShifted && isMeta) { + if (isShifted && isControlOrCommand) { getActiveDisplayPlugin()->cycleDebugOutput(); } break; @@ -4460,16 +4461,16 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_L: - if (isShifted && isMeta) { + if (isShifted && isControlOrCommand) { Menu::getInstance()->triggerOption(MenuOption::Log); - } else if (isMeta) { + } else if (isControlOrCommand) { auto dialogsManager = DependencyManager::get(); dialogsManager->toggleAddressBar(); } break; case Qt::Key_R: - if (isMeta && !event->isAutoRepeat()) { + if (isControlOrCommand && !event->isAutoRepeat()) { DependencyManager::get()->reloadAllScripts(); getOffscreenUI()->clearCache(); } @@ -4480,7 +4481,7 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_M: - if (isMeta) { + if (isControlOrCommand) { auto audioClient = DependencyManager::get(); audioClient->setMuted(!audioClient->isMuted()); QSharedPointer audioScriptingInterface = qSharedPointerDynamicCast(DependencyManager::get()); @@ -4491,13 +4492,13 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_S: - if (isShifted && isMeta && !isOption) { + if (isShifted && isControlOrCommand && !isOption) { Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings); } break; case Qt::Key_Apostrophe: { - if (isMeta) { + if (isControlOrCommand) { auto cursor = Cursor::Manager::instance().getCursor(); auto curIcon = cursor->getIcon(); if (curIcon == Cursor::Icon::DEFAULT) { @@ -4524,7 +4525,7 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_Plus: { - if (isMeta && event->modifiers().testFlag(Qt::KeypadModifier)) { + if (isControlOrCommand && event->modifiers().testFlag(Qt::KeypadModifier)) { auto& cursorManager = Cursor::Manager::instance(); cursorManager.setScale(cursorManager.getScale() * 1.1f); } else { @@ -4534,7 +4535,7 @@ void Application::keyPressEvent(QKeyEvent* event) { } case Qt::Key_Minus: { - if (isMeta && event->modifiers().testFlag(Qt::KeypadModifier)) { + if (isControlOrCommand && event->modifiers().testFlag(Qt::KeypadModifier)) { auto& cursorManager = Cursor::Manager::instance(); cursorManager.setScale(cursorManager.getScale() / 1.1f); } else { From af14245d45e4c45f0bfa83ec8f41004e848e208c Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 7 Sep 2019 18:00:45 -0700 Subject: [PATCH 15/40] provide a way to get at _sensorResetMat from outside class --- plugins/openvr/src/OpenVrDisplayPlugin.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index 25427c6dcd..9212a42639 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -76,6 +76,8 @@ public: int visionSqueezePerEye, float visionSqueezeGroundPlaneY, float visionSqueezeSpotlightSize) override; + glm::mat4 getSensorResetMatrix() const { return _sensorResetMat; } + protected: bool internalActivate() override; void internalDeactivate() override; From 64e4ff88e6dee937e9af7af407a6ce0c18c292fa Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 7 Sep 2019 18:01:12 -0700 Subject: [PATCH 16/40] findClosestApproachOfLines will find where two lines get the closest to each other --- libraries/shared/src/GeometryUtil.cpp | 24 +++++++++++++++++++++++- libraries/shared/src/GeometryUtil.h | 1 + 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index b6fca03403..23d2f0f71b 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -547,6 +547,28 @@ bool doLineSegmentsIntersect(glm::vec2 r1p1, glm::vec2 r1p2, glm::vec2 r2p1, glm (d4 == 0 && isOnSegment(r1p1.x, r1p1.y, r1p2.x, r1p2.y, r2p2.x, r2p2.y)); } +bool findClosestApproachOfLines(glm::vec3 p1, glm::vec3 d1, glm::vec3 p2, glm::vec3 d2, + // return values... + float& t1, float& t2) { + // https://math.stackexchange.com/questions/1993953/closest-points-between-two-lines/1993990#1993990 + // https://en.wikipedia.org/wiki/Skew_lines#Nearest_Points + glm::vec3 n1 = glm::cross(d1, glm::cross(d2, d1)); + glm::vec3 n2 = glm::cross(d2, glm::cross(d1, d2)); + + float denom1 = glm::dot(d1, n2); + float denom2 = glm::dot(d2, n1); + + if (denom1 != 0.0f && denom2 != 0.0f) { + t1 = glm::dot((p2 - p1), n2) / denom1; + t2 = glm::dot((p1 - p2), n1) / denom2; + return true; + } else { + t1 = 0.0f; + t2 = 0.0f; + return false; + } +} + bool isOnSegment(float xi, float yi, float xj, float yj, float xk, float yk) { return (xi <= xk || xj <= xk) && (xk <= xi || xk <= xj) && (yi <= yk || yj <= yk) && (yk <= yi || yk <= yj); @@ -1813,4 +1835,4 @@ bool solve_quartic(float a, float b, float c, float d, glm::vec4& roots) { bool computeRealQuarticRoots(float a, float b, float c, float d, float e, glm::vec4& roots) { return solve_quartic(b / a, c / a, d / a, e / a, roots); -} \ No newline at end of file +} diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index 764eeb1500..d786d63980 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -150,6 +150,7 @@ int clipTriangleWithPlane(const Triangle& triangle, const Plane& plane, Triangle int clipTriangleWithPlanes(const Triangle& triangle, const Plane* planes, int planeCount, Triangle* clippedTriangles, int maxClippedTriangleCount); bool doLineSegmentsIntersect(glm::vec2 r1p1, glm::vec2 r1p2, glm::vec2 r2p1, glm::vec2 r2p2); +bool findClosestApproachOfLines(glm::vec3 p1, glm::vec3 d1, glm::vec3 p2, glm::vec3 d2, float& t1, float& t2); bool isOnSegment(float xi, float yi, float xj, float yj, float xk, float yk); int computeDirection(float xi, float yi, float xj, float yj, float xk, float yk); From 73e6be9c3705bc53bd36ce584c4307b61b67dd5d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 7 Sep 2019 18:03:58 -0700 Subject: [PATCH 17/40] allow drawing DebugDraw spheres with a specific size --- libraries/render-utils/src/AnimDebugDraw.cpp | 4 ++-- libraries/shared/src/DebugDraw.cpp | 12 +++++++----- libraries/shared/src/DebugDraw.h | 16 +++++++++++----- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/libraries/render-utils/src/AnimDebugDraw.cpp b/libraries/render-utils/src/AnimDebugDraw.cpp index bf528ee5f0..c4020cb4c4 100644 --- a/libraries/render-utils/src/AnimDebugDraw.cpp +++ b/libraries/render-utils/src/AnimDebugDraw.cpp @@ -393,7 +393,7 @@ void AnimDebugDraw::update() { glm::quat rot = std::get<0>(iter.second); glm::vec3 pos = std::get<1>(iter.second); glm::vec4 color = std::get<2>(iter.second); - const float radius = POSE_RADIUS; + const float radius = std::get<3>(iter.second) * POSE_RADIUS; addBone(AnimPose::identity, AnimPose(glm::vec3(1), rot, pos), radius, color, v); } @@ -402,7 +402,7 @@ void AnimDebugDraw::update() { glm::quat rot = std::get<0>(iter.second); glm::vec3 pos = std::get<1>(iter.second); glm::vec4 color = std::get<2>(iter.second); - const float radius = POSE_RADIUS; + const float radius = std::get<3>(iter.second) * POSE_RADIUS; addBone(myAvatarPose, AnimPose(glm::vec3(1), rot, pos), radius, color, v); } diff --git a/libraries/shared/src/DebugDraw.cpp b/libraries/shared/src/DebugDraw.cpp index 1b2418f7c7..2539a43672 100644 --- a/libraries/shared/src/DebugDraw.cpp +++ b/libraries/shared/src/DebugDraw.cpp @@ -31,9 +31,10 @@ void DebugDraw::drawRay(const glm::vec3& start, const glm::vec3& end, const glm: _rays.push_back(Ray(start, end, color)); } -void DebugDraw::addMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color) { +void DebugDraw::addMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, + const glm::vec4& color, float size) { Lock lock(_mapMutex); - _markers[key] = MarkerInfo(rotation, position, color); + _markers[key] = MarkerInfo(rotation, position, color, size); } void DebugDraw::removeMarker(const QString& key) { @@ -41,9 +42,10 @@ void DebugDraw::removeMarker(const QString& key) { _markers.erase(key); } -void DebugDraw::addMyAvatarMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color) { +void DebugDraw::addMyAvatarMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, + const glm::vec4& color, float size) { Lock lock(_mapMutex); - _myAvatarMarkers[key] = MarkerInfo(rotation, position, color); + _myAvatarMarkers[key] = MarkerInfo(rotation, position, color, size); } void DebugDraw::removeMyAvatarMarker(const QString& key) { @@ -83,4 +85,4 @@ void DebugDraw::drawRays(const std::vector>& lin auto point2 = translation + rotation * line.second; _rays.push_back(Ray(point1, point2, color)); } -} \ No newline at end of file +} diff --git a/libraries/shared/src/DebugDraw.h b/libraries/shared/src/DebugDraw.h index 9e3140ca9b..9db48b759b 100644 --- a/libraries/shared/src/DebugDraw.h +++ b/libraries/shared/src/DebugDraw.h @@ -95,19 +95,22 @@ public: * @param {Quat} rotation - The orientation of the marker in world coordinates. * @param {Vec3} position - The position of the market in world coordinates. * @param {Vec4} color - The color of the marker. + * @param {float} size - A float between 0.0 and 1.0 (10 cm) to control the size of the marker. * @example Briefly draw a debug marker in front of your avatar, in world coordinates. * var MARKER_NAME = "my marker"; * DebugDraw.addMarker( * MARKER_NAME, * Quat.ZERO, * Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5})), - * { red: 255, green: 0, blue: 0 } + * { red: 255, green: 0, blue: 0 }, + * 1.0 * ); * Script.setTimeout(function () { * DebugDraw.removeMarker(MARKER_NAME); * }, 5000); */ - Q_INVOKABLE void addMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color); + Q_INVOKABLE void addMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, + const glm::vec4& color, float size = 1.0f); /**jsdoc * Removes a debug marker that was added in world coordinates. @@ -125,19 +128,22 @@ public: * @param {Quat} rotation - The orientation of the marker in avatar coordinates. * @param {Vec3} position - The position of the market in avatar coordinates. * @param {Vec4} color - color of the marker. + * @param {float} size - A float between 0.0 and 1.0 (10 cm) to control the size of the marker. * @example Briefly draw a debug marker in front of your avatar, in avatar coordinates. * var MARKER_NAME = "My avatar marker"; * DebugDraw.addMyAvatarMarker( * MARKER_NAME, * Quat.ZERO, * { x: 0, y: 0, z: -5 }, - * { red: 255, green: 0, blue: 0 } + * { red: 255, green: 0, blue: 0 }, + * 1.0 * ); * Script.setTimeout(function () { * DebugDraw.removeMyAvatarMarker(MARKER_NAME); * }, 5000); */ - Q_INVOKABLE void addMyAvatarMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, const glm::vec4& color); + Q_INVOKABLE void addMyAvatarMarker(const QString& key, const glm::quat& rotation, const glm::vec3& position, + const glm::vec4& color, float size = 1.0f); /**jsdoc * Removes a debug marker that was added in avatar coordinates. @@ -146,7 +152,7 @@ public: */ Q_INVOKABLE void removeMyAvatarMarker(const QString& key); - using MarkerInfo = std::tuple; + using MarkerInfo = std::tuple; using MarkerMap = std::map; using Ray = std::tuple; using Rays = std::vector; From 954aeb5e257315875358d32b375a44d806843996 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 7 Sep 2019 18:05:38 -0700 Subject: [PATCH 18/40] AxisValue action channels now have a 'valid' flag, like Poses do --- .../controllers/src/controllers/AxisValue.cpp | 9 ++++++--- libraries/controllers/src/controllers/AxisValue.h | 4 ++-- .../controllers/src/controllers/InputDevice.cpp | 4 ++-- .../src/controllers/StandardController.cpp | 4 ++++ .../src/controllers/StandardControls.h | 4 ++++ .../src/controllers/UserInputMapper.cpp | 15 +++++++++++++++ .../controllers/src/controllers/UserInputMapper.h | 8 +++++--- .../controllers/src/controllers/impl/Endpoint.h | 2 +- .../controllers/impl/endpoints/ActionEndpoint.cpp | 2 +- .../controllers/impl/endpoints/ActionEndpoint.h | 2 +- .../impl/endpoints/CompositeEndpoint.cpp | 8 ++++++-- .../controllers/impl/endpoints/InputEndpoint.cpp | 2 +- .../controllers/impl/endpoints/ScriptEndpoint.h | 2 +- .../src/controllers/impl/filters/ClampFilter.h | 2 +- .../impl/filters/ConstrainToIntegerFilter.h | 2 +- .../filters/ConstrainToPositiveIntegerFilter.h | 2 +- .../controllers/impl/filters/DeadZoneFilter.cpp | 2 +- .../controllers/impl/filters/HysteresisFilter.cpp | 3 +-- .../src/controllers/impl/filters/NotFilter.cpp | 2 +- .../src/controllers/impl/filters/PulseFilter.cpp | 2 +- .../src/controllers/impl/filters/ScaleFilter.h | 2 +- 21 files changed, 57 insertions(+), 26 deletions(-) diff --git a/libraries/controllers/src/controllers/AxisValue.cpp b/libraries/controllers/src/controllers/AxisValue.cpp index 4b7913754c..0acbc6dbc7 100644 --- a/libraries/controllers/src/controllers/AxisValue.cpp +++ b/libraries/controllers/src/controllers/AxisValue.cpp @@ -12,10 +12,13 @@ namespace controller { - AxisValue::AxisValue(const float value, const quint64 timestamp) : - value(value), timestamp(timestamp) { } + AxisValue::AxisValue(const float value, const quint64 timestamp, bool valid) : + value(value), timestamp(timestamp), valid(valid) { + } bool AxisValue::operator==(const AxisValue& right) const { - return value == right.value && timestamp == right.timestamp; + return value == right.value && + timestamp == right.timestamp && + valid == right.valid; } } diff --git a/libraries/controllers/src/controllers/AxisValue.h b/libraries/controllers/src/controllers/AxisValue.h index e4bc20f7d2..ec356e1fb3 100644 --- a/libraries/controllers/src/controllers/AxisValue.h +++ b/libraries/controllers/src/controllers/AxisValue.h @@ -21,14 +21,14 @@ namespace controller { float value { 0.0f }; // The value can be timestamped to determine if consecutive identical values should be output (e.g., mouse movement). quint64 timestamp { 0 }; + bool valid { false }; AxisValue() {} - AxisValue(const float value, const quint64 timestamp); + AxisValue(const float value, const quint64 timestamp, bool valid = true); bool operator ==(const AxisValue& right) const; bool operator !=(const AxisValue& right) const { return !(*this == right); } }; - } #endif // hifi_controllers_AxisValue_h diff --git a/libraries/controllers/src/controllers/InputDevice.cpp b/libraries/controllers/src/controllers/InputDevice.cpp index dd430263fa..b28e7cfc82 100644 --- a/libraries/controllers/src/controllers/InputDevice.cpp +++ b/libraries/controllers/src/controllers/InputDevice.cpp @@ -77,13 +77,13 @@ namespace controller { return { getButton(channel), 0 }; case ChannelType::POSE: - return { getPose(channel).valid ? 1.0f : 0.0f, 0 }; + return { getPose(channel).valid ? 1.0f : 0.0f, 0, getPose(channel).valid }; default: break; } - return { 0.0f, 0 }; + return { 0.0f, 0, false }; } AxisValue InputDevice::getValue(const Input& input) const { diff --git a/libraries/controllers/src/controllers/StandardController.cpp b/libraries/controllers/src/controllers/StandardController.cpp index ece10ecca3..dbc92cc7e5 100644 --- a/libraries/controllers/src/controllers/StandardController.cpp +++ b/libraries/controllers/src/controllers/StandardController.cpp @@ -354,6 +354,10 @@ Input::NamedVector StandardController::getAvailableInputs() const { makePair(HIPS, "Hips"), makePair(SPINE2, "Spine2"), makePair(HEAD, "Head"), + makePair(LEFT_EYE, "LeftEye"), + makePair(RIGHT_EYE, "RightEye"), + makePair(LEFT_EYE_BLINK, "LeftEyeBlink"), + makePair(RIGHT_EYE_BLINK, "RightEyeBlink"), // Aliases, PlayStation style names makePair(LB, "L1"), diff --git a/libraries/controllers/src/controllers/StandardControls.h b/libraries/controllers/src/controllers/StandardControls.h index f521ab81cf..99d9246264 100644 --- a/libraries/controllers/src/controllers/StandardControls.h +++ b/libraries/controllers/src/controllers/StandardControls.h @@ -90,6 +90,8 @@ namespace controller { // Grips LEFT_GRIP, RIGHT_GRIP, + LEFT_EYE_BLINK, + RIGHT_EYE_BLINK, NUM_STANDARD_AXES, LZ = LT, RZ = RT @@ -174,6 +176,8 @@ namespace controller { TRACKED_OBJECT_13, TRACKED_OBJECT_14, TRACKED_OBJECT_15, + LEFT_EYE, + RIGHT_EYE, NUM_STANDARD_POSES }; diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 33dc37312e..1eb1a9fa1a 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -256,6 +256,9 @@ void UserInputMapper::update(float deltaTime) { for (auto& channel : _actionStates) { channel = 0.0f; } + for (unsigned int i = 0; i < _actionStatesValid.size(); i++) { + _actionStatesValid[i] = true; + } for (auto& channel : _poseStates) { channel = Pose(); @@ -1233,5 +1236,17 @@ void UserInputMapper::disableMapping(const Mapping::Pointer& mapping) { } } +void UserInputMapper::setActionState(Action action, float value, bool valid) { + _actionStates[toInt(action)] = value; + _actionStatesValid[toInt(action)] = valid; +} + +void UserInputMapper::deltaActionState(Action action, float delta, bool valid) { + _actionStates[toInt(action)] += delta; + bool wasValid = _actionStatesValid[toInt(action)]; + _actionStatesValid[toInt(action)] = wasValid & valid; +} + + } diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index 2b3c947491..cd44f3226c 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -82,13 +82,14 @@ namespace controller { QString getActionName(Action action) const; QString getStandardPoseName(uint16_t pose); float getActionState(Action action) const { return _actionStates[toInt(action)]; } + bool getActionStateValid(Action action) const { return _actionStatesValid[toInt(action)]; } Pose getPoseState(Action action) const; int findAction(const QString& actionName) const; QVector getActionNames() const; Input inputFromAction(Action action) const { return getActionInputs()[toInt(action)].first; } - void setActionState(Action action, float value) { _actionStates[toInt(action)] = value; } - void deltaActionState(Action action, float delta) { _actionStates[toInt(action)] += delta; } + void setActionState(Action action, float value, bool valid = true); + void deltaActionState(Action action, float delta, bool valid = true); void setActionState(Action action, const Pose& value) { _poseStates[toInt(action)] = value; } bool triggerHapticPulse(float strength, float duration, controller::Hand hand); bool triggerHapticPulseOnDevice(uint16 deviceID, float strength, float duration, controller::Hand hand); @@ -146,6 +147,7 @@ namespace controller { std::vector _actionStates = std::vector(toInt(Action::NUM_ACTIONS), 0.0f); std::vector _actionScales = std::vector(toInt(Action::NUM_ACTIONS), 1.0f); std::vector _lastActionStates = std::vector(toInt(Action::NUM_ACTIONS), 0.0f); + std::vector _actionStatesValid = std::vector(toInt(Action::NUM_ACTIONS), false); std::vector _poseStates = std::vector(toInt(Action::NUM_ACTIONS)); std::vector _lastStandardStates = std::vector(); @@ -167,7 +169,7 @@ namespace controller { ConditionalPointer conditionalFor(const QJSValue& endpoint); ConditionalPointer conditionalFor(const QScriptValue& endpoint); ConditionalPointer conditionalFor(const Input& endpoint) const; - + MappingPointer parseMapping(const QJsonValue& json); RoutePointer parseRoute(const QJsonValue& value); EndpointPointer parseDestination(const QJsonValue& value); diff --git a/libraries/controllers/src/controllers/impl/Endpoint.h b/libraries/controllers/src/controllers/impl/Endpoint.h index bcf71f3094..692e427e16 100644 --- a/libraries/controllers/src/controllers/impl/Endpoint.h +++ b/libraries/controllers/src/controllers/impl/Endpoint.h @@ -100,7 +100,7 @@ namespace controller { _currentPose = value; } protected: - AxisValue _currentValue { 0.0f, 0 }; + AxisValue _currentValue { 0.0f, 0, false }; Pose _currentPose {}; }; diff --git a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp index 58744c468c..4be23b0c29 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp @@ -26,7 +26,7 @@ void ActionEndpoint::apply(AxisValue newValue, const Pointer& source) { _currentValue.value += newValue.value; if (_input != Input::INVALID_INPUT) { - userInputMapper->deltaActionState(Action(_input.getChannel()), newValue.value); + userInputMapper->deltaActionState(Action(_input.getChannel()), newValue.value, newValue.valid); } } diff --git a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.h index 94da4663aa..7ab21031a7 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.h @@ -32,7 +32,7 @@ public: virtual void reset() override; private: - AxisValue _currentValue { 0.0f, 0 }; + AxisValue _currentValue { 0.0f, 0, false }; Pose _currentPose{}; }; diff --git a/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.cpp index f54c786a33..82a44bd7ae 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.cpp @@ -27,7 +27,9 @@ bool CompositeEndpoint::readable() const { AxisValue CompositeEndpoint::peek() const { auto negative = first->peek(); auto positive = second->peek(); - auto result = AxisValue(positive.value - negative.value, std::max(positive.timestamp, negative.timestamp)); + auto result = AxisValue(positive.value - negative.value, + std::max(positive.timestamp, negative.timestamp), + negative.valid && positive.valid); return result; } @@ -35,7 +37,9 @@ AxisValue CompositeEndpoint::peek() const { AxisValue CompositeEndpoint::value() { auto negative = first->value(); auto positive = second->value(); - auto result = AxisValue(positive.value - negative.value, std::max(positive.timestamp, negative.timestamp)); + auto result = AxisValue(positive.value - negative.value, + std::max(positive.timestamp, negative.timestamp), + negative.valid && positive.valid); return result; } diff --git a/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.cpp index 3755d860b6..db0aed47e1 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.cpp @@ -16,7 +16,7 @@ using namespace controller; AxisValue InputEndpoint::peek() const { if (isPose()) { - return peekPose().valid ? AxisValue(1.0f, 0) : AxisValue(0.0f, 0); + return peekPose().valid ? AxisValue(1.0f, 0) : AxisValue(0.0f, 0, false); } auto userInputMapper = DependencyManager::get(); auto deviceProxy = userInputMapper->getDevice(_input); diff --git a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h index e739ab0b01..1aa1746b24 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h @@ -41,7 +41,7 @@ protected: private: QScriptValue _callable; float _lastValueRead { 0.0f }; - AxisValue _lastValueWritten { 0.0f, 0 }; + AxisValue _lastValueWritten { 0.0f, 0, false }; bool _returnPose { false }; Pose _lastPoseRead; diff --git a/libraries/controllers/src/controllers/impl/filters/ClampFilter.h b/libraries/controllers/src/controllers/impl/filters/ClampFilter.h index 04684655c9..7ec8173c2a 100644 --- a/libraries/controllers/src/controllers/impl/filters/ClampFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/ClampFilter.h @@ -19,7 +19,7 @@ class ClampFilter : public Filter { public: ClampFilter(float min = 0.0, float max = 1.0) : _min(min), _max(max) {}; virtual AxisValue apply(AxisValue value) const override { - return { glm::clamp(value.value, _min, _max), value.timestamp }; + return { glm::clamp(value.value, _min, _max), value.timestamp, value.valid }; } virtual Pose apply(Pose value) const override { return value; } diff --git a/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h b/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h index 2cce5f828d..3adcbbd008 100644 --- a/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h @@ -20,7 +20,7 @@ public: ConstrainToIntegerFilter() = default; virtual AxisValue apply(AxisValue value) const override { - return { glm::sign(value.value), value.timestamp }; + return { glm::sign(value.value), value.timestamp, value.valid }; } virtual Pose apply(Pose value) const override { return value; } diff --git a/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h b/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h index 07dd6654f1..5dcc43b3f7 100644 --- a/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h @@ -20,7 +20,7 @@ public: ConstrainToPositiveIntegerFilter() = default; virtual AxisValue apply(AxisValue value) const override { - return { (value.value <= 0.0f) ? 0.0f : 1.0f, value.timestamp }; + return { (value.value <= 0.0f) ? 0.0f : 1.0f, value.timestamp, value.valid }; } virtual Pose apply(Pose value) const override { return value; } diff --git a/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.cpp b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.cpp index 84d3b9de60..4c396fee70 100644 --- a/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.cpp @@ -18,7 +18,7 @@ AxisValue DeadZoneFilter::apply(AxisValue value) const { if (magnitude < _min) { return { 0.0f, value.timestamp }; } - return { (magnitude - _min) * scale, value.timestamp }; + return { (magnitude - _min) * scale, value.timestamp, value.valid }; } bool DeadZoneFilter::parseParameters(const QJsonValue& parameters) { diff --git a/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.cpp b/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.cpp index 91e59a39b9..b7b573a98c 100644 --- a/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.cpp @@ -19,7 +19,6 @@ HysteresisFilter::HysteresisFilter(float min, float max) : _min(min), _max(max) } }; - AxisValue HysteresisFilter::apply(AxisValue value) const { if (_signaled) { if (value.value <= _min) { @@ -30,7 +29,7 @@ AxisValue HysteresisFilter::apply(AxisValue value) const { _signaled = true; } } - return { _signaled ? 1.0f : 0.0f, value.timestamp }; + return { _signaled ? 1.0f : 0.0f, value.timestamp, value.valid }; } bool HysteresisFilter::parseParameters(const QJsonValue& parameters) { diff --git a/libraries/controllers/src/controllers/impl/filters/NotFilter.cpp b/libraries/controllers/src/controllers/impl/filters/NotFilter.cpp index c0396857e5..dd181b7857 100644 --- a/libraries/controllers/src/controllers/impl/filters/NotFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/NotFilter.cpp @@ -6,5 +6,5 @@ NotFilter::NotFilter() { } AxisValue NotFilter::apply(AxisValue value) const { - return { (value.value == 0.0f) ? 1.0f : 0.0f, value.timestamp }; + return { (value.value == 0.0f) ? 1.0f : 0.0f, value.timestamp, value.valid }; } diff --git a/libraries/controllers/src/controllers/impl/filters/PulseFilter.cpp b/libraries/controllers/src/controllers/impl/filters/PulseFilter.cpp index d37eb99ca9..353bf7dca9 100644 --- a/libraries/controllers/src/controllers/impl/filters/PulseFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/PulseFilter.cpp @@ -29,7 +29,7 @@ AxisValue PulseFilter::apply(AxisValue value) const { _lastEmitTime = DEFAULT_LAST_EMIT_TIME; } - return { result, value.timestamp }; + return { result, value.timestamp, value.valid }; } bool PulseFilter::parseParameters(const QJsonValue& parameters) { diff --git a/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h b/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h index 3eb58e7f47..7c146d4e4a 100644 --- a/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h @@ -23,7 +23,7 @@ public: ScaleFilter(float scale) : _scale(scale) {} virtual AxisValue apply(AxisValue value) const override { - return { value.value * _scale, value.timestamp }; + return { value.value * _scale, value.timestamp, value.valid }; } virtual Pose apply(Pose value) const override { From b64ff70d6cc1a4759576e42b714a0c530133e04d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 7 Sep 2019 18:07:37 -0700 Subject: [PATCH 19/40] add show-lookat-targets DebugDraw. remove old/unused eyetracker code. --- interface/src/Menu.cpp | 22 ++++--------------- interface/src/Menu.h | 2 ++ .../src/avatars-renderer/Avatar.cpp | 18 +++++++++++++++ .../src/avatars-renderer/Avatar.h | 2 ++ 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 193de2792d..0b1cb789f1 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -534,32 +534,18 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::AutoMuteAudio, 0, false); #endif -#ifdef HAVE_IVIEWHMD - // Developer > Avatar > Eye Tracking - MenuWrapper* eyeTrackingMenu = avatarDebugMenu->addMenu("Eye Tracking"); - addCheckableActionToQMenuAndActionHash(eyeTrackingMenu, MenuOption::SMIEyeTracking, 0, false, - qApp, SLOT(setActiveEyeTracker())); - { - MenuWrapper* calibrateEyeTrackingMenu = eyeTrackingMenu->addMenu("Calibrate"); - addActionToQMenuAndActionHash(calibrateEyeTrackingMenu, MenuOption::OnePointCalibration, 0, - qApp, SLOT(calibrateEyeTracker1Point())); - addActionToQMenuAndActionHash(calibrateEyeTrackingMenu, MenuOption::ThreePointCalibration, 0, - qApp, SLOT(calibrateEyeTracker3Points())); - addActionToQMenuAndActionHash(calibrateEyeTrackingMenu, MenuOption::FivePointCalibration, 0, - qApp, SLOT(calibrateEyeTracker5Points())); - } - addCheckableActionToQMenuAndActionHash(eyeTrackingMenu, MenuOption::SimulateEyeTracking, 0, false, - qApp, SLOT(setActiveEyeTracker())); -#endif - action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false); connect(action, &QAction::triggered, [this]{ Avatar::setShowReceiveStats(isOptionChecked(MenuOption::AvatarReceiveStats)); }); action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowBoundingCollisionShapes, 0, false); connect(action, &QAction::triggered, [this]{ Avatar::setShowCollisionShapes(isOptionChecked(MenuOption::ShowBoundingCollisionShapes)); }); action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowMyLookAtVectors, 0, false); connect(action, &QAction::triggered, [this]{ Avatar::setShowMyLookAtVectors(isOptionChecked(MenuOption::ShowMyLookAtVectors)); }); + action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowMyLookAtTarget, 0, false); + connect(action, &QAction::triggered, [this]{ Avatar::setShowMyLookAtTarget(isOptionChecked(MenuOption::ShowMyLookAtTarget)); }); action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowOtherLookAtVectors, 0, false); connect(action, &QAction::triggered, [this]{ Avatar::setShowOtherLookAtVectors(isOptionChecked(MenuOption::ShowOtherLookAtVectors)); }); + action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowOtherLookAtTarget, 0, false); + connect(action, &QAction::triggered, [this]{ Avatar::setShowOtherLookAtTarget(isOptionChecked(MenuOption::ShowOtherLookAtTarget)); }); auto avatarManager = DependencyManager::get(); auto avatar = avatarManager->getMyAvatar(); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 7dff264adc..c299dd8c10 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -188,7 +188,9 @@ namespace MenuOption { const QString ShowBoundingCollisionShapes = "Show Bounding Collision Shapes"; const QString ShowDSConnectTable = "Show Domain Connection Timing"; const QString ShowMyLookAtVectors = "Show My Eye Vectors"; + const QString ShowMyLookAtTarget = "Show My Look-At Target"; const QString ShowOtherLookAtVectors = "Show Other Eye Vectors"; + const QString ShowOtherLookAtTarget = "Show Other Look-At Target"; const QString EnableLookAtSnapping = "Enable LookAt Snapping"; const QString ShowRealtimeEntityStats = "Show Realtime Entity Stats"; const QString SimulateEyeTracking = "Simulate"; diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 7f363dd36f..b0f3934278 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -108,11 +108,21 @@ void Avatar::setShowMyLookAtVectors(bool showMine) { showMyLookAtVectors = showMine; } +static bool showMyLookAtTarget = false; +void Avatar::setShowMyLookAtTarget(bool showMine) { + showMyLookAtTarget = showMine; +} + static bool showOtherLookAtVectors = false; void Avatar::setShowOtherLookAtVectors(bool showOthers) { showOtherLookAtVectors = showOthers; } +static bool showOtherLookAtTarget = false; +void Avatar::setShowOtherLookAtTarget(bool showOthers) { + showOtherLookAtTarget = showOthers; +} + static bool showCollisionShapes = false; void Avatar::setShowCollisionShapes(bool render) { showCollisionShapes = render; @@ -711,6 +721,14 @@ void Avatar::updateRenderItem(render::Transaction& transaction) { void Avatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { + if (isMyAvatar() ? showMyLookAtTarget : showOtherLookAtTarget) { + glm::vec3 lookAtTarget = getHead()->getLookAtPosition(); + DebugDraw::getInstance().addMarker(QString("look-at-") + getID().toString(), + glm::quat(), lookAtTarget, glm::vec4(1), 1.0f); + } else { + DebugDraw::getInstance().removeMarker(QString("look-at-") + getID().toString()); + } + if (isMyAvatar() ? showMyLookAtVectors : showOtherLookAtVectors) { const float EYE_RAY_LENGTH = 10.0; const glm::vec4 BLUE(0.0f, 0.0f, _lookAtSnappingEnabled ? 1.0f : 0.25f, 1.0f); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 7bb15ecbf7..b969449d5e 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -140,7 +140,9 @@ public: static void setShowAvatars(bool render); static void setShowReceiveStats(bool receiveStats); static void setShowMyLookAtVectors(bool showMine); + static void setShowMyLookAtTarget(bool showMine); static void setShowOtherLookAtVectors(bool showOthers); + static void setShowOtherLookAtTarget(bool showOthers); static void setShowCollisionShapes(bool render); static void setShowNamesAboveHeads(bool show); From 7fa24efca0b050ca0579b1ccd6fd9780961409f6 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 7 Sep 2019 18:08:28 -0700 Subject: [PATCH 20/40] add a way to query Rig to see if a joint has been overridden by a script --- libraries/animation/src/Rig.cpp | 16 +++++++++++++++- libraries/animation/src/Rig.h | 4 +++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 5fe2cb33ff..19e3878d84 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -715,7 +715,7 @@ void Rig::reset(const HFMModel& hfmModel) { } } -bool Rig::jointStatesEmpty() { +bool Rig::jointStatesEmpty() const { return _internalPoseSet._relativePoses.empty(); } @@ -878,6 +878,20 @@ void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, flo } } +bool Rig::getIsJointOverridden(int jointIndex) const { + if (QThread::currentThread() == thread()) { + if (isIndexValid(jointIndex)) { + return _internalPoseSet._overrideFlags[jointIndex]; + } + } else { + QReadLocker readLock(&_externalPoseSetLock); + if (jointIndex >= 0 && jointIndex < (int)_externalPoseSet._overrideFlags.size()) { + return _externalPoseSet._overrideFlags[jointIndex]; + } + } + return false; +} + bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const { bool success { false }; glm::vec3 originalPosition = position; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 99794fd0a7..a70659b0ae 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -135,7 +135,7 @@ public: void initJointStates(const HFMModel& hfmModel, const glm::mat4& modelOffset); void reset(const HFMModel& hfmModel); - bool jointStatesEmpty(); + bool jointStatesEmpty() const; int getJointStateCount() const; int indexOfJoint(const QString& jointName) const; QString nameOfJoint(int jointIndex) const; @@ -163,6 +163,8 @@ public: void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority); void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority); + bool getIsJointOverridden(int jointIndex) const; + // if translation and rotation is identity, position will be in rig space bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const; From 943348ba5171b5c19c27df09911b4be3ed207beb Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 7 Sep 2019 18:11:04 -0700 Subject: [PATCH 21/40] input action channels for eyes and eyelids and fingers. connect eyelid input actions to blendshapes. --- interface/resources/controllers/standard.json | 9 +++- .../controllers/standard_nomovement.json | 8 +++- interface/resources/controllers/vive.json | 44 +++++++++++++++++++ interface/src/avatar/MyHead.cpp | 37 +++++++++++----- .../controllers/src/controllers/Actions.cpp | 4 ++ .../controllers/src/controllers/Actions.h | 5 +++ 6 files changed, 93 insertions(+), 14 deletions(-) diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json index 0a5bd12460..195f909942 100644 --- a/interface/resources/controllers/standard.json +++ b/interface/resources/controllers/standard.json @@ -162,7 +162,14 @@ { "from": "Standard.Head", "to": "Actions.Head" }, { "from": "Standard.LeftArm", "to": "Actions.LeftArm" }, { "from": "Standard.RightArm", "to": "Actions.RightArm" }, - + + { "from": "Standard.LeftEye", "to": "Actions.LeftEye" }, + { "from": "Standard.RightEye", "to": "Actions.RightEye" }, + + { "from": "Standard.LeftEyeBlink", "to": "Actions.LeftEyeBlink" }, + { "from": "Standard.RightEyeBlink", "to": "Actions.RightEyeBlink" }, + + { "from": "Standard.TrackedObject00", "to" : "Actions.TrackedObject00" }, { "from": "Standard.TrackedObject01", "to" : "Actions.TrackedObject01" }, { "from": "Standard.TrackedObject02", "to" : "Actions.TrackedObject02" }, diff --git a/interface/resources/controllers/standard_nomovement.json b/interface/resources/controllers/standard_nomovement.json index 015bc33056..602d3bb798 100644 --- a/interface/resources/controllers/standard_nomovement.json +++ b/interface/resources/controllers/standard_nomovement.json @@ -57,7 +57,13 @@ { "from": "Standard.Head", "to": "Actions.Head" }, { "from": "Standard.LeftArm", "to": "Actions.LeftArm" }, { "from": "Standard.RightArm", "to": "Actions.RightArm" }, - + + { "from": "Standard.LeftEye", "to": "Actions.LeftEye" }, + { "from": "Standard.RightEye", "to": "Actions.RightEye" }, + + { "from": "Standard.LeftEyeBlink", "to": "Actions.LeftEyeBlink" }, + { "from": "Standard.RightEyeBlink", "to": "Actions.RightEyeBlink" }, + { "from": "Standard.TrackedObject00", "to" : "Actions.TrackedObject00" }, { "from": "Standard.TrackedObject01", "to" : "Actions.TrackedObject01" }, { "from": "Standard.TrackedObject02", "to" : "Actions.TrackedObject02" }, diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 730e1bcb58..b6fae1dd79 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -54,8 +54,52 @@ { "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" }, { "from": "Vive.LeftHand", "to": "Standard.LeftHand" }, + { "from": "Vive.LeftHandThumb1", "to": "Standard.LeftHandThumb1"}, + { "from": "Vive.LeftHandThumb2", "to": "Standard.LeftHandThumb2"}, + { "from": "Vive.LeftHandThumb3", "to": "Standard.LeftHandThumb3"}, + { "from": "Vive.LeftHandThumb4", "to": "Standard.LeftHandThumb4"}, + { "from": "Vive.LeftHandIndex1", "to": "Standard.LeftHandIndex1"}, + { "from": "Vive.LeftHandIndex2", "to": "Standard.LeftHandIndex2"}, + { "from": "Vive.LeftHandIndex3", "to": "Standard.LeftHandIndex3"}, + { "from": "Vive.LeftHandIndex4", "to": "Standard.LeftHandIndex4"}, + { "from": "Vive.LeftHandMiddle1", "to": "Standard.LeftHandMiddle1"}, + { "from": "Vive.LeftHandMiddle2", "to": "Standard.LeftHandMiddle2"}, + { "from": "Vive.LeftHandMiddle3", "to": "Standard.LeftHandMiddle3"}, + { "from": "Vive.LeftHandMiddle4", "to": "Standard.LeftHandMiddle4"}, + { "from": "Vive.LeftHandRing1", "to": "Standard.LeftHandRing1"}, + { "from": "Vive.LeftHandRing2", "to": "Standard.LeftHandRing2"}, + { "from": "Vive.LeftHandRing3", "to": "Standard.LeftHandRing3"}, + { "from": "Vive.LeftHandRing4", "to": "Standard.LeftHandRing4"}, + { "from": "Vive.LeftHandPinky1", "to": "Standard.LeftHandPinky1"}, + { "from": "Vive.LeftHandPinky2", "to": "Standard.LeftHandPinky2"}, + { "from": "Vive.LeftHandPinky3", "to": "Standard.LeftHandPinky3"}, + { "from": "Vive.LeftHandPinky4", "to": "Standard.LeftHandPinky4"}, { "from": "Vive.RightHand", "to": "Standard.RightHand" }, + { "from": "Vive.RightHandThumb1", "to": "Standard.RightHandThumb1"}, + { "from": "Vive.RightHandThumb2", "to": "Standard.RightHandThumb2"}, + { "from": "Vive.RightHandThumb3", "to": "Standard.RightHandThumb3"}, + { "from": "Vive.RightHandThumb4", "to": "Standard.RightHandThumb4"}, + { "from": "Vive.RightHandIndex1", "to": "Standard.RightHandIndex1"}, + { "from": "Vive.RightHandIndex2", "to": "Standard.RightHandIndex2"}, + { "from": "Vive.RightHandIndex3", "to": "Standard.RightHandIndex3"}, + { "from": "Vive.RightHandIndex4", "to": "Standard.RightHandIndex4"}, + { "from": "Vive.RightHandMiddle1", "to": "Standard.RightHandMiddle1"}, + { "from": "Vive.RightHandMiddle2", "to": "Standard.RightHandMiddle2"}, + { "from": "Vive.RightHandMiddle3", "to": "Standard.RightHandMiddle3"}, + { "from": "Vive.RightHandMiddle4", "to": "Standard.RightHandMiddle4"}, + { "from": "Vive.RightHandRing1", "to": "Standard.RightHandRing1"}, + { "from": "Vive.RightHandRing2", "to": "Standard.RightHandRing2"}, + { "from": "Vive.RightHandRing3", "to": "Standard.RightHandRing3"}, + { "from": "Vive.RightHandRing4", "to": "Standard.RightHandRing4"}, + { "from": "Vive.RightHandPinky1", "to": "Standard.RightHandPinky1"}, + { "from": "Vive.RightHandPinky2", "to": "Standard.RightHandPinky2"}, + { "from": "Vive.RightHandPinky3", "to": "Standard.RightHandPinky3"}, + { "from": "Vive.RightHandPinky4", "to": "Standard.RightHandPinky4"}, { "from": "Vive.Head", "to" : "Standard.Head" }, + { "from": "Vive.LeftEye", "to" : "Standard.LeftEye" }, + { "from": "Vive.RightEye", "to" : "Standard.RightEye" }, + { "from": "Vive.LeftEyeBlink", "to" : "Standard.LeftEyeBlink" }, + { "from": "Vive.RightEyeBlink", "to" : "Standard.RightEyeBlink" }, { "from": "Vive.LeftFoot", "to" : "Standard.LeftFoot", diff --git a/interface/src/avatar/MyHead.cpp b/interface/src/avatar/MyHead.cpp index 9b05a26c76..b5e8bc2171 100644 --- a/interface/src/avatar/MyHead.cpp +++ b/interface/src/avatar/MyHead.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include "devices/DdeFaceTracker.h" #include "Application.h" @@ -46,18 +45,32 @@ void MyHead::simulate(float deltaTime) { auto player = DependencyManager::get(); // Only use face trackers when not playing back a recording. if (!player->isPlaying()) { - auto faceTracker = qApp->getActiveFaceTracker(); - const bool hasActualFaceTrackerConnected = faceTracker && !faceTracker->isMuted(); - _isFaceTrackerConnected = hasActualFaceTrackerConnected || _owningAvatar->getHasScriptedBlendshapes(); - if (_isFaceTrackerConnected) { - if (hasActualFaceTrackerConnected) { - _blendshapeCoefficients = faceTracker->getBlendshapeCoefficients(); - } - } + // auto faceTracker = qApp->getActiveFaceTracker(); + // const bool hasActualFaceTrackerConnected = faceTracker && !faceTracker->isMuted(); + // _isFaceTrackerConnected = hasActualFaceTrackerConnected || _owningAvatar->getHasScriptedBlendshapes(); + // if (_isFaceTrackerConnected) { + // if (hasActualFaceTrackerConnected) { + // _blendshapeCoefficients = faceTracker->getBlendshapeCoefficients(); + // } + // } - auto eyeTracker = DependencyManager::get(); - _isEyeTrackerConnected = eyeTracker->isTracking(); - // if eye tracker is connected we should get the data here. + auto userInputMapper = DependencyManager::get(); + bool eyeLidsTracked = + userInputMapper->getActionStateValid(controller::Action::LEFT_EYE_BLINK) && + userInputMapper->getActionStateValid(controller::Action::RIGHT_EYE_BLINK); + setFaceTrackerConnected(eyeLidsTracked); + if (eyeLidsTracked) { + float leftEyeBlink = userInputMapper->getActionState(controller::Action::LEFT_EYE_BLINK); + float rightEyeBlink = userInputMapper->getActionState(controller::Action::RIGHT_EYE_BLINK); + _blendshapeCoefficients.resize(std::max(_blendshapeCoefficients.size(), 2)); + _blendshapeCoefficients[0] = leftEyeBlink; + _blendshapeCoefficients[1] = rightEyeBlink; + } else { + const float FULLY_OPEN = 0.0f; + _blendshapeCoefficients.resize(std::max(_blendshapeCoefficients.size(), 2)); + _blendshapeCoefficients[0] = FULLY_OPEN; + _blendshapeCoefficients[1] = FULLY_OPEN; + } } Parent::simulate(deltaTime); } diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index 9f9d92fed7..40011f2682 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -347,6 +347,10 @@ namespace controller { makePosePair(Action::HIPS, "Hips"), makePosePair(Action::SPINE2, "Spine2"), makePosePair(Action::HEAD, "Head"), + makePosePair(Action::LEFT_EYE, "LeftEye"), + makePosePair(Action::RIGHT_EYE, "RightEye"), + makeAxisPair(Action::LEFT_EYE_BLINK, "LeftEyeBlink"), + makeAxisPair(Action::RIGHT_EYE_BLINK, "RightEyeBlink"), makePosePair(Action::LEFT_HAND_THUMB1, "LeftHandThumb1"), makePosePair(Action::LEFT_HAND_THUMB2, "LeftHandThumb2"), diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index 3e99d8d147..f91d9f2522 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -181,6 +181,11 @@ enum class Action { TRACKED_OBJECT_15, SPRINT, + LEFT_EYE, + RIGHT_EYE, + LEFT_EYE_BLINK, + RIGHT_EYE_BLINK, + NUM_ACTIONS }; From 5c570d28a0cb6db782aa6c75a12a7db734c1e8fd Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 7 Sep 2019 18:12:19 -0700 Subject: [PATCH 22/40] remove old/unused eyetracker code --- interface/src/Application.h | 7 ---- .../src/avatars-renderer/Head.cpp | 13 ++++--- libraries/avatars/src/AvatarData.cpp | 15 ++++---- libraries/avatars/src/HeadData.cpp | 37 +++++++++++++++++++ libraries/avatars/src/HeadData.h | 30 +++++++-------- 5 files changed, 66 insertions(+), 36 deletions(-) diff --git a/interface/src/Application.h b/interface/src/Application.h index cd867598c0..af2348d1e9 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -437,13 +437,6 @@ public slots: void sendWrongProtocolVersionsSignature(bool checked) { ::sendWrongProtocolVersionsSignature(checked); } #endif -#ifdef HAVE_IVIEWHMD - void setActiveEyeTracker(); - void calibrateEyeTracker1Point(); - void calibrateEyeTracker3Points(); - void calibrateEyeTracker5Points(); -#endif - static void showHelp(); void cycleCamera(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp index a551793ab0..445184f5f8 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp @@ -17,7 +17,6 @@ #include #include #include -#include #include #include "Logging.h" @@ -58,7 +57,7 @@ void Head::simulate(float deltaTime) { _longTermAverageLoudness = glm::mix(_longTermAverageLoudness, _averageLoudness, glm::min(deltaTime / AUDIO_LONG_TERM_AVERAGING_SECS, 1.0f)); } - if (!_isEyeTrackerConnected) { + if (getHasProceduralEyeMovement()) { // Update eye saccades const float AVERAGE_MICROSACCADE_INTERVAL = 1.0f; const float AVERAGE_SACCADE_INTERVAL = 6.0f; @@ -82,6 +81,7 @@ void Head::simulate(float deltaTime) { const float FULLY_OPEN = 0.0f; const float FULLY_CLOSED = 1.0f; if (getHasProceduralBlinkFaceMovement()) { + // handle automatic blinks // Detect transition from talking to not; force blink after that and a delay bool forceBlink = false; const float TALKING_LOUDNESS = 150.0f; @@ -129,7 +129,7 @@ void Head::simulate(float deltaTime) { _leftEyeBlink = FULLY_OPEN; } - // use data to update fake Faceshift blendshape coefficients + // use data to update fake Faceshift blendshape coefficients if (getHasAudioEnabledFaceMovement()) { // Update audio attack data for facial animation (eyebrows and mouth) float audioAttackAveragingRate = (10.0f - deltaTime * NORMAL_HZ) / 10.0f; // --> 0.9 at 60 Hz @@ -152,7 +152,8 @@ void Head::simulate(float deltaTime) { _mouthTime = 0.0f; } - FaceTracker::updateFakeCoefficients(_leftEyeBlink, + FaceTracker::updateFakeCoefficients( + _leftEyeBlink, _rightEyeBlink, _browAudioLift, _audioJawOpen, @@ -162,6 +163,8 @@ void Head::simulate(float deltaTime) { _transientBlendshapeCoefficients); if (getHasProceduralEyeFaceMovement()) { + // This controls two things, the eye brow and the upper eye lid, it is driven by the vertical up/down angle of the + // eyes relative to the head. This is to try to help prevent sleepy eyes/crazy eyes. applyEyelidOffset(getOrientation()); } @@ -292,7 +295,7 @@ glm::quat Head::getFinalOrientationInLocalFrame() const { } // Everyone else's head keeps track of a lookAtPosition that everybody sees the same, and refers to where that head -// is looking in model space -- e.g., at someone's eyeball, or between their eyes, or mouth, etc. Everyon's Interface +// is looking in model space -- e.g., at someone's eyeball, or between their eyes, or mouth, etc. Everyone's Interface // will have the same value for the lookAtPosition of any given head. // // Everyone else's head also keeps track of a correctedLookAtPosition that may be different for the same head within diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index c03f9430be..a91154ff15 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -245,9 +245,10 @@ QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail, bool dro } QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, - const QVector& lastSentJointData, - AvatarDataPacket::SendStatus& sendStatus, bool dropFaceTracking, bool distanceAdjust, - glm::vec3 viewerPosition, QVector* sentJointDataOut, int maxDataSize, AvatarDataRate* outboundDataRateOut) const { + const QVector& lastSentJointData, AvatarDataPacket::SendStatus& sendStatus, + bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition, + QVector* sentJointDataOut, + int maxDataSize, AvatarDataRate* outboundDataRateOut) const { bool cullSmallChanges = (dataDetail == CullSmallData); bool sendAll = (dataDetail == SendAllData); @@ -532,7 +533,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent setAtBit16(flags, IS_FACE_TRACKER_CONNECTED); } // eye tracker state - if (_headData->_isEyeTrackerConnected) { + if (!_headData->_hasProceduralEyeMovement) { setAtBit16(flags, IS_EYE_TRACKER_CONNECTED); } // referential state @@ -1150,7 +1151,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { + (oneAtBit16(bitItems, HAND_STATE_FINGER_POINTING_BIT) ? IS_FINGER_POINTING_FLAG : 0); auto newFaceTrackerConnected = oneAtBit16(bitItems, IS_FACE_TRACKER_CONNECTED); - auto newEyeTrackerConnected = oneAtBit16(bitItems, IS_EYE_TRACKER_CONNECTED); + auto newHasntProceduralEyeMovement = oneAtBit16(bitItems, IS_EYE_TRACKER_CONNECTED); auto newHasAudioEnabledFaceMovement = oneAtBit16(bitItems, AUDIO_ENABLED_FACE_MOVEMENT); auto newHasProceduralEyeFaceMovement = oneAtBit16(bitItems, PROCEDURAL_EYE_FACE_MOVEMENT); @@ -1161,7 +1162,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { bool keyStateChanged = (_keyState != newKeyState); bool handStateChanged = (_handState != newHandState); bool faceStateChanged = (_headData->_isFaceTrackerConnected != newFaceTrackerConnected); - bool eyeStateChanged = (_headData->_isEyeTrackerConnected != newEyeTrackerConnected); + bool eyeStateChanged = (_headData->_hasProceduralEyeMovement == newHasntProceduralEyeMovement); bool audioEnableFaceMovementChanged = (_headData->getHasAudioEnabledFaceMovement() != newHasAudioEnabledFaceMovement); bool proceduralEyeFaceMovementChanged = (_headData->getHasProceduralEyeFaceMovement() != newHasProceduralEyeFaceMovement); bool proceduralBlinkFaceMovementChanged = (_headData->getHasProceduralBlinkFaceMovement() != newHasProceduralBlinkFaceMovement); @@ -1174,7 +1175,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { _keyState = newKeyState; _handState = newHandState; _headData->_isFaceTrackerConnected = newFaceTrackerConnected; - _headData->_isEyeTrackerConnected = newEyeTrackerConnected; + _headData->setHasProceduralEyeMovement(!newHasntProceduralEyeMovement); _headData->setHasAudioEnabledFaceMovement(newHasAudioEnabledFaceMovement); _headData->setHasProceduralEyeFaceMovement(newHasProceduralEyeFaceMovement); _headData->setHasProceduralBlinkFaceMovement(newHasProceduralBlinkFaceMovement); diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index 19f5efcd16..c86e534929 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -196,3 +196,40 @@ void HeadData::fromJson(const QJsonObject& json) { setHeadOrientation(quatFromJsonValue(json[JSON_AVATAR_HEAD_ROTATION])); } } + +bool HeadData::getHasProceduralEyeFaceMovement() const { + return _hasProceduralEyeFaceMovement; +} + +void HeadData::setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement) { + _hasProceduralEyeFaceMovement = hasProceduralEyeFaceMovement; +} + +bool HeadData::getHasProceduralBlinkFaceMovement() const { + // return _hasProceduralBlinkFaceMovement; + return _hasProceduralBlinkFaceMovement && !_isFaceTrackerConnected; +} + +void HeadData::setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement) { + _hasProceduralBlinkFaceMovement = hasProceduralBlinkFaceMovement; +} + +bool HeadData::getHasAudioEnabledFaceMovement() const { + return _hasAudioEnabledFaceMovement; +} + +void HeadData::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) { + _hasAudioEnabledFaceMovement = hasAudioEnabledFaceMovement; +} + +bool HeadData::getHasProceduralEyeMovement() const { + return _hasProceduralEyeMovement; +} + +void HeadData::setHasProceduralEyeMovement(bool hasProceduralEyeMovement) { + _hasProceduralEyeMovement = hasProceduralEyeMovement; +} + +void HeadData::setFaceTrackerConnected(bool value) { + _isFaceTrackerConnected = value; +} diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index 6d211da2cd..dc5aaf2595 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -72,23 +72,17 @@ public: } bool lookAtPositionChangedSince(quint64 time) { return _lookAtPositionChanged >= time; } - bool getHasProceduralEyeFaceMovement() const { return _hasProceduralEyeFaceMovement; } + bool getHasProceduralEyeFaceMovement() const; + void setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement); + bool getHasProceduralBlinkFaceMovement() const; + void setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement); + bool getHasAudioEnabledFaceMovement() const; + void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement); + bool getHasProceduralEyeMovement() const; + void setHasProceduralEyeMovement(bool hasProceduralEyeMovement); - void setHasProceduralEyeFaceMovement(const bool hasProceduralEyeFaceMovement) { - _hasProceduralEyeFaceMovement = hasProceduralEyeFaceMovement; - } - - bool getHasProceduralBlinkFaceMovement() const { return _hasProceduralBlinkFaceMovement; } - - void setHasProceduralBlinkFaceMovement(const bool hasProceduralBlinkFaceMovement) { - _hasProceduralBlinkFaceMovement = hasProceduralBlinkFaceMovement; - } - - bool getHasAudioEnabledFaceMovement() const { return _hasAudioEnabledFaceMovement; } - - void setHasAudioEnabledFaceMovement(const bool hasAudioEnabledFaceMovement) { - _hasAudioEnabledFaceMovement = hasAudioEnabledFaceMovement; - } + void setFaceTrackerConnected(bool value); + bool getFaceTrackerConnected() const { return _isFaceTrackerConnected; } friend class AvatarData; @@ -107,8 +101,10 @@ protected: bool _hasAudioEnabledFaceMovement { true }; bool _hasProceduralBlinkFaceMovement { true }; bool _hasProceduralEyeFaceMovement { true }; + bool _hasProceduralEyeMovement { true }; + bool _isFaceTrackerConnected { false }; - bool _isEyeTrackerConnected { false }; + float _leftEyeBlink { 0.0f }; float _rightEyeBlink { 0.0f }; float _averageLoudness { 0.0f }; From a288c0a52d73a9974283aa7e751885828672cbdd Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 7 Sep 2019 18:14:27 -0700 Subject: [PATCH 23/40] remove old/unused eyetracker code. --- interface/src/Application.cpp | 143 +--------- interface/src/avatar/MyAvatar.cpp | 244 +++++++++++++++++- interface/src/avatar/MyAvatar.h | 8 + interface/src/avatar/MySkeletonModel.cpp | 13 +- .../src/avatars-renderer/SkeletonModel.cpp | 35 ++- .../src/avatars-renderer/SkeletonModel.h | 3 + 6 files changed, 284 insertions(+), 162 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b719f26c68..a6b903ec09 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -60,6 +60,7 @@ #include #include #include +#include #include #include #include @@ -154,7 +155,6 @@ #include #include #include -#include #include #include #include @@ -878,7 +878,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); #endif - DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -1997,12 +1996,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(ddeTracker.data(), &FaceTracker::muteToggled, this, &Application::faceTrackerMuteToggled); #endif -#ifdef HAVE_IVIEWHMD - auto eyeTracker = DependencyManager::get(); - eyeTracker->init(); - setActiveEyeTracker(); -#endif - // If launched from Steam, let it handle updates const QString HIFI_NO_UPDATER_COMMAND_LINE_KEY = "--no-updater"; bool noUpdater = arguments().indexOf(HIFI_NO_UPDATER_COMMAND_LINE_KEY) != -1; @@ -2746,9 +2739,6 @@ void Application::cleanupBeforeQuit() { // Stop third party processes so that they're not left running in the event of a subsequent shutdown crash. #ifdef HAVE_DDE DependencyManager::get()->setEnabled(false); -#endif -#ifdef HAVE_IVIEWHMD - DependencyManager::get()->setEnabled(false, true); #endif AnimDebugDraw::getInstance().shutdown(); @@ -2823,9 +2813,6 @@ void Application::cleanupBeforeQuit() { #ifdef HAVE_DDE DependencyManager::destroy(); #endif -#ifdef HAVE_IVIEWHMD - DependencyManager::destroy(); -#endif DependencyManager::destroy(); // Must be destroyed before TabletScriptingInterface @@ -2834,7 +2821,7 @@ void Application::cleanupBeforeQuit() { DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); - + DependencyManager::destroy(); _snapshotSoundInjector = nullptr; @@ -5328,35 +5315,6 @@ void Application::setActiveFaceTracker() const { #endif } -#ifdef HAVE_IVIEWHMD -void Application::setActiveEyeTracker() { - auto eyeTracker = DependencyManager::get(); - if (!eyeTracker->isInitialized()) { - return; - } - - bool isEyeTracking = Menu::getInstance()->isOptionChecked(MenuOption::SMIEyeTracking); - bool isSimulating = Menu::getInstance()->isOptionChecked(MenuOption::SimulateEyeTracking); - eyeTracker->setEnabled(isEyeTracking, isSimulating); - - Menu::getInstance()->getActionForOption(MenuOption::OnePointCalibration)->setEnabled(isEyeTracking && !isSimulating); - Menu::getInstance()->getActionForOption(MenuOption::ThreePointCalibration)->setEnabled(isEyeTracking && !isSimulating); - Menu::getInstance()->getActionForOption(MenuOption::FivePointCalibration)->setEnabled(isEyeTracking && !isSimulating); -} - -void Application::calibrateEyeTracker1Point() { - DependencyManager::get()->calibrate(1); -} - -void Application::calibrateEyeTracker3Points() { - DependencyManager::get()->calibrate(3); -} - -void Application::calibrateEyeTracker5Points() { - DependencyManager::get()->calibrate(5); -} -#endif - bool Application::exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset) { @@ -5830,8 +5788,8 @@ void Application::pushPostUpdateLambda(void* key, const std::function& f _postUpdateLambdas[key] = func; } -// Called during Application::update immediately before AvatarManager::updateMyAvatar, updating my data that is then sent to everyone. -// (Maybe this code should be moved there?) +// Called during Application::update immediately before AvatarManager::updateMyAvatar, updating my data that is then sent +// to everyone. // The principal result is to call updateLookAtTargetAvatar() and then setLookAtPosition(). // Note that it is called BEFORE we update position or joints based on sensors, etc. void Application::updateMyAvatarLookAtPosition() { @@ -5840,91 +5798,8 @@ void Application::updateMyAvatarLookAtPosition() { PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()"); auto myAvatar = getMyAvatar(); - myAvatar->updateLookAtTargetAvatar(); FaceTracker* faceTracker = getActiveFaceTracker(); - auto eyeTracker = DependencyManager::get(); - - bool isLookingAtSomeone = false; - bool isHMD = qApp->isHMDMode(); - glm::vec3 lookAtSpot; - if (eyeTracker->isTracking() && (isHMD || eyeTracker->isSimulating())) { - // Look at the point that the user is looking at. - glm::vec3 lookAtPosition = eyeTracker->getLookAtPosition(); - if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { - lookAtPosition.x = -lookAtPosition.x; - } - if (isHMD) { - // TODO -- this code is probably wrong, getHeadPose() returns something in sensor frame, not avatar - glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose(); - glm::quat hmdRotation = glm::quat_cast(headPose); - lookAtSpot = _myCamera.getPosition() + myAvatar->getWorldOrientation() * (hmdRotation * lookAtPosition); - } else { - lookAtSpot = myAvatar->getHead()->getEyePosition() - + (myAvatar->getHead()->getFinalOrientationInWorldFrame() * lookAtPosition); - } - } else { - AvatarSharedPointer lookingAt = myAvatar->getLookAtTargetAvatar().lock(); - bool haveLookAtCandidate = lookingAt && myAvatar.get() != lookingAt.get(); - auto avatar = static_pointer_cast(lookingAt); - bool mutualLookAtSnappingEnabled = avatar && avatar->getLookAtSnappingEnabled() && myAvatar->getLookAtSnappingEnabled(); - if (haveLookAtCandidate && mutualLookAtSnappingEnabled) { - // If I am looking at someone else, look directly at one of their eyes - isLookingAtSomeone = true; - auto lookingAtHead = avatar->getHead(); - - const float MAXIMUM_FACE_ANGLE = 65.0f * RADIANS_PER_DEGREE; - glm::vec3 lookingAtFaceOrientation = lookingAtHead->getFinalOrientationInWorldFrame() * IDENTITY_FORWARD; - glm::vec3 fromLookingAtToMe = glm::normalize(myAvatar->getHead()->getEyePosition() - - lookingAtHead->getEyePosition()); - float faceAngle = glm::angle(lookingAtFaceOrientation, fromLookingAtToMe); - - if (faceAngle < MAXIMUM_FACE_ANGLE) { - // Randomly look back and forth between look targets - eyeContactTarget target = Menu::getInstance()->isOptionChecked(MenuOption::FixGaze) ? - LEFT_EYE : myAvatar->getEyeContactTarget(); - switch (target) { - case LEFT_EYE: - lookAtSpot = lookingAtHead->getLeftEyePosition(); - break; - case RIGHT_EYE: - lookAtSpot = lookingAtHead->getRightEyePosition(); - break; - case MOUTH: - lookAtSpot = lookingAtHead->getMouthPosition(); - break; - } - } else { - // Just look at their head (mid point between eyes) - lookAtSpot = lookingAtHead->getEyePosition(); - } - } else { - // I am not looking at anyone else, so just look forward - auto headPose = myAvatar->getControllerPoseInWorldFrame(controller::Action::HEAD); - if (headPose.isValid()) { - lookAtSpot = transformPoint(headPose.getMatrix(), glm::vec3(0.0f, 0.0f, TREE_SCALE)); - } else { - lookAtSpot = myAvatar->getHead()->getEyePosition() + - (myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); - } - } - - // Deflect the eyes a bit to match the detected gaze from the face tracker if active. - if (faceTracker && !faceTracker->isMuted()) { - float eyePitch = faceTracker->getEstimatedEyePitch(); - float eyeYaw = faceTracker->getEstimatedEyeYaw(); - const float GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT = 0.1f; - glm::vec3 origin = myAvatar->getHead()->getEyePosition(); - float deflection = faceTracker->getEyeDeflection(); - if (isLookingAtSomeone) { - deflection *= GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT; - } - lookAtSpot = origin + _myCamera.getOrientation() * glm::quat(glm::radians(glm::vec3( - eyePitch * deflection, eyeYaw * deflection, 0.0f))) * - glm::inverse(_myCamera.getOrientation()) * (lookAtSpot - origin); - } - } - - myAvatar->getHead()->setLookAtPosition(lookAtSpot); + myAvatar->updateLookAtPosition(faceTracker, _myCamera); } void Application::updateThreads(float deltaTime) { @@ -6496,7 +6371,10 @@ void Application::update(float deltaTime) { controller::Action::LEFT_UP_LEG, controller::Action::RIGHT_UP_LEG, controller::Action::LEFT_TOE_BASE, - controller::Action::RIGHT_TOE_BASE + controller::Action::RIGHT_TOE_BASE, + controller::Action::LEFT_EYE, + controller::Action::RIGHT_EYE + }; // copy controller poses from userInputMapper to myAvatar. @@ -7171,8 +7049,7 @@ void Application::resetSensors(bool andReload) { #ifdef HAVE_DDE DependencyManager::get()->reset(); #endif - - DependencyManager::get()->reset(); + _overlayConductor.centerUI(); getActiveDisplayPlugin()->resetSensors(); getMyAvatar()->reset(true, andReload); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4d1c20010c..a4000c5233 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -772,6 +772,18 @@ void MyAvatar::update(float deltaTime) { emit energyChanged(currentEnergy); updateEyeContactTarget(deltaTime); + + // if we're getting eye rotations from a tracker, disable observer-side procedural eye motions + auto userInputMapper = DependencyManager::get(); + bool eyesTracked = + userInputMapper->getPoseState(controller::Action::LEFT_EYE).valid && + userInputMapper->getPoseState(controller::Action::RIGHT_EYE).valid; + + int leftEyeJointIndex = getJointIndex("LeftEye"); + int rightEyeJointIndex = getJointIndex("RightEye"); + bool eyesAreOverridden = getIsJointOverridden(leftEyeJointIndex) || getIsJointOverridden(rightEyeJointIndex); + + _headData->setHasProceduralEyeMovement(!(eyesTracked || eyesAreOverridden)); } void MyAvatar::updateEyeContactTarget(float deltaTime) { @@ -1454,8 +1466,50 @@ void MyAvatar::setEnableDebugDrawHandControllers(bool isEnabled) { _enableDebugDrawHandControllers = isEnabled; if (!isEnabled) { - DebugDraw::getInstance().removeMarker("leftHandController"); - DebugDraw::getInstance().removeMarker("rightHandController"); + DebugDraw::getInstance().removeMarker("LEFT_HAND"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND"); + + DebugDraw::getInstance().removeMarker("LEFT_HAND_THUMB1"); + DebugDraw::getInstance().removeMarker("LEFT_HAND_THUMB2"); + DebugDraw::getInstance().removeMarker("LEFT_HAND_THUMB3"); + DebugDraw::getInstance().removeMarker("LEFT_HAND_THUMB4"); + DebugDraw::getInstance().removeMarker("LEFT_HAND_INDEX1"); + DebugDraw::getInstance().removeMarker("LEFT_HAND_INDEX2"); + DebugDraw::getInstance().removeMarker("LEFT_HAND_INDEX3"); + DebugDraw::getInstance().removeMarker("LEFT_HAND_INDEX4"); + DebugDraw::getInstance().removeMarker("LEFT_HAND_MIDDLE1"); + DebugDraw::getInstance().removeMarker("LEFT_HAND_MIDDLE2"); + DebugDraw::getInstance().removeMarker("LEFT_HAND_MIDDLE3"); + DebugDraw::getInstance().removeMarker("LEFT_HAND_MIDDLE4"); + DebugDraw::getInstance().removeMarker("LEFT_HAND_RING1"); + DebugDraw::getInstance().removeMarker("LEFT_HAND_RING2"); + DebugDraw::getInstance().removeMarker("LEFT_HAND_RING3"); + DebugDraw::getInstance().removeMarker("LEFT_HAND_RING4"); + DebugDraw::getInstance().removeMarker("LEFT_HAND_PINKY1"); + DebugDraw::getInstance().removeMarker("LEFT_HAND_PINKY2"); + DebugDraw::getInstance().removeMarker("LEFT_HAND_PINKY3"); + DebugDraw::getInstance().removeMarker("LEFT_HAND_PINKY4"); + + DebugDraw::getInstance().removeMarker("RIGHT_HAND_THUMB1"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND_THUMB2"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND_THUMB3"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND_THUMB4"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND_INDEX1"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND_INDEX2"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND_INDEX3"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND_INDEX4"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND_MIDDLE1"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND_MIDDLE2"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND_MIDDLE3"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND_MIDDLE4"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND_RING1"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND_RING2"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND_RING3"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND_RING4"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND_PINKY1"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND_PINKY2"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND_PINKY3"); + DebugDraw::getInstance().removeMarker("RIGHT_HAND_PINKY4"); } } @@ -3097,6 +3151,16 @@ void MyAvatar::animGraphLoaded() { disconnect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded())); } +void MyAvatar::debugDrawPose(controller::Action action, const char* channelName, float size) { + auto pose = getControllerPoseInWorldFrame(action); + if (pose.isValid()) { + DebugDraw::getInstance().addMarker(channelName, pose.getRotation(), pose.getTranslation(), glm::vec4(1), size); + } else { + DebugDraw::getInstance().removeMarker(channelName); + } +} + + void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { Avatar::postUpdate(deltaTime, scene); @@ -3137,20 +3201,50 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { } if (_enableDebugDrawHandControllers) { - auto leftHandPose = getControllerPoseInWorldFrame(controller::Action::LEFT_HAND); - auto rightHandPose = getControllerPoseInWorldFrame(controller::Action::RIGHT_HAND); + debugDrawPose(controller::Action::LEFT_HAND, "LEFT_HAND", 1.0); + debugDrawPose(controller::Action::RIGHT_HAND, "RIGHT_HAND", 1.0); - if (leftHandPose.isValid()) { - DebugDraw::getInstance().addMarker("leftHandController", leftHandPose.getRotation(), leftHandPose.getTranslation(), glm::vec4(1)); - } else { - DebugDraw::getInstance().removeMarker("leftHandController"); - } + debugDrawPose(controller::Action::LEFT_HAND_THUMB1, "LEFT_HAND_THUMB1", 0.1f); + debugDrawPose(controller::Action::LEFT_HAND_THUMB2, "LEFT_HAND_THUMB2", 0.1f); + debugDrawPose(controller::Action::LEFT_HAND_THUMB3, "LEFT_HAND_THUMB3", 0.1f); + debugDrawPose(controller::Action::LEFT_HAND_THUMB4, "LEFT_HAND_THUMB4", 0.1f); + debugDrawPose(controller::Action::LEFT_HAND_INDEX1, "LEFT_HAND_INDEX1", 0.1f); + debugDrawPose(controller::Action::LEFT_HAND_INDEX2, "LEFT_HAND_INDEX2", 0.1f); + debugDrawPose(controller::Action::LEFT_HAND_INDEX3, "LEFT_HAND_INDEX3", 0.1f); + debugDrawPose(controller::Action::LEFT_HAND_INDEX4, "LEFT_HAND_INDEX4", 0.1f); + debugDrawPose(controller::Action::LEFT_HAND_MIDDLE1, "LEFT_HAND_MIDDLE1", 0.1f); + debugDrawPose(controller::Action::LEFT_HAND_MIDDLE2, "LEFT_HAND_MIDDLE2", 0.1f); + debugDrawPose(controller::Action::LEFT_HAND_MIDDLE3, "LEFT_HAND_MIDDLE3", 0.1f); + debugDrawPose(controller::Action::LEFT_HAND_MIDDLE4, "LEFT_HAND_MIDDLE4", 0.1f); + debugDrawPose(controller::Action::LEFT_HAND_RING1, "LEFT_HAND_RING1", 0.1f); + debugDrawPose(controller::Action::LEFT_HAND_RING2, "LEFT_HAND_RING2", 0.1f); + debugDrawPose(controller::Action::LEFT_HAND_RING3, "LEFT_HAND_RING3", 0.1f); + debugDrawPose(controller::Action::LEFT_HAND_RING4, "LEFT_HAND_RING4", 0.1f); + debugDrawPose(controller::Action::LEFT_HAND_PINKY1, "LEFT_HAND_PINKY1", 0.1f); + debugDrawPose(controller::Action::LEFT_HAND_PINKY2, "LEFT_HAND_PINKY2", 0.1f); + debugDrawPose(controller::Action::LEFT_HAND_PINKY3, "LEFT_HAND_PINKY3", 0.1f); + debugDrawPose(controller::Action::LEFT_HAND_PINKY4, "LEFT_HAND_PINKY4", 0.1f); - if (rightHandPose.isValid()) { - DebugDraw::getInstance().addMarker("rightHandController", rightHandPose.getRotation(), rightHandPose.getTranslation(), glm::vec4(1)); - } else { - DebugDraw::getInstance().removeMarker("rightHandController"); - } + debugDrawPose(controller::Action::RIGHT_HAND_THUMB1, "RIGHT_HAND_THUMB1", 0.1f); + debugDrawPose(controller::Action::RIGHT_HAND_THUMB2, "RIGHT_HAND_THUMB2", 0.1f); + debugDrawPose(controller::Action::RIGHT_HAND_THUMB3, "RIGHT_HAND_THUMB3", 0.1f); + debugDrawPose(controller::Action::RIGHT_HAND_THUMB4, "RIGHT_HAND_THUMB4", 0.1f); + debugDrawPose(controller::Action::RIGHT_HAND_INDEX1, "RIGHT_HAND_INDEX1", 0.1f); + debugDrawPose(controller::Action::RIGHT_HAND_INDEX2, "RIGHT_HAND_INDEX2", 0.1f); + debugDrawPose(controller::Action::RIGHT_HAND_INDEX3, "RIGHT_HAND_INDEX3", 0.1f); + debugDrawPose(controller::Action::RIGHT_HAND_INDEX4, "RIGHT_HAND_INDEX4", 0.1f); + debugDrawPose(controller::Action::RIGHT_HAND_MIDDLE1, "RIGHT_HAND_MIDDLE1", 0.1f); + debugDrawPose(controller::Action::RIGHT_HAND_MIDDLE2, "RIGHT_HAND_MIDDLE2", 0.1f); + debugDrawPose(controller::Action::RIGHT_HAND_MIDDLE3, "RIGHT_HAND_MIDDLE3", 0.1f); + debugDrawPose(controller::Action::RIGHT_HAND_MIDDLE4, "RIGHT_HAND_MIDDLE4", 0.1f); + debugDrawPose(controller::Action::RIGHT_HAND_RING1, "RIGHT_HAND_RING1", 0.1f); + debugDrawPose(controller::Action::RIGHT_HAND_RING2, "RIGHT_HAND_RING2", 0.1f); + debugDrawPose(controller::Action::RIGHT_HAND_RING3, "RIGHT_HAND_RING3", 0.1f); + debugDrawPose(controller::Action::RIGHT_HAND_RING4, "RIGHT_HAND_RING4", 0.1f); + debugDrawPose(controller::Action::RIGHT_HAND_PINKY1, "RIGHT_HAND_PINKY1", 0.1f); + debugDrawPose(controller::Action::RIGHT_HAND_PINKY2, "RIGHT_HAND_PINKY2", 0.1f); + debugDrawPose(controller::Action::RIGHT_HAND_PINKY3, "RIGHT_HAND_PINKY3", 0.1f); + debugDrawPose(controller::Action::RIGHT_HAND_PINKY4, "RIGHT_HAND_PINKY4", 0.1f); } DebugDraw::getInstance().updateMyAvatarPos(getWorldPosition()); @@ -6290,3 +6384,125 @@ void MyAvatar::endSit(const glm::vec3& position, const glm::quat& rotation) { }); } } + +bool MyAvatar::getIsJointOverridden(int jointIndex) const { + // has this joint been set by a script? + return _skeletonModel->getIsJointOverridden(jointIndex); +} + +void MyAvatar::updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera) { + + updateLookAtTargetAvatar(); + + bool isLookingAtSomeone = false; + glm::vec3 lookAtSpot; + + const MyHead* myHead = getMyHead(); + + int leftEyeJointIndex = getJointIndex("LeftEye"); + int rightEyeJointIndex = getJointIndex("RightEye"); + bool eyesAreOverridden = getIsJointOverridden(leftEyeJointIndex) || + getIsJointOverridden(rightEyeJointIndex); + if (eyesAreOverridden) { + // A script has set the eye rotations, so use these to set lookAtSpot + glm::quat leftEyeRotation = getAbsoluteJointRotationInObjectFrame(leftEyeJointIndex); + glm::quat rightEyeRotation = getAbsoluteJointRotationInObjectFrame(rightEyeJointIndex); + glm::vec3 leftVec = getWorldOrientation() * leftEyeRotation * IDENTITY_FORWARD; + glm::vec3 rightVec = getWorldOrientation() * rightEyeRotation * IDENTITY_FORWARD; + glm::vec3 leftEyePosition = myHead->getLeftEyePosition(); + glm::vec3 rightEyePosition = myHead->getRightEyePosition(); + float t1, t2; + bool success = findClosestApproachOfLines(leftEyePosition, leftVec, rightEyePosition, rightVec, t1, t2); + if (success) { + glm::vec3 leftFocus = leftEyePosition + leftVec * t1; + glm::vec3 rightFocus = rightEyePosition + rightVec * t2; + lookAtSpot = (leftFocus + rightFocus) / 2.0f; // average + } else { + lookAtSpot = myHead->getEyePosition() + glm::normalize(leftVec) * 1000.0f; + } + } else { + controller::Pose leftEyePose = getControllerPoseInAvatarFrame(controller::Action::LEFT_EYE); + controller::Pose rightEyePose = getControllerPoseInAvatarFrame(controller::Action::RIGHT_EYE); + if (leftEyePose.isValid() && rightEyePose.isValid()) { + // an eye tracker is in use, set lookAtSpot from this + glm::vec3 leftVec = getWorldOrientation() * leftEyePose.rotation * glm::vec3(0.0f, 0.0f, -1.0f); + glm::vec3 rightVec = getWorldOrientation() * rightEyePose.rotation * glm::vec3(0.0f, 0.0f, -1.0f); + + glm::vec3 leftEyePosition = myHead->getLeftEyePosition(); + glm::vec3 rightEyePosition = myHead->getRightEyePosition(); + float t1, t2; + bool success = findClosestApproachOfLines(leftEyePosition, leftVec, rightEyePosition, rightVec, t1, t2); + if (success) { + glm::vec3 leftFocus = leftEyePosition + leftVec * t1; + glm::vec3 rightFocus = rightEyePosition + rightVec * t2; + lookAtSpot = (leftFocus + rightFocus) / 2.0f; // average + } else { + lookAtSpot = myHead->getEyePosition() + glm::normalize(leftVec) * 1000.0f; + } + } else { + // no script override, no eye tracker, so do procedural eye motion + AvatarSharedPointer lookingAt = getLookAtTargetAvatar().lock(); + bool haveLookAtCandidate = lookingAt && this != lookingAt.get(); + auto avatar = static_pointer_cast(lookingAt); + bool mutualLookAtSnappingEnabled = + avatar && avatar->getLookAtSnappingEnabled() && getLookAtSnappingEnabled(); + if (haveLookAtCandidate && mutualLookAtSnappingEnabled) { + // If I am looking at someone else, look directly at one of their eyes + isLookingAtSomeone = true; + auto lookingAtHead = avatar->getHead(); + + const float MAXIMUM_FACE_ANGLE = 65.0f * RADIANS_PER_DEGREE; + glm::vec3 lookingAtFaceOrientation = lookingAtHead->getFinalOrientationInWorldFrame() * IDENTITY_FORWARD; + glm::vec3 fromLookingAtToMe = glm::normalize(getHead()->getEyePosition() + - lookingAtHead->getEyePosition()); + float faceAngle = glm::angle(lookingAtFaceOrientation, fromLookingAtToMe); + + if (faceAngle < MAXIMUM_FACE_ANGLE) { + // Randomly look back and forth between look targets + eyeContactTarget target = Menu::getInstance()->isOptionChecked(MenuOption::FixGaze) ? + LEFT_EYE : getEyeContactTarget(); + switch (target) { + case LEFT_EYE: + lookAtSpot = lookingAtHead->getLeftEyePosition(); + break; + case RIGHT_EYE: + lookAtSpot = lookingAtHead->getRightEyePosition(); + break; + case MOUTH: + lookAtSpot = lookingAtHead->getMouthPosition(); + break; + } + } else { + // Just look at their head (mid point between eyes) + lookAtSpot = lookingAtHead->getEyePosition(); + } + } else { + // I am not looking at anyone else, so just look forward + auto headPose = getControllerPoseInWorldFrame(controller::Action::HEAD); + if (headPose.isValid()) { + lookAtSpot = transformPoint(headPose.getMatrix(), glm::vec3(0.0f, 0.0f, TREE_SCALE)); + } else { + lookAtSpot = myHead->getEyePosition() + + (getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE)); + } + } + + // Deflect the eyes a bit to match the detected gaze from the face tracker if active. + if (faceTracker && !faceTracker->isMuted()) { + float eyePitch = faceTracker->getEstimatedEyePitch(); + float eyeYaw = faceTracker->getEstimatedEyeYaw(); + const float GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT = 0.1f; + glm::vec3 origin = myHead->getEyePosition(); + float deflection = faceTracker->getEyeDeflection(); + if (isLookingAtSomeone) { + deflection *= GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT; + } + lookAtSpot = origin + myCamera.getOrientation() * glm::quat(glm::radians(glm::vec3( + eyePitch * deflection, eyeYaw * deflection, 0.0f))) * + glm::inverse(myCamera.getOrientation()) * (lookAtSpot - origin); + } + } + } + + getHead()->setLookAtPosition(lookAtSpot); +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 0108fb5eda..9cc863471a 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -29,10 +29,12 @@ #include #include #include +#include #include "AtRestDetector.h" #include "MyCharacterController.h" #include "RingBufferHistory.h" +#include "devices/DdeFaceTracker.h" class AvatarActionHold; class ModelItemID; @@ -1864,6 +1866,8 @@ public: bool getFlowActive() const; bool getNetworkGraphActive() const; + void updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera); + // sets the reaction enabled and triggered parameters of the passed in params // also clears internal reaction triggers void updateRigControllerParameters(Rig::ControllerParameters& params); @@ -1871,6 +1875,10 @@ public: // Don't substitute verify-fail: virtual const QUrl& getSkeletonModelURL() const override { return _skeletonModelURL; } + void debugDrawPose(controller::Action action, const char* channelName, float size); + + bool getIsJointOverridden(int jointIndex) const; + public slots: /**jsdoc diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 0229eeee76..38065c8095 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -114,13 +114,12 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { Head* head = _owningAvatar->getHead(); - // make sure lookAt is not too close to face (avoid crosseyes) - glm::vec3 lookAt = head->getLookAtPosition(); - glm::vec3 focusOffset = lookAt - _owningAvatar->getHead()->getEyePosition(); - float focusDistance = glm::length(focusOffset); - const float MIN_LOOK_AT_FOCUS_DISTANCE = 1.0f; - if (focusDistance < MIN_LOOK_AT_FOCUS_DISTANCE && focusDistance > EPSILON) { - lookAt = _owningAvatar->getHead()->getEyePosition() + (MIN_LOOK_AT_FOCUS_DISTANCE / focusDistance) * focusOffset; + bool eyePosesValid = !head->getHasProceduralEyeMovement(); + glm::vec3 lookAt; + if (eyePosesValid) { + lookAt = head->getLookAtPosition(); // don't apply no-crosseyes code when eyes are being tracked + } else { + lookAt = avoidCrossedEyes(head->getLookAtPosition()); } MyAvatar* myAvatar = static_cast(_owningAvatar); diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index 40b65c54a1..e0fed08955 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -93,19 +93,30 @@ void SkeletonModel::initJointStates() { emit skeletonLoaded(); } +glm::vec3 SkeletonModel::avoidCrossedEyes(const glm::vec3& lookAt) { + // make sure lookAt is not too close to face (avoid crosseyes) + glm::vec3 focusOffset = lookAt - _owningAvatar->getHead()->getEyePosition(); + float focusDistance = glm::length(focusOffset); + const float MIN_LOOK_AT_FOCUS_DISTANCE = 1.0f; + if (focusDistance < MIN_LOOK_AT_FOCUS_DISTANCE && focusDistance > EPSILON) { + return _owningAvatar->getHead()->getEyePosition() + (MIN_LOOK_AT_FOCUS_DISTANCE / focusDistance) * focusOffset; + } else { + return lookAt; + } +} + // Called within Model::simulate call, below. void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { assert(!_owningAvatar->isMyAvatar()); Head* head = _owningAvatar->getHead(); - // make sure lookAt is not too close to face (avoid crosseyes) - glm::vec3 lookAt = head->getCorrectedLookAtPosition(); - glm::vec3 focusOffset = lookAt - _owningAvatar->getHead()->getEyePosition(); - float focusDistance = glm::length(focusOffset); - const float MIN_LOOK_AT_FOCUS_DISTANCE = 1.0f; - if (focusDistance < MIN_LOOK_AT_FOCUS_DISTANCE && focusDistance > EPSILON) { - lookAt = _owningAvatar->getHead()->getEyePosition() + (MIN_LOOK_AT_FOCUS_DISTANCE / focusDistance) * focusOffset; + bool eyePosesValid = !head->getHasProceduralEyeMovement(); + glm::vec3 lookAt; + if (eyePosesValid) { + lookAt = head->getLookAtPosition(); // don't apply no-crosseyes code etc when eyes are being tracked + } else { + lookAt = avoidCrossedEyes(head->getCorrectedLookAtPosition()); } // no need to call Model::updateRig() because otherAvatars get their joint state @@ -288,6 +299,15 @@ bool SkeletonModel::getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& return false; } + +bool SkeletonModel::getIsJointOverridden(int jointIndex) const { + // has this joint been set by a script? + if (!isLoaded() || _rig.jointStatesEmpty()) { + return false; + } + return _rig.getIsJointOverridden(jointIndex); +} + bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const { if (getEyeModelPositions(firstEyePosition, secondEyePosition)) { firstEyePosition = _translation + _rotation * firstEyePosition; @@ -352,4 +372,3 @@ bool SkeletonModel::hasSkeleton() { void SkeletonModel::onInvalidate() { } - diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h index 99f6632306..6b0bd79f0b 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h @@ -37,9 +37,12 @@ public: void initJointStates() override; void simulate(float deltaTime, bool fullUpdate = true) override; + glm::vec3 avoidCrossedEyes(const glm::vec3& lookAt); void updateRig(float deltaTime, glm::mat4 parentTransform) override; void updateAttitude(const glm::quat& orientation); + bool getIsJointOverridden(int jointIndex) const; + /// Returns the index of the left hand joint, or -1 if not found. int getLeftHandJointIndex() const { return isActive() ? _rig.indexOfJoint("LeftHand") : -1; } From 8875453585e18b42af7ea7f3fe1a0b6417ce8e2a Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 7 Sep 2019 18:15:19 -0700 Subject: [PATCH 24/40] remove old/unused eyetracker code --- cmake/modules/FindiViewHMD.cmake | 38 --- interface/external/iViewHMD/readme.txt | 14 - .../trackers/src/trackers/EyeTracker.cpp | 307 ------------------ libraries/trackers/src/trackers/EyeTracker.h | 68 ---- 4 files changed, 427 deletions(-) delete mode 100644 cmake/modules/FindiViewHMD.cmake delete mode 100644 interface/external/iViewHMD/readme.txt delete mode 100644 libraries/trackers/src/trackers/EyeTracker.cpp delete mode 100644 libraries/trackers/src/trackers/EyeTracker.h diff --git a/cmake/modules/FindiViewHMD.cmake b/cmake/modules/FindiViewHMD.cmake deleted file mode 100644 index e408c92380..0000000000 --- a/cmake/modules/FindiViewHMD.cmake +++ /dev/null @@ -1,38 +0,0 @@ -# -# FindiViewHMD.cmake -# -# Try to find the SMI iViewHMD eye tracker library -# -# You must provide a IVIEWHMD_ROOT_DIR which contains 3rdParty, include, and libs directories -# -# Once done this will define -# -# IVIEWHMD_FOUND - system found iViewHMD -# IVIEWHMD_INCLUDE_DIRS - the iViewHMD include directory -# IVIEWHMD_LIBRARIES - link this to use iViewHMD -# -# Created on 27 Jul 2015 by David Rowe -# Copyright 2015 High Fidelity, Inc. -# - -if (WIN32) - - include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") - hifi_library_search_hints("iViewHMD") - - find_path(IVIEWHMD_INCLUDE_DIRS iViewHMDAPI.h PATH_SUFFIXES include HINTS ${IVIEWHMD_SEARCH_DIRS}) - find_library(IVIEWHMD_LIBRARIES NAMES iViewHMDAPI PATH_SUFFIXES libs/x86 HINTS ${IVIEWHMD_SEARCH_DIRS}) - find_path(IVIEWHMD_API_DLL_PATH iViewHMDAPI.dll PATH_SUFFIXES libs/x86 HINTS ${IVIEWHMD_SEARCH_DIRS}) - list(APPEND IVIEWHMD_REQUIREMENTS IVIEWHMD_INCLUDE_DIRS IVIEWHMD_LIBRARIES IVIEWHMD_API_DLL_PATH) - - find_path(IVIEWHMD_DLL_PATH_3RD_PARTY libiViewNG.dll PATH_SUFFIXES 3rdParty HINTS ${IVIEWHMD_SEARCH_DIRS}) - list(APPEND IVIEWHMD_REQUIREMENTS IVIEWHMD_DLL_PATH_3RD_PARTY) - - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(IVIEWHMD DEFAULT_MSG ${IVIEWHMD_REQUIREMENTS}) - - add_paths_to_fixup_libs(${IVIEWHMD_API_DLL_PATH} ${IVIEWHMD_DLL_PATH_3RD_PARTY}) - - mark_as_advanced(IVIEWHMD_INCLUDE_DIRS IVIEWHMD_LIBRARIES IVIEWHMD_SEARCH_DIRS) - -endif() diff --git a/interface/external/iViewHMD/readme.txt b/interface/external/iViewHMD/readme.txt deleted file mode 100644 index 4b3d59349b..0000000000 --- a/interface/external/iViewHMD/readme.txt +++ /dev/null @@ -1,14 +0,0 @@ - -Instructions for adding SMI HMD Eye Tracking to Interface on Windows -David Rowe, 27 Jul 2015. - -1. Download and install the SMI HMD Eye Tracking software from http://update.smivision.com/iViewNG-HMD.exe. - -2. Copy the SDK folders (3rdParty, include, libs) from the SDK installation folder C:\Program Files (x86)\SMI\iViewNG-HMD\SDK - into the interface/externals/iViewHMD folder. This readme.txt should be there as well. - - You may optionally choose to copy the SDK folders to a location outside the repository (so you can re-use with different - checkouts and different projects). If so, set the ENV variable "HIFI_LIB_DIR" to a directory containing a subfolder - "iViewHMD" that contains the folders mentioned above. - -3. Clear your build directory, run cmake and build, and you should be all set. diff --git a/libraries/trackers/src/trackers/EyeTracker.cpp b/libraries/trackers/src/trackers/EyeTracker.cpp deleted file mode 100644 index a64b945c55..0000000000 --- a/libraries/trackers/src/trackers/EyeTracker.cpp +++ /dev/null @@ -1,307 +0,0 @@ -// -// Created by David Rowe on 27 Jul 2015. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "EyeTracker.h" - -#include -#include - -#include - -#include "Logging.h" -#include - -#ifdef HAVE_IVIEWHMD -char* HIGH_FIDELITY_EYE_TRACKER_CALIBRATION = "HighFidelityEyeTrackerCalibration"; -#endif - -#ifdef HAVE_IVIEWHMD -static void CALLBACK eyeTrackerCallback(smi_CallbackDataStruct* data) { - auto eyeTracker = DependencyManager::get(); - if (eyeTracker) { // Guard against a few callbacks that continue to be received after smi_quit(). - eyeTracker->processData(data); - } -} -#endif - -EyeTracker::~EyeTracker() { -#ifdef HAVE_IVIEWHMD - if (_isStreaming) { - int result = smi_quit(); - if (result != SMI_RET_SUCCESS) { - qCWarning(interfaceapp) << "Eye Tracker: Error terminating tracking:" << smiReturnValueToString(result); - } - } -#endif -} - -#ifdef HAVE_IVIEWHMD -void EyeTracker::processData(smi_CallbackDataStruct* data) { - _lastProcessDataTimestamp = usecTimestampNow(); - - if (!_isEnabled) { - return; - } - - if (data->type == SMI_SIMPLE_GAZE_SAMPLE) { - // Calculate the intersections of the left and right eye look-at vectors with a vertical plane along the monocular - // gaze direction. Average these positions to give the look-at point. - // If the eyes are parallel or diverged, gaze at a distant look-at point calculated the same as for non eye tracking. - // Line-plane intersection: https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection - - smi_SampleHMDStruct* sample = (smi_SampleHMDStruct*)data->result; - // The iViewHMD coordinate system has x and z axes reversed compared to Interface, i.e., wearing the HMD: - // - x is left - // - y is up - // - z is forwards - - // Plane - smi_Vec3d point = sample->gazeBasePoint; // mm - smi_Vec3d direction = sample->gazeDirection; - glm::vec3 planePoint = glm::vec3(-point.x, point.y, -point.z) / 1000.0f; - glm::vec3 planeNormal = glm::vec3(-direction.z, 0.0f, direction.x); - glm::vec3 monocularDirection = glm::vec3(-direction.x, direction.y, -direction.z); - - // Left eye - point = sample->left.gazeBasePoint; // mm - direction = sample->left.gazeDirection; - glm::vec3 leftLinePoint = glm::vec3(-point.x, point.y, -point.z) / 1000.0f; - glm::vec3 leftLineDirection = glm::vec3(-direction.x, direction.y, -direction.z); - - // Right eye - point = sample->right.gazeBasePoint; // mm - direction = sample->right.gazeDirection; - glm::vec3 rightLinePoint = glm::vec3(-point.x, point.y, -point.z) / 1000.0f; - glm::vec3 rightLineDirection = glm::vec3(-direction.x, direction.y, -direction.z); - - // Plane - line dot products - float leftLinePlaneDotProduct = glm::dot(leftLineDirection, planeNormal); - float rightLinePlaneDotProduct = glm::dot(rightLineDirection, planeNormal); - - // Gaze into distance if eyes are parallel or diverged; otherwise the look-at is the average of look-at points - glm::vec3 lookAtPosition; - if (abs(leftLinePlaneDotProduct) <= FLT_EPSILON || abs(rightLinePlaneDotProduct) <= FLT_EPSILON) { - lookAtPosition = monocularDirection * (float)TREE_SCALE; - } else { - float leftDistance = glm::dot(planePoint - leftLinePoint, planeNormal) / leftLinePlaneDotProduct; - float rightDistance = glm::dot(planePoint - rightLinePoint, planeNormal) / rightLinePlaneDotProduct; - if (leftDistance <= 0.0f || rightDistance <= 0.0f - || leftDistance > (float)TREE_SCALE || rightDistance > (float)TREE_SCALE) { - lookAtPosition = monocularDirection * (float)TREE_SCALE; - } else { - glm::vec3 leftIntersectionPoint = leftLinePoint + leftDistance * leftLineDirection; - glm::vec3 rightIntersectionPoint = rightLinePoint + rightDistance * rightLineDirection; - lookAtPosition = (leftIntersectionPoint + rightIntersectionPoint) / 2.0f; - } - } - - if (glm::isnan(lookAtPosition.x) || glm::isnan(lookAtPosition.y) || glm::isnan(lookAtPosition.z)) { - return; - } - - _lookAtPosition = lookAtPosition; - } -} -#endif - -void EyeTracker::init() { - if (_isInitialized) { - qCWarning(trackers) << "Eye Tracker: Already initialized"; - return; - } -} - -#ifdef HAVE_IVIEWHMD -int EyeTracker::startStreaming(bool simulate) { - return smi_startStreaming(simulate); // This call blocks execution. -} -#endif - -#ifdef HAVE_IVIEWHMD -void EyeTracker::onStreamStarted() { - if (!_isInitialized) { - return; - } - - int result = _startStreamingWatcher.result(); - _isStreaming = (result == SMI_RET_SUCCESS); - - if (result != SMI_RET_SUCCESS) { - qCWarning(interfaceapp) << "Eye Tracker: Error starting streaming:" << smiReturnValueToString(result); - // Display error dialog unless SMI SDK has already displayed an error message. - if (result != SMI_ERROR_HMD_NOT_SUPPORTED) { - OffscreenUi::asyncWarning(nullptr, "Eye Tracker Error", smiReturnValueToString(result)); - } - } else { - qCDebug(interfaceapp) << "Eye Tracker: Started streaming"; - } - - if (_isStreaming) { - // Automatically load calibration if one has been saved. - QString availableCalibrations = QString(smi_getAvailableCalibrations()); - if (availableCalibrations.contains(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION)) { - result = smi_loadCalibration(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION); - if (result != SMI_RET_SUCCESS) { - qCWarning(interfaceapp) << "Eye Tracker: Error loading calibration:" << smiReturnValueToString(result); - OffscreenUi::asyncWarning(nullptr, "Eye Tracker Error", "Error loading calibration" - + smiReturnValueToString(result)); - } else { - qCDebug(interfaceapp) << "Eye Tracker: Loaded calibration"; - } - } - } -} -#endif - -void EyeTracker::setEnabled(bool enabled, bool simulate) { - if (enabled && !_isInitialized) { -#ifdef HAVE_IVIEWHMD - int result = smi_setCallback(eyeTrackerCallback); - if (result != SMI_RET_SUCCESS) { - qCWarning(interfaceapp) << "Eye Tracker: Error setting callback:" << smiReturnValueToString(result); - OffscreenUi::asyncWarning(nullptr, "Eye Tracker Error", smiReturnValueToString(result)); - } else { - _isInitialized = true; - } - - connect(&_startStreamingWatcher, SIGNAL(finished()), this, SLOT(onStreamStarted())); -#endif - } - - if (!_isInitialized) { - return; - } - -#ifdef HAVE_IVIEWHMD - qCDebug(interfaceapp) << "Eye Tracker: Set enabled =" << enabled << ", simulate =" << simulate; - - // There is no smi_stopStreaming() method and after an smi_quit(), streaming cannot be restarted (at least not for - // simulated data). So keep streaming once started in case tracking is re-enabled after stopping. - - // Try to stop streaming if changing whether simulating or not. - if (enabled && _isStreaming && _isStreamSimulating != simulate) { - int result = smi_quit(); - if (result != SMI_RET_SUCCESS) { - qCWarning(interfaceapp) << "Eye Tracker: Error stopping streaming:" << smiReturnValueToString(result); - } - _isStreaming = false; - } - - if (enabled && !_isStreaming) { - // Start SMI streaming in a separate thread because it blocks. - QFuture future = QtConcurrent::run(this, &EyeTracker::startStreaming, simulate); - _startStreamingWatcher.setFuture(future); - _isStreamSimulating = simulate; - } - - _isEnabled = enabled; - _isSimulating = simulate; - -#endif -} - -void EyeTracker::reset() { - // Nothing to do. -} - -bool EyeTracker::isTracking() const { - static const quint64 ACTIVE_TIMEOUT_USECS = 2000000; // 2 secs - return _isEnabled && (usecTimestampNow() - _lastProcessDataTimestamp < ACTIVE_TIMEOUT_USECS); -} - -#ifdef HAVE_IVIEWHMD -void EyeTracker::calibrate(int points) { - - if (!_isStreaming) { - qCWarning(interfaceapp) << "Eye Tracker: Cannot calibrate because not streaming"; - return; - } - - smi_CalibrationHMDStruct* calibrationHMDStruct; - smi_createCalibrationHMDStruct(&calibrationHMDStruct); - - smi_CalibrationTypeEnum calibrationType; - switch (points) { - case 1: - calibrationType = SMI_ONE_POINT_CALIBRATION; - qCDebug(interfaceapp) << "Eye Tracker: One point calibration"; - break; - case 3: - calibrationType = SMI_THREE_POINT_CALIBRATION; - qCDebug(interfaceapp) << "Eye Tracker: Three point calibration"; - break; - case 5: - calibrationType = SMI_FIVE_POINT_CALIBRATION; - qCDebug(interfaceapp) << "Eye Tracker: Five point calibration"; - break; - default: - qCWarning(interfaceapp) << "Eye Tracker: Invalid calibration specified"; - return; - } - - calibrationHMDStruct->type = calibrationType; - calibrationHMDStruct->backgroundColor->blue = 0.5; - calibrationHMDStruct->backgroundColor->green = 0.5; - calibrationHMDStruct->backgroundColor->red = 0.5; - calibrationHMDStruct->foregroundColor->blue = 1.0; - calibrationHMDStruct->foregroundColor->green = 1.0; - calibrationHMDStruct->foregroundColor->red = 1.0; - - int result = smi_setupCalibration(calibrationHMDStruct); - if (result != SMI_RET_SUCCESS) { - qCWarning(interfaceapp) << "Eye Tracker: Error setting up calibration:" << smiReturnValueToString(result); - return; - } else { - result = smi_calibrate(); - if (result != SMI_RET_SUCCESS) { - qCWarning(interfaceapp) << "Eye Tracker: Error performing calibration:" << smiReturnValueToString(result); - } else { - result = smi_saveCalibration(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION); - if (result != SMI_RET_SUCCESS) { - qCWarning(interfaceapp) << "Eye Tracker: Error saving calibration:" << smiReturnValueToString(result); - } - } - } - - if (result != SMI_RET_SUCCESS) { - OffscreenUi::asyncWarning(nullptr, "Eye Tracker Error", "Calibration error: " + smiReturnValueToString(result)); - } -} -#endif - -#ifdef HAVE_IVIEWHMD -QString EyeTracker::smiReturnValueToString(int value) { - switch (value) - { - case smi_ErrorReturnValue::SMI_ERROR_NO_CALLBACK_SET: - return "No callback set"; - case smi_ErrorReturnValue::SMI_ERROR_CONNECTING_TO_HMD: - return "Error connecting to HMD"; - case smi_ErrorReturnValue::SMI_ERROR_HMD_NOT_SUPPORTED: - return "HMD not supported"; - case smi_ErrorReturnValue::SMI_ERROR_NOT_IMPLEMENTED: - return "Not implmented"; - case smi_ErrorReturnValue::SMI_ERROR_INVALID_PARAMETER: - return "Invalid parameter"; - case smi_ErrorReturnValue::SMI_ERROR_EYECAMERAS_NOT_AVAILABLE: - return "Eye cameras not available"; - case smi_ErrorReturnValue::SMI_ERROR_OCULUS_RUNTIME_NOT_SUPPORTED: - return "Oculus runtime not supported"; - case smi_ErrorReturnValue::SMI_ERROR_FILE_NOT_FOUND: - return "File not found"; - case smi_ErrorReturnValue::SMI_ERROR_FILE_EMPTY: - return "File empty"; - case smi_ErrorReturnValue::SMI_ERROR_UNKNOWN: - return "Unknown error"; - default: - QString number; - number.setNum(value); - return number; - } -} -#endif diff --git a/libraries/trackers/src/trackers/EyeTracker.h b/libraries/trackers/src/trackers/EyeTracker.h deleted file mode 100644 index 9cf35d0f2a..0000000000 --- a/libraries/trackers/src/trackers/EyeTracker.h +++ /dev/null @@ -1,68 +0,0 @@ -// -// Created by David Rowe on 27 Jul 2015. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_EyeTracker_h -#define hifi_EyeTracker_h - -#include -#include - -#include - -#include -#ifdef HAVE_IVIEWHMD -#include -#endif - - -class EyeTracker : public QObject, public Dependency { - Q_OBJECT - SINGLETON_DEPENDENCY - -public: - ~EyeTracker(); - - void init(); - void setEnabled(bool enabled, bool simulate); - void reset(); - - bool isInitialized() const { return _isInitialized; } - bool isEnabled() const { return _isEnabled; } - bool isTracking() const; - bool isSimulating() const { return _isSimulating; } - - glm::vec3 getLookAtPosition() const { return _lookAtPosition; } // From mid eye point in head frame. - -#ifdef HAVE_IVIEWHMD - void processData(smi_CallbackDataStruct* data); - - void calibrate(int points); - - int startStreaming(bool simulate); - -private slots: - void onStreamStarted(); -#endif - -private: - QString smiReturnValueToString(int value); - - bool _isInitialized = false; - bool _isEnabled = false; - bool _isSimulating = false; - bool _isStreaming = false; - bool _isStreamSimulating = false; - - quint64 _lastProcessDataTimestamp; - - glm::vec3 _lookAtPosition; - - QFutureWatcher _startStreamingWatcher; -}; - -#endif // hifi_EyeTracker_h From 58cf51058b61b7440fa5ef38bd5251a6edb74ee6 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 17 Sep 2019 09:42:26 -0700 Subject: [PATCH 25/40] code review --- interface/src/avatar/MyHead.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/MyHead.cpp b/interface/src/avatar/MyHead.cpp index b5e8bc2171..1fc134169a 100644 --- a/interface/src/avatar/MyHead.cpp +++ b/interface/src/avatar/MyHead.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include "devices/DdeFaceTracker.h" #include "Application.h" @@ -45,6 +46,11 @@ void MyHead::simulate(float deltaTime) { auto player = DependencyManager::get(); // Only use face trackers when not playing back a recording. if (!player->isPlaying()) { + // TODO -- finish removing face-tracker specific code. To do this, add input channels for + // each blendshape-coefficient and update the various json files to relay them in a useful way. + // After that, input plugins can be used to drive the avatar's face, and the various "DDE" files + // can be ported into the plugin and removed. + // // auto faceTracker = qApp->getActiveFaceTracker(); // const bool hasActualFaceTrackerConnected = faceTracker && !faceTracker->isMuted(); // _isFaceTrackerConnected = hasActualFaceTrackerConnected || _owningAvatar->getHasScriptedBlendshapes(); @@ -63,13 +69,13 @@ void MyHead::simulate(float deltaTime) { float leftEyeBlink = userInputMapper->getActionState(controller::Action::LEFT_EYE_BLINK); float rightEyeBlink = userInputMapper->getActionState(controller::Action::RIGHT_EYE_BLINK); _blendshapeCoefficients.resize(std::max(_blendshapeCoefficients.size(), 2)); - _blendshapeCoefficients[0] = leftEyeBlink; - _blendshapeCoefficients[1] = rightEyeBlink; + _blendshapeCoefficients[EYE_BLINK_L_INDEX] = leftEyeBlink; + _blendshapeCoefficients[EYE_BLINK_R_INDEX] = rightEyeBlink; } else { const float FULLY_OPEN = 0.0f; _blendshapeCoefficients.resize(std::max(_blendshapeCoefficients.size(), 2)); - _blendshapeCoefficients[0] = FULLY_OPEN; - _blendshapeCoefficients[1] = FULLY_OPEN; + _blendshapeCoefficients[EYE_BLINK_L_INDEX] = FULLY_OPEN; + _blendshapeCoefficients[EYE_BLINK_R_INDEX] = FULLY_OPEN; } } Parent::simulate(deltaTime); From 3ece763a8e52c1b14721cbfd8f41f428dca469f3 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 17 Sep 2019 10:52:55 -0700 Subject: [PATCH 26/40] code review --- interface/src/avatar/MyHead.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/MyHead.cpp b/interface/src/avatar/MyHead.cpp index 1fc134169a..e5c8b71ea2 100644 --- a/interface/src/avatar/MyHead.cpp +++ b/interface/src/avatar/MyHead.cpp @@ -69,13 +69,13 @@ void MyHead::simulate(float deltaTime) { float leftEyeBlink = userInputMapper->getActionState(controller::Action::LEFT_EYE_BLINK); float rightEyeBlink = userInputMapper->getActionState(controller::Action::RIGHT_EYE_BLINK); _blendshapeCoefficients.resize(std::max(_blendshapeCoefficients.size(), 2)); - _blendshapeCoefficients[EYE_BLINK_L_INDEX] = leftEyeBlink; - _blendshapeCoefficients[EYE_BLINK_R_INDEX] = rightEyeBlink; + _blendshapeCoefficients[EYE_BLINK_INDICES[0]] = leftEyeBlink; + _blendshapeCoefficients[EYE_BLINK_INDICES[1]] = rightEyeBlink; } else { const float FULLY_OPEN = 0.0f; _blendshapeCoefficients.resize(std::max(_blendshapeCoefficients.size(), 2)); - _blendshapeCoefficients[EYE_BLINK_L_INDEX] = FULLY_OPEN; - _blendshapeCoefficients[EYE_BLINK_R_INDEX] = FULLY_OPEN; + _blendshapeCoefficients[EYE_BLINK_INDICES[0]] = FULLY_OPEN; + _blendshapeCoefficients[EYE_BLINK_INDICES[1]] = FULLY_OPEN; } } Parent::simulate(deltaTime); From 60f4189eb36def23a7aab5e9f8bdd99607013648 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 17 Sep 2019 11:36:12 -0700 Subject: [PATCH 27/40] BUGZ-1485: Fix Copyright date in About dialog --- interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml index 213540b334..87f307b9bc 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml @@ -106,7 +106,7 @@ Rectangle { } RalewayRegular { color: "white" - text: "© 2018 High Fidelity. All rights reserved." + text: "© 2012 - 2019 High Fidelity, Inc.. All rights reserved." size: 14 } RalewayRegular { From 1157d59f6750a8e3040b7d35ac7a4e6d8ff327df Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 17 Sep 2019 13:40:43 -0700 Subject: [PATCH 28/40] Sit bug fixes * You should not be able to move after being seated, even if you switch seats. * You should not be able to jump out of the chair by holding the space bar. * Fixed small issue with the sitting to standing transition being delayed. (causing the user to look like there were sitting in mid-air) This was due to a missing transition in the animation.json --- .../resources/avatar/avatar-animation.json | 4 ++ interface/src/avatar/MyAvatar.cpp | 42 +++++++++++-------- interface/src/avatar/MyAvatar.h | 3 ++ 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 1cf4663b1b..738d25e8ee 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -4598,6 +4598,10 @@ { "state": "strafeLeftHmd", "var": "isMovingLeftHmd" + }, + { + "state": "idle", + "var": "isNotSeated" } ] }, diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4d1c20010c..5311d41d76 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -4374,8 +4374,15 @@ float MyAvatar::getRawDriveKey(DriveKeys key) const { } void MyAvatar::relayDriveKeysToCharacterController() { - if (getDriveKey(TRANSLATE_Y) > 0.0f && (!qApp->isHMDMode() || (useAdvancedMovementControls() && getFlyingHMDPref()))) { - _characterController.jump(); + if (_endSitKeyPressComplete) { + if (getDriveKey(TRANSLATE_Y) > 0.0f && (!qApp->isHMDMode() || (useAdvancedMovementControls() && getFlyingHMDPref()))) { + _characterController.jump(); + } + } else { + // used to prevent character from jumping after endSit is called. + if (getDriveKey(TRANSLATE_Y) == 0.0f) { + _endSitKeyPressComplete = true; + } } } @@ -6255,15 +6262,17 @@ void MyAvatar::beginSit(const glm::vec3& position, const glm::quat& rotation) { return; } - _characterController.setSeated(true); - setCollisionsEnabled(false); - setHMDLeanRecenterEnabled(false); - // Disable movement - setSitDriveKeysStatus(false); - centerBody(); - int hipIndex = getJointIndex("Hips"); - clearPinOnJoint(hipIndex); - pinJoint(hipIndex, position, rotation); + if (!_characterController.getSeated()) { + _characterController.setSeated(true); + setCollisionsEnabled(false); + setHMDLeanRecenterEnabled(false); + // Disable movement + setSitDriveKeysStatus(false); + centerBody(); + int hipIndex = getJointIndex("Hips"); + clearPinOnJoint(hipIndex); + pinJoint(hipIndex, position, rotation); + } } void MyAvatar::endSit(const glm::vec3& position, const glm::quat& rotation) { @@ -6281,12 +6290,9 @@ void MyAvatar::endSit(const glm::vec3& position, const glm::quat& rotation) { slamPosition(position); setWorldOrientation(rotation); - // the jump key is used to exit the chair. We add a delay here to prevent - // the avatar from jumping right as they exit the chair. - float TIME_BEFORE_DRIVE_ENABLED_MS = 150.0f; - QTimer::singleShot(TIME_BEFORE_DRIVE_ENABLED_MS, [this]() { - // Enable movement again - setSitDriveKeysStatus(true); - }); + // used to prevent character from jumping after endSit is called. + _endSitKeyPressComplete = false; + + setSitDriveKeysStatus(true); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 0108fb5eda..a6cc315631 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -2903,6 +2903,9 @@ private: int _reactionEnabledRefCounts[NUM_AVATAR_BEGIN_END_REACTIONS] { 0, 0, 0 }; mutable std::mutex _reactionLock; + + // used to prevent character from jumping after endSit is called. + bool _endSitKeyPressComplete { false }; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); From 1119b9f29c92ce049a91f6c46abce7edf7d64def Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 17 Sep 2019 15:05:37 -0700 Subject: [PATCH 29/40] update local QueryAACube on deactivation --- libraries/physics/src/EntityMotionState.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index de82dd6ace..7e4723a7d1 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -122,6 +122,13 @@ void EntityMotionState::handleDeactivation() { _body->setWorldTransform(worldTrans); // no need to update velocities... should already be zero } + if (!isLocallyOwned()) { + // HACK: To allow the ESS to move entities around in a kinematic way we had to remove the requirement that + // every moving+simulated entity has an authoritative simulation owner. As a result, we cannot rely + // on a simulation owner to update the QueryAACube on the entity-server. This HACK updates the local + // QueryAACube but the one on the ES will still be broken. + _entity->updateQueryAACube(); + } } // virtual From 54d3ceda284057ea55ecc4dbb4466900ba472ce6 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 17 Sep 2019 15:31:16 -0700 Subject: [PATCH 30/40] update QueryAACube of kinematic things with known parentage --- libraries/entities/src/EntitySimulation.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index b5e4fed0fd..9f81572a4a 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -242,11 +242,21 @@ void EntitySimulation::moveSimpleKinematics(uint64_t now) { entity->getMaximumAACube(ancestryIsKnown); bool hasAvatarAncestor = entity->hasAncestorOfType(NestableType::Avatar); - if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo() && ancestryIsKnown && !hasAvatarAncestor) { + bool isMoving = entity->isMovingRelativeToParent(); + if (isMoving && !entity->getPhysicsInfo() && ancestryIsKnown && !hasAvatarAncestor) { entity->simulate(now); + if (ancestryIsKnown && !hasAvatarAncestor) { + entity->updateQueryAACube(); + } _entitiesToSort.insert(entity); ++itemItr; } else { + if (!isMoving && ancestryIsKnown && !hasAvatarAncestor) { + // HACK: This catches most cases where the entity's QueryAACube (and spatial sorting in the EntityTree) + // would otherwise be out of date at conclusion of its "unowned" simpleKinematicMotion. + entity->updateQueryAACube(); + _entitiesToSort.insert(entity); + } // the entity is no longer non-physical-kinematic itemItr = _simpleKinematicEntities.erase(itemItr); } From 5ad0dd20acfabf1f891794b3ab8f50ae15621af6 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 17 Sep 2019 15:43:53 -0700 Subject: [PATCH 31/40] update QueryAACube of unowned moving physical entities --- libraries/physics/src/PhysicalEntitySimulation.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 85c53af10a..43ced16119 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -528,6 +528,8 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta addOwnership(entityState); } else if (entityState->shouldSendBid()) { addOwnershipBid(entityState); + } else { + entityState->getEntity()->updateQueryAACube(); } } } From 434f5ff300bd8814fce2206f681ef6594ab1e416 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 17 Sep 2019 15:57:20 -0700 Subject: [PATCH 32/40] fix comment --- libraries/physics/src/EntityMotionState.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 7e4723a7d1..e48f0603bd 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -125,8 +125,7 @@ void EntityMotionState::handleDeactivation() { if (!isLocallyOwned()) { // HACK: To allow the ESS to move entities around in a kinematic way we had to remove the requirement that // every moving+simulated entity has an authoritative simulation owner. As a result, we cannot rely - // on a simulation owner to update the QueryAACube on the entity-server. This HACK updates the local - // QueryAACube but the one on the ES will still be broken. + // on a simulation owner to update the QueryAACube on the entity-server. _entity->updateQueryAACube(); } } From 59184edc8936cf0b6d470cef4b0ae3bad435cbe8 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 18 Sep 2019 13:31:22 -0700 Subject: [PATCH 33/40] DEV-521: Update Emoji window's search field to display emoji's name during hover --- .../simplifiedControls/TextField.qml | 6 +- .../emojiApp/ui/qml/SimplifiedEmoji.qml | 191 ++++++++++-------- 2 files changed, 108 insertions(+), 89 deletions(-) diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml index fd370be0ec..1dd3a80a52 100644 --- a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml @@ -22,6 +22,8 @@ TextField { } property string rightGlyph: "" + property alias bottomBorderVisible: bottomRectangle.visible + property alias backgroundColor: textFieldBackground.color color: simplifiedUI.colors.text.white font.family: "Graphik Medium" @@ -45,7 +47,9 @@ TextField { } } - background: Item { + background: Rectangle { + id: textFieldBackground + color: Qt.rgba(0, 0, 0, 0); anchors.fill: parent Rectangle { diff --git a/scripts/simplifiedUI/simplifiedEmote/emojiApp/ui/qml/SimplifiedEmoji.qml b/scripts/simplifiedUI/simplifiedEmote/emojiApp/ui/qml/SimplifiedEmoji.qml index dd78b8fe09..a890be46f1 100644 --- a/scripts/simplifiedUI/simplifiedEmote/emojiApp/ui/qml/SimplifiedEmoji.qml +++ b/scripts/simplifiedUI/simplifiedEmote/emojiApp/ui/qml/SimplifiedEmoji.qml @@ -66,6 +66,7 @@ Rectangle { .forEach(function(item, index){ item.code = { utf: item.code[0] } item.keywords = { keywords: item.keywords } + item.shortName = item.shortName mainModel.append(item); filteredModel.append(item); }); @@ -73,6 +74,7 @@ Rectangle { .forEach(function(item, index){ item.code = { utf: item.name } item.keywords = { keywords: item.keywords } + item.shortName = item.name mainModel.append(item); filteredModel.append(item); }); @@ -230,7 +232,7 @@ Rectangle { anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right - height: 200 + height: 160 clip: true color: simplifiedUI.colors.darkBackground @@ -248,8 +250,8 @@ Rectangle { Image { id: mainEmojiImage - width: 180 - height: 180 + width: emojiIndicatorContainer.width - 20 + height: emojiIndicatorContainer.height - 20 anchors.centerIn: parent source: "" fillMode: Image.PreserveAspectFit @@ -281,6 +283,64 @@ Rectangle { } + Item { + id: searchBarContainer + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: emojiIndicatorContainer.bottom + width: Math.min(parent.width, 420) + height: 48 + + SimplifiedControls.TextField { + id: emojiSearchTextField + readonly property string defaultPlaceholderText: "Search Emojis" + bottomBorderVisible: false + backgroundColor: "#313131" + placeholderText: emojiSearchTextField.defaultPlaceholderText + maximumLength: 100 + clip: true + selectByMouse: true + autoScroll: true + horizontalAlignment: TextInput.AlignHCenter + anchors.left: parent.left + anchors.leftMargin: 16 + anchors.right: parent.right + anchors.rightMargin: 16 + anchors.verticalCenter: parent.verticalCenter + onTextChanged: { + if (text.length === 0) { + root.filterEmoji(emojiSearchTextField.text); + } else { + waitForMoreInputTimer.restart(); + } + } + onAccepted: { + root.filterEmoji(emojiSearchTextField.text); + waitForMoreInputTimer.stop(); + if (filteredModel.count === 1) { + root.selectEmoji(filteredModel.get(0).code.utf); + } else { + grid.forceActiveFocus(); + } + } + + KeyNavigation.backtab: grid + KeyNavigation.tab: grid + } + + Timer { + id: waitForMoreInputTimer + repeat: false + running: false + triggeredOnStart: false + interval: 300 + + onTriggered: { + root.filterEmoji(emojiSearchTextField.text); + } + } + } + + function selectEmoji(code) { sendToScript({ "source": "SimplifiedEmoji.qml", @@ -294,13 +354,45 @@ Rectangle { Rectangle { id: emojiIconListContainer - anchors.top: emojiIndicatorContainer.bottom + anchors.top: searchBarContainer.bottom + anchors.topMargin: 10 anchors.left: parent.left anchors.right: parent.right - anchors.bottom: bottomContainer.top + anchors.bottom: parent.bottom + anchors.bottomMargin: 8 clip: true color: simplifiedUI.colors.darkBackground + Item { + id: helpGlyphContainer + anchors.left: parent.left + anchors.leftMargin: 4 + anchors.bottom: parent.bottom + anchors.bottomMargin: 2 + width: 22 + height: width + + HifiStylesUit.GraphikRegular { + text: "?" + anchors.fill: parent + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: simplifiedUI.colors.text.darkGrey + opacity: attributionMouseArea.containsMouse ? 1.0 : 0.8 + size: 22 + } + + MouseArea { + id: attributionMouseArea + hoverEnabled: enabled + anchors.fill: parent + propagateComposedEvents: false + onClicked: { + popupContainer.visible = true; + } + } + } + GridView { id: grid anchors.fill: parent @@ -319,13 +411,18 @@ Rectangle { anchors.fill: parent propagateComposedEvents: false onEntered: { - grid.currentIndex = index + grid.currentIndex = index; // don't allow a hover image change of the main emoji image if (root.isSelected) { return; } // Updates the selected image root.currentCode = model.code.utf; + // Ensures that the placeholder text is visible and updated + if (emojiSearchTextField.text === "") { + grid.forceActiveFocus(); + } + emojiSearchTextField.placeholderText = "::" + model.shortName + "::"; } onClicked: { root.selectEmoji(model.code.utf); @@ -379,88 +476,6 @@ Rectangle { } } - - Item { - id: bottomContainer - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - height: 40 - - SimplifiedControls.TextField { - id: emojiSearchTextField - placeholderText: "Search" - maximumLength: 100 - clip: true - selectByMouse: true - autoScroll: true - anchors.left: parent.left - anchors.leftMargin: 16 - anchors.right: helpGlyphContainer.left - anchors.rightMargin: 16 - anchors.verticalCenter: parent.verticalCenter - onTextChanged: { - if (text.length === 0) { - root.filterEmoji(emojiSearchTextField.text); - } else { - waitForMoreInputTimer.restart(); - } - } - onAccepted: { - root.filterEmoji(emojiSearchTextField.text); - waitForMoreInputTimer.stop(); - if (filteredModel.count === 1) { - root.selectEmoji(filteredModel.get(0).code.utf); - } else { - grid.forceActiveFocus(); - } - } - - KeyNavigation.backtab: grid - KeyNavigation.tab: grid - } - - Timer { - id: waitForMoreInputTimer - repeat: false - running: false - triggeredOnStart: false - interval: 300 - - onTriggered: { - root.filterEmoji(emojiSearchTextField.text); - } - } - - Item { - id: helpGlyphContainer - anchors.right: parent.right - anchors.rightMargin: 8 - width: height - height: parent.height - - HifiStylesUit.GraphikRegular { - text: "?" - anchors.fill: parent - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - color: simplifiedUI.colors.text.darkGrey - opacity: attributionMouseArea.containsMouse ? 1.0 : 0.8 - size: 22 - } - - MouseArea { - id: attributionMouseArea - hoverEnabled: enabled - anchors.fill: parent - propagateComposedEvents: false - onClicked: { - popupContainer.visible = true; - } - } - } - } - function filterEmoji(filterText) { filteredModel.clear(); From f92c5d0882200a73123a24f76cadea63122d28b7 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 18 Sep 2019 14:23:04 -0700 Subject: [PATCH 34/40] DEV-576: Allow users to type HiFi locations into the SimplifiedUI top bar just like GOTO --- .../simplifiedUI/topBar/SimplifiedTopBar.qml | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml index d87431ea9c..5e82804cf6 100644 --- a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml +++ b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml @@ -9,6 +9,7 @@ // import QtQuick 2.10 +import hifi.simplifiedUI.simplifiedControls 1.0 as SimplifiedControls import "../simplifiedConstants" as SimplifiedConstants import "../inputDeviceButton" as InputDeviceButton import stylesUit 1.0 as HifiStylesUit @@ -157,7 +158,7 @@ Rectangle { Image { id: avatarButtonImage - source: "./images/defaultAvatar.svg" + source: "../images/defaultAvatar.svg" anchors.centerIn: parent width: 32 height: width @@ -350,6 +351,48 @@ Rectangle { } + TextMetrics { + id: goToTextFieldMetrics + font: goToTextField.font + text: goToTextField.longPlaceholderText + } + + + Item { + id: goToTextFieldContainer + anchors.left: statusButtonContainer.right + anchors.leftMargin: 12 + anchors.right: (hmdButtonContainer.visible ? hmdButtonContainer.left : helpButtonContainer.left) + anchors.rightMargin: 12 + anchors.verticalCenter: parent.verticalCenter + height: parent.height + + SimplifiedControls.TextField { + id: goToTextField + readonly property string shortPlaceholderText: "Jump to..." + readonly property string longPlaceholderText: "Type the name of a location to quickly jump there..." + anchors.centerIn: parent + width: Math.min(parent.width, 600) + height: parent.height - 11 + leftPadding: 8 + rightPadding: 8 + bottomBorderVisible: false + backgroundColor: "#313131" + placeholderText: width - leftPadding - rightPadding < goToTextFieldMetrics.width ? shortPlaceholderText : longPlaceholderText + clip: true + selectByMouse: true + autoScroll: true + onAccepted: { + if (goToTextField.length > 0) { + AddressManager.handleLookupString(goToTextField.text); + goToTextField.text = ""; + parent.forceActiveFocus(); + } + } + } + } + + Item { id: hmdButtonContainer From 41978aa4a44082bd01c4f88c6362d9854a2174c0 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 18 Sep 2019 14:42:57 -0700 Subject: [PATCH 35/40] Fix additive blending on avatars with non identity scale. Some avatars that have non 1.0 scale values were incorrectly being scaled during additive blends, this fixes that. --- libraries/animation/src/AnimUtil.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libraries/animation/src/AnimUtil.cpp b/libraries/animation/src/AnimUtil.cpp index 8830cb78b1..0a8b2fe286 100644 --- a/libraries/animation/src/AnimUtil.cpp +++ b/libraries/animation/src/AnimUtil.cpp @@ -54,12 +54,14 @@ void blend4(size_t numPoses, const AnimPose* a, const AnimPose* b, const AnimPos // additive blend void blendAdd(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result) { - const glm::quat identity = glm::quat(); + const glm::vec3 IDENTITY_SCALE = glm::vec3(1.0f); + const glm::quat IDENTITY_ROT = glm::quat(); + for (size_t i = 0; i < numPoses; i++) { const AnimPose& aPose = a[i]; const AnimPose& bPose = b[i]; - result[i].scale() = lerp(aPose.scale(), bPose.scale(), alpha); + result[i].scale() = aPose.scale() * lerp(IDENTITY_SCALE, bPose.scale(), alpha); // ensure that delta has the same "polarity" as the identity quat. // we don't need to do a full dot product, just sign of w is sufficient. @@ -67,7 +69,7 @@ void blendAdd(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha if (delta.w < 0.0f) { delta = -delta; } - delta = glm::lerp(identity, delta, alpha); + delta = glm::lerp(IDENTITY_ROT, delta, alpha); result[i].rot() = glm::normalize(aPose.rot() * delta); result[i].trans() = aPose.trans() + (alpha * bPose.trans()); } From ab419ddd236a534eb1ffcf28e63dbe1f03b7e27c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 19 Sep 2019 08:25:01 -0700 Subject: [PATCH 36/40] beware null sessionID --- libraries/entities/src/EntityScriptingInterface.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 87f15896f2..7cfdc8a68d 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -481,11 +481,15 @@ QUuid EntityScriptingInterface::addEntityInternal(const EntityItemProperties& pr _activityTracking.addedEntityCount++; auto nodeList = DependencyManager::get(); - const auto sessionID = nodeList->getSessionUUID(); + auto sessionID = nodeList->getSessionUUID(); EntityItemProperties propertiesWithSimID = properties; propertiesWithSimID.setEntityHostType(entityHostType); if (entityHostType == entity::HostType::AVATAR) { + if (sessionID.isNull()) { + // null sessionID is unacceptable in this case + sessionID = AVATAR_SELF_ID; + } propertiesWithSimID.setOwningAvatarID(sessionID); } else if (entityHostType == entity::HostType::LOCAL) { // For now, local entities are always collisionless @@ -801,7 +805,7 @@ QUuid EntityScriptingInterface::editEntity(const QUuid& id, const EntityItemProp return; } - if (entity->isAvatarEntity() && entity->getOwningAvatarID() != sessionID) { + if (entity->isAvatarEntity() && entity->getOwningAvatarID() != sessionID && entity->getOwningAvatarID() != AVATAR_SELF_ID) { // don't edit other avatar's avatarEntities properties = EntityItemProperties(); return; From 5e96ce8822d699bf317b7e8de6e99079406fde2e Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 19 Sep 2019 09:48:11 -0700 Subject: [PATCH 37/40] DEV-152: Implement 'Smooth/Snap Turn' setting in SimplifiedUI > Settings > VR --- .../hifi/simplifiedUI/settingsApp/vr/VR.qml | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml index 5f0fbe49d5..420ee11a05 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml @@ -71,7 +71,7 @@ Flickable { ColumnLayout { id: controlsContainer Layout.preferredWidth: parent.width - Layout.topMargin: 24 + Layout.topMargin: 24 spacing: 0 HifiStylesUit.GraphikSemiBold { @@ -154,6 +154,45 @@ Flickable { } } } + + ColumnLayout { + Layout.preferredWidth: parent.width + spacing: 0 + + HifiStylesUit.GraphikSemiBold { + text: "VR Rotation Mode" + Layout.preferredWidth: parent.width + height: paintedHeight + size: 22 + color: simplifiedUI.colors.text.white + } + + ColumnLayout { + width: parent.width + Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin + spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons + + ButtonGroup { id: rotationButtonGroup } + + SimplifiedControls.RadioButton { + text: "Snap Turn" + ButtonGroup.group: rotationButtonGroup + checked: MyAvatar.getSnapTurn() === true + onClicked: { + MyAvatar.setSnapTurn(true); + } + } + + SimplifiedControls.RadioButton { + text: "Smooth Turn" + ButtonGroup.group: rotationButtonGroup + checked: MyAvatar.getSnapTurn() === false + onClicked: { + MyAvatar.setSnapTurn(false); + } + } + } + } ColumnLayout { id: micControlsContainer From 5077089518588c48ef21a323dff8b13af6b31925 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 19 Sep 2019 11:15:53 -0700 Subject: [PATCH 38/40] BUGZ-1529: Iteration on JumpTo Field visuals/interaction states. NOTE - need to fix placeholder text swap --- .../simplifiedUI/simplifiedControls/TextField.qml | 15 +++++++++++++++ .../hifi/simplifiedUI/topBar/SimplifiedTopBar.qml | 9 +++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml index 1dd3a80a52..44f34c3356 100644 --- a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml @@ -24,6 +24,8 @@ TextField { property string rightGlyph: "" property alias bottomBorderVisible: bottomRectangle.visible property alias backgroundColor: textFieldBackground.color + property string unfocusedPlaceholderText + property bool blankPlaceholderTextOnFocus: true color: simplifiedUI.colors.text.white font.family: "Graphik Medium" @@ -47,6 +49,19 @@ TextField { } } + onFocusChanged: { + if (!root.blankPlaceholderTextOnFocus) { + return; + } + + if (focus) { + root.unfocusedPlaceholderText = root.placeholderText; + root.placeholderText = ""; + } else { + root.placeholderText = root.unfocusedPlaceholderText; + } + } + background: Rectangle { id: textFieldBackground color: Qt.rgba(0, 0, 0, 0); diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml index 5e82804cf6..f53a72bc00 100644 --- a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml +++ b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml @@ -372,12 +372,13 @@ Rectangle { readonly property string shortPlaceholderText: "Jump to..." readonly property string longPlaceholderText: "Type the name of a location to quickly jump there..." anchors.centerIn: parent - width: Math.min(parent.width, 600) - height: parent.height - 11 + width: Math.min(parent.width, 445) + height: 35 leftPadding: 8 rightPadding: 8 bottomBorderVisible: false - backgroundColor: "#313131" + backgroundColor: "#1D1D1D" + font.pixelSize: 14 placeholderText: width - leftPadding - rightPadding < goToTextFieldMetrics.width ? shortPlaceholderText : longPlaceholderText clip: true selectByMouse: true @@ -386,8 +387,8 @@ Rectangle { if (goToTextField.length > 0) { AddressManager.handleLookupString(goToTextField.text); goToTextField.text = ""; - parent.forceActiveFocus(); } + parent.forceActiveFocus(); } } } From 80414a6f1c3bd44f8915cdf39499b9f0718d6268 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 19 Sep 2019 11:17:39 -0700 Subject: [PATCH 39/40] DEV-613: Remove unnecessary audio injector logging --- libraries/audio/src/AudioInjector.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index c09dba6190..2df766377f 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -210,7 +210,6 @@ qint64 writeStringToStream(const QString& string, QDataStream& stream) { int64_t AudioInjector::injectNextFrame() { if (stateHas(AudioInjectorState::NetworkInjectionFinished)) { - qCDebug(audio) << "AudioInjector::injectNextFrame called but AudioInjector has finished and was not restarted. Returning."; return NEXT_FRAME_DELTA_ERROR_OR_FINISHED; } From 10352f1cb26f88ade382bb7b987675c8be4e61a1 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 19 Sep 2019 13:38:37 -0700 Subject: [PATCH 40/40] New placeholder text color --- .../resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml index f53a72bc00..427e45abba 100644 --- a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml +++ b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml @@ -378,6 +378,7 @@ Rectangle { rightPadding: 8 bottomBorderVisible: false backgroundColor: "#1D1D1D" + placeholderTextColor: "#8E8E8E" font.pixelSize: 14 placeholderText: width - leftPadding - rightPadding < goToTextFieldMetrics.width ? shortPlaceholderText : longPlaceholderText clip: true