mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-25 17:14:59 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into skin
This commit is contained in:
commit
938bc77b1f
33 changed files with 629 additions and 543 deletions
25
cmake/externals/sixense/CMakeLists.txt
vendored
25
cmake/externals/sixense/CMakeLists.txt
vendored
|
@ -57,30 +57,7 @@ if (WIN32)
|
|||
|
||||
elseif(APPLE)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/lib/osx_x64/release_dll/libsixense_x64.dylib CACHE TYPE INTERNAL)
|
||||
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
|
||||
)
|
||||
# We no longer support Sixense on Macs due to bugs in the Sixense DLL
|
||||
|
||||
elseif(NOT ANDROID)
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ macro(optional_win_executable_signing)
|
|||
# setup a post build command to sign the executable
|
||||
add_custom_command(
|
||||
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 ()
|
||||
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
|
||||
#
|
||||
macro(TARGET_SIXENSE)
|
||||
add_dependency_external_projects(sixense)
|
||||
find_package(Sixense REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES})
|
||||
add_definitions(-DHAVE_SIXENSE)
|
||||
if(NOT APPLE)
|
||||
add_dependency_external_projects(sixense)
|
||||
find_package(Sixense REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES})
|
||||
add_definitions(-DHAVE_SIXENSE)
|
||||
endif()
|
||||
endmacro()
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
; The Inner invocation has written an uninstaller binary for us.
|
||||
; We need to sign it if it's a production or PR build.
|
||||
!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
|
||||
|
||||
; Good. Now we can carry on writing the real installer.
|
||||
|
|
|
@ -384,7 +384,7 @@
|
|||
"name": "standard_permissions",
|
||||
"type": "table",
|
||||
"label": "Domain-Wide User Permissions",
|
||||
"help": "Indicate which users or groups can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
|
||||
"help": "Indicate which users or groups can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
|
||||
"caption": "Standard Permissions",
|
||||
"can_add_new_rows": false,
|
||||
|
||||
|
@ -394,7 +394,7 @@
|
|||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
|
||||
"span": 6
|
||||
}
|
||||
],
|
||||
|
@ -463,7 +463,7 @@
|
|||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
|
||||
"span": 6
|
||||
}
|
||||
],
|
||||
|
|
|
@ -457,6 +457,8 @@ function disonnectHighFidelityAccount() {
|
|||
}, function(){
|
||||
// we need to post to settings to clear the access-token
|
||||
$(Settings.ACCESS_TOKEN_SELECTOR).val('').change();
|
||||
// reset the domain id to get a new temporary name
|
||||
$(Settings.DOMAIN_ID_SELECTOR).val('').change();
|
||||
saveSettings();
|
||||
});
|
||||
}
|
||||
|
@ -555,7 +557,7 @@ function createNewDomainID(description, justConnected) {
|
|||
// get the JSON object ready that we'll use to create a new domain
|
||||
var domainJSON = {
|
||||
"domain": {
|
||||
"description": description
|
||||
"private_description": description
|
||||
},
|
||||
"access_token": $(Settings.ACCESS_TOKEN_SELECTOR).val()
|
||||
}
|
||||
|
@ -748,8 +750,8 @@ function chooseFromHighFidelityDomains(clickedButton) {
|
|||
_.each(data.data.domains, function(domain){
|
||||
var domainString = "";
|
||||
|
||||
if (domain.description) {
|
||||
domainString += '"' + domain.description + '" - ';
|
||||
if (domain.private_description) {
|
||||
domainString += '"' + domain.private_description + '" - ';
|
||||
}
|
||||
|
||||
domainString += domain.id;
|
||||
|
|
|
@ -76,6 +76,8 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
setApplicationVersion(BuildInfo::VERSION);
|
||||
QSettings::setDefaultFormat(QSettings::IniFormat);
|
||||
|
||||
qDebug() << "Setting up domain-server";
|
||||
|
||||
// make sure we have a fresh AccountManager instance
|
||||
// (need this since domain-server can restart itself and maintain static variables)
|
||||
DependencyManager::set<AccountManager>();
|
||||
|
@ -104,23 +106,31 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions,
|
||||
&_gatekeeper, &DomainGatekeeper::updateNodePermissions);
|
||||
|
||||
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) {
|
||||
// we either read a certificate and private key or were not passed one
|
||||
// and completed login or did not need to
|
||||
|
||||
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);
|
||||
// if we were given a certificate/private key or oauth credentials they must succeed
|
||||
if (!(optionallyReadX509KeyAndCertificate() && optionallySetupOAuth())) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
||||
qDebug() << "domain-server is running";
|
||||
}
|
||||
|
||||
DomainServer::~DomainServer() {
|
||||
|
@ -148,6 +158,10 @@ void DomainServer::restart() {
|
|||
exit(DomainServer::EXIT_CODE_REBOOT);
|
||||
}
|
||||
|
||||
const QUuid& DomainServer::getID() {
|
||||
return DependencyManager::get<LimitedNodeList>()->getSessionUUID();
|
||||
}
|
||||
|
||||
bool DomainServer::optionallyReadX509KeyAndCertificate() {
|
||||
const QString X509_CERTIFICATE_OPTION = "cert";
|
||||
const QString X509_PRIVATE_KEY_OPTION = "key";
|
||||
|
@ -233,34 +247,26 @@ bool DomainServer::optionallySetupOAuth() {
|
|||
|
||||
static const QString METAVERSE_DOMAIN_ID_KEY_PATH = "metaverse.id";
|
||||
|
||||
void DomainServer::optionallyGetTemporaryName(const QStringList& arguments) {
|
||||
// check for the temporary name parameter
|
||||
const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name";
|
||||
void DomainServer::getTemporaryName(bool force) {
|
||||
// check if we already have a domain ID
|
||||
const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH);
|
||||
|
||||
if (arguments.contains(GET_TEMPORARY_NAME_SWITCH)) {
|
||||
|
||||
// make sure we don't already have a domain ID
|
||||
const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH);
|
||||
if (idValueVariant) {
|
||||
qWarning() << "Temporary domain name requested but a domain ID is already present in domain-server settings."
|
||||
<< "Will not request temporary name.";
|
||||
qInfo() << "Requesting temporary domain name";
|
||||
if (idValueVariant) {
|
||||
qDebug() << "A domain ID is already present in domain-server settings:" << idValueVariant->toString();
|
||||
if (force) {
|
||||
qDebug() << "Requesting temporary domain name to replace current ID:" << getID();
|
||||
} else {
|
||||
qInfo() << "Abandoning request of temporary domain name.";
|
||||
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) {
|
||||
|
@ -271,11 +277,13 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
|
|||
static const QString DOMAIN_KEY = "domain";
|
||||
static const QString ID_KEY = "id";
|
||||
static const QString NAME_KEY = "name";
|
||||
static const QString KEY_KEY = "api_key";
|
||||
|
||||
auto domainObject = jsonObject[DATA_KEY].toObject()[DOMAIN_KEY].toObject();
|
||||
if (!domainObject.isEmpty()) {
|
||||
auto id = domainObject[ID_KEY].toString();
|
||||
auto name = domainObject[NAME_KEY].toString();
|
||||
auto key = domainObject[KEY_KEY].toString();
|
||||
|
||||
qInfo() << "Received new temporary domain name" << name;
|
||||
qDebug() << "The temporary domain ID is" << id;
|
||||
|
@ -291,9 +299,13 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
|
|||
// change our domain ID immediately
|
||||
DependencyManager::get<LimitedNodeList>()->setSessionUUID(QUuid { id });
|
||||
|
||||
// change our automatic networking settings so that we're communicating with the ICE server
|
||||
setupICEHeartbeatForFullNetworking();
|
||||
// store the new token to the account info
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
accountManager->setTemporaryDomain(id, key);
|
||||
|
||||
// update our heartbeats to use the correct id
|
||||
setupICEHeartbeatForFullNetworking();
|
||||
setupHeartbeatToMetaverse();
|
||||
} else {
|
||||
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.";
|
||||
|
@ -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";
|
||||
|
||||
QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION);
|
||||
|
@ -458,29 +469,23 @@ bool DomainServer::resetAccountManagerAccessToken() {
|
|||
}
|
||||
|
||||
void DomainServer::setupAutomaticNetworking() {
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting;
|
||||
|
||||
resetAccountManagerAccessToken();
|
||||
|
||||
_automaticNetworkingSetting =
|
||||
_settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString();
|
||||
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
const QUuid& domainID = getID();
|
||||
|
||||
if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||
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 ||
|
||||
_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||
|
||||
const QUuid& domainID = nodeList->getSessionUUID();
|
||||
|
||||
if (!domainID.isNull()) {
|
||||
qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
|
||||
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
|
||||
|
@ -492,9 +497,6 @@ void DomainServer::setupAutomaticNetworking() {
|
|||
|
||||
// have the LNL enable public socket updating via STUN
|
||||
nodeList->startSTUNPublicSocketUpdate();
|
||||
} else {
|
||||
// send our heartbeat to data server so it knows what our network settings are
|
||||
sendHeartbeatToMetaverse();
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
|
||||
|
@ -502,18 +504,20 @@ void DomainServer::setupAutomaticNetworking() {
|
|||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
sendHeartbeatToMetaverse();
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting;
|
||||
|
||||
// no matter the auto networking settings we should heartbeat to the data-server every 15s
|
||||
void DomainServer::setupHeartbeatToMetaverse() {
|
||||
// heartbeat to the data-server every 15s
|
||||
const int DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS = 15 * 1000;
|
||||
|
||||
QTimer* dataHeartbeatTimer = new QTimer(this);
|
||||
connect(dataHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToMetaverse()));
|
||||
dataHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS);
|
||||
if (!_metaverseHeartbeatTimer) {
|
||||
// setup a timer to heartbeat with the metaverse-server
|
||||
_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() {
|
||||
|
@ -532,22 +536,21 @@ void DomainServer::setupICEHeartbeatForFullNetworking() {
|
|||
limitedNodeList->startSTUNPublicSocketUpdate();
|
||||
|
||||
// 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
|
||||
// we should generate a new keypair
|
||||
if (!accountManager->getAccountInfo().hasPrivateKey() || domainID != limitedNodeList->getSessionUUID()) {
|
||||
accountManager->generateNewDomainKeypair(limitedNodeList->getSessionUUID());
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
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
|
||||
connect(accountManager.data(), &AccountManager::newKeypair, this, &DomainServer::handleKeypairChange);
|
||||
|
||||
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 };
|
||||
connect(_iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::sendHeartbeatToIceServer);
|
||||
sendHeartbeatToIceServer();
|
||||
_iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS);
|
||||
}
|
||||
}
|
||||
|
@ -1075,12 +1078,14 @@ void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr)
|
|||
}
|
||||
|
||||
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
|
||||
QJsonObject domainObject;
|
||||
|
||||
// add the version
|
||||
static const QString VERSION_KEY = "version";
|
||||
domainObject[VERSION_KEY] = BuildInfo::VERSION;
|
||||
|
||||
// add networking
|
||||
if (!networkAddress.isEmpty()) {
|
||||
static const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address";
|
||||
domainObject[PUBLIC_NETWORK_ADDRESS_KEY] = networkAddress;
|
||||
|
@ -1089,13 +1094,20 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
|||
static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";
|
||||
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);
|
||||
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) {
|
||||
// Add the metadata to the 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)));
|
||||
|
||||
static const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
|
||||
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)),
|
||||
AccountManagerAuth::Required,
|
||||
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(getID())),
|
||||
AccountManagerAuth::Optional,
|
||||
QNetworkAccessManager::PutOperation,
|
||||
JSONCallbackParameters(),
|
||||
JSONCallbackParameters(nullptr, QString(), this, "handleMetaverseHeartbeatError"),
|
||||
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() {
|
||||
if (!_iceServerSocket.isNull()) {
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
const QUuid& domainID = nodeList->getSessionUUID();
|
||||
|
||||
const QString ICE_SERVER_ADDRESS = "ice_server_address";
|
||||
|
||||
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
|
||||
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()));
|
||||
|
||||
// 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";
|
||||
|
||||
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_ICE_ADDRESS_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)),
|
||||
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_ICE_ADDRESS_UPDATE.arg(uuidStringWithoutCurlyBraces(getID())),
|
||||
AccountManagerAuth::Optional,
|
||||
QNetworkAccessManager::PutOperation,
|
||||
callbackParameters,
|
||||
|
|
|
@ -80,6 +80,8 @@ private slots:
|
|||
void handleTempDomainSuccess(QNetworkReply& requestReply);
|
||||
void handleTempDomainError(QNetworkReply& requestReply);
|
||||
|
||||
void handleMetaverseHeartbeatError(QNetworkReply& requestReply);
|
||||
|
||||
void queuedQuit(QString quitMessage, int exitCode);
|
||||
|
||||
void handleKeypairChange();
|
||||
|
@ -96,11 +98,13 @@ signals:
|
|||
void userDisconnected();
|
||||
|
||||
private:
|
||||
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
||||
const QUuid& getID();
|
||||
|
||||
void setupNodeListAndAssignments();
|
||||
bool optionallySetupOAuth();
|
||||
bool optionallyReadX509KeyAndCertificate();
|
||||
|
||||
void optionallyGetTemporaryName(const QStringList& arguments);
|
||||
void getTemporaryName(bool force = false);
|
||||
|
||||
static bool packetVersionMatch(const udt::Packet& packet);
|
||||
|
||||
|
@ -108,6 +112,7 @@ private:
|
|||
|
||||
void setupAutomaticNetworking();
|
||||
void setupICEHeartbeatForFullNetworking();
|
||||
void setupHeartbeatToMetaverse();
|
||||
void sendHeartbeatToMetaverse(const QString& networkAddress);
|
||||
|
||||
void randomizeICEServerAddress(bool shouldTriggerHostLookup);
|
||||
|
@ -178,6 +183,7 @@ private:
|
|||
// These will be parented to this, they are not dangling
|
||||
DomainMetadata* _metadata { nullptr };
|
||||
QTimer* _iceHeartbeatTimer { nullptr };
|
||||
QTimer* _metaverseHeartbeatTimer { nullptr };
|
||||
|
||||
QList<QHostAddress> _iceServerAddresses;
|
||||
QSet<QHostAddress> _failedIceServerAddresses;
|
||||
|
@ -186,8 +192,6 @@ private:
|
|||
int _numHeartbeatDenials { 0 };
|
||||
bool _connectedToICEServer { false };
|
||||
|
||||
bool _hasAccessToken { false };
|
||||
|
||||
friend class DomainGatekeeper;
|
||||
friend class DomainMetadata;
|
||||
};
|
||||
|
|
|
@ -921,17 +921,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
cycleCamera();
|
||||
} else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) {
|
||||
if (!offscreenUi->navigationFocused()) {
|
||||
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
||||
offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y));
|
||||
toggleMenuUnderReticle();
|
||||
}
|
||||
} else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
|
||||
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
||||
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));
|
||||
}
|
||||
toggleMenuUnderReticle();
|
||||
} else if (action == controller::toInt(controller::Action::RETICLE_X)) {
|
||||
auto oldPos = getApplicationCompositor().getReticlePosition();
|
||||
getApplicationCompositor().setReticlePosition({ oldPos.x + state, oldPos.y });
|
||||
|
@ -1240,7 +1233,16 @@ QString Application::getUserAgent() {
|
|||
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() {
|
||||
QMutexLocker locker(&_changeCursorLock);
|
||||
|
@ -2462,9 +2464,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
|
||||
void Application::keyReleaseEvent(QKeyEvent* event) {
|
||||
if (event->key() == Qt::Key_Alt && _altPressed && hasFocus()) {
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
||||
offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y));
|
||||
toggleMenuUnderReticle();
|
||||
}
|
||||
|
||||
_keysPressed.remove(event->key());
|
||||
|
|
|
@ -408,6 +408,7 @@ private:
|
|||
static void dragEnterEvent(QDragEnterEvent* event);
|
||||
|
||||
void maybeToggleMenuVisible(QMouseEvent* event) const;
|
||||
void toggleMenuUnderReticle() const;
|
||||
|
||||
MainWindow* _window;
|
||||
QElapsedTimer& _sessionRunTimer;
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
#include "Application.h"
|
||||
|
||||
HMDScriptingInterface::HMDScriptingInterface() {
|
||||
connect(qApp, &Application::activeDisplayPluginChanged, [this]{
|
||||
emit displayModeChanged(isHMDMode());
|
||||
});
|
||||
}
|
||||
|
||||
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 drivingChanged = prevDriving != isDriving;
|
||||
bool isAtRest = updateAvatarIsAtRest();
|
||||
bool shouldRecenter = false;
|
||||
|
||||
if (_flags & SuppressedByDrive) {
|
||||
if (!isDriving) {
|
||||
_flags &= ~SuppressedByDrive;
|
||||
shouldRecenter = true;
|
||||
}
|
||||
} else {
|
||||
if (myAvatar->getClearOverlayWhenMoving() && drivingChanged && isDriving) {
|
||||
|
@ -132,6 +134,7 @@ void OverlayConductor::update(float dt) {
|
|||
if (_flags & SuppressedByHead) {
|
||||
if (isAtRest) {
|
||||
_flags &= ~SuppressedByHead;
|
||||
shouldRecenter = true;
|
||||
}
|
||||
} else {
|
||||
if (_hmdMode && headOutsideOverlay()) {
|
||||
|
@ -143,8 +146,8 @@ void OverlayConductor::update(float dt) {
|
|||
bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask));
|
||||
if (targetVisible != currentVisible) {
|
||||
offscreenUi->setPinned(!targetVisible);
|
||||
if (targetVisible && _hmdMode) {
|
||||
centerUI();
|
||||
}
|
||||
}
|
||||
if (shouldRecenter && !_flags) {
|
||||
centerUI();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,7 +102,6 @@ AudioClient::AudioClient() :
|
|||
_reverbOptions(&_scriptReverbOptions),
|
||||
_inputToNetworkResampler(NULL),
|
||||
_networkToOutputResampler(NULL),
|
||||
_loopbackResampler(NULL),
|
||||
_outgoingAvatarAudioSequenceNumber(0),
|
||||
_audioOutputIODevice(_receivedAudioStream, this),
|
||||
_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.
|
||||
// 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 audio format is not supported by this device");
|
||||
qCDebug(audioclient) << "The desired format for audio I/O is" << desiredAudioFormat;
|
||||
|
||||
if (desiredAudioFormat.channelCount() == 1) {
|
||||
adjustedAudioFormat = desiredAudioFormat;
|
||||
adjustedAudioFormat.setChannelCount(2);
|
||||
|
||||
if (false && audioDevice.isFormatSupported(adjustedAudioFormat)) {
|
||||
return true;
|
||||
} else {
|
||||
adjustedAudioFormat.setChannelCount(1);
|
||||
}
|
||||
}
|
||||
|
||||
const int FORTY_FOUR = 44100;
|
||||
|
||||
adjustedAudioFormat = desiredAudioFormat;
|
||||
const int FORTY_FOUR = 44100;
|
||||
adjustedAudioFormat = desiredAudioFormat;
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
adjustedAudioFormat.setSampleRate(FORTY_FOUR);
|
||||
adjustedAudioFormat.setSampleRate(FORTY_FOUR);
|
||||
#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)) {
|
||||
// use 48, which is a sample downsample, upsample
|
||||
adjustedAudioFormat.setSampleRate(AudioConstants::SAMPLE_RATE * 2);
|
||||
} else if (audioDevice.supportedSampleRates().contains(HALF_FORTY_FOUR)) {
|
||||
// use 22050, resample but closer to 24
|
||||
adjustedAudioFormat.setSampleRate(HALF_FORTY_FOUR);
|
||||
} else if (audioDevice.supportedSampleRates().contains(FORTY_FOUR)) {
|
||||
// use 48000, resample
|
||||
adjustedAudioFormat.setSampleRate(FORTY_FOUR);
|
||||
}
|
||||
if (audioDevice.supportedSampleRates().contains(AudioConstants::SAMPLE_RATE * 2)) {
|
||||
// use 48, which is a simple downsample, upsample
|
||||
adjustedAudioFormat.setSampleRate(AudioConstants::SAMPLE_RATE * 2);
|
||||
} else if (audioDevice.supportedSampleRates().contains(HALF_FORTY_FOUR)) {
|
||||
// use 22050, resample but closer to 24
|
||||
adjustedAudioFormat.setSampleRate(HALF_FORTY_FOUR);
|
||||
} else if (audioDevice.supportedSampleRates().contains(FORTY_FOUR)) {
|
||||
// use 44100, resample
|
||||
adjustedAudioFormat.setSampleRate(FORTY_FOUR);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (adjustedAudioFormat != desiredAudioFormat) {
|
||||
// return the nearest in case it needs 2 channels
|
||||
adjustedAudioFormat = audioDevice.nearestFormat(adjustedAudioFormat);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// set the adjustedAudioFormat to the desiredAudioFormat, since it will work
|
||||
adjustedAudioFormat = desiredAudioFormat;
|
||||
if (adjustedAudioFormat != desiredAudioFormat) {
|
||||
// return the nearest in case it needs 2 channels
|
||||
adjustedAudioFormat = audioDevice.nearestFormat(adjustedAudioFormat);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -467,11 +447,6 @@ void AudioClient::stop() {
|
|||
// "switch" to invalid devices in order to shut down the state
|
||||
switchInputToAudioDevice(QAudioDeviceInfo());
|
||||
switchOutputToAudioDevice(QAudioDeviceInfo());
|
||||
|
||||
if (_loopbackResampler) {
|
||||
delete _loopbackResampler;
|
||||
_loopbackResampler = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioClient::handleAudioEnvironmentDataPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
|
@ -675,16 +650,10 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) {
|
|||
}
|
||||
}
|
||||
|
||||
// do we need to setup a resampler?
|
||||
if (_inputFormat.sampleRate() != _outputFormat.sampleRate() && !_loopbackResampler) {
|
||||
qCDebug(audioclient) << "Attemping to create a resampler for input format to output format for audio loopback.";
|
||||
|
||||
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);
|
||||
}
|
||||
// NOTE: we assume the inputFormat and the outputFormat are the same, since on any modern
|
||||
// multimedia OS they should be. If there is a device that this is not true for, we can
|
||||
// add back support to do resampling.
|
||||
Q_ASSERT(_inputFormat.sampleRate() == _outputFormat.sampleRate());
|
||||
|
||||
static QByteArray loopBackByteArray;
|
||||
|
||||
|
@ -696,7 +665,8 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) {
|
|||
int16_t* inputSamples = reinterpret_cast<int16_t*>(inputByteArray.data());
|
||||
int16_t* loopbackSamples = reinterpret_cast<int16_t*>(loopBackByteArray.data());
|
||||
|
||||
possibleResampling(_loopbackResampler,
|
||||
auto NO_RESAMPLER = nullptr;
|
||||
possibleResampling(NO_RESAMPLER,
|
||||
inputSamples, loopbackSamples,
|
||||
numInputSamples, numLoopbackSamples,
|
||||
_inputFormat, _outputFormat);
|
||||
|
@ -1039,12 +1009,6 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice
|
|||
_networkToOutputResampler = NULL;
|
||||
}
|
||||
|
||||
if (_loopbackResampler) {
|
||||
// if we were using an input to output resample, delete it here
|
||||
delete _loopbackResampler;
|
||||
_loopbackResampler = NULL;
|
||||
}
|
||||
|
||||
if (!outputDeviceInfo.isNull()) {
|
||||
qCDebug(audioclient) << "The audio output device " << outputDeviceInfo.deviceName() << "is available.";
|
||||
_outputAudioDeviceName = outputDeviceInfo.deviceName().trimmed();
|
||||
|
|
|
@ -260,7 +260,6 @@ private:
|
|||
// possible streams needed for resample
|
||||
AudioSRC* _inputToNetworkResampler;
|
||||
AudioSRC* _networkToOutputResampler;
|
||||
AudioSRC* _loopbackResampler;
|
||||
|
||||
// Adds Reverb
|
||||
void configureReverb();
|
||||
|
|
|
@ -31,6 +31,7 @@ public:
|
|||
|
||||
signals:
|
||||
void IPDScaleChanged();
|
||||
void displayModeChanged(bool isHMDMode);
|
||||
|
||||
private:
|
||||
float _IPDScale{ 1.0 };
|
||||
|
|
|
@ -179,6 +179,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
|
|||
}
|
||||
|
||||
entity->setLastBroadcast(usecTimestampNow());
|
||||
propertiesWithSimID.setLastEdited(entity->getLastEdited());
|
||||
} else {
|
||||
qCDebug(entities) << "script failed to add new Entity to local Octree";
|
||||
success = false;
|
||||
|
@ -376,6 +377,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
|
|||
properties.setQueryAACube(entity->getQueryAACube());
|
||||
}
|
||||
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 they've changed.
|
||||
|
|
|
@ -130,16 +130,13 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI
|
|||
EntityItemProperties properties = origProperties;
|
||||
|
||||
bool allowLockChange;
|
||||
bool canRezPermanentEntities;
|
||||
QUuid senderID;
|
||||
if (senderNode.isNull()) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
allowLockChange = nodeList->isAllowedEditor();
|
||||
canRezPermanentEntities = nodeList->getThisNodeCanRez();
|
||||
senderID = nodeList->getSessionUUID();
|
||||
} else {
|
||||
allowLockChange = senderNode->isAllowedEditor();
|
||||
canRezPermanentEntities = senderNode->getCanRez();
|
||||
senderID = senderNode->getUUID();
|
||||
}
|
||||
|
||||
|
@ -148,14 +145,6 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI
|
|||
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
|
||||
// property we allow you to change is the locked property.
|
||||
if (entity->getLocked()) {
|
||||
|
@ -321,26 +310,9 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI
|
|||
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 result = NULL;
|
||||
EntityItemProperties props = properties;
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
if (!nodeList) {
|
||||
|
@ -348,16 +320,8 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti
|
|||
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;
|
||||
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
|
||||
// and we must record its creation time
|
||||
recordCreationTime = true;
|
||||
|
@ -372,8 +336,8 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti
|
|||
}
|
||||
|
||||
// construct the instance of the entity
|
||||
EntityTypes::EntityType type = properties.getType();
|
||||
result = EntityTypes::constructEntityItem(type, entityID, properties);
|
||||
EntityTypes::EntityType type = props.getType();
|
||||
result = EntityTypes::constructEntityItem(type, entityID, props);
|
||||
|
||||
if (result) {
|
||||
if (recordCreationTime) {
|
||||
|
@ -890,6 +854,13 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList<Q
|
|||
QString::number((int)pos.y) + "," +
|
||||
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,
|
||||
|
@ -922,11 +893,23 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
EntityItemID entityItemID;
|
||||
EntityItemProperties properties;
|
||||
startDecode = usecTimestampNow();
|
||||
|
||||
|
||||
bool validEditPacket = EntityItemProperties::decodeEntityEditPacket(editData, maxLength, processedBytes,
|
||||
entityItemID, properties);
|
||||
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
|
||||
// an existing entity... handle appropriately
|
||||
if (validEditPacket) {
|
||||
|
@ -955,7 +938,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
|
|||
endUpdate = usecTimestampNow();
|
||||
_totalUpdates++;
|
||||
} 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
|
||||
properties.setCreated(properties.getLastEdited());
|
||||
startCreate = usecTimestampNow();
|
||||
|
|
|
@ -64,7 +64,6 @@ public:
|
|||
|
||||
|
||||
void setEntityMaxTmpLifetime(float maxTmpEntityLifetime) { _maxTmpEntityLifetime = maxTmpEntityLifetime; }
|
||||
bool permissionsAllowRez(const EntityItemProperties& properties, bool canRez, bool canRezTmp);
|
||||
|
||||
/// Implements our type specific root element factory
|
||||
virtual OctreeElementPointer createNewElement(unsigned char* octalCode = NULL) override;
|
||||
|
|
|
@ -474,6 +474,11 @@ void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken)
|
|||
persistAccountToFile();
|
||||
}
|
||||
|
||||
void AccountManager::setTemporaryDomain(const QUuid& domainID, const QString& key) {
|
||||
_accountInfo.setTemporaryDomain(domainID, key);
|
||||
persistAccountToFile();
|
||||
}
|
||||
|
||||
void AccountManager::requestAccessToken(const QString& login, const QString& password) {
|
||||
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
|
@ -650,22 +655,33 @@ void AccountManager::processGeneratedKeypair() {
|
|||
const QString DOMAIN_PUBLIC_KEY_UPDATE_PATH = "api/v1/domains/%1/public_key";
|
||||
|
||||
QString uploadPath;
|
||||
if (keypairGenerator->getDomainID().isNull()) {
|
||||
const auto& domainID = keypairGenerator->getDomainID();
|
||||
if (domainID.isNull()) {
|
||||
uploadPath = USER_PUBLIC_KEY_UPDATE_PATH;
|
||||
} 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
|
||||
QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||||
|
||||
QHttpPart keyPart;
|
||||
keyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
|
||||
keyPart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||
QVariant("form-data; name=\"public_key\"; filename=\"public_key\""));
|
||||
keyPart.setBody(keypairGenerator->getPublicKey());
|
||||
QHttpPart publicKeyPart;
|
||||
publicKeyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
|
||||
|
||||
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
|
||||
JSONCallbackParameters callbackParameters;
|
||||
|
|
|
@ -89,6 +89,9 @@ public:
|
|||
QUuid getSessionID() const { return _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:
|
||||
void requestAccessToken(const QString& login, const QString& password);
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
#endif
|
||||
|
||||
const QString DataServerAccountInfo::EMPTY_KEY = QString();
|
||||
|
||||
DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) : QObject() {
|
||||
_accessToken = otherInfo._accessToken;
|
||||
_username = otherInfo._username;
|
||||
|
@ -33,6 +35,8 @@ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherI
|
|||
_walletID = otherInfo._walletID;
|
||||
_privateKey = otherInfo._privateKey;
|
||||
_domainID = otherInfo._domainID;
|
||||
_temporaryDomainID = otherInfo._temporaryDomainID;
|
||||
_temporaryDomainApiKey = otherInfo._temporaryDomainApiKey;
|
||||
}
|
||||
|
||||
DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) {
|
||||
|
@ -51,6 +55,8 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) {
|
|||
swap(_walletID, otherInfo._walletID);
|
||||
swap(_privateKey, otherInfo._privateKey);
|
||||
swap(_domainID, otherInfo._domainID);
|
||||
swap(_temporaryDomainID, otherInfo._temporaryDomainID);
|
||||
swap(_temporaryDomainApiKey, otherInfo._temporaryDomainApiKey);
|
||||
}
|
||||
|
||||
void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) {
|
||||
|
@ -145,13 +151,14 @@ QByteArray DataServerAccountInfo::signPlaintext(const QByteArray& plaintext) {
|
|||
|
||||
QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) {
|
||||
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;
|
||||
}
|
||||
|
||||
QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ const float SATOSHIS_PER_CREDIT = 100000000.0f;
|
|||
|
||||
class DataServerAccountInfo : public QObject {
|
||||
Q_OBJECT
|
||||
const static QString EMPTY_KEY;
|
||||
public:
|
||||
DataServerAccountInfo() {};
|
||||
DataServerAccountInfo(const DataServerAccountInfo& otherInfo);
|
||||
|
@ -52,6 +53,9 @@ public:
|
|||
void setDomainID(const QUuid& domainID) { _domainID = 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;
|
||||
|
||||
void setProfileInfoFromJSON(const QJsonObject& jsonObject);
|
||||
|
@ -67,7 +71,9 @@ private:
|
|||
QString _xmppPassword;
|
||||
QString _discourseApiKey;
|
||||
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;
|
||||
|
||||
};
|
||||
|
|
|
@ -492,10 +492,9 @@ void OffscreenUi::unfocusWindows() {
|
|||
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
|
||||
auto virtualPos = mapToVirtualScreen(screenPosition, nullptr);
|
||||
QMetaObject::invokeMethod(_desktop, "toggleMenu", Q_ARG(QVariant, virtualPos));
|
||||
QMetaObject::invokeMethod(_desktop, "toggleMenu", Q_ARG(QVariant, screenPosition));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -66,14 +66,8 @@ const QString SHOW_DEBUG_RAW = "Debug Draw Raw Data";
|
|||
const QString SHOW_DEBUG_CALIBRATED = "Debug Draw Calibrated Data";
|
||||
|
||||
bool SixenseManager::isSupported() const {
|
||||
#ifdef HAVE_SIXENSE
|
||||
|
||||
#if defined(Q_OS_OSX)
|
||||
return QSysInfo::macVersion() <= QSysInfo::MV_MAVERICKS;
|
||||
#else
|
||||
#if defined(HAVE_SIXENSE) && !defined(Q_OS_OSX)
|
||||
return true;
|
||||
#endif
|
||||
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
|
@ -83,6 +77,7 @@ bool SixenseManager::activate() {
|
|||
InputPlugin::activate();
|
||||
|
||||
#ifdef HAVE_SIXENSE
|
||||
#if !defined(Q_OS_LINUX)
|
||||
_container->addMenu(MENU_PATH);
|
||||
_container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, TOGGLE_SMOOTH,
|
||||
[this] (bool clicked) { setSixenseFilter(clicked); },
|
||||
|
@ -95,6 +90,7 @@ bool SixenseManager::activate() {
|
|||
_container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, SHOW_DEBUG_CALIBRATED,
|
||||
[this] (bool clicked) { _inputDevice->setDebugDrawCalibrated(clicked); },
|
||||
true, false);
|
||||
#endif
|
||||
|
||||
auto userInputMapper = DependencyManager::get<controller::UserInputMapper>();
|
||||
userInputMapper->registerDevice(_inputDevice);
|
||||
|
@ -112,8 +108,10 @@ void SixenseManager::deactivate() {
|
|||
InputPlugin::deactivate();
|
||||
|
||||
#ifdef HAVE_SIXENSE
|
||||
#if !defined(Q_OS_LINUX)
|
||||
_container->removeMenuItem(MENU_NAME, TOGGLE_SMOOTH);
|
||||
_container->removeMenu(MENU_PATH);
|
||||
#endif
|
||||
|
||||
_inputDevice->_poseStateMap.clear();
|
||||
|
||||
|
@ -137,6 +135,7 @@ void SixenseManager::setSixenseFilter(bool filter) {
|
|||
void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
|
||||
BAIL_IF_NOT_LOADED
|
||||
|
||||
#ifdef HAVE_SIXENSE
|
||||
static bool sixenseHasBeenConnected { false };
|
||||
if (!sixenseHasBeenConnected && sixenseIsBaseConnected(0)) {
|
||||
sixenseHasBeenConnected = true;
|
||||
|
@ -152,6 +151,7 @@ void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibr
|
|||
_container->requestReset();
|
||||
_inputDevice->_requestReset = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
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);
|
||||
_sensorResetMat = glm::inverse(createMatFromQuatAndPos(glm::quat(), uiPos));
|
||||
} else {
|
||||
qDebug() << "OpenVR: error could not get chaperone pointer";
|
||||
#if DEV_BUILD
|
||||
qDebug() << "OpenVR: error could not get chaperone pointer";
|
||||
#endif
|
||||
}
|
||||
|
||||
return Parent::internalActivate();
|
||||
|
|
|
@ -64,17 +64,25 @@ vr::IVRSystem* acquireOpenVrSystem() {
|
|||
if (hmdPresent) {
|
||||
Lock lock(mutex);
|
||||
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;
|
||||
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) {
|
||||
qCDebug(displayplugins) << "OpenVR: incrementing refcount";
|
||||
#if DEV_BUILD
|
||||
qCDebug(displayplugins) << "OpenVR: incrementing refcount";
|
||||
#endif
|
||||
++refCount;
|
||||
}
|
||||
} else {
|
||||
qCDebug(displayplugins) << "OpenVR: no hmd present";
|
||||
#if DEV_BUILD
|
||||
qCDebug(displayplugins) << "OpenVR: no hmd present";
|
||||
#endif
|
||||
}
|
||||
return activeHmd;
|
||||
}
|
||||
|
@ -82,10 +90,14 @@ vr::IVRSystem* acquireOpenVrSystem() {
|
|||
void releaseOpenVrSystem() {
|
||||
if (activeHmd) {
|
||||
Lock lock(mutex);
|
||||
qCDebug(displayplugins) << "OpenVR: decrementing refcount";
|
||||
#if DEV_BUILD
|
||||
qCDebug(displayplugins) << "OpenVR: decrementing refcount";
|
||||
#endif
|
||||
--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();
|
||||
activeHmd = nullptr;
|
||||
}
|
||||
|
@ -261,7 +273,9 @@ void handleOpenVrEvents() {
|
|||
default:
|
||||
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/grab.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 PICK_WITH_HAND_RAY = true;
|
||||
|
||||
var DRAW_GRAB_BOXES = false;
|
||||
var DRAW_HAND_SPHERES = false;
|
||||
var DROP_WITHOUT_SHAKE = false;
|
||||
|
||||
//
|
||||
|
@ -67,16 +70,22 @@ var LINE_ENTITY_DIMENSIONS = {
|
|||
|
||||
var LINE_LENGTH = 500;
|
||||
var PICK_MAX_DISTANCE = 500; // max length of pick-ray
|
||||
|
||||
//
|
||||
// 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_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 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
|
||||
|
||||
//
|
||||
|
@ -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() {
|
||||
this.cache = {};
|
||||
}
|
||||
|
@ -264,34 +274,55 @@ EntityPropertiesCache.prototype.findEntities = function(position, radius) {
|
|||
var entities = Entities.findEntities(position, radius);
|
||||
var _this = this;
|
||||
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 grabbableProps = getEntityCustomData(GRABBABLE_DATA_KEY, entityID, DEFAULT_GRABBABLE_DATA);
|
||||
var grabProps = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {});
|
||||
var wearableProps = getEntityCustomData("wearable", entityID, {});
|
||||
this.cache[entityID] = { props: props, grabbableProps: grabbableProps, grabProps: grabProps, wearableProps: wearableProps };
|
||||
|
||||
// convert props.userData from a string to an object.
|
||||
var userData = {};
|
||||
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() {
|
||||
return Object.keys(this.cache);
|
||||
}
|
||||
EntityPropertiesCache.prototype.getProps = function(entityID) {
|
||||
var obj = this.cache[entityID]
|
||||
return obj ? obj.props : undefined;
|
||||
return obj ? obj : undefined;
|
||||
};
|
||||
EntityPropertiesCache.prototype.getGrabbableProps = function(entityID) {
|
||||
var obj = this.cache[entityID]
|
||||
return obj ? obj.grabbableProps : undefined;
|
||||
var props = this.cache[entityID];
|
||||
if (props) {
|
||||
return props.userData.grabbableKey ? props.userData.grabbableKey : DEFAULT_GRABBABLE_DATA;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
EntityPropertiesCache.prototype.getGrabProps = function(entityID) {
|
||||
var obj = this.cache[entityID]
|
||||
return obj ? obj.grabProps : undefined;
|
||||
var props = this.cache[entityID];
|
||||
if (props) {
|
||||
return props.userData.grabKey ? props.userData.grabKey : {};
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
EntityPropertiesCache.prototype.getWearableProps = function(entityID) {
|
||||
var obj = this.cache[entityID]
|
||||
return obj ? obj.wearableProps : undefined;
|
||||
var props = this.cache[entityID];
|
||||
if (props) {
|
||||
return props.userData.wearable ? props.userData.wearable : {};
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
function MyController(hand) {
|
||||
|
@ -322,7 +353,6 @@ function MyController(hand) {
|
|||
//for visualizations
|
||||
this.overlayLine = null;
|
||||
this.particleBeamObject = null;
|
||||
this.grabSphere = null;
|
||||
|
||||
//for lights
|
||||
this.spotlight = null;
|
||||
|
@ -383,7 +413,7 @@ function MyController(hand) {
|
|||
}
|
||||
|
||||
this.setState = function(newState, reason) {
|
||||
this.grabSphereOff();
|
||||
|
||||
if (WANT_DEBUG || WANT_DEBUG_STATE) {
|
||||
var oldStateName = stateToName(this.state);
|
||||
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) {
|
||||
if (this.overlayLine === null) {
|
||||
var lineProperties = {
|
||||
|
@ -872,43 +869,147 @@ function MyController(hand) {
|
|||
}
|
||||
};
|
||||
|
||||
this.searchEnter = function() {
|
||||
this.equipHotspotOverlays = [];
|
||||
this.createHotspots = function () {
|
||||
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.
|
||||
var entities = Entities.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE);
|
||||
var i, length = entities.length;
|
||||
for (i = 0; i < length; i++) {
|
||||
var grabProps = Entities.getEntityProperties(entities[i], GRABBABLE_PROPERTIES);
|
||||
// does this entity have an attach point?
|
||||
var wearableData = getEntityCustomData("wearable", entities[i], undefined);
|
||||
if (wearableData && wearableData.joints) {
|
||||
var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand";
|
||||
if (wearableData.joints[handJointName]) {
|
||||
// draw the hotspot
|
||||
this.equipHotspotOverlays.push(Overlays.addOverlay("sphere", {
|
||||
position: grabProps.position,
|
||||
size: 0.2,
|
||||
color: { red: 90, green: 255, blue: 90 },
|
||||
alpha: 0.7,
|
||||
solid: true,
|
||||
visible: true,
|
||||
ignoreRayIntersection: false,
|
||||
drawInFront: false
|
||||
}));
|
||||
}
|
||||
this.entityPropertyCache.clear();
|
||||
this.entityPropertyCache.findEntities(MyAvatar.position, HOTSPOT_DRAW_DISTANCE);
|
||||
|
||||
var _this = this;
|
||||
this.entityPropertyCache.getEntities().forEach(function (entityID) {
|
||||
if (_this.entityIsEquippableWithoutDistanceCheck(entityID)) {
|
||||
props = _this.entityPropertyCache.getProps(entityID);
|
||||
|
||||
overlay = Overlays.addOverlay("sphere", {
|
||||
rotation: props.rotation,
|
||||
position: props.position,
|
||||
size: EQUIP_RADIUS * 2,
|
||||
color: EQUIP_SPHERE_COLOR,
|
||||
alpha: EQUIP_SPHERE_ALPHA,
|
||||
solid: true,
|
||||
visible: true,
|
||||
ignoreRayIntersection: true,
|
||||
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() {
|
||||
|
||||
// delete all equip hotspots
|
||||
var i, l = this.equipHotspotOverlays.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
Overlays.deleteOverlay(this.equipHotspotOverlays[i]);
|
||||
}
|
||||
this.equipHotspotOverlays = [];
|
||||
this.destroyHotspots();
|
||||
};
|
||||
|
||||
///
|
||||
|
@ -975,7 +1076,13 @@ function MyController(hand) {
|
|||
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 distance = Vec3.distance(props.position, handPosition);
|
||||
var grabProps = this.entityPropertyCache.getGrabProps(entityID);
|
||||
|
@ -989,9 +1096,9 @@ function MyController(hand) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (distance > NEAR_PICK_MAX_DISTANCE) {
|
||||
if (distance > EQUIP_RADIUS) {
|
||||
if (debug) {
|
||||
print("equip is skipping '" + props.name + "': too far away.");
|
||||
print("equip is skipping '" + props.name + "': too far away, " + distance + " meters");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -1104,7 +1211,7 @@ function MyController(hand) {
|
|||
return true;
|
||||
};
|
||||
|
||||
this.entityIsNearGrabbable = function(entityID, handPosition) {
|
||||
this.entityIsNearGrabbable = function(entityID, handPosition, maxDistance) {
|
||||
|
||||
if (!this.entityIsGrabbable(entityID)) {
|
||||
return false;
|
||||
|
@ -1114,7 +1221,7 @@ function MyController(hand) {
|
|||
var distance = Vec3.distance(props.position, handPosition);
|
||||
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
|
||||
if (debug) {
|
||||
print(" grab is skipping '" + props.name + "': too far away.");
|
||||
|
@ -1129,6 +1236,8 @@ function MyController(hand) {
|
|||
var _this = this;
|
||||
var name;
|
||||
|
||||
this.updateHotspots();
|
||||
|
||||
this.grabbedEntity = null;
|
||||
this.isInitialGrab = false;
|
||||
this.shouldResetParentOnRelease = false;
|
||||
|
@ -1142,16 +1251,12 @@ function MyController(hand) {
|
|||
|
||||
var handPosition = this.getHandPosition();
|
||||
|
||||
if (SHOW_GRAB_SPHERE) {
|
||||
this.grabSphereOn();
|
||||
}
|
||||
|
||||
this.entityPropertyCache.clear();
|
||||
this.entityPropertyCache.findEntities(handPosition, GRAB_RADIUS);
|
||||
this.entityPropertyCache.findEntities(handPosition, NEAR_GRAB_RADIUS);
|
||||
var candidateEntities = this.entityPropertyCache.getEntities();
|
||||
|
||||
var equippableEntities = candidateEntities.filter(function (entity) {
|
||||
return _this.entityIsEquippable(entity, handPosition);
|
||||
return _this.entityIsEquippableWithDistanceCheck(entity, handPosition);
|
||||
});
|
||||
|
||||
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) {
|
||||
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) {
|
||||
// sort by distance
|
||||
grabbableEntities.sort(function (a, b) {
|
||||
|
@ -1723,11 +1832,11 @@ function MyController(hand) {
|
|||
this.lastUnequipCheckTime = now;
|
||||
|
||||
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();
|
||||
// the center of the equipped object being far from the hand isn't enough to autoequip -- we also
|
||||
// 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) {
|
||||
// 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." +
|
||||
|
@ -2173,17 +2282,32 @@ function cleanup() {
|
|||
Script.scriptEnding.connect(cleanup);
|
||||
Script.update.connect(update);
|
||||
|
||||
if (!Menu.menuExists("Developer > Grab Script")) {
|
||||
Menu.addMenu("Developer > Grab Script");
|
||||
}
|
||||
|
||||
Menu.addMenuItem({
|
||||
menuName: "Developer > Hands",
|
||||
menuName: "Developer > Grab Script",
|
||||
menuItemName: "Drop Without Shake",
|
||||
isCheckable: true,
|
||||
isChecked: DROP_WITHOUT_SHAKE
|
||||
});
|
||||
|
||||
Menu.addMenuItem({
|
||||
menuName: "Developer > Grab Script",
|
||||
menuItemName: "Draw Grab Boxes",
|
||||
isCheckable: true,
|
||||
isChecked: DRAW_GRAB_BOXES
|
||||
});
|
||||
|
||||
function handleMenuItemEvent(menuItem) {
|
||||
if (menuItem === "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);
|
||||
|
|
|
@ -208,9 +208,9 @@ function isShakingMouse() { // True if the person is waving the mouse around try
|
|||
return isShaking;
|
||||
}
|
||||
var NON_LINEAR_DIVISOR = 2;
|
||||
var MINIMUM_SEEK_DISTANCE = 0.01;
|
||||
function updateSeeking() {
|
||||
if (!Reticle.visible || isShakingMouse()) {
|
||||
var MINIMUM_SEEK_DISTANCE = 0.1;
|
||||
function updateSeeking(doNotStartSeeking) {
|
||||
if (!doNotStartSeeking && (!Reticle.visible || isShakingMouse())) {
|
||||
if (!isSeeking) {
|
||||
print('Start seeking mouse.');
|
||||
isSeeking = true;
|
||||
|
@ -224,8 +224,8 @@ function updateSeeking() {
|
|||
if (!lookAt2D) { // If this happens, something has gone terribly wrong.
|
||||
print('Cannot seek without lookAt position');
|
||||
isSeeking = false;
|
||||
return;
|
||||
} // E.g., if parallel to location in HUD
|
||||
return; // E.g., if parallel to location in HUD
|
||||
}
|
||||
var copy = Reticle.position;
|
||||
function updateDimension(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(Controller.Standard.RightSecondaryThumb).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.
|
||||
clickMapping.from(rightTrigger.partial).to(makeToggleAction(Controller.Standard.RightHand));
|
||||
clickMapping.from(leftTrigger.partial).to(makeToggleAction(Controller.Standard.LeftHand));
|
||||
|
@ -386,6 +396,7 @@ function update() {
|
|||
expireMouseCursor();
|
||||
clearSystemLaser();
|
||||
}
|
||||
updateSeeking(true);
|
||||
if (!handControllerLockOut.expired(now)) {
|
||||
return off(); // Let them use mouse it in peace.
|
||||
}
|
||||
|
|
|
@ -334,7 +334,7 @@ var toolBar = (function() {
|
|||
|
||||
that.setActive = function(active) {
|
||||
if (active != isActive) {
|
||||
if (active && !Entities.canAdjustLocks()) {
|
||||
if (active && !Entities.canRez() && !Entities.canRezTmp()) {
|
||||
Window.alert(INSUFFICIENT_PERMISSIONS_ERROR_MSG);
|
||||
} else {
|
||||
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