mirror of
https://github.com/overte-org/overte.git
synced 2025-08-06 19:59:28 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into groups
This commit is contained in:
commit
c7d56493b1
33 changed files with 627 additions and 541 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.
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -478,6 +478,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();
|
||||||
|
@ -654,22 +659,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;
|
||||||
|
|
|
@ -91,6 +91,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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -118,7 +118,7 @@ void QmlWindowClass::initQml(QVariantMap properties) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool visible = !properties.contains(VISIBILE_PROPERTY) || properties[VISIBILE_PROPERTY].toBool();
|
bool visible = !properties.contains(VISIBILE_PROPERTY) || properties[VISIBILE_PROPERTY].toBool();
|
||||||
object->setProperty(VISIBILE_PROPERTY, visible);
|
object->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible);
|
||||||
object->setProperty(SOURCE_PROPERTY, _source);
|
object->setProperty(SOURCE_PROPERTY, _source);
|
||||||
|
|
||||||
// Forward messages received from QML on to the script
|
// Forward messages received from QML on to the script
|
||||||
|
|
|
@ -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