mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-04 04:24:47 +02:00
Merge branch 'vive-ui' into bug-fix/grab-visualization-improvements
This commit is contained in:
commit
41fcfb45ca
42 changed files with 717 additions and 495 deletions
25
cmake/externals/sixense/CMakeLists.txt
vendored
25
cmake/externals/sixense/CMakeLists.txt
vendored
|
@ -57,30 +57,7 @@ if (WIN32)
|
||||||
|
|
||||||
elseif(APPLE)
|
elseif(APPLE)
|
||||||
|
|
||||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/lib/osx_x64/release_dll/libsixense_x64.dylib CACHE TYPE INTERNAL)
|
# We no longer support Sixense on Macs due to bugs in the Sixense DLL
|
||||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${SOURCE_DIR}/lib/osx_x64/debug_dll/libsixensed_x64.dylib CACHE TYPE INTERNAL)
|
|
||||||
|
|
||||||
set(_SIXENSE_LIB_DIR "${SOURCE_DIR}/lib/osx_x64")
|
|
||||||
ExternalProject_Add_Step(
|
|
||||||
${EXTERNAL_NAME}
|
|
||||||
change-install-name-release
|
|
||||||
COMMENT "Calling install_name_tool on libraries to fix install name for dylib linking"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${_SIXENSE_LIB_DIR}/release_dll -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake
|
|
||||||
DEPENDEES install
|
|
||||||
WORKING_DIRECTORY <SOURCE_DIR>
|
|
||||||
LOG 1
|
|
||||||
)
|
|
||||||
|
|
||||||
set(_SIXENSE_LIB_DIR "${SOURCE_DIR}/lib/osx_x64")
|
|
||||||
ExternalProject_Add_Step(
|
|
||||||
${EXTERNAL_NAME}
|
|
||||||
change-install-name-debug
|
|
||||||
COMMENT "Calling install_name_tool on libraries to fix install name for dylib linking"
|
|
||||||
COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${_SIXENSE_LIB_DIR}/debug_dll -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake
|
|
||||||
DEPENDEES install
|
|
||||||
WORKING_DIRECTORY <SOURCE_DIR>
|
|
||||||
LOG 1
|
|
||||||
)
|
|
||||||
|
|
||||||
elseif(NOT ANDROID)
|
elseif(NOT ANDROID)
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,11 @@
|
||||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
#
|
#
|
||||||
macro(TARGET_SIXENSE)
|
macro(TARGET_SIXENSE)
|
||||||
add_dependency_external_projects(sixense)
|
if(NOT APPLE)
|
||||||
find_package(Sixense REQUIRED)
|
add_dependency_external_projects(sixense)
|
||||||
target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS})
|
find_package(Sixense REQUIRED)
|
||||||
target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES})
|
target_include_directories(${TARGET_NAME} PRIVATE ${SIXENSE_INCLUDE_DIRS})
|
||||||
add_definitions(-DHAVE_SIXENSE)
|
target_link_libraries(${TARGET_NAME} ${SIXENSE_LIBRARIES})
|
||||||
|
add_definitions(-DHAVE_SIXENSE)
|
||||||
|
endif()
|
||||||
endmacro()
|
endmacro()
|
||||||
|
|
|
@ -457,6 +457,8 @@ function disonnectHighFidelityAccount() {
|
||||||
}, function(){
|
}, function(){
|
||||||
// we need to post to settings to clear the access-token
|
// we need to post to settings to clear the access-token
|
||||||
$(Settings.ACCESS_TOKEN_SELECTOR).val('').change();
|
$(Settings.ACCESS_TOKEN_SELECTOR).val('').change();
|
||||||
|
// reset the domain id to get a new temporary name
|
||||||
|
$(Settings.DOMAIN_ID_SELECTOR).val('').change();
|
||||||
saveSettings();
|
saveSettings();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -555,7 +557,7 @@ function createNewDomainID(description, justConnected) {
|
||||||
// get the JSON object ready that we'll use to create a new domain
|
// get the JSON object ready that we'll use to create a new domain
|
||||||
var domainJSON = {
|
var domainJSON = {
|
||||||
"domain": {
|
"domain": {
|
||||||
"description": description
|
"private_description": description
|
||||||
},
|
},
|
||||||
"access_token": $(Settings.ACCESS_TOKEN_SELECTOR).val()
|
"access_token": $(Settings.ACCESS_TOKEN_SELECTOR).val()
|
||||||
}
|
}
|
||||||
|
@ -748,8 +750,8 @@ function chooseFromHighFidelityDomains(clickedButton) {
|
||||||
_.each(data.data.domains, function(domain){
|
_.each(data.data.domains, function(domain){
|
||||||
var domainString = "";
|
var domainString = "";
|
||||||
|
|
||||||
if (domain.description) {
|
if (domain.private_description) {
|
||||||
domainString += '"' + domain.description + '" - ';
|
domainString += '"' + domain.private_description + '" - ';
|
||||||
}
|
}
|
||||||
|
|
||||||
domainString += domain.id;
|
domainString += domain.id;
|
||||||
|
|
|
@ -76,6 +76,8 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
||||||
setApplicationVersion(BuildInfo::VERSION);
|
setApplicationVersion(BuildInfo::VERSION);
|
||||||
QSettings::setDefaultFormat(QSettings::IniFormat);
|
QSettings::setDefaultFormat(QSettings::IniFormat);
|
||||||
|
|
||||||
|
qDebug() << "Setting up domain-server";
|
||||||
|
|
||||||
// make sure we have a fresh AccountManager instance
|
// make sure we have a fresh AccountManager instance
|
||||||
// (need this since domain-server can restart itself and maintain static variables)
|
// (need this since domain-server can restart itself and maintain static variables)
|
||||||
DependencyManager::set<AccountManager>();
|
DependencyManager::set<AccountManager>();
|
||||||
|
@ -104,23 +106,31 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
||||||
connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions,
|
connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions,
|
||||||
&_gatekeeper, &DomainGatekeeper::updateNodePermissions);
|
&_gatekeeper, &DomainGatekeeper::updateNodePermissions);
|
||||||
|
|
||||||
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) {
|
// if we were given a certificate/private key or oauth credentials they must succeed
|
||||||
// we either read a certificate and private key or were not passed one
|
if (!(optionallyReadX509KeyAndCertificate() && optionallySetupOAuth())) {
|
||||||
// and completed login or did not need to
|
return;
|
||||||
|
|
||||||
qDebug() << "Setting up LimitedNodeList and assignments.";
|
|
||||||
setupNodeListAndAssignments();
|
|
||||||
|
|
||||||
// setup automatic networking settings with data server
|
|
||||||
setupAutomaticNetworking();
|
|
||||||
|
|
||||||
// preload some user public keys so they can connect on first request
|
|
||||||
_gatekeeper.preloadAllowedUserPublicKeys();
|
|
||||||
|
|
||||||
optionallyGetTemporaryName(args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupNodeListAndAssignments();
|
||||||
|
setupAutomaticNetworking();
|
||||||
|
if (!getID().isNull()) {
|
||||||
|
setupHeartbeatToMetaverse();
|
||||||
|
// send the first heartbeat immediately
|
||||||
|
sendHeartbeatToMetaverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for the temporary name parameter
|
||||||
|
const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name";
|
||||||
|
if (args.contains(GET_TEMPORARY_NAME_SWITCH)) {
|
||||||
|
getTemporaryName();
|
||||||
|
}
|
||||||
|
|
||||||
|
_gatekeeper.preloadAllowedUserPublicKeys(); // so they can connect on first request
|
||||||
|
|
||||||
_metadata = new DomainMetadata(this);
|
_metadata = new DomainMetadata(this);
|
||||||
|
|
||||||
|
|
||||||
|
qDebug() << "domain-server is running";
|
||||||
}
|
}
|
||||||
|
|
||||||
DomainServer::~DomainServer() {
|
DomainServer::~DomainServer() {
|
||||||
|
@ -148,6 +158,10 @@ void DomainServer::restart() {
|
||||||
exit(DomainServer::EXIT_CODE_REBOOT);
|
exit(DomainServer::EXIT_CODE_REBOOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QUuid& DomainServer::getID() {
|
||||||
|
return DependencyManager::get<LimitedNodeList>()->getSessionUUID();
|
||||||
|
}
|
||||||
|
|
||||||
bool DomainServer::optionallyReadX509KeyAndCertificate() {
|
bool DomainServer::optionallyReadX509KeyAndCertificate() {
|
||||||
const QString X509_CERTIFICATE_OPTION = "cert";
|
const QString X509_CERTIFICATE_OPTION = "cert";
|
||||||
const QString X509_PRIVATE_KEY_OPTION = "key";
|
const QString X509_PRIVATE_KEY_OPTION = "key";
|
||||||
|
@ -233,34 +247,26 @@ bool DomainServer::optionallySetupOAuth() {
|
||||||
|
|
||||||
static const QString METAVERSE_DOMAIN_ID_KEY_PATH = "metaverse.id";
|
static const QString METAVERSE_DOMAIN_ID_KEY_PATH = "metaverse.id";
|
||||||
|
|
||||||
void DomainServer::optionallyGetTemporaryName(const QStringList& arguments) {
|
void DomainServer::getTemporaryName(bool force) {
|
||||||
// check for the temporary name parameter
|
// check if we already have a domain ID
|
||||||
const QString GET_TEMPORARY_NAME_SWITCH = "--get-temp-name";
|
const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH);
|
||||||
|
|
||||||
if (arguments.contains(GET_TEMPORARY_NAME_SWITCH)) {
|
qInfo() << "Requesting temporary domain name";
|
||||||
|
if (idValueVariant) {
|
||||||
// make sure we don't already have a domain ID
|
qDebug() << "A domain ID is already present in domain-server settings:" << idValueVariant->toString();
|
||||||
const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH);
|
if (force) {
|
||||||
if (idValueVariant) {
|
qDebug() << "Requesting temporary domain name to replace current ID:" << getID();
|
||||||
qWarning() << "Temporary domain name requested but a domain ID is already present in domain-server settings."
|
} else {
|
||||||
<< "Will not request temporary name.";
|
qInfo() << "Abandoning request of temporary domain name.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we've been asked to grab a temporary name from the API
|
|
||||||
// so fire off that request now
|
|
||||||
auto accountManager = DependencyManager::get<AccountManager>();
|
|
||||||
|
|
||||||
// get callbacks for temporary domain result
|
|
||||||
JSONCallbackParameters callbackParameters;
|
|
||||||
callbackParameters.jsonCallbackReceiver = this;
|
|
||||||
callbackParameters.jsonCallbackMethod = "handleTempDomainSuccess";
|
|
||||||
callbackParameters.errorCallbackReceiver = this;
|
|
||||||
callbackParameters.errorCallbackMethod = "handleTempDomainError";
|
|
||||||
|
|
||||||
accountManager->sendRequest("/api/v1/domains/temporary", AccountManagerAuth::None,
|
|
||||||
QNetworkAccessManager::PostOperation, callbackParameters);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// request a temporary name from the metaverse
|
||||||
|
auto accountManager = DependencyManager::get<AccountManager>();
|
||||||
|
JSONCallbackParameters callbackParameters { this, "handleTempDomainSuccess", this, "handleTempDomainError" };
|
||||||
|
accountManager->sendRequest("/api/v1/domains/temporary", AccountManagerAuth::None,
|
||||||
|
QNetworkAccessManager::PostOperation, callbackParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
|
void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
|
||||||
|
@ -271,11 +277,13 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
|
||||||
static const QString DOMAIN_KEY = "domain";
|
static const QString DOMAIN_KEY = "domain";
|
||||||
static const QString ID_KEY = "id";
|
static const QString ID_KEY = "id";
|
||||||
static const QString NAME_KEY = "name";
|
static const QString NAME_KEY = "name";
|
||||||
|
static const QString KEY_KEY = "api_key";
|
||||||
|
|
||||||
auto domainObject = jsonObject[DATA_KEY].toObject()[DOMAIN_KEY].toObject();
|
auto domainObject = jsonObject[DATA_KEY].toObject()[DOMAIN_KEY].toObject();
|
||||||
if (!domainObject.isEmpty()) {
|
if (!domainObject.isEmpty()) {
|
||||||
auto id = domainObject[ID_KEY].toString();
|
auto id = domainObject[ID_KEY].toString();
|
||||||
auto name = domainObject[NAME_KEY].toString();
|
auto name = domainObject[NAME_KEY].toString();
|
||||||
|
auto key = domainObject[KEY_KEY].toString();
|
||||||
|
|
||||||
qInfo() << "Received new temporary domain name" << name;
|
qInfo() << "Received new temporary domain name" << name;
|
||||||
qDebug() << "The temporary domain ID is" << id;
|
qDebug() << "The temporary domain ID is" << id;
|
||||||
|
@ -291,9 +299,13 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
|
||||||
// change our domain ID immediately
|
// change our domain ID immediately
|
||||||
DependencyManager::get<LimitedNodeList>()->setSessionUUID(QUuid { id });
|
DependencyManager::get<LimitedNodeList>()->setSessionUUID(QUuid { id });
|
||||||
|
|
||||||
// change our automatic networking settings so that we're communicating with the ICE server
|
// store the new token to the account info
|
||||||
setupICEHeartbeatForFullNetworking();
|
auto accountManager = DependencyManager::get<AccountManager>();
|
||||||
|
accountManager->setTemporaryDomain(id, key);
|
||||||
|
|
||||||
|
// update our heartbeats to use the correct id
|
||||||
|
setupICEHeartbeatForFullNetworking();
|
||||||
|
setupHeartbeatToMetaverse();
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "There were problems parsing the API response containing a temporary domain name. Please try again"
|
qWarning() << "There were problems parsing the API response containing a temporary domain name. Please try again"
|
||||||
<< "via domain-server relaunch or from the domain-server settings.";
|
<< "via domain-server relaunch or from the domain-server settings.";
|
||||||
|
@ -332,8 +344,7 @@ bool DomainServer::packetVersionMatch(const udt::Packet& packet) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
void DomainServer::setupNodeListAndAssignments() {
|
||||||
|
|
||||||
const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port";
|
const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port";
|
||||||
|
|
||||||
QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION);
|
QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION);
|
||||||
|
@ -458,29 +469,23 @@ bool DomainServer::resetAccountManagerAccessToken() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServer::setupAutomaticNetworking() {
|
void DomainServer::setupAutomaticNetworking() {
|
||||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting;
|
||||||
|
|
||||||
|
resetAccountManagerAccessToken();
|
||||||
|
|
||||||
_automaticNetworkingSetting =
|
_automaticNetworkingSetting =
|
||||||
_settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString();
|
_settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString();
|
||||||
|
|
||||||
|
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
const QUuid& domainID = getID();
|
||||||
|
|
||||||
if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||||
setupICEHeartbeatForFullNetworking();
|
setupICEHeartbeatForFullNetworking();
|
||||||
}
|
}
|
||||||
|
|
||||||
_hasAccessToken = resetAccountManagerAccessToken();
|
|
||||||
|
|
||||||
if (!_hasAccessToken) {
|
|
||||||
qDebug() << "Will not send heartbeat to Metaverse API without an access token.";
|
|
||||||
qDebug() << "If this is not a temporary domain add an access token to your config file or via the web interface.";
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
|
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
|
||||||
_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||||
|
|
||||||
const QUuid& domainID = nodeList->getSessionUUID();
|
|
||||||
|
|
||||||
if (!domainID.isNull()) {
|
if (!domainID.isNull()) {
|
||||||
qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
|
qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
|
||||||
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
|
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
|
||||||
|
@ -492,9 +497,6 @@ void DomainServer::setupAutomaticNetworking() {
|
||||||
|
|
||||||
// have the LNL enable public socket updating via STUN
|
// have the LNL enable public socket updating via STUN
|
||||||
nodeList->startSTUNPublicSocketUpdate();
|
nodeList->startSTUNPublicSocketUpdate();
|
||||||
} else {
|
|
||||||
// send our heartbeat to data server so it knows what our network settings are
|
|
||||||
sendHeartbeatToMetaverse();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
|
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
|
||||||
|
@ -502,18 +504,20 @@ void DomainServer::setupAutomaticNetworking() {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
sendHeartbeatToMetaverse();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting;
|
void DomainServer::setupHeartbeatToMetaverse() {
|
||||||
|
// heartbeat to the data-server every 15s
|
||||||
// no matter the auto networking settings we should heartbeat to the data-server every 15s
|
|
||||||
const int DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS = 15 * 1000;
|
const int DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS = 15 * 1000;
|
||||||
|
|
||||||
QTimer* dataHeartbeatTimer = new QTimer(this);
|
if (!_metaverseHeartbeatTimer) {
|
||||||
connect(dataHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToMetaverse()));
|
// setup a timer to heartbeat with the metaverse-server
|
||||||
dataHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS);
|
_metaverseHeartbeatTimer = new QTimer { this };
|
||||||
|
connect(_metaverseHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToMetaverse()));
|
||||||
|
// do not send a heartbeat immediately - this avoids flooding if the heartbeat fails with a 401
|
||||||
|
_metaverseHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServer::setupICEHeartbeatForFullNetworking() {
|
void DomainServer::setupICEHeartbeatForFullNetworking() {
|
||||||
|
@ -532,22 +536,21 @@ void DomainServer::setupICEHeartbeatForFullNetworking() {
|
||||||
limitedNodeList->startSTUNPublicSocketUpdate();
|
limitedNodeList->startSTUNPublicSocketUpdate();
|
||||||
|
|
||||||
// to send ICE heartbeats we'd better have a private key locally with an uploaded public key
|
// to send ICE heartbeats we'd better have a private key locally with an uploaded public key
|
||||||
auto accountManager = DependencyManager::get<AccountManager>();
|
|
||||||
auto domainID = accountManager->getAccountInfo().getDomainID();
|
|
||||||
|
|
||||||
// if we have an access token and we don't have a private key or the current domain ID has changed
|
// if we have an access token and we don't have a private key or the current domain ID has changed
|
||||||
// we should generate a new keypair
|
// we should generate a new keypair
|
||||||
if (!accountManager->getAccountInfo().hasPrivateKey() || domainID != limitedNodeList->getSessionUUID()) {
|
auto accountManager = DependencyManager::get<AccountManager>();
|
||||||
accountManager->generateNewDomainKeypair(limitedNodeList->getSessionUUID());
|
if (!accountManager->getAccountInfo().hasPrivateKey() || accountManager->getAccountInfo().getDomainID() != getID()) {
|
||||||
|
accountManager->generateNewDomainKeypair(getID());
|
||||||
}
|
}
|
||||||
|
|
||||||
// hookup to the signal from account manager that tells us when keypair is available
|
// hookup to the signal from account manager that tells us when keypair is available
|
||||||
connect(accountManager.data(), &AccountManager::newKeypair, this, &DomainServer::handleKeypairChange);
|
connect(accountManager.data(), &AccountManager::newKeypair, this, &DomainServer::handleKeypairChange);
|
||||||
|
|
||||||
if (!_iceHeartbeatTimer) {
|
if (!_iceHeartbeatTimer) {
|
||||||
// setup a timer to heartbeat with the ice-server every so often
|
// setup a timer to heartbeat with the ice-server
|
||||||
_iceHeartbeatTimer = new QTimer { this };
|
_iceHeartbeatTimer = new QTimer { this };
|
||||||
connect(_iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::sendHeartbeatToIceServer);
|
connect(_iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::sendHeartbeatToIceServer);
|
||||||
|
sendHeartbeatToIceServer();
|
||||||
_iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS);
|
_iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1075,12 +1078,14 @@ void DomainServer::performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
||||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
|
||||||
const QUuid& domainID = nodeList->getSessionUUID();
|
|
||||||
|
|
||||||
// Setup the domain object to send to the data server
|
// Setup the domain object to send to the data server
|
||||||
QJsonObject domainObject;
|
QJsonObject domainObject;
|
||||||
|
|
||||||
|
// add the version
|
||||||
|
static const QString VERSION_KEY = "version";
|
||||||
|
domainObject[VERSION_KEY] = BuildInfo::VERSION;
|
||||||
|
|
||||||
|
// add networking
|
||||||
if (!networkAddress.isEmpty()) {
|
if (!networkAddress.isEmpty()) {
|
||||||
static const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address";
|
static const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address";
|
||||||
domainObject[PUBLIC_NETWORK_ADDRESS_KEY] = networkAddress;
|
domainObject[PUBLIC_NETWORK_ADDRESS_KEY] = networkAddress;
|
||||||
|
@ -1089,13 +1094,20 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
||||||
static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";
|
static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";
|
||||||
domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting;
|
domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting;
|
||||||
|
|
||||||
// add a flag to indicate if this domain uses restricted access - for now that will exclude it from listings
|
|
||||||
const QString RESTRICTED_ACCESS_FLAG = "restricted";
|
|
||||||
|
|
||||||
// consider the domain to have restricted access if "anonymous" connections can't connect to the domain.
|
// add access level for anonymous connections
|
||||||
|
// consider the domain to be "restricted" if anonymous connections are disallowed
|
||||||
|
static const QString RESTRICTED_ACCESS_FLAG = "restricted";
|
||||||
NodePermissions anonymousPermissions = _settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous);
|
NodePermissions anonymousPermissions = _settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||||
domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.canConnectToDomain;
|
domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.canConnectToDomain;
|
||||||
|
|
||||||
|
const auto& temporaryDomainKey = DependencyManager::get<AccountManager>()->getTemporaryDomainKey(getID());
|
||||||
|
if (!temporaryDomainKey.isEmpty()) {
|
||||||
|
// add the temporary domain token
|
||||||
|
const QString KEY_KEY = "api_key";
|
||||||
|
domainObject[KEY_KEY] = temporaryDomainKey;
|
||||||
|
}
|
||||||
|
|
||||||
if (_metadata) {
|
if (_metadata) {
|
||||||
// Add the metadata to the heartbeat
|
// Add the metadata to the heartbeat
|
||||||
static const QString DOMAIN_HEARTBEAT_KEY = "heartbeat";
|
static const QString DOMAIN_HEARTBEAT_KEY = "heartbeat";
|
||||||
|
@ -1105,18 +1117,60 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
||||||
QString domainUpdateJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(domainObject).toJson(QJsonDocument::Compact)));
|
QString domainUpdateJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(domainObject).toJson(QJsonDocument::Compact)));
|
||||||
|
|
||||||
static const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
|
static const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
|
||||||
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)),
|
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(getID())),
|
||||||
AccountManagerAuth::Required,
|
AccountManagerAuth::Optional,
|
||||||
QNetworkAccessManager::PutOperation,
|
QNetworkAccessManager::PutOperation,
|
||||||
JSONCallbackParameters(),
|
JSONCallbackParameters(nullptr, QString(), this, "handleMetaverseHeartbeatError"),
|
||||||
domainUpdateJSON.toUtf8());
|
domainUpdateJSON.toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DomainServer::handleMetaverseHeartbeatError(QNetworkReply& requestReply) {
|
||||||
|
if (!_metaverseHeartbeatTimer) {
|
||||||
|
// avoid rehandling errors from the same issue
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we need to force a new temporary domain name
|
||||||
|
switch (requestReply.error()) {
|
||||||
|
// if we have a temporary domain with a bad token, we get a 401
|
||||||
|
case QNetworkReply::NetworkError::AuthenticationRequiredError: {
|
||||||
|
static const QString DATA_KEY = "data";
|
||||||
|
static const QString TOKEN_KEY = "api_key";
|
||||||
|
|
||||||
|
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||||
|
auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY];
|
||||||
|
|
||||||
|
if (!tokenFailure.isNull()) {
|
||||||
|
qWarning() << "Temporary domain name lacks a valid API key, and is being reset.";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// if the domain does not (or no longer) exists, we get a 404
|
||||||
|
case QNetworkReply::NetworkError::ContentNotFoundError:
|
||||||
|
qWarning() << "Domain not found, getting a new temporary domain.";
|
||||||
|
break;
|
||||||
|
// otherwise, we erred on something else, and should not force a temporary domain
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// halt heartbeats until we have a token
|
||||||
|
_metaverseHeartbeatTimer->deleteLater();
|
||||||
|
_metaverseHeartbeatTimer = nullptr;
|
||||||
|
|
||||||
|
// give up eventually to avoid flooding traffic
|
||||||
|
static const int MAX_ATTEMPTS = 5;
|
||||||
|
static int attempt = 0;
|
||||||
|
if (++attempt < MAX_ATTEMPTS) {
|
||||||
|
// get a new temporary name and token
|
||||||
|
getTemporaryName(true);
|
||||||
|
} else {
|
||||||
|
qWarning() << "Already attempted too many temporary domain requests. Please set a domain ID manually or restart.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void DomainServer::sendICEServerAddressToMetaverseAPI() {
|
void DomainServer::sendICEServerAddressToMetaverseAPI() {
|
||||||
if (!_iceServerSocket.isNull()) {
|
if (!_iceServerSocket.isNull()) {
|
||||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
|
||||||
const QUuid& domainID = nodeList->getSessionUUID();
|
|
||||||
|
|
||||||
const QString ICE_SERVER_ADDRESS = "ice_server_address";
|
const QString ICE_SERVER_ADDRESS = "ice_server_address";
|
||||||
|
|
||||||
QJsonObject domainObject;
|
QJsonObject domainObject;
|
||||||
|
@ -1124,6 +1178,13 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() {
|
||||||
// we're using full automatic networking and we have a current ice-server socket, use that now
|
// we're using full automatic networking and we have a current ice-server socket, use that now
|
||||||
domainObject[ICE_SERVER_ADDRESS] = _iceServerSocket.getAddress().toString();
|
domainObject[ICE_SERVER_ADDRESS] = _iceServerSocket.getAddress().toString();
|
||||||
|
|
||||||
|
const auto& temporaryDomainKey = DependencyManager::get<AccountManager>()->getTemporaryDomainKey(getID());
|
||||||
|
if (!temporaryDomainKey.isEmpty()) {
|
||||||
|
// add the temporary domain token
|
||||||
|
const QString KEY_KEY = "api_key";
|
||||||
|
domainObject[KEY_KEY] = temporaryDomainKey;
|
||||||
|
}
|
||||||
|
|
||||||
QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson()));
|
QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson()));
|
||||||
|
|
||||||
// make sure we hear about failure so we can retry
|
// make sure we hear about failure so we can retry
|
||||||
|
@ -1135,7 +1196,7 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() {
|
||||||
|
|
||||||
static const QString DOMAIN_ICE_ADDRESS_UPDATE = "/api/v1/domains/%1/ice_server_address";
|
static const QString DOMAIN_ICE_ADDRESS_UPDATE = "/api/v1/domains/%1/ice_server_address";
|
||||||
|
|
||||||
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_ICE_ADDRESS_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)),
|
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_ICE_ADDRESS_UPDATE.arg(uuidStringWithoutCurlyBraces(getID())),
|
||||||
AccountManagerAuth::Optional,
|
AccountManagerAuth::Optional,
|
||||||
QNetworkAccessManager::PutOperation,
|
QNetworkAccessManager::PutOperation,
|
||||||
callbackParameters,
|
callbackParameters,
|
||||||
|
|
|
@ -80,6 +80,8 @@ private slots:
|
||||||
void handleTempDomainSuccess(QNetworkReply& requestReply);
|
void handleTempDomainSuccess(QNetworkReply& requestReply);
|
||||||
void handleTempDomainError(QNetworkReply& requestReply);
|
void handleTempDomainError(QNetworkReply& requestReply);
|
||||||
|
|
||||||
|
void handleMetaverseHeartbeatError(QNetworkReply& requestReply);
|
||||||
|
|
||||||
void queuedQuit(QString quitMessage, int exitCode);
|
void queuedQuit(QString quitMessage, int exitCode);
|
||||||
|
|
||||||
void handleKeypairChange();
|
void handleKeypairChange();
|
||||||
|
@ -96,11 +98,13 @@ signals:
|
||||||
void userDisconnected();
|
void userDisconnected();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
const QUuid& getID();
|
||||||
|
|
||||||
|
void setupNodeListAndAssignments();
|
||||||
bool optionallySetupOAuth();
|
bool optionallySetupOAuth();
|
||||||
bool optionallyReadX509KeyAndCertificate();
|
bool optionallyReadX509KeyAndCertificate();
|
||||||
|
|
||||||
void optionallyGetTemporaryName(const QStringList& arguments);
|
void getTemporaryName(bool force = false);
|
||||||
|
|
||||||
static bool packetVersionMatch(const udt::Packet& packet);
|
static bool packetVersionMatch(const udt::Packet& packet);
|
||||||
|
|
||||||
|
@ -108,6 +112,7 @@ private:
|
||||||
|
|
||||||
void setupAutomaticNetworking();
|
void setupAutomaticNetworking();
|
||||||
void setupICEHeartbeatForFullNetworking();
|
void setupICEHeartbeatForFullNetworking();
|
||||||
|
void setupHeartbeatToMetaverse();
|
||||||
void sendHeartbeatToMetaverse(const QString& networkAddress);
|
void sendHeartbeatToMetaverse(const QString& networkAddress);
|
||||||
|
|
||||||
void randomizeICEServerAddress(bool shouldTriggerHostLookup);
|
void randomizeICEServerAddress(bool shouldTriggerHostLookup);
|
||||||
|
@ -178,6 +183,7 @@ private:
|
||||||
// These will be parented to this, they are not dangling
|
// These will be parented to this, they are not dangling
|
||||||
DomainMetadata* _metadata { nullptr };
|
DomainMetadata* _metadata { nullptr };
|
||||||
QTimer* _iceHeartbeatTimer { nullptr };
|
QTimer* _iceHeartbeatTimer { nullptr };
|
||||||
|
QTimer* _metaverseHeartbeatTimer { nullptr };
|
||||||
|
|
||||||
QList<QHostAddress> _iceServerAddresses;
|
QList<QHostAddress> _iceServerAddresses;
|
||||||
QSet<QHostAddress> _failedIceServerAddresses;
|
QSet<QHostAddress> _failedIceServerAddresses;
|
||||||
|
@ -186,8 +192,6 @@ private:
|
||||||
int _numHeartbeatDenials { 0 };
|
int _numHeartbeatDenials { 0 };
|
||||||
bool _connectedToICEServer { false };
|
bool _connectedToICEServer { false };
|
||||||
|
|
||||||
bool _hasAccessToken { false };
|
|
||||||
|
|
||||||
friend class DomainGatekeeper;
|
friend class DomainGatekeeper;
|
||||||
friend class DomainMetadata;
|
friend class DomainMetadata;
|
||||||
};
|
};
|
||||||
|
|
|
@ -921,17 +921,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
||||||
cycleCamera();
|
cycleCamera();
|
||||||
} else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) {
|
} else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) {
|
||||||
if (!offscreenUi->navigationFocused()) {
|
if (!offscreenUi->navigationFocused()) {
|
||||||
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
toggleMenuUnderReticle();
|
||||||
offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y));
|
|
||||||
}
|
}
|
||||||
} else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
|
} else if (action == controller::toInt(controller::Action::CONTEXT_MENU)) {
|
||||||
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
toggleMenuUnderReticle();
|
||||||
offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y));
|
|
||||||
} else if (action == controller::toInt(controller::Action::UI_NAV_SELECT)) {
|
|
||||||
if (!offscreenUi->navigationFocused()) {
|
|
||||||
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
|
||||||
offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y));
|
|
||||||
}
|
|
||||||
} else if (action == controller::toInt(controller::Action::RETICLE_X)) {
|
} else if (action == controller::toInt(controller::Action::RETICLE_X)) {
|
||||||
auto oldPos = getApplicationCompositor().getReticlePosition();
|
auto oldPos = getApplicationCompositor().getReticlePosition();
|
||||||
getApplicationCompositor().setReticlePosition({ oldPos.x + state, oldPos.y });
|
getApplicationCompositor().setReticlePosition({ oldPos.x + state, oldPos.y });
|
||||||
|
@ -1240,7 +1233,16 @@ QString Application::getUserAgent() {
|
||||||
return userAgent;
|
return userAgent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::toggleMenuUnderReticle() const {
|
||||||
|
// In HMD, if the menu is near the mouse but not under it, the reticle can be at a significantly
|
||||||
|
// different depth. When you focus on the menu, the cursor can appear to your crossed eyes as both
|
||||||
|
// on the menu and off.
|
||||||
|
// Even in 2D, it is arguable whether the user would want the menu to be to the side.
|
||||||
|
const float X_LEFT_SHIFT = 50.0;
|
||||||
|
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||||
|
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
||||||
|
offscreenUi->toggleMenu(QPoint(reticlePosition.x - X_LEFT_SHIFT, reticlePosition.y));
|
||||||
|
}
|
||||||
|
|
||||||
void Application::checkChangeCursor() {
|
void Application::checkChangeCursor() {
|
||||||
QMutexLocker locker(&_changeCursorLock);
|
QMutexLocker locker(&_changeCursorLock);
|
||||||
|
@ -2462,9 +2464,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
||||||
|
|
||||||
void Application::keyReleaseEvent(QKeyEvent* event) {
|
void Application::keyReleaseEvent(QKeyEvent* event) {
|
||||||
if (event->key() == Qt::Key_Alt && _altPressed && hasFocus()) {
|
if (event->key() == Qt::Key_Alt && _altPressed && hasFocus()) {
|
||||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
toggleMenuUnderReticle();
|
||||||
auto reticlePosition = getApplicationCompositor().getReticlePosition();
|
|
||||||
offscreenUi->toggleMenu(QPoint(reticlePosition.x, reticlePosition.y));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_keysPressed.remove(event->key());
|
_keysPressed.remove(event->key());
|
||||||
|
|
|
@ -408,6 +408,7 @@ private:
|
||||||
static void dragEnterEvent(QDragEnterEvent* event);
|
static void dragEnterEvent(QDragEnterEvent* event);
|
||||||
|
|
||||||
void maybeToggleMenuVisible(QMouseEvent* event) const;
|
void maybeToggleMenuVisible(QMouseEvent* event) const;
|
||||||
|
void toggleMenuUnderReticle() const;
|
||||||
|
|
||||||
MainWindow* _window;
|
MainWindow* _window;
|
||||||
QElapsedTimer& _sessionRunTimer;
|
QElapsedTimer& _sessionRunTimer;
|
||||||
|
|
|
@ -593,23 +593,25 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
|
||||||
|
|
||||||
// the model is still being downloaded.
|
// the model is still being downloaded.
|
||||||
return false;
|
return false;
|
||||||
|
} else if (type == SHAPE_TYPE_STATIC_MESH) {
|
||||||
|
return (_model && _model->isLoaded());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
||||||
ShapeType type = getShapeType();
|
ShapeType type = getShapeType();
|
||||||
|
glm::vec3 dimensions = getDimensions();
|
||||||
if (type == SHAPE_TYPE_COMPOUND) {
|
if (type == SHAPE_TYPE_COMPOUND) {
|
||||||
updateModelBounds();
|
updateModelBounds();
|
||||||
|
|
||||||
// should never fall in here when collision model not fully loaded
|
// should never fall in here when collision model not fully loaded
|
||||||
// hence we assert that all geometries exist and are loaded
|
// hence we assert that all geometries exist and are loaded
|
||||||
assert(_model->isLoaded() && _model->isCollisionLoaded());
|
assert(_model->isLoaded() && _model->isCollisionLoaded());
|
||||||
const FBXGeometry& renderGeometry = _model->getFBXGeometry();
|
|
||||||
const FBXGeometry& collisionGeometry = _model->getCollisionFBXGeometry();
|
const FBXGeometry& collisionGeometry = _model->getCollisionFBXGeometry();
|
||||||
|
|
||||||
QVector<QVector<glm::vec3>>& points = info.getPoints();
|
ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
|
||||||
points.clear();
|
pointCollection.clear();
|
||||||
uint32_t i = 0;
|
uint32_t i = 0;
|
||||||
|
|
||||||
// the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect
|
// the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect
|
||||||
|
@ -619,8 +621,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
||||||
foreach (const FBXMesh& mesh, collisionGeometry.meshes) {
|
foreach (const FBXMesh& mesh, collisionGeometry.meshes) {
|
||||||
// each meshPart is a convex hull
|
// each meshPart is a convex hull
|
||||||
foreach (const FBXMeshPart &meshPart, mesh.parts) {
|
foreach (const FBXMeshPart &meshPart, mesh.parts) {
|
||||||
points.push_back(QVector<glm::vec3>());
|
pointCollection.push_back(QVector<glm::vec3>());
|
||||||
QVector<glm::vec3>& pointsInPart = points[i];
|
ShapeInfo::PointList& pointsInPart = pointCollection[i];
|
||||||
|
|
||||||
// run through all the triangles and (uniquely) add each point to the hull
|
// run through all the triangles and (uniquely) add each point to the hull
|
||||||
uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size();
|
uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size();
|
||||||
|
@ -664,7 +666,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
||||||
|
|
||||||
if (pointsInPart.size() == 0) {
|
if (pointsInPart.size() == 0) {
|
||||||
qCDebug(entitiesrenderer) << "Warning -- meshPart has no faces";
|
qCDebug(entitiesrenderer) << "Warning -- meshPart has no faces";
|
||||||
points.pop_back();
|
pointCollection.pop_back();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
++i;
|
++i;
|
||||||
|
@ -677,29 +679,136 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
||||||
// to the visual model and apply them to the collision model (without regard for the
|
// to the visual model and apply them to the collision model (without regard for the
|
||||||
// collision model's extents).
|
// collision model's extents).
|
||||||
|
|
||||||
glm::vec3 scale = getDimensions() / renderGeometry.getUnscaledMeshExtents().size();
|
glm::vec3 scaleToFit = dimensions / _model->getFBXGeometry().getUnscaledMeshExtents().size();
|
||||||
// multiply each point by scale before handing the point-set off to the physics engine.
|
// multiply each point by scale before handing the point-set off to the physics engine.
|
||||||
// also determine the extents of the collision model.
|
// also determine the extents of the collision model.
|
||||||
AABox box;
|
for (int i = 0; i < pointCollection.size(); i++) {
|
||||||
for (int i = 0; i < points.size(); i++) {
|
for (int j = 0; j < pointCollection[i].size(); j++) {
|
||||||
for (int j = 0; j < points[i].size(); j++) {
|
|
||||||
// compensate for registration
|
// compensate for registration
|
||||||
points[i][j] += _model->getOffset();
|
pointCollection[i][j] += _model->getOffset();
|
||||||
// scale so the collision points match the model points
|
// scale so the collision points match the model points
|
||||||
points[i][j] *= scale;
|
pointCollection[i][j] *= scaleToFit;
|
||||||
// this next subtraction is done so we can give info the offset, which will cause
|
}
|
||||||
// the shape-key to change.
|
}
|
||||||
points[i][j] -= _model->getOffset();
|
info.setParams(type, dimensions, _compoundShapeURL);
|
||||||
box += points[i][j];
|
} else if (type == SHAPE_TYPE_STATIC_MESH) {
|
||||||
|
// compute meshPart local transforms
|
||||||
|
QVector<glm::mat4> localTransforms;
|
||||||
|
const FBXGeometry& geometry = _model->getFBXGeometry();
|
||||||
|
int numberOfMeshes = geometry.meshes.size();
|
||||||
|
for (int i = 0; i < numberOfMeshes; i++) {
|
||||||
|
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||||
|
if (mesh.clusters.size() > 0) {
|
||||||
|
const FBXCluster& cluster = mesh.clusters.at(0);
|
||||||
|
auto jointMatrix = _model->getRig()->getJointTransform(cluster.jointIndex);
|
||||||
|
localTransforms.push_back(jointMatrix * cluster.inverseBindMatrix);
|
||||||
|
} else {
|
||||||
|
glm::mat4 identity;
|
||||||
|
localTransforms.push_back(identity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec3 collisionModelDimensions = box.getDimensions();
|
updateModelBounds();
|
||||||
info.setParams(type, collisionModelDimensions, _compoundShapeURL);
|
|
||||||
info.setOffset(_model->getOffset());
|
// should never fall in here when collision model not fully loaded
|
||||||
|
assert(_model->isLoaded());
|
||||||
|
|
||||||
|
ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
|
||||||
|
pointCollection.clear();
|
||||||
|
|
||||||
|
ShapeInfo::PointList points;
|
||||||
|
ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices();
|
||||||
|
auto& meshes = _model->getGeometry()->getGeometry()->getMeshes();
|
||||||
|
|
||||||
|
Extents extents;
|
||||||
|
int meshCount = 0;
|
||||||
|
for (auto& mesh : meshes) {
|
||||||
|
const gpu::BufferView& vertices = mesh->getVertexBuffer();
|
||||||
|
const gpu::BufferView& indices = mesh->getIndexBuffer();
|
||||||
|
const gpu::BufferView& parts = mesh->getPartBuffer();
|
||||||
|
|
||||||
|
// copy points
|
||||||
|
const glm::mat4& localTransform = localTransforms[meshCount];
|
||||||
|
uint32_t meshIndexOffset = (uint32_t)points.size();
|
||||||
|
gpu::BufferView::Iterator<const glm::vec3> vertexItr = vertices.cbegin<const glm::vec3>();
|
||||||
|
points.reserve((int32_t)((gpu::Size)points.size() + vertices.getNumElements()));
|
||||||
|
while (vertexItr != vertices.cend<const glm::vec3>()) {
|
||||||
|
glm::vec3 point = extractTranslation(localTransform * glm::translate(*vertexItr));
|
||||||
|
points.push_back(point);
|
||||||
|
extents.addPoint(point);
|
||||||
|
++vertexItr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy triangleIndices
|
||||||
|
triangleIndices.reserve((int32_t)((gpu::Size)(triangleIndices.size()) + indices.getNumElements()));
|
||||||
|
gpu::BufferView::Iterator<const model::Mesh::Part> partItr = parts.cbegin<const model::Mesh::Part>();
|
||||||
|
while (partItr != parts.cend<const model::Mesh::Part>()) {
|
||||||
|
|
||||||
|
if (partItr->_topology == model::Mesh::TRIANGLES) {
|
||||||
|
assert(partItr->_numIndices % 3 == 0);
|
||||||
|
auto indexItr = indices.cbegin<const gpu::BufferView::Index>() + partItr->_startIndex;
|
||||||
|
auto indexEnd = indexItr + partItr->_numIndices;
|
||||||
|
while (indexItr != indexEnd) {
|
||||||
|
triangleIndices.push_back(*indexItr + meshIndexOffset);
|
||||||
|
++indexItr;
|
||||||
|
}
|
||||||
|
} else if (partItr->_topology == model::Mesh::TRIANGLE_STRIP) {
|
||||||
|
assert(partItr->_numIndices > 2);
|
||||||
|
uint32_t approxNumIndices = 3 * partItr->_numIndices;
|
||||||
|
if (approxNumIndices > (uint32_t)(triangleIndices.capacity() - triangleIndices.size())) {
|
||||||
|
// we underestimated the final size of triangleIndices so we pre-emptively expand it
|
||||||
|
triangleIndices.reserve(triangleIndices.size() + approxNumIndices);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto indexItr = indices.cbegin<const gpu::BufferView::Index>() + partItr->_startIndex;
|
||||||
|
auto indexEnd = indexItr + (partItr->_numIndices - 2);
|
||||||
|
|
||||||
|
// first triangle uses the first three indices
|
||||||
|
triangleIndices.push_back(*(indexItr++) + meshIndexOffset);
|
||||||
|
triangleIndices.push_back(*(indexItr++) + meshIndexOffset);
|
||||||
|
triangleIndices.push_back(*(indexItr++) + meshIndexOffset);
|
||||||
|
|
||||||
|
// the rest use previous and next index
|
||||||
|
uint32_t triangleCount = 1;
|
||||||
|
while (indexItr != indexEnd) {
|
||||||
|
if ((*indexItr) != model::Mesh::PRIMITIVE_RESTART_INDEX) {
|
||||||
|
if (triangleCount % 2 == 0) {
|
||||||
|
// even triangles use first two indices in order
|
||||||
|
triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset);
|
||||||
|
triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset);
|
||||||
|
} else {
|
||||||
|
// odd triangles swap order of first two indices
|
||||||
|
triangleIndices.push_back(*(indexItr - 1) + meshIndexOffset);
|
||||||
|
triangleIndices.push_back(*(indexItr - 2) + meshIndexOffset);
|
||||||
|
}
|
||||||
|
triangleIndices.push_back(*indexItr + meshIndexOffset);
|
||||||
|
++triangleCount;
|
||||||
|
}
|
||||||
|
++indexItr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++partItr;
|
||||||
|
}
|
||||||
|
++meshCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// scale and shift
|
||||||
|
glm::vec3 extentsSize = extents.size();
|
||||||
|
glm::vec3 scaleToFit = dimensions / extentsSize;
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
if (extentsSize[i] < 1.0e-6f) {
|
||||||
|
scaleToFit[i] = 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < points.size(); ++i) {
|
||||||
|
points[i] = (points[i] * scaleToFit);
|
||||||
|
}
|
||||||
|
|
||||||
|
pointCollection.push_back(points);
|
||||||
|
info.setParams(SHAPE_TYPE_STATIC_MESH, 0.5f * dimensions, _modelURL);
|
||||||
} else {
|
} else {
|
||||||
ModelEntityItem::computeShapeInfo(info);
|
ModelEntityItem::computeShapeInfo(info);
|
||||||
info.setParams(type, 0.5f * getDimensions());
|
info.setParams(type, 0.5f * dimensions);
|
||||||
adjustShapeInfoByRegistration(info);
|
adjustShapeInfoByRegistration(info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1198,7 +1198,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
|
||||||
|
|
||||||
QtConcurrent::run([entity, voxelSurfaceStyle, voxelVolumeSize, mesh] {
|
QtConcurrent::run([entity, voxelSurfaceStyle, voxelVolumeSize, mesh] {
|
||||||
auto polyVoxEntity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity);
|
auto polyVoxEntity = std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity);
|
||||||
QVector<QVector<glm::vec3>> points;
|
QVector<QVector<glm::vec3>> pointCollection;
|
||||||
AABox box;
|
AABox box;
|
||||||
glm::mat4 vtoM = std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity)->voxelToLocalMatrix();
|
glm::mat4 vtoM = std::static_pointer_cast<RenderablePolyVoxEntityItem>(entity)->voxelToLocalMatrix();
|
||||||
|
|
||||||
|
@ -1207,7 +1207,7 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
|
||||||
// pull each triangle in the mesh into a polyhedron which can be collided with
|
// pull each triangle in the mesh into a polyhedron which can be collided with
|
||||||
unsigned int i = 0;
|
unsigned int i = 0;
|
||||||
|
|
||||||
const gpu::BufferView vertexBufferView = mesh->getVertexBuffer();
|
const gpu::BufferView& vertexBufferView = mesh->getVertexBuffer();
|
||||||
const gpu::BufferView& indexBufferView = mesh->getIndexBuffer();
|
const gpu::BufferView& indexBufferView = mesh->getIndexBuffer();
|
||||||
|
|
||||||
gpu::BufferView::Iterator<const uint32_t> it = indexBufferView.cbegin<uint32_t>();
|
gpu::BufferView::Iterator<const uint32_t> it = indexBufferView.cbegin<uint32_t>();
|
||||||
|
@ -1241,9 +1241,9 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
|
||||||
pointsInPart << p3Model;
|
pointsInPart << p3Model;
|
||||||
// add next convex hull
|
// add next convex hull
|
||||||
QVector<glm::vec3> newMeshPoints;
|
QVector<glm::vec3> newMeshPoints;
|
||||||
points << newMeshPoints;
|
pointCollection << newMeshPoints;
|
||||||
// add points to the new convex hull
|
// add points to the new convex hull
|
||||||
points[i++] << pointsInPart;
|
pointCollection[i++] << pointsInPart;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
unsigned int i = 0;
|
unsigned int i = 0;
|
||||||
|
@ -1299,19 +1299,19 @@ void RenderablePolyVoxEntityItem::computeShapeInfoWorker() {
|
||||||
|
|
||||||
// add next convex hull
|
// add next convex hull
|
||||||
QVector<glm::vec3> newMeshPoints;
|
QVector<glm::vec3> newMeshPoints;
|
||||||
points << newMeshPoints;
|
pointCollection << newMeshPoints;
|
||||||
// add points to the new convex hull
|
// add points to the new convex hull
|
||||||
points[i++] << pointsInPart;
|
pointCollection[i++] << pointsInPart;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
polyVoxEntity->setCollisionPoints(points, box);
|
polyVoxEntity->setCollisionPoints(pointCollection, box);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderablePolyVoxEntityItem::setCollisionPoints(const QVector<QVector<glm::vec3>> points, AABox box) {
|
void RenderablePolyVoxEntityItem::setCollisionPoints(ShapeInfo::PointCollection pointCollection, AABox box) {
|
||||||
// this catches the payload from computeShapeInfoWorker
|
// this catches the payload from computeShapeInfoWorker
|
||||||
if (points.isEmpty()) {
|
if (pointCollection.isEmpty()) {
|
||||||
EntityItem::computeShapeInfo(_shapeInfo);
|
EntityItem::computeShapeInfo(_shapeInfo);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1325,7 +1325,7 @@ void RenderablePolyVoxEntityItem::setCollisionPoints(const QVector<QVector<glm::
|
||||||
QString::number(_registrationPoint.y) + "," +
|
QString::number(_registrationPoint.y) + "," +
|
||||||
QString::number(_registrationPoint.z);
|
QString::number(_registrationPoint.z);
|
||||||
_shapeInfo.setParams(SHAPE_TYPE_COMPOUND, collisionModelDimensions, shapeKey);
|
_shapeInfo.setParams(SHAPE_TYPE_COMPOUND, collisionModelDimensions, shapeKey);
|
||||||
_shapeInfo.setConvexHulls(points);
|
_shapeInfo.setPointCollection(pointCollection);
|
||||||
_meshDirty = false;
|
_meshDirty = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,7 +123,7 @@ public:
|
||||||
std::function<void(int, int, int, uint8_t)> thunk);
|
std::function<void(int, int, int, uint8_t)> thunk);
|
||||||
|
|
||||||
void setMesh(model::MeshPointer mesh);
|
void setMesh(model::MeshPointer mesh);
|
||||||
void setCollisionPoints(const QVector<QVector<glm::vec3>> points, AABox box);
|
void setCollisionPoints(ShapeInfo::PointCollection points, AABox box);
|
||||||
PolyVox::SimpleVolume<uint8_t>* getVolData() { return _volData; }
|
PolyVox::SimpleVolume<uint8_t>* getVolData() { return _volData; }
|
||||||
|
|
||||||
uint8_t getVoxelInternal(int x, int y, int z);
|
uint8_t getVoxelInternal(int x, int y, int z);
|
||||||
|
|
|
@ -1602,14 +1602,20 @@ void EntityItem::updateMass(float mass) {
|
||||||
void EntityItem::updateVelocity(const glm::vec3& value) {
|
void EntityItem::updateVelocity(const glm::vec3& value) {
|
||||||
glm::vec3 velocity = getLocalVelocity();
|
glm::vec3 velocity = getLocalVelocity();
|
||||||
if (velocity != value) {
|
if (velocity != value) {
|
||||||
const float MIN_LINEAR_SPEED = 0.001f;
|
if (getShapeType() == SHAPE_TYPE_STATIC_MESH) {
|
||||||
if (glm::length(value) < MIN_LINEAR_SPEED) {
|
if (velocity != Vectors::ZERO) {
|
||||||
velocity = ENTITY_ITEM_ZERO_VEC3;
|
setLocalVelocity(Vectors::ZERO);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
velocity = value;
|
const float MIN_LINEAR_SPEED = 0.001f;
|
||||||
|
if (glm::length(value) < MIN_LINEAR_SPEED) {
|
||||||
|
velocity = ENTITY_ITEM_ZERO_VEC3;
|
||||||
|
} else {
|
||||||
|
velocity = value;
|
||||||
|
}
|
||||||
|
setLocalVelocity(velocity);
|
||||||
|
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
|
||||||
}
|
}
|
||||||
setLocalVelocity(velocity);
|
|
||||||
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1630,22 +1636,30 @@ void EntityItem::updateDamping(float value) {
|
||||||
|
|
||||||
void EntityItem::updateGravity(const glm::vec3& value) {
|
void EntityItem::updateGravity(const glm::vec3& value) {
|
||||||
if (_gravity != value) {
|
if (_gravity != value) {
|
||||||
_gravity = value;
|
if (getShapeType() == SHAPE_TYPE_STATIC_MESH) {
|
||||||
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
|
_gravity = Vectors::ZERO;
|
||||||
|
} else {
|
||||||
|
_gravity = value;
|
||||||
|
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityItem::updateAngularVelocity(const glm::vec3& value) {
|
void EntityItem::updateAngularVelocity(const glm::vec3& value) {
|
||||||
glm::vec3 angularVelocity = getLocalAngularVelocity();
|
glm::vec3 angularVelocity = getLocalAngularVelocity();
|
||||||
if (angularVelocity != value) {
|
if (angularVelocity != value) {
|
||||||
const float MIN_ANGULAR_SPEED = 0.0002f;
|
if (getShapeType() == SHAPE_TYPE_STATIC_MESH) {
|
||||||
if (glm::length(value) < MIN_ANGULAR_SPEED) {
|
setLocalAngularVelocity(Vectors::ZERO);
|
||||||
angularVelocity = ENTITY_ITEM_ZERO_VEC3;
|
|
||||||
} else {
|
} else {
|
||||||
angularVelocity = value;
|
const float MIN_ANGULAR_SPEED = 0.0002f;
|
||||||
|
if (glm::length(value) < MIN_ANGULAR_SPEED) {
|
||||||
|
angularVelocity = ENTITY_ITEM_ZERO_VEC3;
|
||||||
|
} else {
|
||||||
|
angularVelocity = value;
|
||||||
|
}
|
||||||
|
setLocalAngularVelocity(angularVelocity);
|
||||||
|
_dirtyFlags |= Simulation::DIRTY_ANGULAR_VELOCITY;
|
||||||
}
|
}
|
||||||
setLocalAngularVelocity(angularVelocity);
|
|
||||||
_dirtyFlags |= Simulation::DIRTY_ANGULAR_VELOCITY;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1679,9 +1693,17 @@ void EntityItem::updateCollisionMask(uint8_t value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityItem::updateDynamic(bool value) {
|
void EntityItem::updateDynamic(bool value) {
|
||||||
if (_dynamic != value) {
|
if (getDynamic() != value) {
|
||||||
_dynamic = value;
|
// dynamic and STATIC_MESH are incompatible so we check for that case
|
||||||
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
|
if (value && getShapeType() == SHAPE_TYPE_STATIC_MESH) {
|
||||||
|
if (_dynamic) {
|
||||||
|
_dynamic = false;
|
||||||
|
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_dynamic = value;
|
||||||
|
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1731,7 +1753,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask
|
||||||
group = BULLET_COLLISION_GROUP_COLLISIONLESS;
|
group = BULLET_COLLISION_GROUP_COLLISIONLESS;
|
||||||
mask = 0;
|
mask = 0;
|
||||||
} else {
|
} else {
|
||||||
if (_dynamic) {
|
if (getDynamic()) {
|
||||||
group = BULLET_COLLISION_GROUP_DYNAMIC;
|
group = BULLET_COLLISION_GROUP_DYNAMIC;
|
||||||
} else if (isMovingRelativeToParent() || hasActions()) {
|
} else if (isMovingRelativeToParent() || hasActions()) {
|
||||||
group = BULLET_COLLISION_GROUP_KINEMATIC;
|
group = BULLET_COLLISION_GROUP_KINEMATIC;
|
||||||
|
|
|
@ -283,7 +283,7 @@ public:
|
||||||
|
|
||||||
void computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask) const;
|
void computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask) const;
|
||||||
|
|
||||||
bool getDynamic() const { return _dynamic; }
|
bool getDynamic() const { return SHAPE_TYPE_STATIC_MESH == getShapeType() ? false : _dynamic; }
|
||||||
void setDynamic(bool value) { _dynamic = value; }
|
void setDynamic(bool value) { _dynamic = value; }
|
||||||
|
|
||||||
virtual bool shouldBePhysical() const { return false; }
|
virtual bool shouldBePhysical() const { return false; }
|
||||||
|
@ -348,7 +348,7 @@ public:
|
||||||
void updateDynamic(bool value);
|
void updateDynamic(bool value);
|
||||||
void updateLifetime(float value);
|
void updateLifetime(float value);
|
||||||
void updateCreated(uint64_t value);
|
void updateCreated(uint64_t value);
|
||||||
virtual void updateShapeType(ShapeType type) { /* do nothing */ }
|
virtual void setShapeType(ShapeType type) { /* do nothing */ }
|
||||||
|
|
||||||
uint32_t getDirtyFlags() const { return _dirtyFlags; }
|
uint32_t getDirtyFlags() const { return _dirtyFlags; }
|
||||||
void clearDirtyFlags(uint32_t mask = 0xffffffff) { _dirtyFlags &= ~mask; }
|
void clearDirtyFlags(uint32_t mask = 0xffffffff) { _dirtyFlags &= ~mask; }
|
||||||
|
|
|
@ -88,8 +88,21 @@ void EntityItemProperties::setLastEdited(quint64 usecTime) {
|
||||||
_lastEdited = usecTime > _created ? usecTime : _created;
|
_lastEdited = usecTime > _created ? usecTime : _created;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* shapeTypeNames[] = {"none", "box", "sphere", "plane", "compound", "capsule-x",
|
const char* shapeTypeNames[] = {
|
||||||
"capsule-y", "capsule-z", "cylinder-x", "cylinder-y", "cylinder-z"};
|
"none",
|
||||||
|
"box",
|
||||||
|
"sphere",
|
||||||
|
"capsule-x",
|
||||||
|
"capsule-y",
|
||||||
|
"capsule-z",
|
||||||
|
"cylinder-x",
|
||||||
|
"cylinder-y",
|
||||||
|
"cylinder-z",
|
||||||
|
"hull",
|
||||||
|
"plane",
|
||||||
|
"compound",
|
||||||
|
"static-mesh"
|
||||||
|
};
|
||||||
|
|
||||||
QHash<QString, ShapeType> stringToShapeTypeLookup;
|
QHash<QString, ShapeType> stringToShapeTypeLookup;
|
||||||
|
|
||||||
|
@ -101,14 +114,16 @@ void buildStringToShapeTypeLookup() {
|
||||||
addShapeType(SHAPE_TYPE_NONE);
|
addShapeType(SHAPE_TYPE_NONE);
|
||||||
addShapeType(SHAPE_TYPE_BOX);
|
addShapeType(SHAPE_TYPE_BOX);
|
||||||
addShapeType(SHAPE_TYPE_SPHERE);
|
addShapeType(SHAPE_TYPE_SPHERE);
|
||||||
addShapeType(SHAPE_TYPE_PLANE);
|
|
||||||
addShapeType(SHAPE_TYPE_COMPOUND);
|
|
||||||
addShapeType(SHAPE_TYPE_CAPSULE_X);
|
addShapeType(SHAPE_TYPE_CAPSULE_X);
|
||||||
addShapeType(SHAPE_TYPE_CAPSULE_Y);
|
addShapeType(SHAPE_TYPE_CAPSULE_Y);
|
||||||
addShapeType(SHAPE_TYPE_CAPSULE_Z);
|
addShapeType(SHAPE_TYPE_CAPSULE_Z);
|
||||||
addShapeType(SHAPE_TYPE_CYLINDER_X);
|
addShapeType(SHAPE_TYPE_CYLINDER_X);
|
||||||
addShapeType(SHAPE_TYPE_CYLINDER_Y);
|
addShapeType(SHAPE_TYPE_CYLINDER_Y);
|
||||||
addShapeType(SHAPE_TYPE_CYLINDER_Z);
|
addShapeType(SHAPE_TYPE_CYLINDER_Z);
|
||||||
|
addShapeType(SHAPE_TYPE_HULL);
|
||||||
|
addShapeType(SHAPE_TYPE_PLANE);
|
||||||
|
addShapeType(SHAPE_TYPE_COMPOUND);
|
||||||
|
addShapeType(SHAPE_TYPE_STATIC_MESH);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString getCollisionGroupAsString(uint8_t group) {
|
QString getCollisionGroupAsString(uint8_t group) {
|
||||||
|
|
|
@ -77,7 +77,7 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) {
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(modelURL, setModelURL);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(modelURL, setModelURL);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotationsSet, setJointRotationsSet);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotationsSet, setJointRotationsSet);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotations, setJointRotations);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotations, setJointRotations);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslationsSet, setJointTranslationsSet);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslationsSet, setJointTranslationsSet);
|
||||||
|
@ -145,7 +145,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
||||||
dataAt += bytesFromAnimation;
|
dataAt += bytesFromAnimation;
|
||||||
}
|
}
|
||||||
|
|
||||||
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType);
|
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType);
|
||||||
|
|
||||||
if (animationPropertiesChanged) {
|
if (animationPropertiesChanged) {
|
||||||
_dirtyFlags |= Simulation::DIRTY_UPDATEABLE;
|
_dirtyFlags |= Simulation::DIRTY_UPDATEABLE;
|
||||||
|
@ -257,37 +257,54 @@ void ModelEntityItem::debugDump() const {
|
||||||
qCDebug(entities) << " compound shape URL:" << getCompoundShapeURL();
|
qCDebug(entities) << " compound shape URL:" << getCompoundShapeURL();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelEntityItem::updateShapeType(ShapeType type) {
|
void ModelEntityItem::setShapeType(ShapeType type) {
|
||||||
// BEGIN_TEMPORARY_WORKAROUND
|
|
||||||
// we have allowed inconsistent ShapeType's to be stored in SVO files in the past (this was a bug)
|
|
||||||
// but we are now enforcing the entity properties to be consistent. To make the possible we're
|
|
||||||
// introducing a temporary workaround: we will ignore ShapeType updates that conflict with the
|
|
||||||
// _compoundShapeURL.
|
|
||||||
if (hasCompoundShapeURL()) {
|
|
||||||
type = SHAPE_TYPE_COMPOUND;
|
|
||||||
}
|
|
||||||
// END_TEMPORARY_WORKAROUND
|
|
||||||
|
|
||||||
if (type != _shapeType) {
|
if (type != _shapeType) {
|
||||||
|
if (type == SHAPE_TYPE_STATIC_MESH && _dynamic) {
|
||||||
|
// dynamic and STATIC_MESH are incompatible
|
||||||
|
// since the shape is being set here we clear the dynamic bit
|
||||||
|
_dynamic = false;
|
||||||
|
_dirtyFlags |= Simulation::DIRTY_MOTION_TYPE;
|
||||||
|
}
|
||||||
_shapeType = type;
|
_shapeType = type;
|
||||||
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
|
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// virtual
|
|
||||||
ShapeType ModelEntityItem::getShapeType() const {
|
ShapeType ModelEntityItem::getShapeType() const {
|
||||||
if (_shapeType == SHAPE_TYPE_COMPOUND) {
|
return computeTrueShapeType();
|
||||||
return hasCompoundShapeURL() ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE;
|
}
|
||||||
} else {
|
|
||||||
return _shapeType;
|
ShapeType ModelEntityItem::computeTrueShapeType() const {
|
||||||
|
ShapeType type = _shapeType;
|
||||||
|
if (type == SHAPE_TYPE_STATIC_MESH && _dynamic) {
|
||||||
|
// dynamic is incompatible with STATIC_MESH
|
||||||
|
// shouldn't fall in here but just in case --> fall back to COMPOUND
|
||||||
|
type = SHAPE_TYPE_COMPOUND;
|
||||||
|
}
|
||||||
|
if (type == SHAPE_TYPE_COMPOUND && !hasCompoundShapeURL()) {
|
||||||
|
// no compoundURL set --> fall back to NONE
|
||||||
|
type = SHAPE_TYPE_NONE;
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelEntityItem::setModelURL(const QString& url) {
|
||||||
|
if (_modelURL != url) {
|
||||||
|
_modelURL = url;
|
||||||
|
_parsedModelURL = QUrl(url);
|
||||||
|
if (_shapeType == SHAPE_TYPE_STATIC_MESH) {
|
||||||
|
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModelEntityItem::setCompoundShapeURL(const QString& url) {
|
void ModelEntityItem::setCompoundShapeURL(const QString& url) {
|
||||||
if (_compoundShapeURL != url) {
|
if (_compoundShapeURL != url) {
|
||||||
|
ShapeType oldType = computeTrueShapeType();
|
||||||
_compoundShapeURL = url;
|
_compoundShapeURL = url;
|
||||||
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
|
if (oldType != computeTrueShapeType()) {
|
||||||
_shapeType = _compoundShapeURL.isEmpty() ? SHAPE_TYPE_NONE : SHAPE_TYPE_COMPOUND;
|
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,9 +50,10 @@ public:
|
||||||
virtual bool needsToCallUpdate() const;
|
virtual bool needsToCallUpdate() const;
|
||||||
virtual void debugDump() const;
|
virtual void debugDump() const;
|
||||||
|
|
||||||
void updateShapeType(ShapeType type);
|
void setShapeType(ShapeType type);
|
||||||
virtual ShapeType getShapeType() const;
|
virtual ShapeType getShapeType() const;
|
||||||
|
|
||||||
|
|
||||||
// TODO: Move these to subclasses, or other appropriate abstraction
|
// TODO: Move these to subclasses, or other appropriate abstraction
|
||||||
// getters/setters applicable to models and particles
|
// getters/setters applicable to models and particles
|
||||||
|
|
||||||
|
@ -76,7 +77,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
// model related properties
|
// model related properties
|
||||||
virtual void setModelURL(const QString& url) { _modelURL = url; _parsedModelURL = QUrl(url); }
|
virtual void setModelURL(const QString& url);
|
||||||
virtual void setCompoundShapeURL(const QString& url);
|
virtual void setCompoundShapeURL(const QString& url);
|
||||||
|
|
||||||
// Animation related items...
|
// Animation related items...
|
||||||
|
@ -130,6 +131,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setAnimationSettings(const QString& value); // only called for old bitstream format
|
void setAnimationSettings(const QString& value); // only called for old bitstream format
|
||||||
|
ShapeType computeTrueShapeType() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// these are used:
|
// these are used:
|
||||||
|
|
|
@ -342,7 +342,7 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert
|
||||||
|
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxParticles, setMaxParticles);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxParticles, setMaxParticles);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifespan, setLifespan);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifespan, setLifespan);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(isEmitting, setIsEmitting);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(isEmitting, setIsEmitting);
|
||||||
|
@ -406,7 +406,7 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch
|
||||||
READ_ENTITY_PROPERTY(PROP_EMITTING_PARTICLES, bool, setIsEmitting);
|
READ_ENTITY_PROPERTY(PROP_EMITTING_PARTICLES, bool, setIsEmitting);
|
||||||
}
|
}
|
||||||
|
|
||||||
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType);
|
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType);
|
||||||
READ_ENTITY_PROPERTY(PROP_MAX_PARTICLES, quint32, setMaxParticles);
|
READ_ENTITY_PROPERTY(PROP_MAX_PARTICLES, quint32, setMaxParticles);
|
||||||
READ_ENTITY_PROPERTY(PROP_LIFESPAN, float, setLifespan);
|
READ_ENTITY_PROPERTY(PROP_LIFESPAN, float, setLifespan);
|
||||||
READ_ENTITY_PROPERTY(PROP_EMIT_RATE, float, setEmitRate);
|
READ_ENTITY_PROPERTY(PROP_EMIT_RATE, float, setEmitRate);
|
||||||
|
@ -584,7 +584,7 @@ void ParticleEffectEntityItem::debugDump() const {
|
||||||
qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now);
|
qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ParticleEffectEntityItem::updateShapeType(ShapeType type) {
|
void ParticleEffectEntityItem::setShapeType(ShapeType type) {
|
||||||
if (type != _shapeType) {
|
if (type != _shapeType) {
|
||||||
_shapeType = type;
|
_shapeType = type;
|
||||||
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
|
_dirtyFlags |= Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS;
|
||||||
|
|
|
@ -95,7 +95,7 @@ public:
|
||||||
void setAlphaSpread(float alphaSpread);
|
void setAlphaSpread(float alphaSpread);
|
||||||
float getAlphaSpread() const { return _alphaSpread; }
|
float getAlphaSpread() const { return _alphaSpread; }
|
||||||
|
|
||||||
void updateShapeType(ShapeType type);
|
void setShapeType(ShapeType type);
|
||||||
virtual ShapeType getShapeType() const { return _shapeType; }
|
virtual ShapeType getShapeType() const { return _shapeType; }
|
||||||
|
|
||||||
virtual void debugDump() const;
|
virtual void debugDump() const;
|
||||||
|
|
|
@ -73,7 +73,7 @@ bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) {
|
||||||
|
|
||||||
bool somethingChangedInStage = _stageProperties.setProperties(properties);
|
bool somethingChangedInStage = _stageProperties.setProperties(properties);
|
||||||
|
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL);
|
||||||
SET_ENTITY_PROPERTY_FROM_PROPERTIES(backgroundMode, setBackgroundMode);
|
SET_ENTITY_PROPERTY_FROM_PROPERTIES(backgroundMode, setBackgroundMode);
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
|
||||||
bytesRead += bytesFromStage;
|
bytesRead += bytesFromStage;
|
||||||
dataAt += bytesFromStage;
|
dataAt += bytesFromStage;
|
||||||
|
|
||||||
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, updateShapeType);
|
READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType);
|
||||||
READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL);
|
READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL);
|
||||||
READ_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode);
|
READ_ENTITY_PROPERTY(PROP_BACKGROUND_MODE, BackgroundMode, setBackgroundMode);
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ public:
|
||||||
static void setDrawZoneBoundaries(bool value) { _drawZoneBoundaries = value; }
|
static void setDrawZoneBoundaries(bool value) { _drawZoneBoundaries = value; }
|
||||||
|
|
||||||
virtual bool isReadyToComputeShape() { return false; }
|
virtual bool isReadyToComputeShape() { return false; }
|
||||||
void updateShapeType(ShapeType type) { _shapeType = type; }
|
void setShapeType(ShapeType type) { _shapeType = type; }
|
||||||
virtual ShapeType getShapeType() const;
|
virtual ShapeType getShapeType() const;
|
||||||
|
|
||||||
virtual bool hasCompoundShapeURL() const { return !_compoundShapeURL.isEmpty(); }
|
virtual bool hasCompoundShapeURL() const { return !_compoundShapeURL.isEmpty(); }
|
||||||
|
|
|
@ -474,6 +474,11 @@ void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken)
|
||||||
persistAccountToFile();
|
persistAccountToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AccountManager::setTemporaryDomain(const QUuid& domainID, const QString& key) {
|
||||||
|
_accountInfo.setTemporaryDomain(domainID, key);
|
||||||
|
persistAccountToFile();
|
||||||
|
}
|
||||||
|
|
||||||
void AccountManager::requestAccessToken(const QString& login, const QString& password) {
|
void AccountManager::requestAccessToken(const QString& login, const QString& password) {
|
||||||
|
|
||||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||||
|
@ -650,22 +655,33 @@ void AccountManager::processGeneratedKeypair() {
|
||||||
const QString DOMAIN_PUBLIC_KEY_UPDATE_PATH = "api/v1/domains/%1/public_key";
|
const QString DOMAIN_PUBLIC_KEY_UPDATE_PATH = "api/v1/domains/%1/public_key";
|
||||||
|
|
||||||
QString uploadPath;
|
QString uploadPath;
|
||||||
if (keypairGenerator->getDomainID().isNull()) {
|
const auto& domainID = keypairGenerator->getDomainID();
|
||||||
|
if (domainID.isNull()) {
|
||||||
uploadPath = USER_PUBLIC_KEY_UPDATE_PATH;
|
uploadPath = USER_PUBLIC_KEY_UPDATE_PATH;
|
||||||
} else {
|
} else {
|
||||||
uploadPath = DOMAIN_PUBLIC_KEY_UPDATE_PATH.arg(uuidStringWithoutCurlyBraces(keypairGenerator->getDomainID()));
|
uploadPath = DOMAIN_PUBLIC_KEY_UPDATE_PATH.arg(uuidStringWithoutCurlyBraces(domainID));
|
||||||
}
|
}
|
||||||
|
|
||||||
// setup a multipart upload to send up the public key
|
// setup a multipart upload to send up the public key
|
||||||
QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
QHttpMultiPart* requestMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||||||
|
|
||||||
QHttpPart keyPart;
|
QHttpPart publicKeyPart;
|
||||||
keyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
|
publicKeyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
|
||||||
keyPart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
|
||||||
QVariant("form-data; name=\"public_key\"; filename=\"public_key\""));
|
|
||||||
keyPart.setBody(keypairGenerator->getPublicKey());
|
|
||||||
|
|
||||||
requestMultiPart->append(keyPart);
|
publicKeyPart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||||
|
QVariant("form-data; name=\"public_key\"; filename=\"public_key\""));
|
||||||
|
publicKeyPart.setBody(keypairGenerator->getPublicKey());
|
||||||
|
requestMultiPart->append(publicKeyPart);
|
||||||
|
|
||||||
|
if (!domainID.isNull()) {
|
||||||
|
const auto& key = getTemporaryDomainKey(domainID);
|
||||||
|
QHttpPart apiKeyPart;
|
||||||
|
publicKeyPart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
|
||||||
|
apiKeyPart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||||
|
QVariant("form-data; name=\"api_key\""));
|
||||||
|
apiKeyPart.setBody(key.toUtf8());
|
||||||
|
requestMultiPart->append(apiKeyPart);
|
||||||
|
}
|
||||||
|
|
||||||
// setup callback parameters so we know once the keypair upload has succeeded or failed
|
// setup callback parameters so we know once the keypair upload has succeeded or failed
|
||||||
JSONCallbackParameters callbackParameters;
|
JSONCallbackParameters callbackParameters;
|
||||||
|
|
|
@ -89,6 +89,9 @@ public:
|
||||||
QUuid getSessionID() const { return _sessionID; }
|
QUuid getSessionID() const { return _sessionID; }
|
||||||
void setSessionID(const QUuid& sessionID) { _sessionID = sessionID; }
|
void setSessionID(const QUuid& sessionID) { _sessionID = sessionID; }
|
||||||
|
|
||||||
|
void setTemporaryDomain(const QUuid& domainID, const QString& key);
|
||||||
|
const QString& getTemporaryDomainKey(const QUuid& domainID) { return _accountInfo.getTemporaryDomainKey(domainID); }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void requestAccessToken(const QString& login, const QString& password);
|
void requestAccessToken(const QString& login, const QString& password);
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,8 @@
|
||||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
const QString DataServerAccountInfo::EMPTY_KEY = QString();
|
||||||
|
|
||||||
DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) : QObject() {
|
DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherInfo) : QObject() {
|
||||||
_accessToken = otherInfo._accessToken;
|
_accessToken = otherInfo._accessToken;
|
||||||
_username = otherInfo._username;
|
_username = otherInfo._username;
|
||||||
|
@ -33,6 +35,8 @@ DataServerAccountInfo::DataServerAccountInfo(const DataServerAccountInfo& otherI
|
||||||
_walletID = otherInfo._walletID;
|
_walletID = otherInfo._walletID;
|
||||||
_privateKey = otherInfo._privateKey;
|
_privateKey = otherInfo._privateKey;
|
||||||
_domainID = otherInfo._domainID;
|
_domainID = otherInfo._domainID;
|
||||||
|
_temporaryDomainID = otherInfo._temporaryDomainID;
|
||||||
|
_temporaryDomainApiKey = otherInfo._temporaryDomainApiKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) {
|
DataServerAccountInfo& DataServerAccountInfo::operator=(const DataServerAccountInfo& otherInfo) {
|
||||||
|
@ -51,6 +55,8 @@ void DataServerAccountInfo::swap(DataServerAccountInfo& otherInfo) {
|
||||||
swap(_walletID, otherInfo._walletID);
|
swap(_walletID, otherInfo._walletID);
|
||||||
swap(_privateKey, otherInfo._privateKey);
|
swap(_privateKey, otherInfo._privateKey);
|
||||||
swap(_domainID, otherInfo._domainID);
|
swap(_domainID, otherInfo._domainID);
|
||||||
|
swap(_temporaryDomainID, otherInfo._temporaryDomainID);
|
||||||
|
swap(_temporaryDomainApiKey, otherInfo._temporaryDomainApiKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) {
|
void DataServerAccountInfo::setAccessTokenFromJSON(const QJsonObject& jsonObject) {
|
||||||
|
@ -145,13 +151,14 @@ QByteArray DataServerAccountInfo::signPlaintext(const QByteArray& plaintext) {
|
||||||
|
|
||||||
QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) {
|
QDataStream& operator<<(QDataStream &out, const DataServerAccountInfo& info) {
|
||||||
out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey
|
out << info._accessToken << info._username << info._xmppPassword << info._discourseApiKey
|
||||||
<< info._walletID << info._privateKey << info._domainID;
|
<< info._walletID << info._privateKey << info._domainID
|
||||||
|
<< info._temporaryDomainID << info._temporaryDomainApiKey;
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) {
|
QDataStream& operator>>(QDataStream &in, DataServerAccountInfo& info) {
|
||||||
in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey
|
in >> info._accessToken >> info._username >> info._xmppPassword >> info._discourseApiKey
|
||||||
>> info._walletID >> info._privateKey >> info._domainID;
|
>> info._walletID >> info._privateKey >> info._domainID
|
||||||
|
>> info._temporaryDomainID >> info._temporaryDomainApiKey;
|
||||||
return in;
|
return in;
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ const float SATOSHIS_PER_CREDIT = 100000000.0f;
|
||||||
|
|
||||||
class DataServerAccountInfo : public QObject {
|
class DataServerAccountInfo : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
const static QString EMPTY_KEY;
|
||||||
public:
|
public:
|
||||||
DataServerAccountInfo() {};
|
DataServerAccountInfo() {};
|
||||||
DataServerAccountInfo(const DataServerAccountInfo& otherInfo);
|
DataServerAccountInfo(const DataServerAccountInfo& otherInfo);
|
||||||
|
@ -52,6 +53,9 @@ public:
|
||||||
void setDomainID(const QUuid& domainID) { _domainID = domainID; }
|
void setDomainID(const QUuid& domainID) { _domainID = domainID; }
|
||||||
const QUuid& getDomainID() const { return _domainID; }
|
const QUuid& getDomainID() const { return _domainID; }
|
||||||
|
|
||||||
|
void setTemporaryDomain(const QUuid& domainID, const QString& key) { _temporaryDomainID = domainID; _temporaryDomainApiKey = key; }
|
||||||
|
const QString& getTemporaryDomainKey(const QUuid& domainID) { return domainID == _temporaryDomainID ? _temporaryDomainApiKey : EMPTY_KEY; }
|
||||||
|
|
||||||
bool hasProfile() const;
|
bool hasProfile() const;
|
||||||
|
|
||||||
void setProfileInfoFromJSON(const QJsonObject& jsonObject);
|
void setProfileInfoFromJSON(const QJsonObject& jsonObject);
|
||||||
|
@ -67,7 +71,9 @@ private:
|
||||||
QString _xmppPassword;
|
QString _xmppPassword;
|
||||||
QString _discourseApiKey;
|
QString _discourseApiKey;
|
||||||
QUuid _walletID;
|
QUuid _walletID;
|
||||||
QUuid _domainID; // if this holds account info for a domain, this holds the ID of that domain
|
QUuid _domainID;
|
||||||
|
QUuid _temporaryDomainID;
|
||||||
|
QString _temporaryDomainApiKey;
|
||||||
QByteArray _privateKey;
|
QByteArray _privateKey;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -49,7 +49,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
||||||
case PacketType::EntityAdd:
|
case PacketType::EntityAdd:
|
||||||
case PacketType::EntityEdit:
|
case PacketType::EntityEdit:
|
||||||
case PacketType::EntityData:
|
case PacketType::EntityData:
|
||||||
return VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS;
|
return VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH;
|
||||||
case PacketType::AvatarIdentity:
|
case PacketType::AvatarIdentity:
|
||||||
case PacketType::AvatarData:
|
case PacketType::AvatarData:
|
||||||
case PacketType::BulkAvatarData:
|
case PacketType::BulkAvatarData:
|
||||||
|
|
|
@ -180,6 +180,7 @@ const PacketVersion VERSION_LIGHT_HAS_FALLOFF_RADIUS = 57;
|
||||||
const PacketVersion VERSION_ENTITIES_NO_FLY_ZONES = 58;
|
const PacketVersion VERSION_ENTITIES_NO_FLY_ZONES = 58;
|
||||||
const PacketVersion VERSION_ENTITIES_MORE_SHAPES = 59;
|
const PacketVersion VERSION_ENTITIES_MORE_SHAPES = 59;
|
||||||
const PacketVersion VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS = 60;
|
const PacketVersion VERSION_ENTITIES_PROPERLY_ENCODE_SHAPE_EDITS = 60;
|
||||||
|
const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_STATIC_MESH = 61;
|
||||||
|
|
||||||
enum class AvatarMixerPacketVersion : PacketVersion {
|
enum class AvatarMixerPacketVersion : PacketVersion {
|
||||||
TranslationSupport = 17,
|
TranslationSupport = 17,
|
||||||
|
|
|
@ -159,6 +159,11 @@ PhysicsMotionType EntityMotionState::computePhysicsMotionType() const {
|
||||||
}
|
}
|
||||||
assert(entityTreeIsLocked());
|
assert(entityTreeIsLocked());
|
||||||
|
|
||||||
|
if (_entity->getShapeType() == SHAPE_TYPE_STATIC_MESH
|
||||||
|
|| (_body && _body->getCollisionShape()->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE)) {
|
||||||
|
return MOTION_TYPE_STATIC;
|
||||||
|
}
|
||||||
|
|
||||||
if (_entity->getDynamic()) {
|
if (_entity->getDynamic()) {
|
||||||
if (!_entity->getParentID().isNull()) {
|
if (!_entity->getParentID().isNull()) {
|
||||||
// if something would have been dynamic but is a child of something else, force it to be kinematic, instead.
|
// if something would have been dynamic but is a child of something else, force it to be kinematic, instead.
|
||||||
|
|
|
@ -203,35 +203,37 @@ void ObjectMotionState::handleEasyChanges(uint32_t& flags) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags & Simulation::DIRTY_LINEAR_VELOCITY) {
|
if (_body->getCollisionShape()->getShapeType() != TRIANGLE_MESH_SHAPE_PROXYTYPE) {
|
||||||
btVector3 newLinearVelocity = glmToBullet(getObjectLinearVelocity());
|
if (flags & Simulation::DIRTY_LINEAR_VELOCITY) {
|
||||||
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
|
btVector3 newLinearVelocity = glmToBullet(getObjectLinearVelocity());
|
||||||
float delta = (newLinearVelocity - _body->getLinearVelocity()).length();
|
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
|
||||||
if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) {
|
float delta = (newLinearVelocity - _body->getLinearVelocity()).length();
|
||||||
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) {
|
||||||
|
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
_body->setLinearVelocity(newLinearVelocity);
|
||||||
_body->setLinearVelocity(newLinearVelocity);
|
|
||||||
|
|
||||||
btVector3 newGravity = glmToBullet(getObjectGravity());
|
btVector3 newGravity = glmToBullet(getObjectGravity());
|
||||||
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
|
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
|
||||||
float delta = (newGravity - _body->getGravity()).length();
|
float delta = (newGravity - _body->getGravity()).length();
|
||||||
if (delta > ACTIVATION_GRAVITY_DELTA ||
|
if (delta > ACTIVATION_GRAVITY_DELTA ||
|
||||||
(delta > 0.0f && _body->getGravity().length2() == 0.0f)) {
|
(delta > 0.0f && _body->getGravity().length2() == 0.0f)) {
|
||||||
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
_body->setGravity(newGravity);
|
||||||
}
|
}
|
||||||
_body->setGravity(newGravity);
|
if (flags & Simulation::DIRTY_ANGULAR_VELOCITY) {
|
||||||
}
|
btVector3 newAngularVelocity = glmToBullet(getObjectAngularVelocity());
|
||||||
if (flags & Simulation::DIRTY_ANGULAR_VELOCITY) {
|
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
|
||||||
btVector3 newAngularVelocity = glmToBullet(getObjectAngularVelocity());
|
float delta = (newAngularVelocity - _body->getAngularVelocity()).length();
|
||||||
if (!(flags & Simulation::DIRTY_PHYSICS_ACTIVATION)) {
|
if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) {
|
||||||
float delta = (newAngularVelocity - _body->getAngularVelocity()).length();
|
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||||
if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) {
|
}
|
||||||
flags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
|
||||||
}
|
}
|
||||||
|
_body->setAngularVelocity(newAngularVelocity);
|
||||||
}
|
}
|
||||||
_body->setAngularVelocity(newAngularVelocity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags & Simulation::DIRTY_MATERIAL) {
|
if (flags & Simulation::DIRTY_MATERIAL) {
|
||||||
|
|
|
@ -217,7 +217,7 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re
|
||||||
} else if (entity->isReadyToComputeShape()) {
|
} else if (entity->isReadyToComputeShape()) {
|
||||||
ShapeInfo shapeInfo;
|
ShapeInfo shapeInfo;
|
||||||
entity->computeShapeInfo(shapeInfo);
|
entity->computeShapeInfo(shapeInfo);
|
||||||
int numPoints = shapeInfo.getMaxNumPoints();
|
int numPoints = shapeInfo.getLargestSubshapePointCount();
|
||||||
if (numPoints > MAX_HULL_POINTS) {
|
if (numPoints > MAX_HULL_POINTS) {
|
||||||
qWarning() << "convex hull with" << numPoints
|
qWarning() << "convex hull with" << numPoints
|
||||||
<< "points for entity" << entity->getName()
|
<< "points for entity" << entity->getName()
|
||||||
|
@ -231,7 +231,7 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re
|
||||||
result.push_back(motionState);
|
result.push_back(motionState);
|
||||||
entityItr = _entitiesToAddToPhysics.erase(entityItr);
|
entityItr = _entitiesToAddToPhysics.erase(entityItr);
|
||||||
} else {
|
} else {
|
||||||
//qDebug() << "Warning! Failed to generate new shape for entity." << entity->getName();
|
//qWarning() << "Failed to generate new shape for entity." << entity->getName();
|
||||||
++entityItr;
|
++entityItr;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -67,7 +67,8 @@ static const btVector3 _unitSphereDirections[NUM_UNIT_SPHERE_DIRECTIONS] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
btConvexHullShape* ShapeFactory::createConvexHull(const QVector<glm::vec3>& points) {
|
// util method
|
||||||
|
btConvexHullShape* createConvexHull(const ShapeInfo::PointList& points) {
|
||||||
assert(points.size() > 0);
|
assert(points.size() > 0);
|
||||||
|
|
||||||
btConvexHullShape* hull = new btConvexHullShape();
|
btConvexHullShape* hull = new btConvexHullShape();
|
||||||
|
@ -158,6 +159,84 @@ btConvexHullShape* ShapeFactory::createConvexHull(const QVector<glm::vec3>& poin
|
||||||
return hull;
|
return hull;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// util method
|
||||||
|
btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info) {
|
||||||
|
assert(info.getType() == SHAPE_TYPE_STATIC_MESH); // should only get here for mesh shapes
|
||||||
|
|
||||||
|
const ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
|
||||||
|
assert(pointCollection.size() == 1); // should only have one mesh
|
||||||
|
|
||||||
|
const ShapeInfo::PointList& pointList = pointCollection[0];
|
||||||
|
assert(pointList.size() > 2); // should have at least one triangle's worth of points
|
||||||
|
|
||||||
|
const ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices();
|
||||||
|
assert(triangleIndices.size() > 2); // should have at least one triangle's worth of indices
|
||||||
|
|
||||||
|
// allocate mesh buffers
|
||||||
|
btIndexedMesh mesh;
|
||||||
|
int32_t numIndices = triangleIndices.size();
|
||||||
|
const int32_t VERTICES_PER_TRIANGLE = 3;
|
||||||
|
mesh.m_numTriangles = numIndices / VERTICES_PER_TRIANGLE;
|
||||||
|
if (numIndices < INT16_MAX) {
|
||||||
|
// small number of points so we can use 16-bit indices
|
||||||
|
mesh.m_triangleIndexBase = new unsigned char[sizeof(int16_t) * (size_t)numIndices];
|
||||||
|
mesh.m_indexType = PHY_SHORT;
|
||||||
|
mesh.m_triangleIndexStride = VERTICES_PER_TRIANGLE * sizeof(int16_t);
|
||||||
|
} else {
|
||||||
|
mesh.m_triangleIndexBase = new unsigned char[sizeof(int32_t) * (size_t)numIndices];
|
||||||
|
mesh.m_indexType = PHY_INTEGER;
|
||||||
|
mesh.m_triangleIndexStride = VERTICES_PER_TRIANGLE * sizeof(int32_t);
|
||||||
|
}
|
||||||
|
mesh.m_numVertices = pointList.size();
|
||||||
|
mesh.m_vertexBase = new unsigned char[VERTICES_PER_TRIANGLE * sizeof(btScalar) * (size_t)mesh.m_numVertices];
|
||||||
|
mesh.m_vertexStride = VERTICES_PER_TRIANGLE * sizeof(btScalar);
|
||||||
|
mesh.m_vertexType = PHY_FLOAT;
|
||||||
|
|
||||||
|
// copy data into buffers
|
||||||
|
btScalar* vertexData = static_cast<btScalar*>((void*)(mesh.m_vertexBase));
|
||||||
|
for (int32_t i = 0; i < mesh.m_numVertices; ++i) {
|
||||||
|
int32_t j = i * VERTICES_PER_TRIANGLE;
|
||||||
|
const glm::vec3& point = pointList[i];
|
||||||
|
vertexData[j] = point.x;
|
||||||
|
vertexData[j + 1] = point.y;
|
||||||
|
vertexData[j + 2] = point.z;
|
||||||
|
}
|
||||||
|
if (numIndices < INT16_MAX) {
|
||||||
|
int16_t* indices = static_cast<int16_t*>((void*)(mesh.m_triangleIndexBase));
|
||||||
|
for (int32_t i = 0; i < numIndices; ++i) {
|
||||||
|
indices[i] = triangleIndices[i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int32_t* indices = static_cast<int32_t*>((void*)(mesh.m_triangleIndexBase));
|
||||||
|
for (int32_t i = 0; i < numIndices; ++i) {
|
||||||
|
indices[i] = triangleIndices[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// store buffers in a new dataArray and return the pointer
|
||||||
|
// (external StaticMeshShape will own all of the data that was allocated here)
|
||||||
|
btTriangleIndexVertexArray* dataArray = new btTriangleIndexVertexArray;
|
||||||
|
dataArray->addIndexedMesh(mesh, mesh.m_indexType);
|
||||||
|
return dataArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
// util method
|
||||||
|
void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray) {
|
||||||
|
assert(dataArray);
|
||||||
|
IndexedMeshArray& meshes = dataArray->getIndexedMeshArray();
|
||||||
|
for (int32_t i = 0; i < meshes.size(); ++i) {
|
||||||
|
btIndexedMesh mesh = meshes[i];
|
||||||
|
mesh.m_numTriangles = 0;
|
||||||
|
delete [] mesh.m_triangleIndexBase;
|
||||||
|
mesh.m_triangleIndexBase = nullptr;
|
||||||
|
mesh.m_numVertices = 0;
|
||||||
|
delete [] mesh.m_vertexBase;
|
||||||
|
mesh.m_vertexBase = nullptr;
|
||||||
|
}
|
||||||
|
meshes.clear();
|
||||||
|
delete dataArray;
|
||||||
|
}
|
||||||
|
|
||||||
btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
|
btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
|
||||||
btCollisionShape* shape = NULL;
|
btCollisionShape* shape = NULL;
|
||||||
int type = info.getType();
|
int type = info.getType();
|
||||||
|
@ -179,15 +258,15 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SHAPE_TYPE_COMPOUND: {
|
case SHAPE_TYPE_COMPOUND: {
|
||||||
const QVector<QVector<glm::vec3>>& points = info.getPoints();
|
const ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
|
||||||
uint32_t numSubShapes = info.getNumSubShapes();
|
uint32_t numSubShapes = info.getNumSubShapes();
|
||||||
if (numSubShapes == 1) {
|
if (numSubShapes == 1) {
|
||||||
shape = createConvexHull(info.getPoints()[0]);
|
shape = createConvexHull(pointCollection[0]);
|
||||||
} else {
|
} else {
|
||||||
auto compound = new btCompoundShape();
|
auto compound = new btCompoundShape();
|
||||||
btTransform trans;
|
btTransform trans;
|
||||||
trans.setIdentity();
|
trans.setIdentity();
|
||||||
foreach (QVector<glm::vec3> hullPoints, points) {
|
foreach (const ShapeInfo::PointList& hullPoints, pointCollection) {
|
||||||
btConvexHullShape* hull = createConvexHull(hullPoints);
|
btConvexHullShape* hull = createConvexHull(hullPoints);
|
||||||
compound->addChildShape (trans, hull);
|
compound->addChildShape (trans, hull);
|
||||||
}
|
}
|
||||||
|
@ -195,6 +274,11 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case SHAPE_TYPE_STATIC_MESH: {
|
||||||
|
btTriangleIndexVertexArray* dataArray = createStaticMeshArray(info);
|
||||||
|
shape = new StaticMeshShape(dataArray);
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (shape) {
|
if (shape) {
|
||||||
if (glm::length2(info.getOffset()) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET) {
|
if (glm::length2(info.getOffset()) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET) {
|
||||||
|
@ -228,3 +312,14 @@ void ShapeFactory::deleteShape(btCollisionShape* shape) {
|
||||||
}
|
}
|
||||||
delete shape;
|
delete shape;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the dataArray must be created before we create the StaticMeshShape
|
||||||
|
ShapeFactory::StaticMeshShape::StaticMeshShape(btTriangleIndexVertexArray* dataArray)
|
||||||
|
: btBvhTriangleMeshShape(dataArray, true), _dataArray(dataArray) {
|
||||||
|
assert(dataArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
ShapeFactory::StaticMeshShape::~StaticMeshShape() {
|
||||||
|
deleteStaticMeshArray(_dataArray);
|
||||||
|
_dataArray = nullptr;
|
||||||
|
}
|
||||||
|
|
|
@ -20,9 +20,22 @@
|
||||||
// translates between ShapeInfo and btShape
|
// translates between ShapeInfo and btShape
|
||||||
|
|
||||||
namespace ShapeFactory {
|
namespace ShapeFactory {
|
||||||
btConvexHullShape* createConvexHull(const QVector<glm::vec3>& points);
|
|
||||||
btCollisionShape* createShapeFromInfo(const ShapeInfo& info);
|
btCollisionShape* createShapeFromInfo(const ShapeInfo& info);
|
||||||
void deleteShape(btCollisionShape* shape);
|
void deleteShape(btCollisionShape* shape);
|
||||||
|
|
||||||
|
//btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info);
|
||||||
|
//void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray);
|
||||||
|
|
||||||
|
class StaticMeshShape : public btBvhTriangleMeshShape {
|
||||||
|
public:
|
||||||
|
StaticMeshShape() = delete;
|
||||||
|
StaticMeshShape(btTriangleIndexVertexArray* dataArray);
|
||||||
|
~StaticMeshShape();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// the StaticMeshShape owns its vertex/index data
|
||||||
|
btTriangleIndexVertexArray* _dataArray;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_ShapeFactory_h
|
#endif // hifi_ShapeFactory_h
|
||||||
|
|
|
@ -32,15 +32,13 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
|
||||||
if (info.getType() == SHAPE_TYPE_NONE) {
|
if (info.getType() == SHAPE_TYPE_NONE) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (info.getType() != SHAPE_TYPE_COMPOUND) {
|
const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube
|
||||||
// Very small or large non-compound objects are not supported.
|
if (4.0f * glm::length2(info.getHalfExtents()) < MIN_SHAPE_DIAGONAL_SQUARED) {
|
||||||
float diagonal = 4.0f * glm::length2(info.getHalfExtents());
|
// tiny shapes are not supported
|
||||||
const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube
|
// qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal;
|
||||||
if (diagonal < MIN_SHAPE_DIAGONAL_SQUARED) {
|
return NULL;
|
||||||
// qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal;
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DoubleHashKey key = info.getHash();
|
DoubleHashKey key = info.getHash();
|
||||||
ShapeReference* shapeRef = _shapeMap.find(key);
|
ShapeReference* shapeRef = _shapeMap.find(key);
|
||||||
if (shapeRef) {
|
if (shapeRef) {
|
||||||
|
@ -66,8 +64,8 @@ bool ShapeManager::releaseShapeByKey(const DoubleHashKey& key) {
|
||||||
shapeRef->refCount--;
|
shapeRef->refCount--;
|
||||||
if (shapeRef->refCount == 0) {
|
if (shapeRef->refCount == 0) {
|
||||||
_pendingGarbage.push_back(key);
|
_pendingGarbage.push_back(key);
|
||||||
const int MAX_GARBAGE_CAPACITY = 255;
|
const int MAX_SHAPE_GARBAGE_CAPACITY = 255;
|
||||||
if (_pendingGarbage.size() > MAX_GARBAGE_CAPACITY) {
|
if (_pendingGarbage.size() > MAX_SHAPE_GARBAGE_CAPACITY) {
|
||||||
collectGarbage();
|
collectGarbage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1002,7 +1002,7 @@ void Model::scaleToFit() {
|
||||||
Extents modelMeshExtents = getUnscaledMeshExtents();
|
Extents modelMeshExtents = getUnscaledMeshExtents();
|
||||||
|
|
||||||
// size is our "target size in world space"
|
// size is our "target size in world space"
|
||||||
// we need to set our model scale so that the extents of the mesh, fit in a cube that size...
|
// we need to set our model scale so that the extents of the mesh, fit in a box that size...
|
||||||
glm::vec3 meshDimensions = modelMeshExtents.maximum - modelMeshExtents.minimum;
|
glm::vec3 meshDimensions = modelMeshExtents.maximum - modelMeshExtents.minimum;
|
||||||
glm::vec3 rescaleDimensions = _scaleToFitDimensions / meshDimensions;
|
glm::vec3 rescaleDimensions = _scaleToFitDimensions / meshDimensions;
|
||||||
setScaleInternal(rescaleDimensions);
|
setScaleInternal(rescaleDimensions);
|
||||||
|
|
|
@ -16,19 +16,23 @@
|
||||||
#include "NumericalConstants.h" // for MILLIMETERS_PER_METER
|
#include "NumericalConstants.h" // for MILLIMETERS_PER_METER
|
||||||
|
|
||||||
void ShapeInfo::clear() {
|
void ShapeInfo::clear() {
|
||||||
_type = SHAPE_TYPE_NONE;
|
_url.clear();
|
||||||
_halfExtents = _offset = glm::vec3(0.0f);
|
_pointCollection.clear();
|
||||||
|
_triangleIndices.clear();
|
||||||
|
_halfExtents = glm::vec3(0.0f);
|
||||||
|
_offset = glm::vec3(0.0f);
|
||||||
_doubleHashKey.clear();
|
_doubleHashKey.clear();
|
||||||
|
_type = SHAPE_TYPE_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString url) {
|
void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString url) {
|
||||||
_type = type;
|
_type = type;
|
||||||
|
_halfExtents = halfExtents;
|
||||||
switch(type) {
|
switch(type) {
|
||||||
case SHAPE_TYPE_NONE:
|
case SHAPE_TYPE_NONE:
|
||||||
_halfExtents = glm::vec3(0.0f);
|
_halfExtents = glm::vec3(0.0f);
|
||||||
break;
|
break;
|
||||||
case SHAPE_TYPE_BOX:
|
case SHAPE_TYPE_BOX:
|
||||||
_halfExtents = halfExtents;
|
|
||||||
break;
|
break;
|
||||||
case SHAPE_TYPE_SPHERE: {
|
case SHAPE_TYPE_SPHERE: {
|
||||||
// sphere radius is max of halfExtents
|
// sphere radius is max of halfExtents
|
||||||
|
@ -37,11 +41,10 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SHAPE_TYPE_COMPOUND:
|
case SHAPE_TYPE_COMPOUND:
|
||||||
|
case SHAPE_TYPE_STATIC_MESH:
|
||||||
_url = QUrl(url);
|
_url = QUrl(url);
|
||||||
_halfExtents = halfExtents;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
_halfExtents = halfExtents;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_doubleHashKey.clear();
|
_doubleHashKey.clear();
|
||||||
|
@ -61,9 +64,9 @@ void ShapeInfo::setSphere(float radius) {
|
||||||
_doubleHashKey.clear();
|
_doubleHashKey.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShapeInfo::setConvexHulls(const QVector<QVector<glm::vec3>>& points) {
|
void ShapeInfo::setPointCollection(const ShapeInfo::PointCollection& pointCollection) {
|
||||||
_points = points;
|
_pointCollection = pointCollection;
|
||||||
_type = (_points.size() > 0) ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE;
|
_type = (_pointCollection.size() > 0) ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE;
|
||||||
_doubleHashKey.clear();
|
_doubleHashKey.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,15 +86,15 @@ uint32_t ShapeInfo::getNumSubShapes() const {
|
||||||
if (_type == SHAPE_TYPE_NONE) {
|
if (_type == SHAPE_TYPE_NONE) {
|
||||||
return 0;
|
return 0;
|
||||||
} else if (_type == SHAPE_TYPE_COMPOUND) {
|
} else if (_type == SHAPE_TYPE_COMPOUND) {
|
||||||
return _points.size();
|
return _pointCollection.size();
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ShapeInfo::getMaxNumPoints() const {
|
int ShapeInfo::getLargestSubshapePointCount() const {
|
||||||
int numPoints = 0;
|
int numPoints = 0;
|
||||||
for (int i = 0; i < _points.size(); ++i) {
|
for (int i = 0; i < _pointCollection.size(); ++i) {
|
||||||
int n = _points[i].size();
|
int n = _pointCollection[i].size();
|
||||||
if (n > numPoints) {
|
if (n > numPoints) {
|
||||||
numPoints = n;
|
numPoints = n;
|
||||||
}
|
}
|
||||||
|
@ -178,34 +181,31 @@ const DoubleHashKey& ShapeInfo::getHash() const {
|
||||||
// NOTE: we cache the key so we only ever need to compute it once for any valid ShapeInfo instance.
|
// NOTE: we cache the key so we only ever need to compute it once for any valid ShapeInfo instance.
|
||||||
if (_doubleHashKey.isNull() && _type != SHAPE_TYPE_NONE) {
|
if (_doubleHashKey.isNull() && _type != SHAPE_TYPE_NONE) {
|
||||||
bool useOffset = glm::length2(_offset) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET;
|
bool useOffset = glm::length2(_offset) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET;
|
||||||
// The key is not yet cached therefore we must compute it! To this end we bypass the const-ness
|
// The key is not yet cached therefore we must compute it.
|
||||||
// of this method by grabbing a non-const pointer to "this" and a non-const reference to _doubleHashKey.
|
|
||||||
ShapeInfo* thisPtr = const_cast<ShapeInfo*>(this);
|
|
||||||
DoubleHashKey& key = thisPtr->_doubleHashKey;
|
|
||||||
|
|
||||||
// compute hash1
|
// compute hash1
|
||||||
// TODO?: provide lookup table for hash/hash2 of _type rather than recompute?
|
// TODO?: provide lookup table for hash/hash2 of _type rather than recompute?
|
||||||
uint32_t primeIndex = 0;
|
uint32_t primeIndex = 0;
|
||||||
key.computeHash((uint32_t)_type, primeIndex++);
|
_doubleHashKey.computeHash((uint32_t)_type, primeIndex++);
|
||||||
|
|
||||||
// compute hash1
|
// compute hash1
|
||||||
uint32_t hash = key.getHash();
|
uint32_t hash = _doubleHashKey.getHash();
|
||||||
for (int j = 0; j < 3; ++j) {
|
for (int j = 0; j < 3; ++j) {
|
||||||
// NOTE: 0.49f is used to bump the float up almost half a millimeter
|
// NOTE: 0.49f is used to bump the float up almost half a millimeter
|
||||||
// so the cast to int produces a round() effect rather than a floor()
|
// so the cast to int produces a round() effect rather than a floor()
|
||||||
hash ^= DoubleHashKey::hashFunction(
|
hash ^= DoubleHashKey::hashFunction(
|
||||||
(uint32_t)(_halfExtents[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _halfExtents[j]) * 0.49f),
|
(uint32_t)(_halfExtents[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _halfExtents[j]) * 0.49f),
|
||||||
primeIndex++);
|
primeIndex++);
|
||||||
if (useOffset) {
|
if (useOffset) {
|
||||||
hash ^= DoubleHashKey::hashFunction(
|
hash ^= DoubleHashKey::hashFunction(
|
||||||
(uint32_t)(_offset[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _offset[j]) * 0.49f),
|
(uint32_t)(_offset[j] * MILLIMETERS_PER_METER + copysignf(1.0f, _offset[j]) * 0.49f),
|
||||||
primeIndex++);
|
primeIndex++);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
key.setHash(hash);
|
_doubleHashKey.setHash(hash);
|
||||||
|
|
||||||
// compute hash2
|
// compute hash2
|
||||||
hash = key.getHash2();
|
hash = _doubleHashKey.getHash2();
|
||||||
for (int j = 0; j < 3; ++j) {
|
for (int j = 0; j < 3; ++j) {
|
||||||
// NOTE: 0.49f is used to bump the float up almost half a millimeter
|
// NOTE: 0.49f is used to bump the float up almost half a millimeter
|
||||||
// so the cast to int produces a round() effect rather than a floor()
|
// so the cast to int produces a round() effect rather than a floor()
|
||||||
|
@ -222,16 +222,18 @@ const DoubleHashKey& ShapeInfo::getHash() const {
|
||||||
hash += ~(floatHash << 10);
|
hash += ~(floatHash << 10);
|
||||||
hash = (hash << 16) | (hash >> 16);
|
hash = (hash << 16) | (hash >> 16);
|
||||||
}
|
}
|
||||||
key.setHash2(hash);
|
_doubleHashKey.setHash2(hash);
|
||||||
|
|
||||||
QString url = _url.toString();
|
if (_type == SHAPE_TYPE_COMPOUND || _type == SHAPE_TYPE_STATIC_MESH) {
|
||||||
if (!url.isEmpty()) {
|
QString url = _url.toString();
|
||||||
// fold the urlHash into both parts
|
if (!url.isEmpty()) {
|
||||||
QByteArray baUrl = url.toLocal8Bit();
|
// fold the urlHash into both parts
|
||||||
const char *cUrl = baUrl.data();
|
QByteArray baUrl = url.toLocal8Bit();
|
||||||
uint32_t urlHash = qChecksum(cUrl, baUrl.count());
|
const char *cUrl = baUrl.data();
|
||||||
key.setHash(key.getHash() ^ urlHash);
|
uint32_t urlHash = qChecksum(cUrl, baUrl.count());
|
||||||
key.setHash2(key.getHash2() ^ urlHash);
|
_doubleHashKey.setHash(_doubleHashKey.getHash() ^ urlHash);
|
||||||
|
_doubleHashKey.setHash2(_doubleHashKey.getHash2() ^ urlHash);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _doubleHashKey;
|
return _doubleHashKey;
|
||||||
|
|
|
@ -30,26 +30,32 @@ enum ShapeType {
|
||||||
SHAPE_TYPE_NONE,
|
SHAPE_TYPE_NONE,
|
||||||
SHAPE_TYPE_BOX,
|
SHAPE_TYPE_BOX,
|
||||||
SHAPE_TYPE_SPHERE,
|
SHAPE_TYPE_SPHERE,
|
||||||
SHAPE_TYPE_PLANE,
|
|
||||||
SHAPE_TYPE_COMPOUND,
|
|
||||||
SHAPE_TYPE_CAPSULE_X,
|
SHAPE_TYPE_CAPSULE_X,
|
||||||
SHAPE_TYPE_CAPSULE_Y,
|
SHAPE_TYPE_CAPSULE_Y,
|
||||||
SHAPE_TYPE_CAPSULE_Z,
|
SHAPE_TYPE_CAPSULE_Z,
|
||||||
SHAPE_TYPE_CYLINDER_X,
|
SHAPE_TYPE_CYLINDER_X,
|
||||||
SHAPE_TYPE_CYLINDER_Y,
|
SHAPE_TYPE_CYLINDER_Y,
|
||||||
SHAPE_TYPE_CYLINDER_Z,
|
SHAPE_TYPE_CYLINDER_Z,
|
||||||
|
SHAPE_TYPE_HULL,
|
||||||
|
SHAPE_TYPE_PLANE,
|
||||||
|
SHAPE_TYPE_COMPOUND,
|
||||||
SHAPE_TYPE_STATIC_MESH
|
SHAPE_TYPE_STATIC_MESH
|
||||||
};
|
};
|
||||||
|
|
||||||
class ShapeInfo {
|
class ShapeInfo {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
using PointList = QVector<glm::vec3>;
|
||||||
|
using PointCollection = QVector<PointList>;
|
||||||
|
using TriangleIndices = QVector<uint32_t>;
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
void setParams(ShapeType type, const glm::vec3& halfExtents, QString url="");
|
void setParams(ShapeType type, const glm::vec3& halfExtents, QString url="");
|
||||||
void setBox(const glm::vec3& halfExtents);
|
void setBox(const glm::vec3& halfExtents);
|
||||||
void setSphere(float radius);
|
void setSphere(float radius);
|
||||||
void setConvexHulls(const QVector<QVector<glm::vec3>>& points);
|
void setPointCollection(const PointCollection& pointCollection);
|
||||||
void setCapsuleY(float radius, float halfHeight);
|
void setCapsuleY(float radius, float halfHeight);
|
||||||
void setOffset(const glm::vec3& offset);
|
void setOffset(const glm::vec3& offset);
|
||||||
|
|
||||||
|
@ -57,13 +63,15 @@ public:
|
||||||
|
|
||||||
const glm::vec3& getHalfExtents() const { return _halfExtents; }
|
const glm::vec3& getHalfExtents() const { return _halfExtents; }
|
||||||
const glm::vec3& getOffset() const { return _offset; }
|
const glm::vec3& getOffset() const { return _offset; }
|
||||||
|
|
||||||
QVector<QVector<glm::vec3>>& getPoints() { return _points; }
|
|
||||||
const QVector<QVector<glm::vec3>>& getPoints() const { return _points; }
|
|
||||||
uint32_t getNumSubShapes() const;
|
uint32_t getNumSubShapes() const;
|
||||||
|
|
||||||
void appendToPoints (const QVector<glm::vec3>& newPoints) { _points << newPoints; }
|
PointCollection& getPointCollection() { return _pointCollection; }
|
||||||
int getMaxNumPoints() const;
|
const PointCollection& getPointCollection() const { return _pointCollection; }
|
||||||
|
|
||||||
|
TriangleIndices& getTriangleIndices() { return _triangleIndices; }
|
||||||
|
const TriangleIndices& getTriangleIndices() const { return _triangleIndices; }
|
||||||
|
|
||||||
|
int getLargestSubshapePointCount() const;
|
||||||
|
|
||||||
float computeVolume() const;
|
float computeVolume() const;
|
||||||
|
|
||||||
|
@ -74,12 +82,13 @@ public:
|
||||||
const DoubleHashKey& getHash() const;
|
const DoubleHashKey& getHash() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ShapeType _type = SHAPE_TYPE_NONE;
|
QUrl _url; // url for model of convex collision hulls
|
||||||
|
PointCollection _pointCollection;
|
||||||
|
TriangleIndices _triangleIndices;
|
||||||
glm::vec3 _halfExtents = glm::vec3(0.0f);
|
glm::vec3 _halfExtents = glm::vec3(0.0f);
|
||||||
glm::vec3 _offset = glm::vec3(0.0f);
|
glm::vec3 _offset = glm::vec3(0.0f);
|
||||||
DoubleHashKey _doubleHashKey;
|
mutable DoubleHashKey _doubleHashKey;
|
||||||
QVector<QVector<glm::vec3>> _points; // points for convex collision hulls
|
ShapeType _type = SHAPE_TYPE_NONE;
|
||||||
QUrl _url; // url for model of convex collision hulls
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_ShapeInfo_h
|
#endif // hifi_ShapeInfo_h
|
||||||
|
|
|
@ -492,10 +492,9 @@ void OffscreenUi::unfocusWindows() {
|
||||||
Q_ASSERT(invokeResult);
|
Q_ASSERT(invokeResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OffscreenUi::toggleMenu(const QPoint& screenPosition) {
|
void OffscreenUi::toggleMenu(const QPoint& screenPosition) { // caller should already have mapped using getReticlePosition
|
||||||
emit showDesktop(); // we really only want to do this if you're showing the menu, but for now this works
|
emit showDesktop(); // we really only want to do this if you're showing the menu, but for now this works
|
||||||
auto virtualPos = mapToVirtualScreen(screenPosition, nullptr);
|
QMetaObject::invokeMethod(_desktop, "toggleMenu", Q_ARG(QVariant, screenPosition));
|
||||||
QMetaObject::invokeMethod(_desktop, "toggleMenu", Q_ARG(QVariant, virtualPos));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -118,7 +118,7 @@ void QmlWindowClass::initQml(QVariantMap properties) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool visible = !properties.contains(VISIBILE_PROPERTY) || properties[VISIBILE_PROPERTY].toBool();
|
bool visible = !properties.contains(VISIBILE_PROPERTY) || properties[VISIBILE_PROPERTY].toBool();
|
||||||
object->setProperty(VISIBILE_PROPERTY, visible);
|
object->setProperty(OFFSCREEN_VISIBILITY_PROPERTY, visible);
|
||||||
object->setProperty(SOURCE_PROPERTY, _source);
|
object->setProperty(SOURCE_PROPERTY, _source);
|
||||||
|
|
||||||
// Forward messages received from QML on to the script
|
// Forward messages received from QML on to the script
|
||||||
|
|
|
@ -66,14 +66,8 @@ const QString SHOW_DEBUG_RAW = "Debug Draw Raw Data";
|
||||||
const QString SHOW_DEBUG_CALIBRATED = "Debug Draw Calibrated Data";
|
const QString SHOW_DEBUG_CALIBRATED = "Debug Draw Calibrated Data";
|
||||||
|
|
||||||
bool SixenseManager::isSupported() const {
|
bool SixenseManager::isSupported() const {
|
||||||
#ifdef HAVE_SIXENSE
|
#if defined(HAVE_SIXENSE) && !defined(Q_OS_OSX)
|
||||||
|
|
||||||
#if defined(Q_OS_OSX)
|
|
||||||
return QSysInfo::macVersion() <= QSysInfo::MV_MAVERICKS;
|
|
||||||
#else
|
|
||||||
return true;
|
return true;
|
||||||
#endif
|
|
||||||
|
|
||||||
#else
|
#else
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
|
@ -137,6 +131,7 @@ void SixenseManager::setSixenseFilter(bool filter) {
|
||||||
void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
|
void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
|
||||||
BAIL_IF_NOT_LOADED
|
BAIL_IF_NOT_LOADED
|
||||||
|
|
||||||
|
#ifdef HAVE_SIXENSE
|
||||||
static bool sixenseHasBeenConnected { false };
|
static bool sixenseHasBeenConnected { false };
|
||||||
if (!sixenseHasBeenConnected && sixenseIsBaseConnected(0)) {
|
if (!sixenseHasBeenConnected && sixenseIsBaseConnected(0)) {
|
||||||
sixenseHasBeenConnected = true;
|
sixenseHasBeenConnected = true;
|
||||||
|
@ -152,6 +147,7 @@ void SixenseManager::pluginUpdate(float deltaTime, const controller::InputCalibr
|
||||||
_container->requestReset();
|
_container->requestReset();
|
||||||
_inputDevice->_requestReset = false;
|
_inputDevice->_requestReset = false;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void SixenseManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
|
void SixenseManager::InputDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
|
||||||
|
|
|
@ -1,155 +0,0 @@
|
||||||
//
|
|
||||||
// SixenseSupportOSX.cpp
|
|
||||||
// libraries/input-plugins/src/input-plugins
|
|
||||||
//
|
|
||||||
// Created by Clement on 10/20/15.
|
|
||||||
// Copyright 2015 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
// Mock implementation of sixense.h to hide dynamic linking on OS X
|
|
||||||
#if defined(__APPLE__) && defined(HAVE_SIXENSE)
|
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
#include <sixense.h>
|
|
||||||
|
|
||||||
#include <QtCore/QCoreApplication>
|
|
||||||
#include <QtCore/QLibrary>
|
|
||||||
#include <QtCore/QDebug>
|
|
||||||
|
|
||||||
#ifndef SIXENSE_LIB_FILENAME
|
|
||||||
#define SIXENSE_LIB_FILENAME QCoreApplication::applicationDirPath() + "/../Frameworks/libsixense_x64"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using Library = std::unique_ptr<QLibrary>;
|
|
||||||
static Library SIXENSE;
|
|
||||||
|
|
||||||
struct Callable {
|
|
||||||
template<typename... Args>
|
|
||||||
int operator() (Args&&... args){
|
|
||||||
return reinterpret_cast<int(*)(Args...)>(function)(std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
QFunctionPointer function;
|
|
||||||
};
|
|
||||||
|
|
||||||
Callable resolve(const Library& library, const char* name) {
|
|
||||||
Q_ASSERT_X(library && library->isLoaded(), __FUNCTION__, "Sixense library not loaded");
|
|
||||||
auto function = library->resolve(name);
|
|
||||||
Q_ASSERT_X(function, __FUNCTION__, std::string("Could not resolve ").append(name).c_str());
|
|
||||||
return Callable { function };
|
|
||||||
}
|
|
||||||
#define FORWARD resolve(SIXENSE, __FUNCTION__)
|
|
||||||
|
|
||||||
|
|
||||||
void loadSixense() {
|
|
||||||
Q_ASSERT_X(!(SIXENSE && SIXENSE->isLoaded()), __FUNCTION__, "Sixense library already loaded");
|
|
||||||
SIXENSE.reset(new QLibrary(SIXENSE_LIB_FILENAME));
|
|
||||||
Q_CHECK_PTR(SIXENSE);
|
|
||||||
|
|
||||||
if (SIXENSE->load()){
|
|
||||||
qDebug() << "Loaded sixense library for hydra support -" << SIXENSE->fileName();
|
|
||||||
} else {
|
|
||||||
qDebug() << "Sixense library at" << SIXENSE->fileName() << "failed to load:" << SIXENSE->errorString();
|
|
||||||
qDebug() << "Continuing without hydra support.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void unloadSixense() {
|
|
||||||
SIXENSE->unload();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// sixense.h wrapper for OSX dynamic linking
|
|
||||||
int sixenseInit() {
|
|
||||||
loadSixense();
|
|
||||||
if (!SIXENSE || !SIXENSE->isLoaded()) {
|
|
||||||
return SIXENSE_FAILURE;
|
|
||||||
}
|
|
||||||
return FORWARD();
|
|
||||||
}
|
|
||||||
int sixenseExit() {
|
|
||||||
auto returnCode = FORWARD();
|
|
||||||
unloadSixense();
|
|
||||||
return returnCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseGetMaxBases() {
|
|
||||||
return FORWARD();
|
|
||||||
}
|
|
||||||
int sixenseSetActiveBase(int i) {
|
|
||||||
return FORWARD(i);
|
|
||||||
}
|
|
||||||
int sixenseIsBaseConnected(int i) {
|
|
||||||
return FORWARD(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseGetMaxControllers() {
|
|
||||||
return FORWARD();
|
|
||||||
}
|
|
||||||
int sixenseIsControllerEnabled(int which) {
|
|
||||||
return FORWARD(which);
|
|
||||||
}
|
|
||||||
int sixenseGetNumActiveControllers() {
|
|
||||||
return FORWARD();
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseGetHistorySize() {
|
|
||||||
return FORWARD();
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseGetData(int which, int index_back, sixenseControllerData* data) {
|
|
||||||
return FORWARD(which, index_back, data);
|
|
||||||
}
|
|
||||||
int sixenseGetAllData(int index_back, sixenseAllControllerData* data) {
|
|
||||||
return FORWARD(index_back, data);
|
|
||||||
}
|
|
||||||
int sixenseGetNewestData(int which, sixenseControllerData* data) {
|
|
||||||
return FORWARD(which, data);
|
|
||||||
}
|
|
||||||
int sixenseGetAllNewestData(sixenseAllControllerData* data) {
|
|
||||||
return FORWARD(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseSetHemisphereTrackingMode(int which_controller, int state) {
|
|
||||||
return FORWARD(which_controller, state);
|
|
||||||
}
|
|
||||||
int sixenseGetHemisphereTrackingMode(int which_controller, int* state) {
|
|
||||||
return FORWARD(which_controller, state);
|
|
||||||
}
|
|
||||||
int sixenseAutoEnableHemisphereTracking(int which_controller) {
|
|
||||||
return FORWARD(which_controller);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseSetHighPriorityBindingEnabled(int on_or_off) {
|
|
||||||
return FORWARD(on_or_off);
|
|
||||||
}
|
|
||||||
int sixenseGetHighPriorityBindingEnabled(int* on_or_off) {
|
|
||||||
return FORWARD(on_or_off);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseTriggerVibration(int controller_id, int duration_100ms, int pattern_id) {
|
|
||||||
return FORWARD(controller_id, duration_100ms, pattern_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseSetFilterEnabled(int on_or_off) {
|
|
||||||
return FORWARD(on_or_off);
|
|
||||||
}
|
|
||||||
int sixenseGetFilterEnabled(int* on_or_off) {
|
|
||||||
return FORWARD(on_or_off);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseSetFilterParams(float near_range, float near_val, float far_range, float far_val) {
|
|
||||||
return FORWARD(near_range, near_val, far_range, far_val);
|
|
||||||
}
|
|
||||||
int sixenseGetFilterParams(float* near_range, float* near_val, float* far_range, float* far_val) {
|
|
||||||
return FORWARD(near_range, near_val, far_range, far_val);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sixenseSetBaseColor(unsigned char red, unsigned char green, unsigned char blue) {
|
|
||||||
return FORWARD(red, green, blue);
|
|
||||||
}
|
|
||||||
int sixenseGetBaseColor(unsigned char* red, unsigned char* green, unsigned char* blue) {
|
|
||||||
return FORWARD(red, green, blue);
|
|
||||||
}
|
|
||||||
#endif
|
|
|
@ -1282,13 +1282,14 @@ function MyController(hand) {
|
||||||
});
|
});
|
||||||
|
|
||||||
var rayPickInfo = this.calcRayPickInfo(this.hand);
|
var rayPickInfo = this.calcRayPickInfo(this.hand);
|
||||||
this.intersectionDistance = rayPickInfo.distance;
|
|
||||||
|
|
||||||
if (rayPickInfo.entityID) {
|
if (rayPickInfo.entityID) {
|
||||||
|
this.intersectionDistance = rayPickInfo.distance;
|
||||||
this.entityPropertyCache.updateEntity(rayPickInfo.entityID);
|
this.entityPropertyCache.updateEntity(rayPickInfo.entityID);
|
||||||
if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) {
|
if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) {
|
||||||
grabbableEntities.push(rayPickInfo.entityID);
|
grabbableEntities.push(rayPickInfo.entityID);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.intersectionDistance = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (grabbableEntities.length > 0) {
|
if (grabbableEntities.length > 0) {
|
||||||
|
|
|
@ -208,9 +208,9 @@ function isShakingMouse() { // True if the person is waving the mouse around try
|
||||||
return isShaking;
|
return isShaking;
|
||||||
}
|
}
|
||||||
var NON_LINEAR_DIVISOR = 2;
|
var NON_LINEAR_DIVISOR = 2;
|
||||||
var MINIMUM_SEEK_DISTANCE = 0.01;
|
var MINIMUM_SEEK_DISTANCE = 0.1;
|
||||||
function updateSeeking() {
|
function updateSeeking(doNotStartSeeking) {
|
||||||
if (!Reticle.visible || isShakingMouse()) {
|
if (!doNotStartSeeking && (!Reticle.visible || isShakingMouse())) {
|
||||||
if (!isSeeking) {
|
if (!isSeeking) {
|
||||||
print('Start seeking mouse.');
|
print('Start seeking mouse.');
|
||||||
isSeeking = true;
|
isSeeking = true;
|
||||||
|
@ -224,8 +224,8 @@ function updateSeeking() {
|
||||||
if (!lookAt2D) { // If this happens, something has gone terribly wrong.
|
if (!lookAt2D) { // If this happens, something has gone terribly wrong.
|
||||||
print('Cannot seek without lookAt position');
|
print('Cannot seek without lookAt position');
|
||||||
isSeeking = false;
|
isSeeking = false;
|
||||||
return;
|
return; // E.g., if parallel to location in HUD
|
||||||
} // E.g., if parallel to location in HUD
|
}
|
||||||
var copy = Reticle.position;
|
var copy = Reticle.position;
|
||||||
function updateDimension(axis) {
|
function updateDimension(axis) {
|
||||||
var distanceBetween = lookAt2D[axis] - Reticle.position[axis];
|
var distanceBetween = lookAt2D[axis] - Reticle.position[axis];
|
||||||
|
@ -353,6 +353,16 @@ clickMapping.from(rightTrigger.full).when(isPointingAtOverlayStartedNonFullTrigg
|
||||||
clickMapping.from(leftTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(leftTrigger)).to(Controller.Actions.ReticleClick);
|
clickMapping.from(leftTrigger.full).when(isPointingAtOverlayStartedNonFullTrigger(leftTrigger)).to(Controller.Actions.ReticleClick);
|
||||||
clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu);
|
clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(Controller.Actions.ContextMenu);
|
||||||
clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu);
|
clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(Controller.Actions.ContextMenu);
|
||||||
|
clickMapping.from(Controller.Hardware.Keyboard.RightMouseClicked).peek().to(function () {
|
||||||
|
// Allow the reticle depth to be set correctly:
|
||||||
|
// Wait a tick for the context menu to be displayed, and then simulate a (non-hand-controller) mouse move
|
||||||
|
// so that the system updates qml state (Reticle.pointingAtSystemOverlay) before it gives us a mouseMove.
|
||||||
|
// We don't want the system code to always do this for us, because, e.g., we do not want to get a mouseMove
|
||||||
|
// after the Left/RightSecondaryThumb gives us a context menu. Only from the mouse.
|
||||||
|
Script.setTimeout(function () {
|
||||||
|
Reticle.setPosition(Reticle.position);
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
// Partial smoothed trigger is activation.
|
// Partial smoothed trigger is activation.
|
||||||
clickMapping.from(rightTrigger.partial).to(makeToggleAction(Controller.Standard.RightHand));
|
clickMapping.from(rightTrigger.partial).to(makeToggleAction(Controller.Standard.RightHand));
|
||||||
clickMapping.from(leftTrigger.partial).to(makeToggleAction(Controller.Standard.LeftHand));
|
clickMapping.from(leftTrigger.partial).to(makeToggleAction(Controller.Standard.LeftHand));
|
||||||
|
@ -386,6 +396,7 @@ function update() {
|
||||||
expireMouseCursor();
|
expireMouseCursor();
|
||||||
clearSystemLaser();
|
clearSystemLaser();
|
||||||
}
|
}
|
||||||
|
updateSeeking(true);
|
||||||
if (!handControllerLockOut.expired(now)) {
|
if (!handControllerLockOut.expired(now)) {
|
||||||
return off(); // Let them use mouse it in peace.
|
return off(); // Let them use mouse it in peace.
|
||||||
}
|
}
|
||||||
|
|
|
@ -1646,6 +1646,7 @@
|
||||||
<option value="box">Box</option>
|
<option value="box">Box</option>
|
||||||
<option value="sphere">Sphere</option>
|
<option value="sphere">Sphere</option>
|
||||||
<option value="compound">Compound</option>
|
<option value="compound">Compound</option>
|
||||||
|
<option value="static-mesh">Static Mesh</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="model-group model-section zone-section property url ">
|
<div class="model-group model-section zone-section property url ">
|
||||||
|
|
|
@ -194,23 +194,23 @@ void ShapeManagerTests::addCompoundShape() {
|
||||||
int numHullPoints = tetrahedron.size();
|
int numHullPoints = tetrahedron.size();
|
||||||
|
|
||||||
// compute the points of the hulls
|
// compute the points of the hulls
|
||||||
QVector< QVector<glm::vec3> > hulls;
|
ShapeInfo::PointCollection pointCollection;
|
||||||
int numHulls = 5;
|
int numHulls = 5;
|
||||||
glm::vec3 offsetNormal(1.0f, 0.0f, 0.0f);
|
glm::vec3 offsetNormal(1.0f, 0.0f, 0.0f);
|
||||||
for (int i = 0; i < numHulls; ++i) {
|
for (int i = 0; i < numHulls; ++i) {
|
||||||
glm::vec3 offset = (float)(i - numHulls/2) * offsetNormal;
|
glm::vec3 offset = (float)(i - numHulls/2) * offsetNormal;
|
||||||
QVector<glm::vec3> hull;
|
ShapeInfo::PointList pointList;
|
||||||
float radius = (float)(i + 1);
|
float radius = (float)(i + 1);
|
||||||
for (int j = 0; j < numHullPoints; ++j) {
|
for (int j = 0; j < numHullPoints; ++j) {
|
||||||
glm::vec3 point = radius * tetrahedron[j] + offset;
|
glm::vec3 point = radius * tetrahedron[j] + offset;
|
||||||
hull.push_back(point);
|
pointList.push_back(point);
|
||||||
}
|
}
|
||||||
hulls.push_back(hull);
|
pointCollection.push_back(pointList);
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the ShapeInfo
|
// create the ShapeInfo
|
||||||
ShapeInfo info;
|
ShapeInfo info;
|
||||||
info.setConvexHulls(hulls);
|
info.setPointCollection(hulls);
|
||||||
|
|
||||||
// create the shape
|
// create the shape
|
||||||
ShapeManager shapeManager;
|
ShapeManager shapeManager;
|
||||||
|
|
Loading…
Reference in a new issue