mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-07-23 06:08:05 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into skin
This commit is contained in:
commit
938bc77b1f
33 changed files with 629 additions and 543 deletions
25
cmake/externals/sixense/CMakeLists.txt
vendored
25
cmake/externals/sixense/CMakeLists.txt
vendored
|
@ -57,30 +57,7 @@ if (WIN32)
|
||||||
|
|
||||||
elseif(APPLE)
|
elseif(APPLE)
|
||||||
|
|
||||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/lib/osx_x64/release_dll/libsixense_x64.dylib CACHE TYPE INTERNAL)
|
# We no longer support Sixense on Macs due to bugs in the Sixense DLL
|
||||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${SOURCE_DIR}/lib/osx_x64/debug_dll/libsixensed_x64.dylib CACHE TYPE INTERNAL)
|
|
||||||
|
|
||||||
set(_SIXENSE_LIB_DIR "${SOURCE_DIR}/lib/osx_x64")
|
|
||||||
ExternalProject_Add_Step(
|
|
||||||
${EXTERNAL_NAME}
|
|
||||||
change-install-name-release
|
|
||||||
COMMENT "Calling install_name_tool on libraries to fix install name for dylib linking"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${_SIXENSE_LIB_DIR}/release_dll -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake
|
|
||||||
DEPENDEES install
|
|
||||||
WORKING_DIRECTORY <SOURCE_DIR>
|
|
||||||
LOG 1
|
|
||||||
)
|
|
||||||
|
|
||||||
set(_SIXENSE_LIB_DIR "${SOURCE_DIR}/lib/osx_x64")
|
|
||||||
ExternalProject_Add_Step(
|
|
||||||
${EXTERNAL_NAME}
|
|
||||||
change-install-name-debug
|
|
||||||
COMMENT "Calling install_name_tool on libraries to fix install name for dylib linking"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${_SIXENSE_LIB_DIR}/debug_dll -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake
|
|
||||||
DEPENDEES install
|
|
||||||
WORKING_DIRECTORY <SOURCE_DIR>
|
|
||||||
LOG 1
|
|
||||||
)
|
|
||||||
|
|
||||||
elseif(NOT ANDROID)
|
elseif(NOT ANDROID)
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ macro(optional_win_executable_signing)
|
||||||
# setup a post build command to sign the executable
|
# setup a post build command to sign the executable
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
TARGET ${TARGET_NAME} POST_BUILD
|
TARGET ${TARGET_NAME} POST_BUILD
|
||||||
COMMAND ${SIGNTOOL_EXECUTABLE} sign /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://tsa.starfieldtech.com /td SHA256 ${EXECUTABLE_PATH}
|
COMMAND ${SIGNTOOL_EXECUTABLE} sign /fd sha256 /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://tsa.starfieldtech.com /td SHA256 ${EXECUTABLE_PATH}
|
||||||
)
|
)
|
||||||
else ()
|
else ()
|
||||||
message(FATAL_ERROR "HF_PFX_PASSPHRASE must be set for executables to be signed.")
|
message(FATAL_ERROR "HF_PFX_PASSPHRASE must be set for executables to be signed.")
|
||||||
|
|
|
@ -6,9 +6,11 @@
|
||||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
#
|
#
|
||||||
macro(TARGET_SIXENSE)
|
macro(TARGET_SIXENSE)
|
||||||
add_dependency_external_projects(sixense)
|
if(NOT APPLE)
|
||||||
find_package(Sixense REQUIRED)
|
add_dependency_external_projects(sixense)
|
||||||
target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS})
|
find_package(Sixense REQUIRED)
|
||||||
target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES})
|
target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS})
|
||||||
add_definitions(-DHAVE_SIXENSE)
|
target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES})
|
||||||
|
add_definitions(-DHAVE_SIXENSE)
|
||||||
|
endif()
|
||||||
endmacro()
|
endmacro()
|
||||||
|
|
|
@ -64,7 +64,7 @@
|
||||||
; The Inner invocation has written an uninstaller binary for us.
|
; The Inner invocation has written an uninstaller binary for us.
|
||||||
; We need to sign it if it's a production or PR build.
|
; We need to sign it if it's a production or PR build.
|
||||||
!if @PRODUCTION_BUILD@ == 1
|
!if @PRODUCTION_BUILD@ == 1
|
||||||
!system '"@SIGNTOOL_EXECUTABLE@" sign /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://tsa.starfieldtech.com /td SHA256 $%TEMP%\@UNINSTALLER_NAME@' = 0
|
!system '"@SIGNTOOL_EXECUTABLE@" sign /fd sha256 /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://tsa.starfieldtech.com /td SHA256 $%TEMP%\@UNINSTALLER_NAME@' = 0
|
||||||
!endif
|
!endif
|
||||||
|
|
||||||
; Good. Now we can carry on writing the real installer.
|
; Good. Now we can carry on writing the real installer.
|
||||||
|
|
|
@ -384,7 +384,7 @@
|
||||||
"name": "standard_permissions",
|
"name": "standard_permissions",
|
||||||
"type": "table",
|
"type": "table",
|
||||||
"label": "Domain-Wide User Permissions",
|
"label": "Domain-Wide User Permissions",
|
||||||
"help": "Indicate which users or groups can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
|
"help": "Indicate which users or groups can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
|
||||||
"caption": "Standard Permissions",
|
"caption": "Standard Permissions",
|
||||||
"can_add_new_rows": false,
|
"can_add_new_rows": false,
|
||||||
|
|
||||||
|
@ -394,7 +394,7 @@
|
||||||
"span": 1
|
"span": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
|
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
|
||||||
"span": 6
|
"span": 6
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -463,7 +463,7 @@
|
||||||
"span": 1
|
"span": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
|
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
|
||||||
"span": 6
|
"span": 6
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -457,6 +457,8 @@ function disonnectHighFidelityAccount() {
|
||||||
}, function(){
|
}, function(){
|
||||||
// we need to post to settings to clear the access-token
|
// we need to post to settings to clear the access-token
|
||||||
$(Settings.ACCESS_TOKEN_SELECTOR).val('').change();
|
$(Settings.ACCESS_TOKEN_SELECTOR).val('').change();
|
||||||
|
// reset the domain id to get a new temporary name
|
||||||
|
$(Settings.DOMAIN_ID_SELECTOR).val('').change();
|
||||||
saveSettings();
|
saveSettings();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -555,7 +557,7 @@ function createNewDomainID(description, justConnected) {
|
||||||
// get the JSON object ready that we'll use to create a new domain
|
// get the JSON object ready that we'll use to create a new domain
|
||||||
var domainJSON = {
|
var domainJSON = {
|
||||||
"domain": {
|
"domain": {
|
||||||
"description": description
|
"private_description": description
|
||||||
},
|
},
|
||||||
"access_token": $(Settings.ACCESS_TOKEN_SELECTOR).val()
|
"access_token": $(Settings.ACCESS_TOKEN_SELECTOR).val()
|
||||||
}
|
}
|
||||||
|
@ -748,8 +750,8 @@ function chooseFromHighFidelityDomains(clickedButton) {
|
||||||
_.each(data.data.domains, function(domain){
|
_.each(data.data.domains, function(domain){
|
||||||
var domainString = "";
|
var domainString = "";
|
||||||
|
|
||||||
if (domain.description) {
|
if (domain.private_description) {
|
||||||
domainString += '"' + domain.description + '" - ';
|
domainString += '"' + domain.private_description + '" - ';
|
||||||
}
|
}
|
||||||
|
|
||||||
domainString += domain.id;
|
domainString += domain.id;
|
||||||
|
|
|
@ -76,6 +76,8 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
||||||
setApplicationVersion(BuildInfo::VERSION);
|
setApplicationVersion(BuildInfo::VERSION);
|
||||||
QSettings::setDefaultFormat(QSettings::IniFormat);
|
QSettings::setDefaultFormat(QSettings::IniFormat);
|
||||||
|
|
||||||
|
qDebug() << "Setting up domain-server";
|
||||||
|
|
||||||
// make sure we have a fresh AccountManager instance
|
// make sure we have a fresh AccountManager instance
|
||||||
// (need this since domain-server can restart itself and maintain static variables)
|
// (need this since domain-server can restart itself and maintain static variables)
|
||||||
DependencyManager::set<AccountManager>();
|
DependencyManager::set<AccountManager>();
|
||||||
|
@ -104,23 +106,31 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
||||||
connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions,
|
connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions,
|
||||||
&_gatekeeper, &DomainGatekeeper::updateNodePermissions);
|
&_gatekeeper, &DomainGatekeeper::updateNodePermissions);
|
||||||
|
|
||||||
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) {
|
// if we were given a certificate/private key or oauth credentials they must succeed
|
||||||
// we either read a certificate and private key or were not passed one
|
if (!(optionallyReadX509KeyAndCertificate() && optionallySetupOAuth())) {
|
||||||
// and completed login or did not need to
|
return;
|
||||||
|
|
||||||
qDebug() << "Setting up LimitedNodeList and assignments.";
|
|
||||||
setupNodeListAndAssignments();
|
|
||||||
|
|
||||||
// setup automatic networking settings with data server
|
|
||||||
setupAutomaticNetworking();
|
|
||||||
|
|
||||||
// preload some user public keys so they can connect on first request
|
|
||||||
_gatekeeper.preloadAllowedUserPublicKeys();
|
|
||||||
|
|
||||||
optionallyGetTemporaryName(args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupNodeListAndAssignments();
|
||||||
|
setupAutomaticNetworking();
|
||||||
|
if (!getID().isNull()) {
|
||||||
|
setupHeartbeatToMetaverse();
|
||||||
|
// send the first heartbeat immediately
|
||||||
|
sendHeartbeatToMetaverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for the temporary name parameter
|
||||||
|
const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name";
|
||||||
|
if (args.contains(GET_TEMPORARY_NAME_SWITCH)) {
|
||||||
|
getTemporaryName();
|
||||||
|
}
|
||||||
|
|
||||||
|
_gatekeeper.preloadAllowedUserPublicKeys(); // so they can connect on first request
|
||||||
|
|
||||||
_metadata = new DomainMetadata(this);
|
_metadata = new DomainMetadata(this);
|
||||||
|
|
||||||
|
|
||||||
|
qDebug() << "domain-server is running";
|
||||||
}
|
}
|
||||||
|
|
||||||
DomainServer::~DomainServer() {
|
DomainServer::~DomainServer() {
|
||||||
|
@ -148,6 +158,10 @@ void DomainServer::restart() {
|
||||||
exit(DomainServer::EXIT_CODE_REBOOT);
|
exit(DomainServer::EXIT_CODE_REBOOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QUuid& DomainServer::getID() {
|
||||||
|
return DependencyManager::get<LimitedNodeList>()->getSessionUUID();
|
||||||
|
}
|
||||||
|
|
||||||
bool DomainServer::optionallyReadX509KeyAndCertificate() {
|
bool DomainServer::optionallyReadX509KeyAndCertificate() {
|
||||||
const QString X509_CERTIFICATE_OPTION = "cert";
|
const QString X509_CERTIFICATE_OPTION = "cert";
|
||||||
const QString X509_PRIVATE_KEY_OPTION = "key";
|
const QString X509_PRIVATE_KEY_OPTION = "key";
|
||||||
|
@ -233,34 +247,26 @@ bool DomainServer::optionallySetupOAuth() {
|
||||||
|
|
||||||
static const QString METAVERSE_DOMAIN_ID_KEY_PATH = "metaverse.id";
|
static const QString METAVERSE_DOMAIN_ID_KEY_PATH = "metaverse.id";
|
||||||
|
|
||||||
void DomainServer::optionallyGetTemporaryName(const QStringList& arguments) {
|
void DomainServer::getTemporaryName(bool force) {
|
||||||
// check for the temporary name parameter
|
// check if we already have a domain ID
|
||||||
const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name";
|
const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH);
|
||||||
|
|
||||||
if (arguments.contains(GET_TEMPORARY_NAME_SWITCH)) {
|
qInfo() << "Requesting temporary domain name";
|
||||||
|
if (idValueVariant) {
|
||||||
// make sure we don't already have a domain ID
|
qDebug() << "A domain ID is already present in domain-server settings:" << idValueVariant->toString();
|
||||||
const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH);
|
if (force) {
|
||||||
if (idValueVariant) {
|
qDebug() << "Requesting temporary domain name to replace current ID:" << getID();
|
||||||
qWarning() << "Temporary domain name requested but a domain ID is already present in domain-server settings."
|
} else {
|
||||||
<< "Will not request temporary name.";
|
qInfo() << "Abandoning request of temporary domain name.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we've been asked to grab a temporary name from the API
|
|
||||||
// so fire off that request now
|
|
||||||
auto accountManager = DependencyManager::get<AccountManager>();
|
|
||||||
|
|
||||||
// get callbacks for temporary domain result
|
|
||||||
JSONCallbackParameters callbackParameters;
|
|
||||||
callbackParameters.jsonCallbackReceiver = this;
|
|
||||||
callbackParameters.jsonCallbackMethod = "handleTempDomainSuccess";
|
|
||||||
callbackParameters.errorCallbackReceiver = this;
|
|
||||||
callbackParameters.errorCallbackMethod = "handleTempDomainError";
|
|
||||||
|
|
||||||
accountManager->sendRequest("/api/v1/domains/temporary", AccountManagerAuth::None,
|
|
||||||
QNetworkAccessManager::PostOperation, callbackParameters);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// request a temporary name from the metaverse
|
||||||
|
auto accountManager = DependencyManager::get<AccountManager>();
|
||||||
|
JSONCallbackParameters callbackParameters { this, "handleTempDomainSuccess", this, "handleTempDomainError" };
|
||||||
|
accountManager->sendRequest("/api/v1/domains/temporary", AccountManagerAuth::None,
|
||||||
|
QNetworkAccessManager::PostOperation, callbackParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
|
void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
|
||||||
|
@ -271,11 +277,13 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
|
||||||
static const QString DOMAIN_KEY = "domain";
|
static const QString DOMAIN_KEY = "domain";
|
||||||
static const QString ID_KEY = "id";
|
static const QString ID_KEY = "id";
|
||||||
static const QString NAME_KEY = "name";
|
static const QString NAME_KEY = "name";
|
||||||
|
static const QString KEY_KEY = "api_key";
|
||||||
|
|
||||||
auto domainObject = jsonObject[DATA_KEY].toObject()[DOMAIN_KEY].toObject();
|
auto domainObject = jsonObject[DATA_KEY].toObject()[DOMAIN_KEY].toObject();
|
||||||
if (!domainObject.isEmpty()) {
|
if (!domainObject.isEmpty()) {
|
||||||
auto id = domainObject[ID_KEY].toString();
|
auto id = domainObject[ID_KEY].toString();
|
||||||
auto name = domainObject[NAME_KEY].toString();
|
auto name = domainObject[NAME_KEY].toString();
|
||||||
|
auto key = domainObject[KEY_KEY].toString();
|
||||||
|
|
||||||
qInfo() << "Received new temporary domain name" << name;
|
qInfo() << "Received new temporary domain name" << name;
|
||||||
qDebug() << "The temporary domain ID is" << id;
|
qDebug() << "The temporary domain ID is" << id;
|
||||||
|
@ -291,9 +299,13 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
|
||||||
// change our domain ID immediately
|
// change our domain ID immediately
|
||||||
DependencyManager::get<LimitedNodeList>()->setSessionUUID(QUuid { id });
|
DependencyManager::get<LimitedNodeList>()->setSessionUUID(QUuid { id });
|
||||||
|
|
||||||
// change our automatic networking settings so that we're communicating with the ICE server
|
// store the new token to the account info
|
||||||
setupICEHeartbeatForFullNetworking();
|
auto accountManager = DependencyManager::get<AccountManager>();
|
||||||
|
accountManager->setTemporaryDomain(id, key);
|
||||||
|
|
||||||
|
// update our heartbeats to use the correct id
|
||||||
|
setupICEHeartbeatForFullNetworking();
|
||||||
|
setupHeartbeatToMetaverse();
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "There were problems parsing the API response containing a temporary domain name. Please try again"
|
qWarning() << "There were problems parsing the API response containing a temporary domain name. Please try again"
|
||||||
<< "via domain-server relaunch or from the domain-server settings.";
|
<< "via domain-server relaunch or from the domain-server settings.";
|
||||||
|
@ -332,8 +344,7 @@ bool DomainServer::packetVersionMatch(const udt::Packet& packet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
void DomainServer::setupNodeListAndAssignments() {
|
||||||
|
|
||||||
const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port";
|
const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port";
|
||||||
|
|
||||||
QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION);
|
QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION);
|
||||||
|
@ -458,29 +469,23 @@ bool DomainServer::resetAccountManagerAccessToken() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServer::setupAutomaticNetworking() {
|
void DomainServer::setupAutomaticNetworking() {
|
||||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting;
|
||||||
|
|
||||||
|
resetAccountManagerAccessToken();
|
||||||
|
|
||||||
_automaticNetworkingSetting =
|
_automaticNetworkingSetting =
|
||||||
_settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString();
|
_settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString();
|
||||||
|
|
||||||
|
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
const QUuid& domainID = getID();
|
||||||
|
|
||||||
if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||||
setupICEHeartbeatForFullNetworking();
|
setupICEHeartbeatForFullNetworking();
|
||||||
}
|
}
|
||||||
|
|
||||||
_hasAccessToken = resetAccountManagerAccessToken();
|
|
||||||
|
|
||||||
if (!_hasAccessToken) {
|
|
||||||
qDebug() << "Will not send heartbeat to Metaverse API without an access token.";
|
|
||||||
qDebug() << "If this is not a temporary domain add an access token to your config file or via the web interface.";
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
|
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
|
||||||
_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||||
|
|
||||||
const QUuid& domainID = nodeList->getSessionUUID();
|
|
||||||
|
|
||||||
if (!domainID.isNull()) {
|
if (!domainID.isNull()) {
|
||||||
qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
|
qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
|
||||||
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
|
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
|
||||||
|
@ -492,9 +497,6 @@ void DomainServer::setupAutomaticNetworking() {
|
||||||
|
|
||||||
// have the LNL enable public socket updating via STUN
|
// have the LNL enable public socket updating via STUN
|
||||||
nodeList->startSTUNPublicSocketUpdate();
|
nodeList->startSTUNPublicSocketUpdate();
|
||||||
} else {
|
|
||||||
// send our heartbeat to data server so it knows what our network settings are
|
|
||||||
sendHeartbeatToMetaverse();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
|
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
|
||||||
|
@ -502,18 +504,20 @@ void DomainServer::setupAutomaticNetworking() {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
sendHeartbeatToMetaverse();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting;
|
void DomainServer::setupHeartbeatToMetaverse() {
|
||||||
|
// heartbeat to the data-server every 15s
|
||||||
// no matter the auto networking settings we should heartbeat to the data-server every 15s
|
|
||||||
const int DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS = 15 * 1000;
|
const int DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS = 15 * 1000;
|
||||||
|
|
||||||
QTimer* dataHeartbeatTimer = new QTimer(this);
|
if (!_metaverseHeartbeatTimer) {
|
||||||
connect(dataHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToMetaverse()));
|
// setup a timer to heartbeat with the metaverse-server
|
||||||
dataHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS);
|
_metaverseHeartbeatTimer = new QTimer { this };
|
||||||
|
connect(_metaverseHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToMetaverse()));
|
||||||
|
// do not send a heartbeat immediately - this avoids flooding if the heartbeat fails with a 401
|
||||||
|
_metaverseHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServer::setupICEHeartbeatForFullNetworking() {
|
void DomainServer::setupICEHeartbeatForFullNetworking() {
|
||||||
|
@ -532,22 +536,21 @@ void DomainServer::setupICEHeartbeatForFullNetworking() {
|
||||||
limitedNodeList->startSTUNPublicSocketUpdate();
|
limitedNodeList->startSTUNPublicSocketUpdate();
|
||||||
|
|
||||||
// to send ICE heartbeats we'd better have a private key locally with an uploaded public key
|
// to send ICE heartbeats we'd better have a private key locally with an uploaded public key
|
||||||
auto accountManager = DependencyManager::get<AccountManager>();
|
|
||||||
auto domainID = accountManager->getAccountInfo().getDomainID();
|
|
||||||
|
|
||||||
// if we have an access token and we don't have a private key or the current domain ID has changed
|
// if we have an access token and we don't have a private key or the current domain ID has changed
|
||||||
// we should generate a new keypair
|
// we should generate a new keypair
|
||||||
if (!accountManager->getAccountInfo().hasPrivateKey() || domainID != limitedNodeList->getSessionUUID()) {
|
auto accountManager = DependencyManager::get<AccountManager>();
|
||||||
accountManager->generateNewDomainKeypair(limitedNodeList->getSessionUUID());
|
if (!accountManager->getAccountInfo().hasPrivateKey() || accountManager->getAccountInfo().getDomainID() != getID()) {
|
||||||
|
accountManager->generateNewDomainKeypair(getID());
|
||||||
}
|
}
|
||||||
|
|
||||||
// hookup to the signal from account manager that tells us when keypair is available
|
// hookup to the signal from account manager that tells us when keypair is available
|
||||||
connect(accountManager.data(), &AccountManager::newKeypair, this, &DomainServer::handleKeypairChange);
|
connect(accountManager.data(), &AccountManager::newKeypair, this, &DomainServer::handleKeypairChange);
|
||||||
|
|
||||||
if (!_iceHeartbeatTimer) {
|
if (!_iceHeartbeatTimer) {
|
||||||
// setup a timer to heartbeat with the ice-server every so often
|
// setup a timer to heartbeat with the ice-server
|
||||||
_iceHeartbeatTimer = new QTimer { this };
|
_iceHeartbeatTimer = new QTimer { this };
|
||||||
connect(_iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::sendHeartbeatToIceServer);
|
connect(_iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::sendHeartbeatToIceServer);
|
||||||
|
sendHeartbeatToIceServer();
|
||||||
_iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS);
|
_iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1075,12 +1078,14 @@ void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
||||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
|
||||||
const QUuid& domainID = nodeList->getSessionUUID();
|
|
||||||
|
|
||||||
// Setup the domain object to send to the data server
|
// Setup the domain object to send to the data server
|
||||||
QJsonObject domainObject;
|
QJsonObject domainObject;
|
||||||
|
|
||||||
|
// add the version
|
||||||
|
static const QString VERSION_KEY = "version";
|
||||||
|
domainObject[VERSION_KEY] = BuildInfo::VERSION;
|
||||||
|
|
||||||
|
// add networking
|
||||||
if (!networkAddress.isEmpty()) {
|
if (!networkAddress.isEmpty()) {
|
||||||
static const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address";
|
static const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address";
|
||||||
domainObject[PUBLIC_NETWORK_ADDRESS_KEY] = networkAddress;
|
domainObject[PUBLIC_NETWORK_ADDRESS_KEY] = networkAddress;
|
||||||
|
@ -1089,13 +1094,20 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
||||||
static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";
|
static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";
|
||||||
domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting;
|
domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting;
|
||||||
|
|
||||||
// add a flag to indicate if this domain uses restricted access - for now that will exclude it from listings
|
|
||||||
const QString RESTRICTED_ACCESS_FLAG = "restricted";
|
|
||||||
|
|
||||||
// consider the domain to have restricted access if "anonymous" connections can't connect to the domain.
|
// add access level for anonymous connections
|
||||||
|
// consider the domain to be "restricted" if anonymous connections are disallowed
|
||||||
|
static const QString RESTRICTED_ACCESS_FLAG = "restricted";
|
||||||
NodePermissions anonymousPermissions = _settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous);
|
NodePermissions anonymousPermissions = _settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||||
domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.canConnectToDomain;
|
domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.canConnectToDomain;
|
||||||
|
|
||||||
|
const auto& temporaryDomainKey = DependencyManager::get<AccountManager>()->getTemporaryDomainKey(getID());
|
||||||
|
if (!temporaryDomainKey.isEmpty()) {
|
||||||
|
// add the temporary domain token
|
||||||
|
const QString KEY_KEY = "api_key";
|
||||||
|
domainObject[KEY_KEY] = temporaryDomainKey;
|
||||||
|
}
|
||||||
|
|
||||||
if (_metadata) {
|
if (_metadata) {
|
||||||
// Add the metadata to the heartbeat
|
// Add the metadata to the heartbeat
|
||||||
static const QString DOMAIN_HEARTBEAT_KEY = "heartbeat";
|
static const QString DOMAIN_HEARTBEAT_KEY = "heartbeat";
|
||||||
|
@ -1105,18 +1117,60 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
||||||
QString domainUpdateJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(domainObject).toJson(QJsonDocument::Compact)));
|
QString domainUpdateJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(domainObject).toJson(QJsonDocument::Compact)));
|
||||||
|
|
||||||
static const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
|
static const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
|
||||||
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)),
|
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(getID())),
|
||||||
AccountManagerAuth::Required,
|
AccountManagerAuth::Optional,
|
||||||
QNetworkAccessManager::PutOperation,
|
QNetworkAccessManager::PutOperation,
|
||||||
JSONCallbackParameters(),
|
JSONCallbackParameters(nullptr, QString(), this, "handleMetaverseHeartbeatError"),
|
||||||
domainUpdateJSON.toUtf8());
|
domainUpdateJSON.toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DomainServer::handleMetaverseHeartbeatError(QNetworkReply& requestReply) {
|
||||||
|
if (!_metaverseHeartbeatTimer) {
|
||||||
|
// avoid rehandling errors from the same issue
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we need to force a new temporary domain name
|
||||||
|
switch (requestReply.error()) {
|
||||||
|
// if we have a temporary domain with a bad token, we get a 401
|
||||||
|
case QNetworkReply::NetworkError::AuthenticationRequiredError: {
|
||||||
|
static const QString DATA_KEY = "data";
|
||||||
|
static const QString TOKEN_KEY = "api_key";
|
||||||
|
|
||||||
|
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||||
|
auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY];
|
||||||
|
|
||||||
|
if (!tokenFailure.isNull()) {
|
||||||
|
qWarning() << "Temporary domain name lacks a valid API key, and is being reset.";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// if the domain does not (or no longer) exists, we get a 404
|
||||||
|
case QNetworkReply::NetworkError::ContentNotFoundError:
|
||||||
|
qWarning() << "Domain not found, getting a new temporary domain.";
|
||||||
|
break;
|
||||||
|
// otherwise, we erred on something else, and should not force a temporary domain
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// halt heartbeats until we have a token
|
||||||
|
_metaverseHeartbeatTimer->deleteLater();
|
||||||
|
_metaverseHeartbeatTimer = nullptr;
|
||||||
|
|
||||||
|
// give up eventually to avoid flooding traffic
|
||||||
|
static const int MAX_ATTEMPTS = 5;
|
||||||
|
static int attempt = 0;
|
||||||
|
if (++attempt < MAX_ATTEMPTS) {
|
||||||
|
// get a new temporary name and token
|
||||||
|
getTemporaryName(true);
|
||||||
|
} else {
|
||||||
|
qWarning() << "Already attempted too many temporary domain requests. Please set a domain ID manually or restart.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DomainServer::sendICEServerAddressToMetaverseAPI() {
|
void DomainServer::sendICEServerAddressToMetaverseAPI() {
|
||||||
if (!_iceServerSocket.isNull()) {
|
if (!_iceServerSocket.isNull()) {
|
||||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
|
||||||
const QUuid& domainID = nodeList->getSessionUUID();
|
|
||||||
|
|
||||||
const QString ICE_SERVER_ADDRESS = "ice_server_address";
|
const QString ICE_SERVER_ADDRESS = "ice_server_address";
|
||||||
|
|
||||||
QJsonObject domainObject;
|
QJsonObject domainObject;
|
||||||
|
@ -1124,6 +1178,13 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() {
|
||||||
// we're using full automatic networking and we have a current ice-server socket, use that now
|
// we're using full automatic networking and we have a current ice-server socket, use that now
|
||||||
domainObject[ICE_SERVER_ADDRESS] = _iceServerSocket.getAddress().toString();
|
domainObject[ICE_SERVER_ADDRESS] = _iceServerSocket.getAddress().toString();
|
||||||
|
|
||||||
|
const auto& temporaryDomainKey = DependencyManager::get<AccountManager>()->getTemporaryDomainKey(getID());
|
||||||
|
if (!temporaryDomainKey.isEmpty()) {
|
||||||
|
// add the temporary domain token
|
||||||
|
const QString KEY_KEY = "api_key";
|
||||||
|
domainObject[KEY_KEY] = temporaryDomainKey;
|
||||||
|
}
|
||||||
|
|
||||||
QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson()));
|
QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson()));
|
||||||
|
|
||||||
// make sure we hear about failure so we can retry
|
// make sure we hear about failure so we can retry
|
||||||
|
@ -1135,7 +1196,7 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() {
|
||||||
|
|
||||||
static const QString DOMAIN_ICE_ADDRESS_UPDATE = "/api/v1/domains/%1/ice_server_address";
|
static const QString DOMAIN_ICE_ADDRESS_UPDATE = "/api/v1/domains/%1/ice_server_address";
|
||||||
|
|
||||||
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_ICE_ADDRESS_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)),
|
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_ICE_ADDRESS_UPDATE.arg(uuidStringWithoutCurlyBraces(getID())),
|
||||||
AccountManagerAuth::Optional,
|
AccountManagerAuth::Optional,
|
||||||
QNetworkAccessManager::PutOperation,
|
QNetworkAccessManager::PutOperation,
|
||||||
callbackParameters,
|
callbackParameters,
|
||||||
|
|
|
@ -80,6 +80,8 @@ private slots:
|
||||||
void handleTempDomainSuccess(QNetworkReply& requestReply);
|
void handleTempDomainSuccess(QNetworkReply& requestReply);
|
||||||
void handleTempDomainError(QNetworkReply& requestReply);
|
void handleTempDomainError(QNetworkReply& requestReply);
|
||||||
|
|
||||||
|
void handleMetaverseHeartbeatError(QNetworkReply& requestReply);
|
||||||
|
|
||||||
void queuedQuit(QString quitMessage, int exitCode);
|
void queuedQuit(QString quitMessage, int exitCode);
|
||||||
|
|
||||||
void handleKeypairChange();
|
void handleKeypairChange();
|
||||||
|
@ -96,11 +98,13 @@ signals:
|
||||||
void userDisconnected();
|
void userDisconnected();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
const QUuid& getID();
|
||||||
|
|
||||||
|
void setupNodeListAndAssignments();
|
||||||
bool optionallySetupOAuth();
|
bool optionallySetupOAuth();
|
||||||
bool optionallyReadX509KeyAndCertificate();
|
bool optionallyReadX509KeyAndCertificate();
|
||||||
|
|
||||||
void optionallyGetTemporaryName(const QStringList& arguments);
|
void getTemporaryName(bool force = false);
|
||||||
|
|
||||||
static bool packetVersionMatch(const udt::Packet& packet);
|
static bool packetVersionMatch(const udt::Packet& packet);
|
||||||
|
|
||||||
|
@ -108,6 +112,7 @@ private:
|
||||||
|
|
||||||
void setupAutomaticNetworking();
|
void setupAutomaticNetworking();
|
||||||
void setupICEHeartbeatForFullNetworking();
|
void setupICEHeartbeatForFullNetworking();
|
||||||
|
void setupHeartbeatToMetaverse();
|
||||||
void sendHeartbeatToMetaverse(const QString& networkAddress);
|
void sendHeartbeatToMetaverse(const QString& networkAddress);
|
||||||
|
|
||||||
void randomizeICEServerAddress(bool shouldTriggerHostLookup);
|
void randomizeICEServerAddress(bool shouldTriggerHostLookup);
|
||||||
|
@ -178,6 +183,7 @@ private:
|
||||||
// These will be parented to this, they are not dangling
|
// These will be parented to this, they are not dangling
|
||||||
DomainMetadata* _metadata { nullptr };
|
DomainMetadata* _metadata { nullptr };
|
||||||
QTimer* _iceHeartbeatTimer { nullptr };
|
QTimer* _iceHeartbeatTimer { nullptr };
|
||||||
|
QTimer* _metaverseHeartbeatTimer { nullptr };
|
||||||
|
|
||||||
QList<QHostAddress> _iceServerAddresses;
|
QList<QHostAddress> _iceServerAddresses;
|
||||||
QSet<QHostAddress> _failedIceServerAddresses;
|
QSet<QHostAddress> _failedIceServerAddresses;
|
||||||
|
@ -186,8 +192,6 @@ private:
|
||||||
int _numHeartbeatDenials { 0 };
|
int _numHeartbeatDenials { 0 };
|
||||||
bool _connectedToICEServer { false };
|
bool _connectedToICEServer { false };
|
||||||
|
|
||||||
bool _hasAccessToken { false };
|
|
||||||
|
|
||||||
friend class DomainGatekeeper;
|
friend class DomainGatekeeper;
|
||||||
friend class DomainMetadata;
|
friend class DomainMetadata;
|
||||||
};
|
};
|
||||||
|
|
|
@ -921,17 +921,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
||||||
cycleCamera();
|
cycleCamera();
|
||||||
} else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) {
|
} else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) {
|
||||||
if (!offscreenUi->navigationFocused()) {
|
if (!offscreenUi->navigationFocused()) {
|
||||||
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
toggleMenuUnderReticle();
|
||||||
offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y));
|
|
||||||
}
|
}
|
||||||
} else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
|
} else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
|
||||||
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
toggleMenuUnderReticle();
|
||||||
offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y));
|
|
||||||
} else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) {
|
|
||||||
if (!offscreenUi->navigationFocused()) {
|
|
||||||
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
|
||||||
offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y));
|
|
||||||
}
|
|
||||||
} else if (action == controller::toInt(controller::Action::RETICLE_X)) {
|
} else if (action == controller::toInt(controller::Action::RETICLE_X)) {
|
||||||
auto oldPos = getApplicationCompositor().getReticlePosition();
|
auto oldPos = getApplicationCompositor().getReticlePosition();
|
||||||
getApplicationCompositor().setReticlePosition({ oldPos.x + state, oldPos.y });
|
getApplicationCompositor().setReticlePosition({ oldPos.x + state, oldPos.y });
|
||||||
|
@ -1240,7 +1233,16 @@ QString Application::getUserAgent() {
|
||||||
return userAgent;
|
return userAgent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::toggleMenuUnderReticle() const {
|
||||||
|
// In HMD, if the menu is near the mouse but not under it, the reticle can be at a significantly
|
||||||
|
// different depth. When you focus on the menu, the cursor can appear to your crossed eyes as both
|
||||||
|
// on the menu and off.
|
||||||
|
// Even in 2D, it is arguable whether the user would want the menu to be to the side.
|
||||||
|
const float X_LEFT_SHIFT = 50.0;
|
||||||
|
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||||
|
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
||||||
|
offscreenUi->toggleMenu(QPoint(reticlePosition.x - X_LEFT_SHIFT, reticlePosition.y));
|
||||||
|
}
|
||||||
|
|
||||||
void Application::checkChangeCursor() {
|
void Application::checkChangeCursor() {
|
||||||
QMutexLocker locker(&_changeCursorLock);
|
QMutexLocker locker(&_changeCursorLock);
|
||||||
|
@ -2462,9 +2464,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
||||||
|
|
||||||
void Application::keyReleaseEvent(QKeyEvent* event) {
|
void Application::keyReleaseEvent(QKeyEvent* event) {
|
||||||
if (event->key() == Qt::Key_Alt && _altPressed && hasFocus()) {
|
if (event->key() == Qt::Key_Alt && _altPressed && hasFocus()) {
|
||||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
toggleMenuUnderReticle();
|
||||||
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
|
||||||
offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_keysPressed.remove(event->key());
|
_keysPressed.remove(event->key());
|
||||||
|
|
|
@ -408,6 +408,7 @@ private:
|
||||||
static void dragEnterEvent(QDragEnterEvent* event);
|
static void dragEnterEvent(QDragEnterEvent* event);
|
||||||
|
|
||||||
void maybeToggleMenuVisible(QMouseEvent* event) const;
|
void maybeToggleMenuVisible(QMouseEvent* event) const;
|
||||||
|
void toggleMenuUnderReticle() const;
|
||||||
|
|
||||||
MainWindow* _window;
|
MainWindow* _window;
|
||||||
QElapsedTimer& _sessionRunTimer;
|
QElapsedTimer& _sessionRunTimer;
|
||||||
|
|
|
@ -19,6 +19,9 @@
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
|
||||||
HMDScriptingInterface::HMDScriptingInterface() {
|
HMDScriptingInterface::HMDScriptingInterface() {
|
||||||
|
connect(qApp, &Application::activeDisplayPluginChanged, [this]{
|
||||||
|
emit displayModeChanged(isHMDMode());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec3 HMDScriptingInterface::calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const {
|
glm::vec3 HMDScriptingInterface::calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const {
|
||||||
|
|
|
@ -118,10 +118,12 @@ void OverlayConductor::update(float dt) {
|
||||||
bool isDriving = updateAvatarHasDriveInput();
|
bool isDriving = updateAvatarHasDriveInput();
|
||||||
bool drivingChanged = prevDriving != isDriving;
|
bool drivingChanged = prevDriving != isDriving;
|
||||||
bool isAtRest = updateAvatarIsAtRest();
|
bool isAtRest = updateAvatarIsAtRest();
|
||||||
|
bool shouldRecenter = false;
|
||||||
|
|
||||||
if (_flags & SuppressedByDrive) {
|
if (_flags & SuppressedByDrive) {
|
||||||
if (!isDriving) {
|
if (!isDriving) {
|
||||||
_flags &= ~SuppressedByDrive;
|
_flags &= ~SuppressedByDrive;
|
||||||
|
shouldRecenter = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) {
|
if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) {
|
||||||
|
@ -132,6 +134,7 @@ void OverlayConductor::update(float dt) {
|
||||||
if (_flags & SuppressedByHead) {
|
if (_flags & SuppressedByHead) {
|
||||||
if (isAtRest) {
|
if (isAtRest) {
|
||||||
_flags &= ~SuppressedByHead;
|
_flags &= ~SuppressedByHead;
|
||||||
|
shouldRecenter = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (_hmdMode && headOutsideOverlay()) {
|
if (_hmdMode && headOutsideOverlay()) {
|
||||||
|
@ -143,8 +146,8 @@ void OverlayConductor::update(float dt) {
|
||||||
bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask));
|
bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask));
|
||||||
if (targetVisible != currentVisible) {
|
if (targetVisible != currentVisible) {
|
||||||
offscreenUi->setPinned(!targetVisible);
|
offscreenUi->setPinned(!targetVisible);
|
||||||
if (targetVisible && _hmdMode) {
|
}
|
||||||
centerUI();
|
if (shouldRecenter && !_flags) {
|
||||||
}
|
centerUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,6 @@ AudioClient::AudioClient() :
|
||||||
_reverbOptions(&_scriptReverbOptions),
|
_reverbOptions(&_scriptReverbOptions),
|
||||||
_inputToNetworkResampler(NULL),
|
_inputToNetworkResampler(NULL),
|
||||||
_networkToOutputResampler(NULL),
|
_networkToOutputResampler(NULL),
|
||||||
_loopbackResampler(NULL),
|
|
||||||
_outgoingAvatarAudioSequenceNumber(0),
|
_outgoingAvatarAudioSequenceNumber(0),
|
||||||
_audioOutputIODevice(_receivedAudioStream, this),
|
_audioOutputIODevice(_receivedAudioStream, this),
|
||||||
_stats(&_receivedAudioStream),
|
_stats(&_receivedAudioStream),
|
||||||
|
@ -315,54 +314,35 @@ bool adjustedFormatForAudioDevice(const QAudioDeviceInfo& audioDevice,
|
||||||
|
|
||||||
// FIXME: directly using 24khz has a bug somewhere that causes channels to be swapped.
|
// FIXME: directly using 24khz has a bug somewhere that causes channels to be swapped.
|
||||||
// Continue using our internal resampler, for now.
|
// Continue using our internal resampler, for now.
|
||||||
if (true || !audioDevice.isFormatSupported(desiredAudioFormat)) {
|
qCDebug(audioclient) << "The desired format for audio I/O is" << desiredAudioFormat;
|
||||||
qCDebug(audioclient) << "The desired format for audio I/O is" << desiredAudioFormat;
|
|
||||||
qCDebug(audioclient, "The desired audio format is not supported by this device");
|
|
||||||
|
|
||||||
if (desiredAudioFormat.channelCount() == 1) {
|
const int FORTY_FOUR = 44100;
|
||||||
adjustedAudioFormat = desiredAudioFormat;
|
adjustedAudioFormat = desiredAudioFormat;
|
||||||
adjustedAudioFormat.setChannelCount(2);
|
|
||||||
|
|
||||||
if (false && audioDevice.isFormatSupported(adjustedAudioFormat)) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
adjustedAudioFormat.setChannelCount(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const int FORTY_FOUR = 44100;
|
|
||||||
|
|
||||||
adjustedAudioFormat = desiredAudioFormat;
|
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
adjustedAudioFormat.setSampleRate(FORTY_FOUR);
|
adjustedAudioFormat.setSampleRate(FORTY_FOUR);
|
||||||
#else
|
#else
|
||||||
|
|
||||||
const int HALF_FORTY_FOUR = FORTY_FOUR / 2;
|
const int HALF_FORTY_FOUR = FORTY_FOUR / 2;
|
||||||
|
|
||||||
if (audioDevice.supportedSampleRates().contains(AudioConstants::SAMPLE_RATE * 2)) {
|
if (audioDevice.supportedSampleRates().contains(AudioConstants::SAMPLE_RATE * 2)) {
|
||||||
// use 48, which is a sample downsample, upsample
|
// use 48, which is a simple downsample, upsample
|
||||||
adjustedAudioFormat.setSampleRate(AudioConstants::SAMPLE_RATE * 2);
|
adjustedAudioFormat.setSampleRate(AudioConstants::SAMPLE_RATE * 2);
|
||||||
} else if (audioDevice.supportedSampleRates().contains(HALF_FORTY_FOUR)) {
|
} else if (audioDevice.supportedSampleRates().contains(HALF_FORTY_FOUR)) {
|
||||||
// use 22050, resample but closer to 24
|
// use 22050, resample but closer to 24
|
||||||
adjustedAudioFormat.setSampleRate(HALF_FORTY_FOUR);
|
adjustedAudioFormat.setSampleRate(HALF_FORTY_FOUR);
|
||||||
} else if (audioDevice.supportedSampleRates().contains(FORTY_FOUR)) {
|
} else if (audioDevice.supportedSampleRates().contains(FORTY_FOUR)) {
|
||||||
// use 48000, resample
|
// use 44100, resample
|
||||||
adjustedAudioFormat.setSampleRate(FORTY_FOUR);
|
adjustedAudioFormat.setSampleRate(FORTY_FOUR);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (adjustedAudioFormat != desiredAudioFormat) {
|
if (adjustedAudioFormat != desiredAudioFormat) {
|
||||||
// return the nearest in case it needs 2 channels
|
// return the nearest in case it needs 2 channels
|
||||||
adjustedAudioFormat = audioDevice.nearestFormat(adjustedAudioFormat);
|
adjustedAudioFormat = audioDevice.nearestFormat(adjustedAudioFormat);
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// set the adjustedAudioFormat to the desiredAudioFormat, since it will work
|
|
||||||
adjustedAudioFormat = desiredAudioFormat;
|
|
||||||
return true;
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -467,11 +447,6 @@ void AudioClient::stop() {
|
||||||
// "switch" to invalid devices in order to shut down the state
|
// "switch" to invalid devices in order to shut down the state
|
||||||
switchInputToAudioDevice(QAudioDeviceInfo());
|
switchInputToAudioDevice(QAudioDeviceInfo());
|
||||||
switchOutputToAudioDevice(QAudioDeviceInfo());
|
switchOutputToAudioDevice(QAudioDeviceInfo());
|
||||||
|
|
||||||
if (_loopbackResampler) {
|
|
||||||
delete _loopbackResampler;
|
|
||||||
_loopbackResampler = NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer<ReceivedMessage> message) {
|
void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer<ReceivedMessage> message) {
|
||||||
|
@ -675,16 +650,10 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// do we need to setup a resampler?
|
// NOTE: we assume the inputFormat and the outputFormat are the same, since on any modern
|
||||||
if (_inputFormat.sampleRate() != _outputFormat.sampleRate() && !_loopbackResampler) {
|
// multimedia OS they should be. If there is a device that this is not true for, we can
|
||||||
qCDebug(audioclient) << "Attemping to create a resampler for input format to output format for audio loopback.";
|
// add back support to do resampling.
|
||||||
|
Q_ASSERT(_inputFormat.sampleRate() == _outputFormat.sampleRate());
|
||||||
assert(_inputFormat.sampleSize() == 16);
|
|
||||||
assert(_outputFormat.sampleSize() == 16);
|
|
||||||
int channelCount = (_inputFormat.channelCount() == 2 && _outputFormat.channelCount() == 2) ? 2 : 1;
|
|
||||||
|
|
||||||
_loopbackResampler = new AudioSRC(_inputFormat.sampleRate(), _outputFormat.sampleRate(), channelCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
static QByteArray loopBackByteArray;
|
static QByteArray loopBackByteArray;
|
||||||
|
|
||||||
|
@ -696,7 +665,8 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) {
|
||||||
int16_t* inputSamples = reinterpret_cast<int16_t*>(inputByteArray.data());
|
int16_t* inputSamples = reinterpret_cast<int16_t*>(inputByteArray.data());
|
||||||
int16_t* loopbackSamples = reinterpret_cast<int16_t*>(loopBackByteArray.data());
|
int16_t* loopbackSamples = reinterpret_cast<int16_t*>(loopBackByteArray.data());
|
||||||
|
|
||||||
possibleResampling(_loopbackResampler,
|
auto NO_RESAMPLER = nullptr;
|
||||||
|
possibleResampling(NO_RESAMPLER,
|
||||||
inputSamples, loopbackSamples,
|
inputSamples, loopbackSamples,
|
||||||
numInputSamples, numLoopbackSamples,
|
numInputSamples, numLoopbackSamples,
|
||||||
_inputFormat, _outputFormat);
|
_inputFormat, _outputFormat);
|
||||||
|
@ -1039,12 +1009,6 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice
|
||||||
_networkToOutputResampler = NULL;
|
_networkToOutputResampler = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_loopbackResampler) {
|
|
||||||
// if we were using an input to output resample, delete it here
|
|
||||||
delete _loopbackResampler;
|
|
||||||
_loopbackResampler = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!outputDeviceInfo.isNull()) {
|
if (!outputDeviceInfo.isNull()) {
|
||||||
qCDebug(audioclient) << "The audio output device " << outputDeviceInfo.deviceName() << "is available.";
|
qCDebug(audioclient) << "The audio output device " << outputDeviceInfo.deviceName() << "is available.";
|
||||||
_outputAudioDeviceName = outputDeviceInfo.deviceName().trimmed();
|
_outputAudioDeviceName = outputDeviceInfo.deviceName().trimmed();
|
||||||
|
|
|
@ -260,7 +260,6 @@ private:
|
||||||
// possible streams needed for resample
|
// possible streams needed for resample
|
||||||
AudioSRC* _inputToNetworkResampler;
|
AudioSRC* _inputToNetworkResampler;
|
||||||
AudioSRC* _networkToOutputResampler;
|
AudioSRC* _networkToOutputResampler;
|
||||||
AudioSRC* _loopbackResampler;
|
|
||||||
|
|
||||||
// Adds Reverb
|
// Adds Reverb
|
||||||
void configureReverb();
|
void configureReverb();
|
||||||
|
|
|
@ -31,6 +31,7 @@ public:
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void IPDScaleChanged();
|
void IPDScaleChanged();
|
||||||
|
void displayModeChanged(bool isHMDMode);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
float _IPDScale{ 1.0 };
|
float _IPDScale{ 1.0 };
|
||||||
|
|
|
@ -179,6 +179,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
|
||||||
}
|
}
|
||||||
|
|
||||||
entity->setLastBroadcast(usecTimestampNow());
|
entity->setLastBroadcast(usecTimestampNow());
|
||||||
|
propertiesWithSimID.setLastEdited(entity->getLastEdited());
|
||||||
} else {
|
} else {
|
||||||
qCDebug(entities) << "script failed to add new Entity to local Octree";
|
qCDebug(entities) << "script failed to add new Entity to local Octree";
|
||||||
success = false;
|
success = false;
|
||||||
|
@ -376,6 +377,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
|
||||||
properties.setQueryAACube(entity->getQueryAACube());
|
properties.setQueryAACube(entity->getQueryAACube());
|
||||||
}
|
}
|
||||||
entity->setLastBroadcast(usecTimestampNow());
|
entity->setLastBroadcast(usecTimestampNow());
|
||||||
|
properties.setLastEdited(entity->getLastEdited());
|
||||||
|
|
||||||
// if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server
|
// if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server
|
||||||
// if they've changed.
|
// if they've changed.
|
||||||
|
|
|
@ -130,16 +130,13 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI
|
||||||
EntityItemProperties properties = origProperties;
|
EntityItemProperties properties = origProperties;
|
||||||
|
|
||||||
bool allowLockChange;
|
bool allowLockChange;
|
||||||
bool canRezPermanentEntities;
|
|
||||||
QUuid senderID;
|
QUuid senderID;
|
||||||
if (senderNode.isNull()) {
|
if (senderNode.isNull()) {
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
allowLockChange = nodeList->isAllowedEditor();
|
allowLockChange = nodeList->isAllowedEditor();
|
||||||
canRezPermanentEntities = nodeList->getThisNodeCanRez();
|
|
||||||
senderID = nodeList->getSessionUUID();
|
senderID = nodeList->getSessionUUID();
|
||||||
} else {
|
} else {
|
||||||
allowLockChange = senderNode->isAllowedEditor();
|
allowLockChange = senderNode->isAllowedEditor();
|
||||||
canRezPermanentEntities = senderNode->getCanRez();
|
|
||||||
senderID = senderNode->getUUID();
|
senderID = senderNode->getUUID();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,14 +145,6 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!canRezPermanentEntities && (entity->getLifetime() != properties.getLifetime())) {
|
|
||||||
// we don't allow a Node that can't create permanent entities to adjust lifetimes on existing ones
|
|
||||||
if (properties.lifetimeChanged()) {
|
|
||||||
qCDebug(entities) << "Refusing disallowed entity lifetime adjustment.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// enforce support for locked entities. If an entity is currently locked, then the only
|
// enforce support for locked entities. If an entity is currently locked, then the only
|
||||||
// property we allow you to change is the locked property.
|
// property we allow you to change is the locked property.
|
||||||
if (entity->getLocked()) {
|
if (entity->getLocked()) {
|
||||||
|
@ -321,26 +310,9 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityTree::permissionsAllowRez(const EntityItemProperties& properties, bool canRez, bool canRezTmp) {
|
|
||||||
float lifeTime = properties.getLifetime();
|
|
||||||
|
|
||||||
if (lifeTime == ENTITY_ITEM_IMMORTAL_LIFETIME || lifeTime > _maxTmpEntityLifetime) {
|
|
||||||
// this is an attempt to rez a permanent or non-temporary entity.
|
|
||||||
if (!canRez) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// this is an attempt to rez a temporary entity.
|
|
||||||
if (!canRezTmp) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||||
EntityItemPointer result = NULL;
|
EntityItemPointer result = NULL;
|
||||||
|
EntityItemProperties props = properties;
|
||||||
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
if (!nodeList) {
|
if (!nodeList) {
|
||||||
|
@ -348,16 +320,8 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool clientOnly = properties.getClientOnly();
|
|
||||||
|
|
||||||
if (!clientOnly && getIsClient() &&
|
|
||||||
!permissionsAllowRez(properties, nodeList->getThisNodeCanRez(), nodeList->getThisNodeCanRezTmp())) {
|
|
||||||
// if our Node isn't allowed to create entities in this domain, don't try.
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool recordCreationTime = false;
|
bool recordCreationTime = false;
|
||||||
if (properties.getCreated() == UNKNOWN_CREATED_TIME) {
|
if (props.getCreated() == UNKNOWN_CREATED_TIME) {
|
||||||
// the entity's creation time was not specified in properties, which means this is a NEW entity
|
// the entity's creation time was not specified in properties, which means this is a NEW entity
|
||||||
// and we must record its creation time
|
// and we must record its creation time
|
||||||
recordCreationTime = true;
|
recordCreationTime = true;
|
||||||
|
@ -372,8 +336,8 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti
|
||||||
}
|
}
|
||||||
|
|
||||||
// construct the instance of the entity
|
// construct the instance of the entity
|
||||||
EntityTypes::EntityType type = properties.getType();
|
EntityTypes::EntityType type = props.getType();
|
||||||
result = EntityTypes::constructEntityItem(type, entityID, properties);
|
result = EntityTypes::constructEntityItem(type, entityID, props);
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
if (recordCreationTime) {
|
if (recordCreationTime) {
|
||||||
|
@ -890,6 +854,13 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList<Q
|
||||||
QString::number((int)pos.y) + "," +
|
QString::number((int)pos.y) + "," +
|
||||||
QString::number((int)pos.z);
|
QString::number((int)pos.z);
|
||||||
}
|
}
|
||||||
|
if (properties.lifetimeChanged()) {
|
||||||
|
int index = changedProperties.indexOf("lifetime");
|
||||||
|
if (index >= 0) {
|
||||||
|
float value = properties.getLifetime();
|
||||||
|
changedProperties[index] = QString("lifetime:") + QString::number((int)value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength,
|
int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength,
|
||||||
|
@ -922,11 +893,23 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
||||||
EntityItemID entityItemID;
|
EntityItemID entityItemID;
|
||||||
EntityItemProperties properties;
|
EntityItemProperties properties;
|
||||||
startDecode = usecTimestampNow();
|
startDecode = usecTimestampNow();
|
||||||
|
|
||||||
bool validEditPacket = EntityItemProperties::decodeEntityEditPacket(editData, maxLength, processedBytes,
|
bool validEditPacket = EntityItemProperties::decodeEntityEditPacket(editData, maxLength, processedBytes,
|
||||||
entityItemID, properties);
|
entityItemID, properties);
|
||||||
endDecode = usecTimestampNow();
|
endDecode = usecTimestampNow();
|
||||||
|
|
||||||
|
const quint64 LAST_EDITED_SERVERSIDE_BUMP = 1; // usec
|
||||||
|
if (!senderNode->getCanRez() && senderNode->getCanRezTmp()) {
|
||||||
|
// this node is only allowed to rez temporary entities. if need be, cap the lifetime.
|
||||||
|
if (properties.getLifetime() == ENTITY_ITEM_IMMORTAL_LIFETIME ||
|
||||||
|
properties.getLifetime() > _maxTmpEntityLifetime) {
|
||||||
|
properties.setLifetime(_maxTmpEntityLifetime);
|
||||||
|
// also bump up the lastEdited time of the properties so that the interface that created this edit
|
||||||
|
// will accept our adjustment to lifetime back into its own entity-tree.
|
||||||
|
properties.setLastEdited(properties.getLastEdited() + LAST_EDITED_SERVERSIDE_BUMP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If we got a valid edit packet, then it could be a new entity or it could be an update to
|
// If we got a valid edit packet, then it could be a new entity or it could be an update to
|
||||||
// an existing entity... handle appropriately
|
// an existing entity... handle appropriately
|
||||||
if (validEditPacket) {
|
if (validEditPacket) {
|
||||||
|
@ -955,7 +938,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
||||||
endUpdate = usecTimestampNow();
|
endUpdate = usecTimestampNow();
|
||||||
_totalUpdates++;
|
_totalUpdates++;
|
||||||
} else if (message.getType() == PacketType::EntityAdd) {
|
} else if (message.getType() == PacketType::EntityAdd) {
|
||||||
if (permissionsAllowRez(properties, senderNode->getCanRez(), senderNode->getCanRezTmp())) {
|
if (senderNode->getCanRez() || senderNode->getCanRezTmp()) {
|
||||||
// this is a new entity... assign a new entityID
|
// this is a new entity... assign a new entityID
|
||||||
properties.setCreated(properties.getLastEdited());
|
properties.setCreated(properties.getLastEdited());
|
||||||
startCreate = usecTimestampNow();
|
startCreate = usecTimestampNow();
|
||||||
|
|
|
@ -64,7 +64,6 @@ public:
|
||||||
|
|
||||||
|
|
||||||
void setEntityMaxTmpLifetime(float maxTmpEntityLifetime) { _maxTmpEntityLifetime = maxTmpEntityLifetime; }
|
void setEntityMaxTmpLifetime(float maxTmpEntityLifetime) { _maxTmpEntityLifetime = maxTmpEntityLifetime; }
|
||||||
bool permissionsAllowRez(const EntityItemProperties& properties, bool canRez, bool canRezTmp);
|
|
||||||
|
|
||||||
/// Implements our type specific root element factory
|
/// Implements our type specific root element factory
|
||||||
virtual OctreeElementPointer createNewElement(unsigned char* octalCode = NULL) override;
|
virtual OctreeElementPointer createNewElement(unsigned char* octalCode = NULL) override;
|
||||||
|
|
|
@ -474,6 +474,11 @@ void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken)
|
||||||
persistAccountToFile();
|
persistAccountToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AccountManager::setTemporaryDomain(const QUuid& domainID, const QString& key) {
|
||||||
|
_accountInfo.setTemporaryDomain(domainID, key);
|
||||||
|
persistAccountToFile();
|
||||||
|
}
|
||||||
|
|
||||||
void AccountManager::requestAccessToken(const QString& login, const QString& password) {
|
void AccountManager::requestAccessToken(const QString& login, const QString& password) {
|
||||||
|
|
||||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||||
|
@ -650,22 +655,33 @@ void AccountManager::processGeneratedKeypair() {
|
||||||
const QString DOMAIN_PUBLIC_KEY_UPDATE_PATH = "api/v1/domains/%1/public_key";
|
const QString DOMAIN_PUBLIC_KEY_UPDATE_PATH = "api/v1/domains/%1/public_key";
|
||||||
|
|
||||||
QString uploadPath;
|
QString uploadPath;
|
||||||
if (keypairGenerator->getDomainID().isNull()) {
|
const auto& domainID = keypairGenerator->getDomainID();
|
||||||
|
if (domainID.isNull()) {
|
||||||
uploadPath = USER_PUBLIC_KEY_UPDATE_PATH;
|
uploadPath = USER_PUBLIC_KEY_UPDATE_PATH;
|
||||||
} else {
|
} else {
|
||||||
uploadPath = DOMAIN_PUBLIC_KEY_UPDATE_PATH.arg(uuidStringWithoutCurlyBraces(keypairGenerator->getDomainID()));
|
uploadPath = DOMAIN_PUBLIC_KEY_UPDATE_PATH.arg(uuidStringWithoutCurlyBraces(domainID));
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup a multipart upload to send up the public key
|
// setup a multipart upload to send up the public key
|
||||||
QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||||||
|
|
||||||
QHttpPart keyPart;
|
QHttpPart publicKeyPart;
|
||||||
keyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
|
publicKeyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
|
||||||
keyPart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
|
||||||
QVariant("form-data; name=\"public_key\"; filename=\"public_key\""));
|
|
||||||
keyPart.setBody(keypairGenerator->getPublicKey());
|
|
||||||
|
|
||||||
requestMultiPart->append(keyPart);
|
publicKeyPart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||||
|
QVariant("form-data; name=\"public_key\"; filename=\"public_key\""));
|
||||||
|
publicKeyPart.setBody(keypairGenerator->getPublicKey());
|
||||||
|
requestMultiPart->append(publicKeyPart);
|
||||||
|
|
||||||
|
if (!domainID.isNull()) {
|
||||||
|
const auto& key = getTemporaryDomainKey(domainID);
|
||||||
|
QHttpPart apiKeyPart;
|
||||||
|
publicKeyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
|
||||||
|
apiKeyPart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||||
|
QVariant("form-data; name=\"api_key\""));
|
||||||
|
apiKeyPart.setBody(key.toUtf8());
|
||||||
|
requestMultiPart->append(apiKeyPart);
|
||||||
|
}
|
||||||
|
|
||||||
// setup callback parameters so we know once the keypair upload has succeeded or failed
|
// setup callback parameters so we know once the keypair upload has succeeded or failed
|
||||||
JSONCallbackParameters callbackParameters;
|
JSONCallbackParameters callbackParameters;
|
||||||
|
|
|
@ -89,6 +89,9 @@ public:
|
||||||
QUuid getSessionID() const { return _sessionID; }
|
QUuid getSessionID() const { return _sessionID; }
|
||||||
void setSessionID(const QUuid& sessionID) { _sessionID = sessionID; }
|
void setSessionID(const QUuid& sessionID) { _sessionID = sessionID; }
|
||||||
|
|
||||||
|
void setTemporaryDomain(const QUuid& domainID, const QString& key);
|
||||||
|
const QString& getTemporaryDomainKey(const QUuid& domainID) { return _accountInfo.getTemporaryDomainKey(domainID); }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void requestAccessToken(const QString& login, const QString& password);
|
void requestAccessToken(const QString& login, const QString& password);
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,8 @@
|
||||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
const QString DataServerAccountInfo::EMPTY_KEY = QString();
|
||||||
|
|
||||||
DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) : QObject() {
|
DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) : QObject() {
|
||||||
_accessToken = otherInfo._accessToken;
|
_accessToken = otherInfo._accessToken;
|
||||||
_username = otherInfo._username;
|
_username = otherInfo._username;
|
||||||
|
@ -33,6 +35,8 @@ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherI
|
||||||
_walletID = otherInfo._walletID;
|
_walletID = otherInfo._walletID;
|
||||||
_privateKey = otherInfo._privateKey;
|
_privateKey = otherInfo._privateKey;
|
||||||
_domainID = otherInfo._domainID;
|
_domainID = otherInfo._domainID;
|
||||||
|
_temporaryDomainID = otherInfo._temporaryDomainID;
|
||||||
|
_temporaryDomainApiKey = otherInfo._temporaryDomainApiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) {
|
DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) {
|
||||||
|
@ -51,6 +55,8 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) {
|
||||||
swap(_walletID, otherInfo._walletID);
|
swap(_walletID, otherInfo._walletID);
|
||||||
swap(_privateKey, otherInfo._privateKey);
|
swap(_privateKey, otherInfo._privateKey);
|
||||||
swap(_domainID, otherInfo._domainID);
|
swap(_domainID, otherInfo._domainID);
|
||||||
|
swap(_temporaryDomainID, otherInfo._temporaryDomainID);
|
||||||
|
swap(_temporaryDomainApiKey, otherInfo._temporaryDomainApiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) {
|
void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) {
|
||||||
|
@ -145,13 +151,14 @@ QByteArray DataServerAccountInfo::signPlaintext(const QByteArray& plaintext) {
|
||||||
|
|
||||||
QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) {
|
QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) {
|
||||||
out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey
|
out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey
|
||||||
<< info._walletID << info._privateKey << info._domainID;
|
<< info._walletID << info._privateKey << info._domainID
|
||||||
|
<< info._temporaryDomainID << info._temporaryDomainApiKey;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) {
|
QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) {
|
||||||
in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey
|
in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey
|
||||||
>> info._walletID >> info._privateKey >> info._domainID;
|
>> info._walletID >> info._privateKey >> info._domainID
|
||||||
|
>> info._temporaryDomainID >> info._temporaryDomainApiKey;
|
||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ const float SATOSHIS_PER_CREDIT = 100000000.0f;
|
||||||
|
|
||||||
class DataServerAccountInfo : public QObject {
|
class DataServerAccountInfo : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
const static QString EMPTY_KEY;
|
||||||
public:
|
public:
|
||||||
DataServerAccountInfo() {};
|
DataServerAccountInfo() {};
|
||||||
DataServerAccountInfo(const DataServerAccountInfo& otherInfo);
|
DataServerAccountInfo(const DataServerAccountInfo& otherInfo);
|
||||||
|
@ -52,6 +53,9 @@ public:
|
||||||
void setDomainID(const QUuid& domainID) { _domainID = domainID; }
|
void setDomainID(const QUuid& domainID) { _domainID = domainID; }
|
||||||
const QUuid& getDomainID() const { return _domainID; }
|
const QUuid& getDomainID() const { return _domainID; }
|
||||||
|
|
||||||
|
void setTemporaryDomain(const QUuid& domainID, const QString& key) { _temporaryDomainID = domainID; _temporaryDomainApiKey = key; }
|
||||||
|
const QString& getTemporaryDomainKey(const QUuid& domainID) { return domainID == _temporaryDomainID ? _temporaryDomainApiKey : EMPTY_KEY; }
|
||||||
|
|
||||||
bool hasProfile() const;
|
bool hasProfile() const;
|
||||||
|
|
||||||
void setProfileInfoFromJSON(const QJsonObject& jsonObject);
|
void setProfileInfoFromJSON(const QJsonObject& jsonObject);
|
||||||
|
@ -67,7 +71,9 @@ private:
|
||||||
QString _xmppPassword;
|
QString _xmppPassword;
|
||||||
QString _discourseApiKey;
|
QString _discourseApiKey;
|
||||||
QUuid _walletID;
|
QUuid _walletID;
|
||||||
QUuid _domainID; // if this holds account info for a domain, this holds the ID of that domain
|
QUuid _domainID;
|
||||||
|
QUuid _temporaryDomainID;
|
||||||
|
QString _temporaryDomainApiKey;
|
||||||
QByteArray _privateKey;
|
QByteArray _privateKey;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -492,10 +492,9 @@ void OffscreenUi::unfocusWindows() {
|
||||||
Q_ASSERT(invokeResult);
|
Q_ASSERT(invokeResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffscreenUi::toggleMenu(const QPoint& screenPosition) {
|
void OffscreenUi::toggleMenu(const QPoint& screenPosition) { // caller should already have mapped using getReticlePosition
|
||||||
emit showDesktop(); // we really only want to do this if you're showing the menu, but for now this works
|
emit showDesktop(); // we really only want to do this if you're showing the menu, but for now this works
|
||||||
auto virtualPos = mapToVirtualScreen(screenPosition, nullptr);
|
QMetaObject::invokeMethod(_desktop, "toggleMenu", Q_ARG(QVariant, screenPosition));
|
||||||
QMetaObject::invokeMethod(_desktop, "toggleMenu", Q_ARG(QVariant, virtualPos));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -66,14 +66,8 @@ const QString SHOW_DEBUG_RAW = "Debug Draw Raw Data";
|
||||||
const QString SHOW_DEBUG_CALIBRATED = "Debug Draw Calibrated Data";
|
const QString SHOW_DEBUG_CALIBRATED = "Debug Draw Calibrated Data";
|
||||||
|
|
||||||
bool SixenseManager::isSupported() const {
|
bool SixenseManager::isSupported() const {
|
||||||
#ifdef HAVE_SIXENSE
|
#if defined(HAVE_SIXENSE) && !defined(Q_OS_OSX)
|
||||||
|
|
||||||
#if defined(Q_OS_OSX)
|
|
||||||
return QSysInfo::macVersion() <= QSysInfo::MV_MAVERICKS;
|
|
||||||
#else
|
|
||||||
return true;
|
return true;
|
||||||
#endif
|
|
||||||
|
|
||||||
#else
|
#else
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
|
@ -83,6 +77,7 @@ bool SixenseManager::activate() {
|
||||||
InputPlugin::activate();
|
InputPlugin::activate();
|
||||||
|
|
||||||
#ifdef HAVE_SIXENSE
|
#ifdef HAVE_SIXENSE
|
||||||
|
#if !defined(Q_OS_LINUX)
|
||||||
_container->addMenu(MENU_PATH);
|
_container->addMenu(MENU_PATH);
|
||||||
_container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, TOGGLE_SMOOTH,
|
_container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, TOGGLE_SMOOTH,
|
||||||
[this] (bool clicked) { setSixenseFilter(clicked); },
|
[this] (bool clicked) { setSixenseFilter(clicked); },
|
||||||
|
@ -95,6 +90,7 @@ bool SixenseManager::activate() {
|
||||||
_container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, SHOW_DEBUG_CALIBRATED,
|
_container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, SHOW_DEBUG_CALIBRATED,
|
||||||
[this] (bool clicked) { _inputDevice->setDebugDrawCalibrated(clicked); },
|
[this] (bool clicked) { _inputDevice->setDebugDrawCalibrated(clicked); },
|
||||||
true, false);
|
true, false);
|
||||||
|
#endif
|
||||||
|
|
||||||
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
|
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
|
||||||
userInputMapper->registerDevice(_inputDevice);
|
userInputMapper->registerDevice(_inputDevice);
|
||||||
|
@ -112,8 +108,10 @@ void SixenseManager::deactivate() {
|
||||||
InputPlugin::deactivate();
|
InputPlugin::deactivate();
|
||||||
|
|
||||||
#ifdef HAVE_SIXENSE
|
#ifdef HAVE_SIXENSE
|
||||||
|
#if !defined(Q_OS_LINUX)
|
||||||
_container->removeMenuItem(MENU_NAME, TOGGLE_SMOOTH);
|
_container->removeMenuItem(MENU_NAME, TOGGLE_SMOOTH);
|
||||||
_container->removeMenu(MENU_PATH);
|
_container->removeMenu(MENU_PATH);
|
||||||
|
#endif
|
||||||
|
|
||||||
_inputDevice->_poseStateMap.clear();
|
_inputDevice->_poseStateMap.clear();
|
||||||
|
|
||||||
|
@ -137,6 +135,7 @@ void SixenseManager::setSixenseFilter(bool filter) {
|
||||||
void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
|
void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
|
||||||
BAIL_IF_NOT_LOADED
|
BAIL_IF_NOT_LOADED
|
||||||
|
|
||||||
|
#ifdef HAVE_SIXENSE
|
||||||
static bool sixenseHasBeenConnected { false };
|
static bool sixenseHasBeenConnected { false };
|
||||||
if (!sixenseHasBeenConnected && sixenseIsBaseConnected(0)) {
|
if (!sixenseHasBeenConnected && sixenseIsBaseConnected(0)) {
|
||||||
sixenseHasBeenConnected = true;
|
sixenseHasBeenConnected = true;
|
||||||
|
@ -152,6 +151,7 @@ void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibr
|
||||||
_container->requestReset();
|
_container->requestReset();
|
||||||
_inputDevice->_requestReset = false;
|
_inputDevice->_requestReset = false;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void SixenseManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
|
void SixenseManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
|
||||||
|
|
|
@ -1,155 +0,0 @@
|
||||||
//
|
|
||||||
// SixenseSupportOSX.cpp
|
|
||||||
// libraries/input-plugins/src/input-plugins
|
|
||||||
//
|
|
||||||
// Created by Clement on 10/20/15.
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
|
|
||||||
// Mock implementation of sixense.h to hide dynamic linking on OS X
|
|
||||||
#if defined(__APPLE__) && defined(HAVE_SIXENSE)
|
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
#include <sixense.h>
|
|
||||||
|
|
||||||
#include <QtCore/QCoreApplication>
|
|
||||||
#include <QtCore/QLibrary>
|
|
||||||
#include <QtCore/QDebug>
|
|
||||||
|
|
||||||
#ifndef SIXENSE_LIB_FILENAME
|
|
||||||
#define SIXENSE_LIB_FILENAME QCoreApplication::applicationDirPath() + "/../Frameworks/libsixense_x64"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using Library = std::unique_ptr<QLibrary>;
|
|
||||||
static Library SIXENSE;
|
|
||||||
|
|
||||||
struct Callable {
|
|
||||||
template<typename... Args>
|
|
||||||
int operator() (Args&&... args){
|
|
||||||
return reinterpret_cast<int(*)(Args...)>(function)(std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
QFunctionPointer function;
|
|
||||||
};
|
|
||||||
|
|
||||||
Callable resolve(const Library& library, const char* name) {
|
|
||||||
Q_ASSERT_X(library && library->isLoaded(), __FUNCTION__, "Sixense library not loaded");
|
|
||||||
auto function = library->resolve(name);
|
|
||||||
Q_ASSERT_X(function, __FUNCTION__, std::string("Could not resolve ").append(name).c_str());
|
|
||||||
return Callable { function };
|
|
||||||
}
|
|
||||||
#define FORWARD resolve(SIXENSE, __FUNCTION__)
|
|
||||||
|
|
||||||
|
|
||||||
void loadSixense() {
|
|
||||||
Q_ASSERT_X(!(SIXENSE && SIXENSE->isLoaded()), __FUNCTION__, "Sixense library already loaded");
|
|
||||||
SIXENSE.reset(new QLibrary(SIXENSE_LIB_FILENAME));
|
|
||||||
Q_CHECK_PTR(SIXENSE);
|
|
||||||
|
|
||||||
if (SIXENSE->load()){
|
|
||||||
qDebug() << "Loaded sixense library for hydra support -" << SIXENSE->fileName();
|
|
||||||
} else {
|
|
||||||
qDebug() << "Sixense library at" << SIXENSE->fileName() << "failed to load:" << SIXENSE->errorString();
|
|
||||||
qDebug() << "Continuing without hydra support.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void unloadSixense() {
|
|
||||||
SIXENSE->unload();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// sixense.h wrapper for OSX dynamic linking
|
|
||||||
int sixenseInit() {
|
|
||||||
loadSixense();
|
|
||||||
if (!SIXENSE || !SIXENSE->isLoaded()) {
|
|
||||||
return SIXENSE_FAILURE;
|
|
||||||
}
|
|
||||||
return FORWARD();
|
|
||||||
}
|
|
||||||
int sixenseExit() {
|
|
||||||
auto returnCode = FORWARD();
|
|
||||||
unloadSixense();
|
|
||||||
return returnCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseGetMaxBases() {
|
|
||||||
return FORWARD();
|
|
||||||
}
|
|
||||||
int sixenseSetActiveBase(int i) {
|
|
||||||
return FORWARD(i);
|
|
||||||
}
|
|
||||||
int sixenseIsBaseConnected(int i) {
|
|
||||||
return FORWARD(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseGetMaxControllers() {
|
|
||||||
return FORWARD();
|
|
||||||
}
|
|
||||||
int sixenseIsControllerEnabled(int which) {
|
|
||||||
return FORWARD(which);
|
|
||||||
}
|
|
||||||
int sixenseGetNumActiveControllers() {
|
|
||||||
return FORWARD();
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseGetHistorySize() {
|
|
||||||
return FORWARD();
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseGetData(int which, int index_back, sixenseControllerData* data) {
|
|
||||||
return FORWARD(which, index_back, data);
|
|
||||||
}
|
|
||||||
int sixenseGetAllData(int index_back, sixenseAllControllerData* data) {
|
|
||||||
return FORWARD(index_back, data);
|
|
||||||
}
|
|
||||||
int sixenseGetNewestData(int which, sixenseControllerData* data) {
|
|
||||||
return FORWARD(which, data);
|
|
||||||
}
|
|
||||||
int sixenseGetAllNewestData(sixenseAllControllerData* data) {
|
|
||||||
return FORWARD(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseSetHemisphereTrackingMode(int which_controller, int state) {
|
|
||||||
return FORWARD(which_controller, state);
|
|
||||||
}
|
|
||||||
int sixenseGetHemisphereTrackingMode(int which_controller, int* state) {
|
|
||||||
return FORWARD(which_controller, state);
|
|
||||||
}
|
|
||||||
int sixenseAutoEnableHemisphereTracking(int which_controller) {
|
|
||||||
return FORWARD(which_controller);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseSetHighPriorityBindingEnabled(int on_or_off) {
|
|
||||||
return FORWARD(on_or_off);
|
|
||||||
}
|
|
||||||
int sixenseGetHighPriorityBindingEnabled(int* on_or_off) {
|
|
||||||
return FORWARD(on_or_off);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseTriggerVibration(int controller_id, int duration_100ms, int pattern_id) {
|
|
||||||
return FORWARD(controller_id, duration_100ms, pattern_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseSetFilterEnabled(int on_or_off) {
|
|
||||||
return FORWARD(on_or_off);
|
|
||||||
}
|
|
||||||
int sixenseGetFilterEnabled(int* on_or_off) {
|
|
||||||
return FORWARD(on_or_off);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseSetFilterParams(float near_range, float near_val, float far_range, float far_val) {
|
|
||||||
return FORWARD(near_range, near_val, far_range, far_val);
|
|
||||||
}
|
|
||||||
int sixenseGetFilterParams(float* near_range, float* near_val, float* far_range, float* far_val) {
|
|
||||||
return FORWARD(near_range, near_val, far_range, far_val);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseSetBaseColor(unsigned char red, unsigned char green, unsigned char blue) {
|
|
||||||
return FORWARD(red, green, blue);
|
|
||||||
}
|
|
||||||
int sixenseGetBaseColor(unsigned char* red, unsigned char* green, unsigned char* blue) {
|
|
||||||
return FORWARD(red, green, blue);
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -96,7 +96,9 @@ bool OpenVrDisplayPlugin::internalActivate() {
|
||||||
glm::vec3 uiPos(0.0f, UI_HEIGHT, UI_RADIUS - (0.5f * zSize) - UI_Z_OFFSET);
|
glm::vec3 uiPos(0.0f, UI_HEIGHT, UI_RADIUS - (0.5f * zSize) - UI_Z_OFFSET);
|
||||||
_sensorResetMat = glm::inverse(createMatFromQuatAndPos(glm::quat(), uiPos));
|
_sensorResetMat = glm::inverse(createMatFromQuatAndPos(glm::quat(), uiPos));
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "OpenVR: error could not get chaperone pointer";
|
#if DEV_BUILD
|
||||||
|
qDebug() << "OpenVR: error could not get chaperone pointer";
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
return Parent::internalActivate();
|
return Parent::internalActivate();
|
||||||
|
|
|
@ -64,17 +64,25 @@ vr::IVRSystem* acquireOpenVrSystem() {
|
||||||
if (hmdPresent) {
|
if (hmdPresent) {
|
||||||
Lock lock(mutex);
|
Lock lock(mutex);
|
||||||
if (!activeHmd) {
|
if (!activeHmd) {
|
||||||
qCDebug(displayplugins) << "OpenVR: No vr::IVRSystem instance active, building";
|
#if DEV_BUILD
|
||||||
|
qCDebug(displayplugins) << "OpenVR: No vr::IVRSystem instance active, building";
|
||||||
|
#endif
|
||||||
vr::EVRInitError eError = vr::VRInitError_None;
|
vr::EVRInitError eError = vr::VRInitError_None;
|
||||||
activeHmd = vr::VR_Init(&eError, vr::VRApplication_Scene);
|
activeHmd = vr::VR_Init(&eError, vr::VRApplication_Scene);
|
||||||
qCDebug(displayplugins) << "OpenVR display: HMD is " << activeHmd << " error is " << eError;
|
#if DEV_BUILD
|
||||||
|
qCDebug(displayplugins) << "OpenVR display: HMD is " << activeHmd << " error is " << eError;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
if (activeHmd) {
|
if (activeHmd) {
|
||||||
qCDebug(displayplugins) << "OpenVR: incrementing refcount";
|
#if DEV_BUILD
|
||||||
|
qCDebug(displayplugins) << "OpenVR: incrementing refcount";
|
||||||
|
#endif
|
||||||
++refCount;
|
++refCount;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
qCDebug(displayplugins) << "OpenVR: no hmd present";
|
#if DEV_BUILD
|
||||||
|
qCDebug(displayplugins) << "OpenVR: no hmd present";
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
return activeHmd;
|
return activeHmd;
|
||||||
}
|
}
|
||||||
|
@ -82,10 +90,14 @@ vr::IVRSystem* acquireOpenVrSystem() {
|
||||||
void releaseOpenVrSystem() {
|
void releaseOpenVrSystem() {
|
||||||
if (activeHmd) {
|
if (activeHmd) {
|
||||||
Lock lock(mutex);
|
Lock lock(mutex);
|
||||||
qCDebug(displayplugins) << "OpenVR: decrementing refcount";
|
#if DEV_BUILD
|
||||||
|
qCDebug(displayplugins) << "OpenVR: decrementing refcount";
|
||||||
|
#endif
|
||||||
--refCount;
|
--refCount;
|
||||||
if (0 == refCount) {
|
if (0 == refCount) {
|
||||||
qCDebug(displayplugins) << "OpenVR: zero refcount, deallocate VR system";
|
#if DEV_BUILD
|
||||||
|
qCDebug(displayplugins) << "OpenVR: zero refcount, deallocate VR system";
|
||||||
|
#endif
|
||||||
vr::VR_Shutdown();
|
vr::VR_Shutdown();
|
||||||
activeHmd = nullptr;
|
activeHmd = nullptr;
|
||||||
}
|
}
|
||||||
|
@ -261,7 +273,9 @@ void handleOpenVrEvents() {
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
qDebug() << "OpenVR: Event " << activeHmd->GetEventTypeNameFromEnum((vr::EVREventType)event.eventType) << "(" << event.eventType << ")";
|
#if DEV_BUILD
|
||||||
|
qDebug() << "OpenVR: Event " << activeHmd->GetEventTypeNameFromEnum((vr::EVREventType)event.eventType) << "(" << event.eventType << ")";
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,3 +24,4 @@ Script.load("system/controllers/handControllerPointer.js");
|
||||||
Script.load("system/controllers/squeezeHands.js");
|
Script.load("system/controllers/squeezeHands.js");
|
||||||
Script.load("system/controllers/grab.js");
|
Script.load("system/controllers/grab.js");
|
||||||
Script.load("system/dialTone.js");
|
Script.load("system/dialTone.js");
|
||||||
|
Script.load("system/firstPersonHMD.js");
|
||||||
|
|
|
@ -37,6 +37,9 @@ var THUMB_ON_VALUE = 0.5;
|
||||||
var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only use head for search/move.
|
var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only use head for search/move.
|
||||||
|
|
||||||
var PICK_WITH_HAND_RAY = true;
|
var PICK_WITH_HAND_RAY = true;
|
||||||
|
|
||||||
|
var DRAW_GRAB_BOXES = false;
|
||||||
|
var DRAW_HAND_SPHERES = false;
|
||||||
var DROP_WITHOUT_SHAKE = false;
|
var DROP_WITHOUT_SHAKE = false;
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -67,16 +70,22 @@ var LINE_ENTITY_DIMENSIONS = {
|
||||||
|
|
||||||
var LINE_LENGTH = 500;
|
var LINE_LENGTH = 500;
|
||||||
var PICK_MAX_DISTANCE = 500; // max length of pick-ray
|
var PICK_MAX_DISTANCE = 500; // max length of pick-ray
|
||||||
|
|
||||||
//
|
//
|
||||||
// near grabbing
|
// near grabbing
|
||||||
//
|
//
|
||||||
|
|
||||||
var GRAB_RADIUS = 0.06; // if the ray misses but an object is this close, it will still be selected
|
var EQUIP_RADIUS = 0.1; // radius used for palm vs equip-hotspot for equipping.
|
||||||
|
|
||||||
var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position
|
var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position
|
||||||
var NEAR_PICK_MAX_DISTANCE = 0.3; // max length of pick-ray for close grabbing to be selected
|
|
||||||
|
var NEAR_GRAB_RADIUS = 0.15; // radius used for palm vs object for near grabbing.
|
||||||
|
var NEAR_GRAB_MAX_DISTANCE = 1.0; // you cannot grab objects that are this far away from your hand
|
||||||
|
|
||||||
|
var NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing.
|
||||||
|
|
||||||
var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object
|
var PICK_BACKOFF_DISTANCE = 0.2; // helps when hand is intersecting the grabble object
|
||||||
var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed
|
var NEAR_GRABBING_KINEMATIC = true; // force objects to be kinematic when near-grabbed
|
||||||
var SHOW_GRAB_SPHERE = false; // draw a green sphere to show the grab search position and size
|
|
||||||
var CHECK_TOO_FAR_UNEQUIP_TIME = 1.0; // seconds
|
var CHECK_TOO_FAR_UNEQUIP_TIME = 1.0; // seconds
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -253,7 +262,8 @@ function restore2DMode() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// constructor
|
// EntityPropertiesCache is a helper class that contains a cache of entity properties.
|
||||||
|
// the hope is to prevent excess calls to Entity.getEntityProperties()
|
||||||
function EntityPropertiesCache() {
|
function EntityPropertiesCache() {
|
||||||
this.cache = {};
|
this.cache = {};
|
||||||
}
|
}
|
||||||
|
@ -264,34 +274,55 @@ EntityPropertiesCache.prototype.findEntities = function(position, radius) {
|
||||||
var entities = Entities.findEntities(position, radius);
|
var entities = Entities.findEntities(position, radius);
|
||||||
var _this = this;
|
var _this = this;
|
||||||
entities.forEach(function (x) {
|
entities.forEach(function (x) {
|
||||||
_this.addEntity(x);
|
_this.updateEntity(x);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
EntityPropertiesCache.prototype.addEntity = function(entityID) {
|
EntityPropertiesCache.prototype.updateEntity = function(entityID) {
|
||||||
var props = Entities.getEntityProperties(entityID, GRABBABLE_PROPERTIES);
|
var props = Entities.getEntityProperties(entityID, GRABBABLE_PROPERTIES);
|
||||||
var grabbableProps = getEntityCustomData(GRABBABLE_DATA_KEY, entityID, DEFAULT_GRABBABLE_DATA);
|
|
||||||
var grabProps = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {});
|
// convert props.userData from a string to an object.
|
||||||
var wearableProps = getEntityCustomData("wearable", entityID, {});
|
var userData = {};
|
||||||
this.cache[entityID] = { props: props, grabbableProps: grabbableProps, grabProps: grabProps, wearableProps: wearableProps };
|
if (props.userData) {
|
||||||
|
try {
|
||||||
|
userData = JSON.parse(props.userData);
|
||||||
|
} catch(err) {
|
||||||
|
print("WARNING: malformed userData on " + entityID + ", name = " + props.name + ", error = " + err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
props.userData = userData;
|
||||||
|
|
||||||
|
this.cache[entityID] = props;
|
||||||
};
|
};
|
||||||
EntityPropertiesCache.prototype.getEntities = function() {
|
EntityPropertiesCache.prototype.getEntities = function() {
|
||||||
return Object.keys(this.cache);
|
return Object.keys(this.cache);
|
||||||
}
|
}
|
||||||
EntityPropertiesCache.prototype.getProps = function(entityID) {
|
EntityPropertiesCache.prototype.getProps = function(entityID) {
|
||||||
var obj = this.cache[entityID]
|
var obj = this.cache[entityID]
|
||||||
return obj ? obj.props : undefined;
|
return obj ? obj : undefined;
|
||||||
};
|
};
|
||||||
EntityPropertiesCache.prototype.getGrabbableProps = function(entityID) {
|
EntityPropertiesCache.prototype.getGrabbableProps = function(entityID) {
|
||||||
var obj = this.cache[entityID]
|
var props = this.cache[entityID];
|
||||||
return obj ? obj.grabbableProps : undefined;
|
if (props) {
|
||||||
|
return props.userData.grabbableKey ? props.userData.grabbableKey : DEFAULT_GRABBABLE_DATA;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
EntityPropertiesCache.prototype.getGrabProps = function(entityID) {
|
EntityPropertiesCache.prototype.getGrabProps = function(entityID) {
|
||||||
var obj = this.cache[entityID]
|
var props = this.cache[entityID];
|
||||||
return obj ? obj.grabProps : undefined;
|
if (props) {
|
||||||
|
return props.userData.grabKey ? props.userData.grabKey : {};
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
EntityPropertiesCache.prototype.getWearableProps = function(entityID) {
|
EntityPropertiesCache.prototype.getWearableProps = function(entityID) {
|
||||||
var obj = this.cache[entityID]
|
var props = this.cache[entityID];
|
||||||
return obj ? obj.wearableProps : undefined;
|
if (props) {
|
||||||
|
return props.userData.wearable ? props.userData.wearable : {};
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function MyController(hand) {
|
function MyController(hand) {
|
||||||
|
@ -322,7 +353,6 @@ function MyController(hand) {
|
||||||
//for visualizations
|
//for visualizations
|
||||||
this.overlayLine = null;
|
this.overlayLine = null;
|
||||||
this.particleBeamObject = null;
|
this.particleBeamObject = null;
|
||||||
this.grabSphere = null;
|
|
||||||
|
|
||||||
//for lights
|
//for lights
|
||||||
this.spotlight = null;
|
this.spotlight = null;
|
||||||
|
@ -383,7 +413,7 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState = function(newState, reason) {
|
this.setState = function(newState, reason) {
|
||||||
this.grabSphereOff();
|
|
||||||
if (WANT_DEBUG || WANT_DEBUG_STATE) {
|
if (WANT_DEBUG || WANT_DEBUG_STATE) {
|
||||||
var oldStateName = stateToName(this.state);
|
var oldStateName = stateToName(this.state);
|
||||||
var newStateName = stateToName(newState);
|
var newStateName = stateToName(newState);
|
||||||
|
@ -490,39 +520,6 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.grabSphereOn = function() {
|
|
||||||
var color = {red: 0, green: 255, blue: 0};
|
|
||||||
if (this.grabSphere === null) {
|
|
||||||
var sphereProperties = {
|
|
||||||
position: this.getHandPosition(),
|
|
||||||
size: GRAB_RADIUS*2,
|
|
||||||
color: color,
|
|
||||||
alpha: 0.1,
|
|
||||||
solid: true,
|
|
||||||
ignoreRayIntersection: true,
|
|
||||||
drawInFront: true, // Even when burried inside of something, show it.
|
|
||||||
visible: true
|
|
||||||
}
|
|
||||||
this.grabSphere = Overlays.addOverlay("sphere", sphereProperties);
|
|
||||||
} else {
|
|
||||||
Overlays.editOverlay(this.grabSphere, {
|
|
||||||
position: this.getHandPosition(),
|
|
||||||
size: GRAB_RADIUS*2,
|
|
||||||
color: color,
|
|
||||||
alpha: 0.1,
|
|
||||||
solid: true,
|
|
||||||
visible: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.grabSphereOff = function() {
|
|
||||||
if (this.grabSphere !== null) {
|
|
||||||
Overlays.deleteOverlay(this.grabSphere);
|
|
||||||
this.grabSphere = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.overlayLineOn = function(closePoint, farPoint, color) {
|
this.overlayLineOn = function(closePoint, farPoint, color) {
|
||||||
if (this.overlayLine === null) {
|
if (this.overlayLine === null) {
|
||||||
var lineProperties = {
|
var lineProperties = {
|
||||||
|
@ -872,43 +869,147 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.searchEnter = function() {
|
this.createHotspots = function () {
|
||||||
this.equipHotspotOverlays = [];
|
var props, overlay;
|
||||||
|
|
||||||
|
var HAND_EQUIP_SPHERE_COLOR = { red: 90, green: 255, blue: 90 };
|
||||||
|
var HAND_EQUIP_SPHERE_ALPHA = 0.7;
|
||||||
|
var HAND_EQUIP_SPHERE_RADIUS = 0.01;
|
||||||
|
|
||||||
|
var HAND_GRAB_SPHERE_COLOR = { red: 90, green: 90, blue: 255 };
|
||||||
|
var HAND_GRAB_SPHERE_ALPHA = 0.3;
|
||||||
|
var HAND_GRAB_SPHERE_RADIUS = NEAR_GRAB_RADIUS;
|
||||||
|
|
||||||
|
var EQUIP_SPHERE_COLOR = { red: 90, green: 255, blue: 90 };
|
||||||
|
var EQUIP_SPHERE_ALPHA = 0.3;
|
||||||
|
|
||||||
|
var GRAB_BOX_COLOR = { red: 90, green: 90, blue: 255 };
|
||||||
|
var GRAB_BOX_ALPHA = 0.1;
|
||||||
|
|
||||||
|
this.hotspotOverlays = [];
|
||||||
|
|
||||||
|
if (DRAW_HAND_SPHERES) {
|
||||||
|
// add tiny green sphere around the palm.
|
||||||
|
var handPosition = this.getHandPosition();
|
||||||
|
overlay = Overlays.addOverlay("sphere", {
|
||||||
|
position: handPosition,
|
||||||
|
size: HAND_EQUIP_SPHERE_RADIUS * 2,
|
||||||
|
color: HAND_EQUIP_SPHERE_COLOR,
|
||||||
|
alpha: HAND_EQUIP_SPHERE_ALPHA,
|
||||||
|
solid: true,
|
||||||
|
visible: true,
|
||||||
|
ignoreRayIntersection: true,
|
||||||
|
drawInFront: false
|
||||||
|
});
|
||||||
|
|
||||||
|
this.hotspotOverlays.push({
|
||||||
|
entityID: undefined,
|
||||||
|
overlay: overlay,
|
||||||
|
type: "hand"
|
||||||
|
});
|
||||||
|
|
||||||
|
// add larger blue sphere around the palm.
|
||||||
|
overlay = Overlays.addOverlay("sphere", {
|
||||||
|
position: handPosition,
|
||||||
|
size: HAND_GRAB_SPHERE_RADIUS * 2,
|
||||||
|
color: HAND_GRAB_SPHERE_COLOR,
|
||||||
|
alpha: HAND_GRAB_SPHERE_ALPHA,
|
||||||
|
solid: true,
|
||||||
|
visible: true,
|
||||||
|
ignoreRayIntersection: true,
|
||||||
|
drawInFront: false
|
||||||
|
});
|
||||||
|
|
||||||
|
this.hotspotOverlays.push({
|
||||||
|
entityID: undefined,
|
||||||
|
overlay: overlay,
|
||||||
|
type: "hand"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// find entities near the avatar that might be equipable.
|
// find entities near the avatar that might be equipable.
|
||||||
var entities = Entities.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE);
|
this.entityPropertyCache.clear();
|
||||||
var i, length = entities.length;
|
this.entityPropertyCache.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE);
|
||||||
for (i = 0; i < length; i++) {
|
|
||||||
var grabProps = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES);
|
var _this = this;
|
||||||
// does this entity have an attach point?
|
this.entityPropertyCache.getEntities().forEach(function (entityID) {
|
||||||
var wearableData = getEntityCustomData("wearable", entities[i], undefined);
|
if (_this.entityIsEquippableWithoutDistanceCheck(entityID)) {
|
||||||
if (wearableData && wearableData.joints) {
|
props = _this.entityPropertyCache.getProps(entityID);
|
||||||
var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand";
|
|
||||||
if (wearableData.joints[handJointName]) {
|
overlay = Overlays.addOverlay("sphere", {
|
||||||
// draw the hotspot
|
rotation: props.rotation,
|
||||||
this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", {
|
position: props.position,
|
||||||
position: grabProps.position,
|
size: EQUIP_RADIUS * 2,
|
||||||
size: 0.2,
|
color: EQUIP_SPHERE_COLOR,
|
||||||
color: { red: 90, green: 255, blue: 90 },
|
alpha: EQUIP_SPHERE_ALPHA,
|
||||||
alpha: 0.7,
|
solid: true,
|
||||||
solid: true,
|
visible: true,
|
||||||
visible: true,
|
ignoreRayIntersection: true,
|
||||||
ignoreRayIntersection: false,
|
drawInFront: false
|
||||||
drawInFront: false
|
});
|
||||||
}));
|
|
||||||
}
|
_this.hotspotOverlays.push({
|
||||||
|
entityID: entityID,
|
||||||
|
overlay: overlay,
|
||||||
|
type: "equip"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (DRAW_GRAB_BOXES && _this.entityIsGrabbable(entityID)) {
|
||||||
|
props = _this.entityPropertyCache.getProps(entityID);
|
||||||
|
|
||||||
|
overlay = Overlays.addOverlay("cube", {
|
||||||
|
rotation: props.rotation,
|
||||||
|
position: props.position,
|
||||||
|
size: props.dimensions, //{x: props.dimensions.x, y: props.dimensions.y, z: props.dimensions.z},
|
||||||
|
color: GRAB_BOX_COLOR,
|
||||||
|
alpha: GRAB_BOX_ALPHA,
|
||||||
|
solid: true,
|
||||||
|
visible: true,
|
||||||
|
ignoreRayIntersection: true,
|
||||||
|
drawInFront: false
|
||||||
|
});
|
||||||
|
|
||||||
|
_this.hotspotOverlays.push({
|
||||||
|
entityID: entityID,
|
||||||
|
overlay: overlay,
|
||||||
|
type: "near"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.updateHotspots = function() {
|
||||||
|
var _this = this;
|
||||||
|
var props;
|
||||||
|
this.hotspotOverlays.forEach(function (overlayInfo) {
|
||||||
|
if (overlayInfo.type === "hand") {
|
||||||
|
Overlays.editOverlay(overlayInfo.overlay, { position: _this.getHandPosition() });
|
||||||
|
} else if (overlayInfo.type === "equip") {
|
||||||
|
_this.entityPropertyCache.updateEntity(overlayInfo.entityID);
|
||||||
|
props = _this.entityPropertyCache.getProps(overlayInfo.entityID);
|
||||||
|
Overlays.editOverlay(overlayInfo.overlay, { position: props.position, rotation: props.rotation });
|
||||||
|
} else if (overlayInfo.type === "near") {
|
||||||
|
_this.entityPropertyCache.updateEntity(overlayInfo.entityID);
|
||||||
|
props = _this.entityPropertyCache.getProps(overlayInfo.entityID);
|
||||||
|
Overlays.editOverlay(overlayInfo.overlay, { position: props.position, rotation: props.rotation });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.destroyHotspots = function() {
|
||||||
|
this.hotspotOverlays.forEach(function (overlayInfo) {
|
||||||
|
Overlays.deleteOverlay(overlayInfo.overlay);
|
||||||
|
});
|
||||||
|
this.hotspotOverlays = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
this.searchEnter = function() {
|
||||||
|
this.createHotspots();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.searchExit = function() {
|
this.searchExit = function() {
|
||||||
|
this.destroyHotspots();
|
||||||
// delete all equip hotspots
|
|
||||||
var i, l = this.equipHotspotOverlays.length;
|
|
||||||
for (i = 0; i < l; i++) {
|
|
||||||
Overlays.deleteOverlay(this.equipHotspotOverlays[i]);
|
|
||||||
}
|
|
||||||
this.equipHotspotOverlays = [];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
///
|
///
|
||||||
|
@ -975,7 +1076,13 @@ function MyController(hand) {
|
||||||
return grabbableProps && grabbableProps.wantsTrigger;
|
return grabbableProps && grabbableProps.wantsTrigger;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.entityIsEquippable = function (entityID, handPosition) {
|
this.entityIsEquippableWithoutDistanceCheck = function (entityID) {
|
||||||
|
var props = this.entityPropertyCache.getProps(entityID);
|
||||||
|
var handPosition = props.position;
|
||||||
|
return this.entityIsEquippableWithDistanceCheck(entityID, handPosition);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.entityIsEquippableWithDistanceCheck = function (entityID, handPosition) {
|
||||||
var props = this.entityPropertyCache.getProps(entityID);
|
var props = this.entityPropertyCache.getProps(entityID);
|
||||||
var distance = Vec3.distance(props.position, handPosition);
|
var distance = Vec3.distance(props.position, handPosition);
|
||||||
var grabProps = this.entityPropertyCache.getGrabProps(entityID);
|
var grabProps = this.entityPropertyCache.getGrabProps(entityID);
|
||||||
|
@ -989,9 +1096,9 @@ function MyController(hand) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (distance > NEAR_PICK_MAX_DISTANCE) {
|
if (distance > EQUIP_RADIUS) {
|
||||||
if (debug) {
|
if (debug) {
|
||||||
print("equip is skipping '" + props.name + "': too far away.");
|
print("equip is skipping '" + props.name + "': too far away, " + distance + " meters");
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1104,7 +1211,7 @@ function MyController(hand) {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.entityIsNearGrabbable = function(entityID, handPosition) {
|
this.entityIsNearGrabbable = function(entityID, handPosition, maxDistance) {
|
||||||
|
|
||||||
if (!this.entityIsGrabbable(entityID)) {
|
if (!this.entityIsGrabbable(entityID)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1114,7 +1221,7 @@ function MyController(hand) {
|
||||||
var distance = Vec3.distance(props.position, handPosition);
|
var distance = Vec3.distance(props.position, handPosition);
|
||||||
var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME);
|
var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME);
|
||||||
|
|
||||||
if (distance > NEAR_PICK_MAX_DISTANCE) {
|
if (distance > maxDistance) {
|
||||||
// too far away, don't grab
|
// too far away, don't grab
|
||||||
if (debug) {
|
if (debug) {
|
||||||
print(" grab is skipping '" + props.name + "': too far away.");
|
print(" grab is skipping '" + props.name + "': too far away.");
|
||||||
|
@ -1129,6 +1236,8 @@ function MyController(hand) {
|
||||||
var _this = this;
|
var _this = this;
|
||||||
var name;
|
var name;
|
||||||
|
|
||||||
|
this.updateHotspots();
|
||||||
|
|
||||||
this.grabbedEntity = null;
|
this.grabbedEntity = null;
|
||||||
this.isInitialGrab = false;
|
this.isInitialGrab = false;
|
||||||
this.shouldResetParentOnRelease = false;
|
this.shouldResetParentOnRelease = false;
|
||||||
|
@ -1142,16 +1251,12 @@ function MyController(hand) {
|
||||||
|
|
||||||
var handPosition = this.getHandPosition();
|
var handPosition = this.getHandPosition();
|
||||||
|
|
||||||
if (SHOW_GRAB_SPHERE) {
|
|
||||||
this.grabSphereOn();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.entityPropertyCache.clear();
|
this.entityPropertyCache.clear();
|
||||||
this.entityPropertyCache.findEntities(handPosition, GRAB_RADIUS);
|
this.entityPropertyCache.findEntities(handPosition, NEAR_GRAB_RADIUS);
|
||||||
var candidateEntities = this.entityPropertyCache.getEntities();
|
var candidateEntities = this.entityPropertyCache.getEntities();
|
||||||
|
|
||||||
var equippableEntities = candidateEntities.filter(function (entity) {
|
var equippableEntities = candidateEntities.filter(function (entity) {
|
||||||
return _this.entityIsEquippable(entity, handPosition);
|
return _this.entityIsEquippableWithDistanceCheck(entity, handPosition);
|
||||||
});
|
});
|
||||||
|
|
||||||
var entity;
|
var entity;
|
||||||
|
@ -1172,17 +1277,21 @@ function MyController(hand) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var rayPickInfo = this.calcRayPickInfo(this.hand);
|
|
||||||
this.intersectionDistance = rayPickInfo.distance;
|
|
||||||
if (rayPickInfo.entityID) {
|
|
||||||
candidateEntities.push(rayPickInfo.entityID);
|
|
||||||
this.entityPropertyCache.addEntity(rayPickInfo.entityID);
|
|
||||||
}
|
|
||||||
|
|
||||||
var grabbableEntities = candidateEntities.filter(function (entity) {
|
var grabbableEntities = candidateEntities.filter(function (entity) {
|
||||||
return _this.entityIsNearGrabbable(entity, handPosition);
|
return _this.entityIsNearGrabbable(entity, handPosition, NEAR_GRAB_MAX_DISTANCE);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var rayPickInfo = this.calcRayPickInfo(this.hand);
|
||||||
|
if (rayPickInfo.entityID) {
|
||||||
|
this.intersectionDistance = rayPickInfo.distance;
|
||||||
|
this.entityPropertyCache.updateEntity(rayPickInfo.entityID);
|
||||||
|
if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) {
|
||||||
|
grabbableEntities.push(rayPickInfo.entityID);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.intersectionDistance = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (grabbableEntities.length > 0) {
|
if (grabbableEntities.length > 0) {
|
||||||
// sort by distance
|
// sort by distance
|
||||||
grabbableEntities.sort(function (a, b) {
|
grabbableEntities.sort(function (a, b) {
|
||||||
|
@ -1723,11 +1832,11 @@ function MyController(hand) {
|
||||||
this.lastUnequipCheckTime = now;
|
this.lastUnequipCheckTime = now;
|
||||||
|
|
||||||
if (props.parentID == MyAvatar.sessionUUID &&
|
if (props.parentID == MyAvatar.sessionUUID &&
|
||||||
Vec3.length(props.localPosition) > NEAR_PICK_MAX_DISTANCE * 2.0) {
|
Vec3.length(props.localPosition) > NEAR_GRAB_MAX_DISTANCE) {
|
||||||
var handPosition = this.getHandPosition();
|
var handPosition = this.getHandPosition();
|
||||||
// the center of the equipped object being far from the hand isn't enough to autoequip -- we also
|
// the center of the equipped object being far from the hand isn't enough to autoequip -- we also
|
||||||
// need to fail the findEntities test.
|
// need to fail the findEntities test.
|
||||||
var nearPickedCandidateEntities = Entities.findEntities(handPosition, GRAB_RADIUS);
|
var nearPickedCandidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS);
|
||||||
if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) {
|
if (nearPickedCandidateEntities.indexOf(this.grabbedEntity) == -1) {
|
||||||
// for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip.
|
// for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip.
|
||||||
print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." +
|
print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." +
|
||||||
|
@ -2173,17 +2282,32 @@ function cleanup() {
|
||||||
Script.scriptEnding.connect(cleanup);
|
Script.scriptEnding.connect(cleanup);
|
||||||
Script.update.connect(update);
|
Script.update.connect(update);
|
||||||
|
|
||||||
|
if (!Menu.menuExists("Developer > Grab Script")) {
|
||||||
|
Menu.addMenu("Developer > Grab Script");
|
||||||
|
}
|
||||||
|
|
||||||
Menu.addMenuItem({
|
Menu.addMenuItem({
|
||||||
menuName: "Developer > Hands",
|
menuName: "Developer > Grab Script",
|
||||||
menuItemName: "Drop Without Shake",
|
menuItemName: "Drop Without Shake",
|
||||||
isCheckable: true,
|
isCheckable: true,
|
||||||
isChecked: DROP_WITHOUT_SHAKE
|
isChecked: DROP_WITHOUT_SHAKE
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Menu.addMenuItem({
|
||||||
|
menuName: "Developer > Grab Script",
|
||||||
|
menuItemName: "Draw Grab Boxes",
|
||||||
|
isCheckable: true,
|
||||||
|
isChecked: DRAW_GRAB_BOXES
|
||||||
|
});
|
||||||
|
|
||||||
function handleMenuItemEvent(menuItem) {
|
function handleMenuItemEvent(menuItem) {
|
||||||
if (menuItem === "Drop Without Shake") {
|
if (menuItem === "Drop Without Shake") {
|
||||||
DROP_WITHOUT_SHAKE = Menu.isOptionChecked("Drop Without Shake");
|
DROP_WITHOUT_SHAKE = Menu.isOptionChecked("Drop Without Shake");
|
||||||
}
|
}
|
||||||
|
if (menuItem === "Draw Grab Boxes") {
|
||||||
|
DRAW_GRAB_BOXES = Menu.isOptionChecked("Draw Grab Boxes");
|
||||||
|
DRAW_HAND_SPHERES = DRAW_GRAB_BOXES;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu.menuItemEvent.connect(handleMenuItemEvent);
|
Menu.menuItemEvent.connect(handleMenuItemEvent);
|
||||||
|
|
|
@ -208,9 +208,9 @@ function isShakingMouse() { // True if the person is waving the mouse around try
|
||||||
return isShaking;
|
return isShaking;
|
||||||
}
|
}
|
||||||
var NON_LINEAR_DIVISOR = 2;
|
var NON_LINEAR_DIVISOR = 2;
|
||||||
var MINIMUM_SEEK_DISTANCE = 0.01;
|
var MINIMUM_SEEK_DISTANCE = 0.1;
|
||||||
function updateSeeking() {
|
function updateSeeking(doNotStartSeeking) {
|
||||||
if (!Reticle.visible || isShakingMouse()) {
|
if (!doNotStartSeeking && (!Reticle.visible || isShakingMouse())) {
|
||||||
if (!isSeeking) {
|
if (!isSeeking) {
|
||||||
print('Start seeking mouse.');
|
print('Start seeking mouse.');
|
||||||
isSeeking = true;
|
isSeeking = true;
|
||||||
|
@ -224,8 +224,8 @@ function updateSeeking() {
|
||||||
if (!lookAt2D) { // If this happens, something has gone terribly wrong.
|
if (!lookAt2D) { // If this happens, something has gone terribly wrong.
|
||||||
print('Cannot seek without lookAt position');
|
print('Cannot seek without lookAt position');
|
||||||
isSeeking = false;
|
isSeeking = false;
|
||||||
return;
|
return; // E.g., if parallel to location in HUD
|
||||||
} // E.g., if parallel to location in HUD
|
}
|
||||||
var copy = Reticle.position;
|
var copy = Reticle.position;
|
||||||
function updateDimension(axis) {
|
function updateDimension(axis) {
|
||||||
var distanceBetween = lookAt2D[axis] - Reticle.position[axis];
|
var distanceBetween = lookAt2D[axis] - Reticle.position[axis];
|
||||||
|
@ -353,6 +353,16 @@ clickMapping.from(rightTrigger.full).when(isPointingAtOverlayStartedNonFullTrigg
|
||||||
clickMapping.from(leftTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(leftTrigger)).to(Controller.Actions.ReticleClick);
|
clickMapping.from(leftTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(leftTrigger)).to(Controller.Actions.ReticleClick);
|
||||||
clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu);
|
clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu);
|
||||||
clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu);
|
clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu);
|
||||||
|
clickMapping.from(Controller.Hardware.Keyboard.RightMouseClicked).peek().to(function () {
|
||||||
|
// Allow the reticle depth to be set correctly:
|
||||||
|
// Wait a tick for the context menu to be displayed, and then simulate a (non-hand-controller) mouse move
|
||||||
|
// so that the system updates qml state (Reticle.pointingAtSystemOverlay) before it gives us a mouseMove.
|
||||||
|
// We don't want the system code to always do this for us, because, e.g., we do not want to get a mouseMove
|
||||||
|
// after the Left/RightSecondaryThumb gives us a context menu. Only from the mouse.
|
||||||
|
Script.setTimeout(function () {
|
||||||
|
Reticle.setPosition(Reticle.position);
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
// Partial smoothed trigger is activation.
|
// Partial smoothed trigger is activation.
|
||||||
clickMapping.from(rightTrigger.partial).to(makeToggleAction(Controller.Standard.RightHand));
|
clickMapping.from(rightTrigger.partial).to(makeToggleAction(Controller.Standard.RightHand));
|
||||||
clickMapping.from(leftTrigger.partial).to(makeToggleAction(Controller.Standard.LeftHand));
|
clickMapping.from(leftTrigger.partial).to(makeToggleAction(Controller.Standard.LeftHand));
|
||||||
|
@ -386,6 +396,7 @@ function update() {
|
||||||
expireMouseCursor();
|
expireMouseCursor();
|
||||||
clearSystemLaser();
|
clearSystemLaser();
|
||||||
}
|
}
|
||||||
|
updateSeeking(true);
|
||||||
if (!handControllerLockOut.expired(now)) {
|
if (!handControllerLockOut.expired(now)) {
|
||||||
return off(); // Let them use mouse it in peace.
|
return off(); // Let them use mouse it in peace.
|
||||||
}
|
}
|
||||||
|
|
|
@ -334,7 +334,7 @@ var toolBar = (function() {
|
||||||
|
|
||||||
that.setActive = function(active) {
|
that.setActive = function(active) {
|
||||||
if (active != isActive) {
|
if (active != isActive) {
|
||||||
if (active && !Entities.canAdjustLocks()) {
|
if (active && !Entities.canRez() && !Entities.canRezTmp()) {
|
||||||
Window.alert(INSUFFICIENT_PERMISSIONS_ERROR_MSG);
|
Window.alert(INSUFFICIENT_PERMISSIONS_ERROR_MSG);
|
||||||
} else {
|
} else {
|
||||||
Messages.sendLocalMessage("edit-events", JSON.stringify({
|
Messages.sendLocalMessage("edit-events", JSON.stringify({
|
||||||
|
|
17
scripts/system/firstPersonHMD.js
Normal file
17
scripts/system/firstPersonHMD.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
//
|
||||||
|
// firstPersonHMD.js
|
||||||
|
// system
|
||||||
|
//
|
||||||
|
// Created by Zander Otavka on 6/24/16
|
||||||
|
// Copyright 2016 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
|
||||||
|
//
|
||||||
|
|
||||||
|
// Automatically enter first person mode when entering HMD mode
|
||||||
|
HMD.displayModeChanged.connect(function(isHMDMode) {
|
||||||
|
if (isHMDMode) {
|
||||||
|
Camera.setModeString("first person");
|
||||||
|
}
|
||||||
|
});
|
40
scripts/system/libraries/Xform.js
Normal file
40
scripts/system/libraries/Xform.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
//
|
||||||
|
// Created by Anthony J. Thibault on 2016/06/21
|
||||||
|
// Copyright 2016 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
|
||||||
|
//
|
||||||
|
|
||||||
|
// ctor
|
||||||
|
Xform = function(rot, pos) {
|
||||||
|
this.rot = rot;
|
||||||
|
this.pos = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
Xform.ident = function() {
|
||||||
|
return new Xform({x: 0, y: 0, z: 0, w: 1}, {x: 0, y: 0, z: 0});
|
||||||
|
};
|
||||||
|
|
||||||
|
Xform.mul = function(lhs, rhs) {
|
||||||
|
var rot = Quat.multiply(lhs.rot, rhs.rot);
|
||||||
|
var pos = Vec3.sum(lhs.pos, Vec3.multiplyQbyV(lhs.rot, rhs.pos));
|
||||||
|
return new Xform(rot, pos);
|
||||||
|
};
|
||||||
|
|
||||||
|
Xform.prototype.inv = function() {
|
||||||
|
var invRot = Quat.inverse(this.rot);
|
||||||
|
var invPos = Vec3.multiply(-1, this.pos);
|
||||||
|
return new Xform(invRot, Vec3.multiplyQbyV(invRot, invPos));
|
||||||
|
};
|
||||||
|
|
||||||
|
Xform.prototype.mirrorX = function() {
|
||||||
|
return new Xform({x: this.rot.x, y: -this.rot.y, z: -this.rot.z, w: this.rot.w},
|
||||||
|
{x: -this.pos.x, y: this.pos.y, z: this.pos.z});
|
||||||
|
};
|
||||||
|
|
||||||
|
Xform.prototype.toString = function() {
|
||||||
|
var rot = this.rot;
|
||||||
|
var pos = this.pos;
|
||||||
|
return "Xform rot = (" + rot.x + ", " + rot.y + ", " + rot.z + ", " + rot.w + "), pos = (" + pos.x + ", " + pos.y + ", " + pos.z + ")";
|
||||||
|
};
|
Loading…
Reference in a new issue