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/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/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js
index 08d0550841..136d5b0ebc 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 += "";
+
+ $('#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..307a43ee88 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,8 +2714,7 @@ 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);
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..73d78a5c70 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,24 @@ 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();
+ 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() && enableKey && enableKey->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) {
@@ -1446,6 +1541,28 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt
}
}
+ // 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,65 @@ 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;
+
// Iterate on the setting groups
foreach(const QString& rootKey, postedObject.keys()) {
const QJsonValue& rootValue = postedObject[rootKey];
@@ -1752,6 +1911,8 @@ void DomainServerSettingsManager::persistToFile() {
_configMap.loadConfig();
return; // defend against future code
}
+
+ QFile(settingsFilename).setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner);
}
QStringList DomainServerSettingsManager::getAllKnownGroupNames() {
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/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/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/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 @@
-
-
+
\ No newline at end of file
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/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 {
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
diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml
index fd370be0ec..44f34c3356 100644
--- a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml
+++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml
@@ -22,6 +22,10 @@ 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"
@@ -45,7 +49,22 @@ TextField {
}
}
- background: Item {
+ 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);
anchors.fill: parent
Rectangle {
diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml
index d87431ea9c..427e45abba 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,50 @@ 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, 445)
+ height: 35
+ leftPadding: 8
+ rightPadding: 8
+ bottomBorderVisible: false
+ backgroundColor: "#1D1D1D"
+ placeholderTextColor: "#8E8E8E"
+ font.pixelSize: 14
+ 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
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index b719f26c68..388775cba5 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;
@@ -4347,14 +4334,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 +4362,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 +4396,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 +4412,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 +4425,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 +4435,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
break;
case Qt::Key_Y:
- if (isShifted && isMeta) {
+ if (isShifted && isControlOrCommand) {
getActiveDisplayPlugin()->cycleDebugOutput();
}
break;
@@ -4460,16 +4448,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 +4468,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 +4479,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 +4512,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 +4522,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 {
@@ -5328,35 +5316,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 +5789,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 +5799,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 +6372,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 +7050,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/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/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/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp
index af6836a8a3..121a2cf9f2 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());
@@ -4374,8 +4468,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 +6356,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 +6384,131 @@ 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);
}
}
+
+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 fb9b605434..190e7d4f61 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;
@@ -1867,6 +1869,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);
@@ -1874,6 +1878,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
@@ -2906,6 +2914,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);
diff --git a/interface/src/avatar/MyHead.cpp b/interface/src/avatar/MyHead.cpp
index 9b05a26c76..e5c8b71ea2 100644
--- a/interface/src/avatar/MyHead.cpp
+++ b/interface/src/avatar/MyHead.cpp
@@ -15,7 +15,7 @@
#include
#include
#include
-#include
+#include
#include "devices/DdeFaceTracker.h"
#include "Application.h"
@@ -46,18 +46,37 @@ 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();
- }
- }
+ // 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();
+ // 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[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_INDICES[0]] = FULLY_OPEN;
+ _blendshapeCoefficients[EYE_BLINK_INDICES[1]] = FULLY_OPEN;
+ }
}
Parent::simulate(deltaTime);
}
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/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());
}
diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp
index d2e6dead58..e48f51ffcc 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;
@@ -1958,8 +1972,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.
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;
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;
}
diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp
index dacd479aef..f6e943b587 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 a1827334d8..610f34ed45 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);
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-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; }
diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp
index 0389dc9149..710bfb8d2a 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 };
diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp
index 9a1f540098..23f7509d9d 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
};
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 04d7c0bef8..ae592485dc 100644
--- a/libraries/controllers/src/controllers/StandardController.cpp
+++ b/libraries/controllers/src/controllers/StandardController.cpp
@@ -353,6 +353,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 {
diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp
index a8ef41643a..3837be5c9c 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);
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/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;
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);
}
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/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp
index 4c01517346..20cb30dbd8 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.unlock();
- {
- 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;
}
}
}
diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp
index 68c8266e9f..e48f0603bd 100644
--- a/libraries/physics/src/EntityMotionState.cpp
+++ b/libraries/physics/src/EntityMotionState.cpp
@@ -84,48 +84,57 @@ 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
+ }
+ 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.
+ _entity->updateQueryAACube();
+ }
}
// virtual
void EntityMotionState::handleEasyChanges(uint32_t& flags) {
- updateServerPhysicsVariables();
+ if (_ownershipState != EntityMotionState::OwnershipState::LocallyOwned) {
+ updateServerPhysicsVariables();
+ }
ObjectMotionState::handleEasyChanges(flags);
if (flags & Simulation::DIRTY_SIMULATOR_ID) {
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();
}
}
}
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;
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);
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
diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp
index c54f63690d..3465138e00 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,33 @@ 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;
}
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 +857,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 +869,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 8cb28c2f20..3a7c81438f 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
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;
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();