mirror of
https://github.com/lubosz/overte.git
synced 2025-04-13 00:43:06 +02:00
Merge branch 'master' into DOC-111
This commit is contained in:
commit
605d272392
81 changed files with 1460 additions and 943 deletions
|
@ -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()
|
|
@ -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",
|
||||
|
|
|
@ -2,6 +2,9 @@ var DomainInfo = null;
|
|||
|
||||
var viewHelpers = {
|
||||
getFormGroup: function(keypath, setting, values, isAdvanced) {
|
||||
if (setting.hidden) {
|
||||
return "";
|
||||
}
|
||||
form_group = "<div class='form-group " +
|
||||
(isAdvanced ? Settings.ADVANCED_CLASS : "") + " " +
|
||||
(setting.deprecated ? Settings.DEPRECATED_CLASS : "" ) + "' " +
|
||||
|
@ -82,8 +85,9 @@ var viewHelpers = {
|
|||
"placeholder='" + (_.has(setting, 'placeholder') ? setting.placeholder : "") +
|
||||
"' value='" + (_.has(setting, 'password_placeholder') ? setting.password_placeholder : setting_value) + "'/>"
|
||||
}
|
||||
|
||||
form_group += "<span class='help-block'>" + setting.help + "</span>"
|
||||
if (setting.help) {
|
||||
form_group += "<span class='help-block'>" + setting.help + "</span>"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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<BR/>";
|
||||
}
|
||||
}
|
||||
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<BR/>";
|
||||
}
|
||||
}
|
||||
if ($('#oauth.panel').length) {
|
||||
if (!$('input[name="oauth.client-id"]').val()) {
|
||||
oauthErrors += "OAuth requires a client Id.<BR/>";
|
||||
}
|
||||
if (!$('input[name="oauth.provider"]').val()) {
|
||||
oauthErrors += "OAuth requires a provider.<BR/>";
|
||||
}
|
||||
if (!$('input[name="oauth.hostname"]').val()) {
|
||||
oauthErrors += "OAuth requires a hostname.<BR/>";
|
||||
}
|
||||
if (!$('input[name="' + SSL_PRIVATE_KEY_PATH + '"]').val() && !$('input[name="' + SSL_PRIVATE_KEY_CONTENTS_NAME + '"]').val()) {
|
||||
oauthErrors += "OAuth requires an SSL Private Key.<BR/>";
|
||||
}
|
||||
if (!$('input[name="' + SSL_CERT_PATH + '"]').val() && !$('input[name="' + SSL_CERT_CONTENTS_NAME + '"]').val()) {
|
||||
oauthErrors += "OAuth requires an SSL Certificate.<BR/>";
|
||||
}
|
||||
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.<BR/>";
|
||||
}
|
||||
}
|
||||
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 = "<div class='form-group undefined'>";
|
||||
html += "<label class='control-label'>SSL Private Key</label><BR/>";
|
||||
html += "<label id='key-path-label'class='control-label'>Path</label>";
|
||||
html += "<input id='" + SSL_PRIVATE_KEY_FILE_ID + "' type='file' accept='.key'/>";
|
||||
html += "<input id='" + SSL_PRIVATE_KEY_CONTENTS_ID + "' name='" + SSL_PRIVATE_KEY_CONTENTS_NAME + "' type='hidden'/>";
|
||||
html += "</div>";
|
||||
html += "<div class='form-group undefined'>";
|
||||
html += "<label class='control-label'>SSL Cert</label>";
|
||||
html += "<div id='cert-fingerprint'><b>Fingerprint:</b><span id='" + SSL_CERT_FINGERPRINT_SPAN_ID + "'>" + data.values.oauth["cert-fingerprint"] + "</span></div>";
|
||||
html += "<label id='cert-path-label' class='control-label'>Path</label>";
|
||||
html += "<input id='" + SSL_CERT_FILE_ID + "' type='file' accept='.cer,.crt'/>";
|
||||
html += "<input id='" + SSL_CERT_CONTENTS_ID + "' name='" + SSL_CERT_CONTENTS_NAME + "' type='hidden'/>";
|
||||
html += "</div>";
|
||||
|
||||
$('#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';
|
||||
|
||||
|
|
|
@ -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>();
|
||||
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<bool, QString> 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<bool, QString> 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);
|
||||
|
|
|
@ -236,6 +236,7 @@ private:
|
|||
|
||||
bool _isUsingDTLS { false };
|
||||
|
||||
bool _oauthEnable { false };
|
||||
QUrl _oauthProviderURL;
|
||||
QString _oauthClientID;
|
||||
QString _oauthClientSecret;
|
||||
|
|
|
@ -22,7 +22,9 @@
|
|||
#include <QtCore/QThread>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QUrlQuery>
|
||||
#include <QtNetwork/QSslKey>
|
||||
#include <QSaveFile>
|
||||
#include <QPair>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <Assignment.h>
|
||||
|
@ -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<QPair<const char*, const char*> > 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() {
|
||||
|
|
14
interface/external/iViewHMD/readme.txt
vendored
14
interface/external/iViewHMD/readme.txt
vendored
|
@ -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.
|
|
@ -4598,6 +4598,10 @@
|
|||
{
|
||||
"state": "strafeLeftHmd",
|
||||
"var": "isMovingLeftHmd"
|
||||
},
|
||||
{
|
||||
"state": "idle",
|
||||
"var": "isNotSeated"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -1,17 +1 @@
|
|||
<svg width="360" height="85" viewBox="0 0 360 85" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M124 30V59H117.974V46.3455H108.026V59H102V30H108.026V41.1899H117.974V30H124Z" fill="#00B4F0"/>
|
||||
<path d="M135 59H129V30H135V59Z" fill="#00B4F0"/>
|
||||
<path d="M151.829 34.9471C150.406 34.9471 149.211 35.0608 148.187 35.3451C147.22 35.6294 146.423 36.0843 145.797 36.7098C145.171 37.3353 144.772 38.2451 144.488 39.3255C144.203 40.4059 144.089 41.7706 144.089 43.3627V45.9216C144.089 47.6275 144.203 48.9922 144.431 50.0726C144.659 51.1529 145.057 52.0059 145.626 52.5745C146.138 53.2 146.878 53.598 147.732 53.8255C148.585 54.0529 149.667 54.1667 150.919 54.1667C151.26 54.1667 151.659 54.1667 152.057 54.1098C152.455 54.1098 152.854 54.0529 153.309 53.9961V46.3765H149.78L150.35 41.7137H159V57.749C157.919 58.1471 156.553 58.4314 154.902 58.6588C153.309 58.8863 151.602 59 149.894 59C147.732 59 145.911 58.7157 144.431 58.2039C142.894 57.6922 141.699 56.8961 140.732 55.8157C139.764 54.7353 139.081 53.4275 138.626 51.7784C138.171 50.1863 138 48.2529 138 46.0922V43.1922C138 41.202 138.228 39.4392 138.626 37.7902C139.081 36.1412 139.821 34.7765 140.846 33.6392C141.87 32.502 143.236 31.5922 144.886 30.9667C146.537 30.3412 148.642 30 151.146 30C152.512 30 153.878 30.1137 155.187 30.3412C156.496 30.5686 157.577 30.8529 158.431 31.1373L157.463 35.6863C156.724 35.4588 155.87 35.2882 154.959 35.1176C154.049 35.0039 153.024 34.9471 151.829 34.9471Z" fill="#00B4F0"/>
|
||||
<path d="M187 30V59H180.959V46.3455H171.041V59H165V30H171.041V41.1899H181.016V30H187Z" fill="#00B4F0"/>
|
||||
<path d="M218 30L217.366 35.0384H207.597V41.0727H216.627L215.993 46.1111H207.597V59H202V30H218Z" fill="#00B4F0"/>
|
||||
<path d="M229 59H223V30H229V59Z" fill="#00B4F0"/>
|
||||
<path d="M233 30H242.404C244.842 30 246.873 30.3508 248.499 30.994C250.124 31.6371 251.401 32.5726 252.388 33.6835C253.375 34.8528 254.013 36.1976 254.42 37.8347C254.826 39.4718 255 41.2258 255 43.0968V45.8448C255 47.7742 254.826 49.5282 254.42 51.1653C254.013 52.8024 253.317 54.1472 252.388 55.3165C251.401 56.4859 250.124 57.3629 248.499 58.006C246.873 58.6492 244.842 59 242.404 59H233.058V30H233ZM239.095 53.9133H241.765C242.926 53.9133 243.913 53.7964 244.842 53.504C245.712 53.2117 246.467 52.744 247.047 52.1008C247.628 51.4577 248.034 50.5806 248.325 49.5282C248.615 48.4758 248.731 47.1311 248.731 45.494V43.3306C248.731 41.6935 248.615 40.3488 248.325 39.2964C248.034 38.2439 247.628 37.3669 247.047 36.7238C246.467 36.0806 245.77 35.6129 244.842 35.379C243.971 35.1452 242.926 34.9698 241.765 34.9698H239.095V53.9133Z" fill="#00B4F0"/>
|
||||
<path d="M275.945 30L275.34 34.8528H264.832V41.1673H274.735L274.184 46.0202H264.887V54.1472H276L275.395 59H259V30H275.945Z" fill="#00B4F0"/>
|
||||
<path d="M286.215 30V53.9616H298L297.296 59H280V30H286.215Z" fill="#00B4F0"/>
|
||||
<path d="M307 59H302V30H307V59Z" fill="#00B4F0"/>
|
||||
<path d="M334 30L333.307 35.0384H325.563V59H319.437V35.0384H311L311.693 30H334Z" fill="#00B4F0"/>
|
||||
<path d="M347.617 41.3071L353.633 30H360L350.537 46.3455V59H344.346V46.3455L335 30H341.776L347.617 41.3071Z" fill="#00B4F0"/>
|
||||
<path d="M42.1132 85C36.4528 85 30.9057 83.856 25.7547 81.6824C20.717 79.5659 16.2453 76.4771 12.3962 72.5875C8.54717 68.6978 5.49057 64.1218 3.39623 59.0882C1.13208 53.7685 -1.07963e-07 48.22 -1.07963e-07 42.4428C-1.07963e-07 36.7227 1.13208 31.1171 3.28302 25.9118C5.37736 20.821 8.43396 16.3022 12.283 12.4125C16.1321 8.52288 20.6604 5.43405 25.6415 3.31763C30.8491 1.08681 36.3396 1.09101e-07 42 1.09101e-07C47.6604 1.09101e-07 53.2076 1.14401 58.3585 3.31763C63.3962 5.43405 67.8679 8.52288 71.717 12.4125C75.566 16.3022 78.6227 20.8782 80.717 25.9118C82.9245 31.1743 84 36.7227 84 42.4428C84 48.1628 82.8679 53.7685 80.717 58.9737C78.6227 64.0646 75.566 68.5834 71.717 72.4731C67.8679 76.3627 63.3396 79.4515 58.3585 81.568C53.3208 83.856 47.7736 85 42.1132 85ZM42.1132 4.34724C21.3396 4.34724 4.41509 21.4502 4.41509 42.4428C4.41509 63.4354 21.3396 80.5384 42.1132 80.5384C62.8868 80.5384 79.8113 63.4354 79.8113 42.4428C79.8113 21.4502 62.8868 4.34724 42.1132 4.34724Z" fill="#00B4F0"/>
|
||||
<path d="M54.8 60.7368V30.5158C56.6857 29.8789 58 28.0842 58 26C58 23.3947 55.8857 21.2526 53.3143 21.2526C50.7429 21.2526 48.6286 23.3947 48.6286 26C48.6286 28.0263 49.8286 29.7053 51.6 30.4579V44.5263L32.1714 35.379V24.2632C34.0571 23.6263 35.3714 21.8316 35.3714 19.7474C35.3714 17.1421 33.2571 15 30.6857 15C28.1143 15 26 17.1421 26 19.7474C26 21.7737 27.2 23.4526 28.9714 24.2053V54.6C27.2571 55.2947 26 57.0316 26 59.0579C26 61.6632 28.1143 63.8053 30.6857 63.8053C33.2571 63.8053 35.3714 61.6632 35.3714 59.0579C35.3714 56.9737 34.0571 55.1789 32.1714 54.5421V39.2L51.6 48.3474V60.7947C49.8857 61.4895 48.6286 63.2263 48.6286 65.2526C48.6286 67.8579 50.7429 70 53.3143 70C55.8857 70 58 67.8579 58 65.2526C58 63.1105 56.6857 61.3737 54.8 60.7368Z" fill="#00B4F0"/>
|
||||
</svg>
|
||||
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360 120.9"><defs><style>.cls-1{fill:#fff;}</style></defs><title>Artboard 1</title><polygon class="cls-1" points="28.69 60.97 28.69 81.59 10.31 81.59 10.31 60.97 0 60.97 0 108.64 10.31 108.64 10.31 89.81 28.69 89.81 28.69 108.64 39 108.64 39 60.97 28.69 60.97"/><path class="cls-1" d="M45,75.17h9.27v33.47H45Zm-.6-9.57a5.31,5.31,0,1,1,5.53,5.08h-.3a5,5,0,0,1-5.23-4.93V65.6"/><path class="cls-1" d="M96.84,61h9.26V80.55c1.65-3.29,5.38-6.13,10.91-6.13,6.58,0,11.21,4,11.21,12.85v21.22H119V88.62c0-4.49-1.79-6.73-5.82-6.73s-7,2.54-7,7.47v19.28H96.84Z"/><polygon class="cls-1" points="145.41 60.97 174.69 60.97 174.69 69.04 155.72 69.04 155.72 83.09 173.05 83.09 173.05 90.86 155.72 90.86 155.72 108.64 145.41 108.64 145.41 60.97"/><path class="cls-1" d="M178.88,75.17h9.26v33.47h-9.26Zm-.75-9.57a5.32,5.32,0,0,1,5.08-5.53,5.25,5.25,0,0,1,5.53,5.09,5.31,5.31,0,0,1-5.08,5.52h-.3a5,5,0,0,1-5.23-4.93V65.6"/><path class="cls-1" d="M216.84,92.05v-.44c0-6.73-3-10-8.07-10S200.7,85,200.7,91.76v.44c0,6.73,3.14,9.87,7.77,9.87,4.93,0,8.37-3.14,8.37-10m-25.56.3v-.44c0-11.06,6.28-17.49,14.65-17.49,5.38,0,8.67,2.39,10.61,5.68V61h9.26v47.67h-9.26v-5.53a12.19,12.19,0,0,1-10.76,6.28c-8.07,0-14.5-5.83-14.5-17"/><path class="cls-1" d="M82.94,91v-.45c0-5.68-3-9.12-8.07-9.12s-8.07,3.74-8.07,9.27v.45c0,5.53,3.29,9,7.92,9,4.78,0,8.22-3.43,8.22-9.11M57.53,109.39H66.8c.6,3,2.69,4.78,7.47,4.78,5.68,0,8.52-3,8.52-8.22v-5.08A12.69,12.69,0,0,1,72,107.15c-8.07,0-14.65-6.13-14.65-16v-.45c0-9.56,6.43-16.29,14.65-16.29,5.38,0,8.67,2.39,10.61,5.68V75.17h9.27V106c0,9.87-6.58,15-17.64,15s-15.69-4.64-16.74-11.51"/><path class="cls-1" d="M253.45,88.32c-.3-5.08-2.84-7.47-7.32-7.47-4.19,0-7,2.69-7.62,7.47Zm-24.51,4v-.44c0-10.61,7.47-17.34,17.19-17.34,8.66,0,16.29,5.08,16.29,17v2.54H238.36c.3,5.53,3.28,8.81,8.36,8.81,4.34,0,6.43-1.79,7-4.63h8.82c-1.05,7.17-6.88,11.21-16,11.21-10.32-.15-17.64-6.58-17.64-17.19"/><rect class="cls-1" x="265.85" y="60.97" width="9.27" height="47.67"/><path class="cls-1" d="M280.2,75.17h9.26v33.47H280.2Zm-.75-9.57A5.31,5.31,0,1,1,285,70.68h-.3a5,5,0,0,1-5.23-4.93V65.6"/><path class="cls-1" d="M294.25,98.63V67.85h9.26V75h7v6.57h-7V97.73c0,2.69,1.35,4,3.74,4a10,10,0,0,0,3.58-.6v7.17a18,18,0,0,1-5.67.9c-7.18-.15-10.91-3.74-10.91-10.61"/><polygon class="cls-1" points="338.03 75.02 330.56 95.19 322.49 75.02 312.48 75.02 325.78 105.06 319.95 119.85 328.92 119.85 347 75.02 338.03 75.02"/><path class="cls-1" d="M336.54,31.68,323.09,25.4V18.08a3.52,3.52,0,0,0,2.39-4.18,3.41,3.41,0,0,0-6.58,1.79,3.5,3.5,0,0,0,2.4,2.39V38.41A3.4,3.4,0,1,0,323.09,45a3.4,3.4,0,0,0,0-6.57V27.65l13.45,6.27v8.67a3.41,3.41,0,0,0,1.79,6.58A3.46,3.46,0,0,0,340.72,45a3.5,3.5,0,0,0-2.39-2.39V22.27a3.53,3.53,0,0,0,2.39-4.19,3.41,3.41,0,1,0-6.57,1.8,3.5,3.5,0,0,0,2.39,2.39Z"/><path class="cls-1" d="M329.81,1.94a28.25,28.25,0,1,0,28.25,28.25h0A28.33,28.33,0,0,0,329.81,1.94m0,58.58A30.26,30.26,0,1,1,360,30.19h0a30.33,30.33,0,0,1-30.19,30.33"/></svg>
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 2.9 KiB |
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
#include <shared/QtHelpers.h>
|
||||
#include <shared/PlatformHelper.h>
|
||||
#include <shared/GlobalAppProperties.h>
|
||||
#include <GeometryUtil.h>
|
||||
#include <StatTracker.h>
|
||||
#include <Trace.h>
|
||||
#include <ResourceScriptingInterface.h>
|
||||
|
@ -154,7 +155,6 @@
|
|||
#include <display-plugins/CompositorHelper.h>
|
||||
#include <display-plugins/hmd/HmdDisplayPlugin.h>
|
||||
#include <display-plugins/RefreshRateController.h>
|
||||
#include <trackers/EyeTracker.h>
|
||||
#include <avatars-renderer/ScriptAvatar.h>
|
||||
#include <RenderableEntityItem.h>
|
||||
#include <RenderableTextEntityItem.h>
|
||||
|
@ -878,7 +878,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
DependencyManager::set<DdeFaceTracker>();
|
||||
#endif
|
||||
|
||||
DependencyManager::set<EyeTracker>();
|
||||
DependencyManager::set<AudioClient>();
|
||||
DependencyManager::set<AudioScope>();
|
||||
DependencyManager::set<DeferredLightingEffect>();
|
||||
|
@ -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>();
|
||||
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<DdeFaceTracker>()->setEnabled(false);
|
||||
#endif
|
||||
#ifdef HAVE_IVIEWHMD
|
||||
DependencyManager::get<EyeTracker>()->setEnabled(false, true);
|
||||
#endif
|
||||
AnimDebugDraw::getInstance().shutdown();
|
||||
|
||||
|
@ -2823,9 +2813,6 @@ void Application::cleanupBeforeQuit() {
|
|||
#ifdef HAVE_DDE
|
||||
DependencyManager::destroy<DdeFaceTracker>();
|
||||
#endif
|
||||
#ifdef HAVE_IVIEWHMD
|
||||
DependencyManager::destroy<EyeTracker>();
|
||||
#endif
|
||||
|
||||
DependencyManager::destroy<ContextOverlayInterface>(); // Must be destroyed before TabletScriptingInterface
|
||||
|
||||
|
@ -2834,7 +2821,7 @@ void Application::cleanupBeforeQuit() {
|
|||
DependencyManager::destroy<TabletScriptingInterface>();
|
||||
DependencyManager::destroy<ToolbarScriptingInterface>();
|
||||
DependencyManager::destroy<OffscreenUi>();
|
||||
|
||||
|
||||
DependencyManager::destroy<OffscreenQmlSurfaceCache>();
|
||||
|
||||
_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<unsigned int>(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<unsigned int>(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>();
|
||||
dialogsManager->toggleAddressBar();
|
||||
}
|
||||
break;
|
||||
|
||||
case Qt::Key_R:
|
||||
if (isMeta && !event->isAutoRepeat()) {
|
||||
if (isControlOrCommand && !event->isAutoRepeat()) {
|
||||
DependencyManager::get<ScriptEngines>()->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>();
|
||||
audioClient->setMuted(!audioClient->isMuted());
|
||||
QSharedPointer<scripting::Audio> audioScriptingInterface = qSharedPointerDynamicCast<scripting::Audio>(DependencyManager::get<AudioScriptingInterface>());
|
||||
|
@ -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<EyeTracker>();
|
||||
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<EyeTracker>()->calibrate(1);
|
||||
}
|
||||
|
||||
void Application::calibrateEyeTracker3Points() {
|
||||
DependencyManager::get<EyeTracker>()->calibrate(3);
|
||||
}
|
||||
|
||||
void Application::calibrateEyeTracker5Points() {
|
||||
DependencyManager::get<EyeTracker>()->calibrate(5);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Application::exportEntities(const QString& filename,
|
||||
const QVector<QUuid>& entityIDs,
|
||||
const glm::vec3* givenOffset) {
|
||||
|
@ -5830,8 +5789,8 @@ void Application::pushPostUpdateLambda(void* key, const std::function<void()>& 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<EyeTracker>();
|
||||
|
||||
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<Avatar>(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<DdeFaceTracker>()->reset();
|
||||
#endif
|
||||
|
||||
DependencyManager::get<EyeTracker>()->reset();
|
||||
|
||||
_overlayConductor.centerUI();
|
||||
getActiveDisplayPlugin()->resetSensors();
|
||||
getMyAvatar()->reset(true, andReload);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<AvatarManager>();
|
||||
auto avatar = avatarManager->getMyAvatar();
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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<UserInputMapper>();
|
||||
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<Avatar>(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);
|
||||
}
|
||||
|
|
|
@ -29,10 +29,12 @@
|
|||
#include <ScriptEngine.h>
|
||||
#include <SettingHandle.h>
|
||||
#include <Sound.h>
|
||||
#include <shared/Camera.h>
|
||||
|
||||
#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);
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include <recording/Deck.h>
|
||||
#include <Rig.h>
|
||||
#include <trackers/FaceTracker.h>
|
||||
#include <trackers/EyeTracker.h>
|
||||
#include <FaceshiftConstants.h>
|
||||
|
||||
#include "devices/DdeFaceTracker.h"
|
||||
#include "Application.h"
|
||||
|
@ -46,18 +46,37 @@ void MyHead::simulate(float deltaTime) {
|
|||
auto player = DependencyManager::get<recording::Deck>();
|
||||
// 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<EyeTracker>();
|
||||
_isEyeTrackerConnected = eyeTracker->isTracking();
|
||||
// if eye tracker is connected we should get the data here.
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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<MyAvatar*>(_owningAvatar);
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
#include <DependencyManager.h>
|
||||
#include <GeometryUtil.h>
|
||||
#include <trackers/FaceTracker.h>
|
||||
#include <trackers/EyeTracker.h>
|
||||
#include <Rig.h>
|
||||
#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
|
||||
|
|
|
@ -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() {
|
||||
}
|
||||
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -245,9 +245,10 @@ QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail, bool dro
|
|||
}
|
||||
|
||||
QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime,
|
||||
const QVector<JointData>& lastSentJointData,
|
||||
AvatarDataPacket::SendStatus& sendStatus, bool dropFaceTracking, bool distanceAdjust,
|
||||
glm::vec3 viewerPosition, QVector<JointData>* sentJointDataOut, int maxDataSize, AvatarDataRate* outboundDataRateOut) const {
|
||||
const QVector<JointData>& lastSentJointData, AvatarDataPacket::SendStatus& sendStatus,
|
||||
bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition,
|
||||
QVector<JointData>* 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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -181,6 +181,11 @@ enum class Action {
|
|||
TRACKED_OBJECT_15,
|
||||
SPRINT,
|
||||
|
||||
LEFT_EYE,
|
||||
RIGHT_EYE,
|
||||
LEFT_EYE_BLINK,
|
||||
RIGHT_EYE_BLINK,
|
||||
|
||||
NUM_ACTIONS
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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<QString> 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<float> _actionStates = std::vector<float>(toInt(Action::NUM_ACTIONS), 0.0f);
|
||||
std::vector<float> _actionScales = std::vector<float>(toInt(Action::NUM_ACTIONS), 1.0f);
|
||||
std::vector<float> _lastActionStates = std::vector<float>(toInt(Action::NUM_ACTIONS), 0.0f);
|
||||
std::vector<bool> _actionStatesValid = std::vector<bool>(toInt(Action::NUM_ACTIONS), false);
|
||||
std::vector<Pose> _poseStates = std::vector<Pose>(toInt(Action::NUM_ACTIONS));
|
||||
std::vector<AxisValue> _lastStandardStates = std::vector<AxisValue>();
|
||||
|
||||
|
@ -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);
|
||||
|
|
|
@ -100,7 +100,7 @@ namespace controller {
|
|||
_currentPose = value;
|
||||
}
|
||||
protected:
|
||||
AxisValue _currentValue { 0.0f, 0 };
|
||||
AxisValue _currentValue { 0.0f, 0, false };
|
||||
Pose _currentPose {};
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ public:
|
|||
virtual void reset() override;
|
||||
|
||||
private:
|
||||
AxisValue _currentValue { 0.0f, 0 };
|
||||
AxisValue _currentValue { 0.0f, 0, false };
|
||||
Pose _currentPose{};
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<UserInputMapper>();
|
||||
auto deviceProxy = userInputMapper->getDevice(_input);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <ui/OffscreenQmlSurface.h>
|
||||
#include <ui/TabletScriptingInterface.h>
|
||||
#include <EntityScriptingInterface.h>
|
||||
#include <shared/LocalFileAccessGate.h>
|
||||
|
||||
#include "EntitiesRendererLogging.h"
|
||||
#include <NetworkingConstants.h>
|
||||
|
@ -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;
|
||||
|
|
|
@ -481,11 +481,15 @@ QUuid EntityScriptingInterface::addEntityInternal(const EntityItemProperties& pr
|
|||
_activityTracking.addedEntityCount++;
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include <ByteCountCoding.h>
|
||||
#include <GeometryUtil.h>
|
||||
#include <shared/LocalFileAccessGate.h>
|
||||
|
||||
#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<bool>([&] {
|
||||
return _localSafeContext;
|
||||
});
|
||||
}
|
||||
|
||||
void WebEntityItem::setAlpha(float alpha) {
|
||||
withWriteLock([&] {
|
||||
_needsRenderUpdate |= _alpha != alpha;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -528,6 +528,8 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta
|
|||
addOwnership(entityState);
|
||||
} else if (entityState->shouldSendBid()) {
|
||||
addOwnershipBid(entityState);
|
||||
} else {
|
||||
entityState->getEntity()->updateQueryAACube();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<std::pair<glm::vec3, glm::vec3>>& lin
|
|||
auto point2 = translation + rotation * line.second;
|
||||
_rays.push_back(Ray(point1, point2, color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <caption>Briefly draw a debug marker in front of your avatar, in world coordinates.</caption>
|
||||
* 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 <caption>Briefly draw a debug marker in front of your avatar, in avatar coordinates.</caption>
|
||||
* 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<glm::quat, glm::vec3, glm::vec4>;
|
||||
using MarkerInfo = std::tuple<glm::quat, glm::vec3, glm::vec4, float>;
|
||||
using MarkerMap = std::map<QString, MarkerInfo>;
|
||||
using Ray = std::tuple<glm::vec3, glm::vec3, glm::vec4>;
|
||||
using Rays = std::vector<Ray>;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 <QFuture>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "Logging.h"
|
||||
#include <OctreeConstants.h>
|
||||
|
||||
#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<EyeTracker>();
|
||||
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<int> 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
|
|
@ -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 <QObject>
|
||||
#include <QFutureWatcher>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#ifdef HAVE_IVIEWHMD
|
||||
#include <iViewHMDAPI.h>
|
||||
#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<int> _startStreamingWatcher;
|
||||
};
|
||||
|
||||
#endif // hifi_EyeTracker_h
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Reference in a new issue