diff --git a/android/app/build.gradle b/android/app/build.gradle index 92dece2414..3537df033d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -28,7 +28,7 @@ android { '-DSTABLE_BUILD=' + STABLE_BUILD, '-DDISABLE_QML=OFF', '-DDISABLE_KTX_CACHE=OFF', - '-DUSE_BREAKPAD=' + (project.hasProperty("BACKTRACE_URL") && project.hasProperty("BACKTRACE_TOKEN") ? 'ON' : 'OFF'); + '-DUSE_BREAKPAD=' + (System.getenv("CMAKE_BACKTRACE_URL") && System.getenv("CMAKE_BACKTRACE_TOKEN") ? 'ON' : 'OFF'); } } signingConfigs { @@ -48,8 +48,8 @@ android { buildTypes { debug { - buildConfigField "String", "BACKTRACE_URL", "\"" + (project.hasProperty("BACKTRACE_URL") ? BACKTRACE_URL : '') + "\"" - buildConfigField "String", "BACKTRACE_TOKEN", "\"" + (project.hasProperty("BACKTRACE_TOKEN") ? BACKTRACE_TOKEN : '') + "\"" + buildConfigField "String", "BACKTRACE_URL", "\"" + (System.getenv("CMAKE_BACKTRACE_URL") ? System.getenv("CMAKE_BACKTRACE_URL") : '') + "\"" + buildConfigField "String", "BACKTRACE_TOKEN", "\"" + (System.getenv("CMAKE_BACKTRACE_TOKEN") ? System.getenv("CMAKE_BACKTRACE_TOKEN") : '') + "\"" } release { minifyEnabled false @@ -58,8 +58,8 @@ android { project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") && project.hasProperty("HIFI_ANDROID_KEY_ALIAS") && project.hasProperty("HIFI_ANDROID_KEY_PASSWORD")? signingConfigs.release : null - buildConfigField "String", "BACKTRACE_URL", "\"" + (project.hasProperty("BACKTRACE_URL") ? BACKTRACE_URL : '') + "\"" - buildConfigField "String", "BACKTRACE_TOKEN", "\"" + (project.hasProperty("BACKTRACE_TOKEN") ? BACKTRACE_TOKEN : '') + "\"" + buildConfigField "String", "BACKTRACE_URL", "\"" + (System.getenv("CMAKE_BACKTRACE_URL") ? System.getenv("CMAKE_BACKTRACE_URL") : '') + "\"" + buildConfigField "String", "BACKTRACE_TOKEN", "\"" + (System.getenv("CMAKE_BACKTRACE_TOKEN") ? System.getenv("CMAKE_BACKTRACE_TOKEN") : '') + "\"" } } @@ -74,6 +74,10 @@ android { // so our merge has to depend on the external native build variant.externalNativeBuildTasks.each { task -> variant.mergeResources.dependsOn(task) + def uploadDumpSymsTask = rootProject.getTasksByName("uploadBreakpadDumpSyms${variant.name.capitalize()}", false).first() + def runDumpSymsTask = rootProject.getTasksByName("runBreakpadDumpSyms${variant.name.capitalize()}", false).first() + runDumpSymsTask.dependsOn(task) + variant.assemble.dependsOn(uploadDumpSymsTask) } variant.mergeAssets.doLast { diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp index 4b4edbc30b..f94e5f2bbf 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -247,7 +247,7 @@ Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeLogin(JNIEnv *en env->ReleaseStringUTFChars(username_, c_username); env->ReleaseStringUTFChars(password_, c_password); - auto accountManager = AndroidHelper::instance().getAccountManager(); + auto accountManager = DependencyManager::get(); __loginCompletedListener = QAndroidJniObject(instance); __usernameChangedListener = QAndroidJniObject(usernameChangedListener); @@ -289,18 +289,18 @@ Java_io_highfidelity_hifiinterface_SplashActivity_registerLoadCompleteListener(J } JNIEXPORT jboolean JNICALL Java_io_highfidelity_hifiinterface_MainActivity_nativeIsLoggedIn(JNIEnv *env, jobject instance) { - return AndroidHelper::instance().getAccountManager()->isLoggedIn(); + return DependencyManager::get()->isLoggedIn(); } JNIEXPORT void JNICALL Java_io_highfidelity_hifiinterface_MainActivity_nativeLogout(JNIEnv *env, jobject instance) { - AndroidHelper::instance().getAccountManager()->logout(); + DependencyManager::get()->logout(); } JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_MainActivity_nativeGetDisplayName(JNIEnv *env, jobject instance) { - QString username = AndroidHelper::instance().getAccountManager()->getAccountInfo().getUsername(); + QString username = DependencyManager::get()->getAccountInfo().getUsername(); return env->NewStringUTF(username.toLatin1().data()); } diff --git a/android/build.gradle b/android/build.gradle index e2d92fe2f6..373569ca77 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -21,6 +21,7 @@ buildscript { plugins { id 'de.undercouch.download' version '3.3.0' id "cz.malohlava" version "1.0.3" + id "io.github.http-builder-ng.http-plugin" version "0.1.1" } allprojects { @@ -67,6 +68,7 @@ def baseFolder = new File(HIFI_ANDROID_PRECOMPILED) def appDir = new File(projectDir, 'app') def jniFolder = new File(appDir, 'src/main/jniLibs/arm64-v8a') def baseUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/android/' +def breakpadDumpSymsDir = new File("${appDir}/build/tmp/breakpadDumpSyms") def qtFile='qt-5.9.3_linux_armv8-libcpp_openssl.tgz' def qtChecksum='04599670ccca84bd2b15f6915568eb2d' @@ -151,11 +153,11 @@ def packages = [ checksum: '14b02795d774457a33bbc60e00a786bc' ], breakpad: [ - file: 'breakpad.zip', - versionId: '2OwvCCZrF171wnte5T44AnjTYFhhJsGJ', - checksum: 'a46062a3167dfedd4fb4916136e204d2', + file: 'breakpad.tgz', + versionId: '8VrYXz7oyc.QBxNia0BVJOUBvrFO61jI', + checksum: 'ddcb23df336b08017042ba4786db1d9e', sharedLibFolder: 'lib', - includeLibs: ['libbreakpad_client.a','libbreakpad.a'] + includeLibs: ['libbreakpad_client.a'] ] ] @@ -549,7 +551,93 @@ task cleanDependencies(type: Delete) { delete 'app/src/main/res/values/libs.xml' } +def runBreakpadDumpSyms = { buildType -> + gradle.startParameter.showStacktrace = ShowStacktrace.ALWAYS + def objDir = new File("${appDir}/build/intermediates/cmake/${buildType}/obj/arm64-v8a") + def stripDebugSymbol = "${appDir}/build/intermediates/transforms/stripDebugSymbol/${buildType}/0/lib/arm64-v8a/" + def outputDir = new File(breakpadDumpSymsDir, buildType) + if (!outputDir.exists()) { + outputDir.mkdirs() + } + + objDir.eachFileRecurse (FileType.FILES) { file -> + if (file.name.endsWith('.so')) { + def output = file.name + ".sym" + def cmdArgs = [ + file.toString(), + stripDebugSymbol + ] + def result = exec { + workingDir HIFI_ANDROID_PRECOMPILED + '/breakpad/bin' + commandLine './dump_syms' + args cmdArgs + ignoreExitValue true + standardOutput = new BufferedOutputStream(new FileOutputStream(new File(outputDir, output))) + } + } + } +} + +task runBreakpadDumpSymsDebug() { + doLast { + runBreakpadDumpSyms("debug"); + } +} + +task runBreakpadDumpSymsRelease() { + doLast { + runBreakpadDumpSyms("release"); + } +} + +task zipDumpSymsDebug(type: Zip, dependsOn: runBreakpadDumpSymsDebug) { + from (new File(breakpadDumpSymsDir, "debug").absolutePath) + archiveName "symbols-${RELEASE_NUMBER}-debug.zip" + destinationDir(new File("${appDir}/build/tmp/")) +} + +task zipDumpSymsRelease(type: Zip, dependsOn: runBreakpadDumpSymsRelease) { + from (new File(breakpadDumpSymsDir, "release").absolutePath) + archiveName "symbols-${RELEASE_NUMBER}-release.zip" + destinationDir(new File("${appDir}/build/tmp/")) +} + +task uploadBreakpadDumpSymsDebug(type:io.github.httpbuilderng.http.HttpTask, dependsOn: zipDumpSymsDebug) { + onlyIf { + System.getenv("CMAKE_BACKTRACE_URL") && System.getenv("CMAKE_BACKTRACE_SYMBOLS_TOKEN") + } + config { + request.uri = System.getenv("CMAKE_BACKTRACE_URL") + } + post { + request.uri.path = '/post' + request.uri.query = [format: 'symbols', token: System.getenv("CMAKE_BACKTRACE_SYMBOLS_TOKEN")] + request.body = new File("${appDir}/build/tmp/", "symbols-${RELEASE_NUMBER}-debug.zip").bytes + request.contentType = 'application/octet-stream' + response.success { + println ("${appDir}/build/tmp/symbols-${RELEASE_NUMBER}-debug.zip uploaded") + } + } +} + +task uploadBreakpadDumpSymsRelease(type:io.github.httpbuilderng.http.HttpTask, dependsOn: zipDumpSymsRelease) { + onlyIf { + System.getenv("CMAKE_BACKTRACE_URL") && System.getenv("CMAKE_BACKTRACE_SYMBOLS_TOKEN") + } + config { + request.uri = System.getenv("CMAKE_BACKTRACE_URL") + } + post { + request.uri.path = '/post' + request.uri.query = [format: 'symbols', token: System.getenv("CMAKE_BACKTRACE_SYMBOLS_TOKEN")] + request.body = new File("${appDir}/build/tmp/", "symbols-${RELEASE_NUMBER}-release.zip").bytes + request.contentType = 'application/octet-stream' + response.success { + println ("${appDir}/build/tmp/symbols-${RELEASE_NUMBER}-release.zip uploaded") + } + } +} // FIXME this code is prototyping the desired functionality for doing build time binary dependency resolution. // See the comment on the qtBundle task above diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 3ca8c1ecd1..8b86ba5eb2 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -504,6 +504,11 @@ void EntityServer::startDynamicDomainVerification() { QString thisDomainID = DependencyManager::get()->getDomainID().remove(QRegExp("\\{|\\}")); if (jsonObject["domain_id"].toString() != thisDomainID) { EntityItemPointer entity = tree->findEntityByEntityItemID(entityID); + if (!entity) { + qCDebug(entities) << "Entity undergoing dynamic domain verification is no longer available:" << entityID; + networkReply->deleteLater(); + return; + } if (entity->getAge() > (_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS/MSECS_PER_SECOND)) { qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString() << "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << entityID; diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index b4685b907f..2a6b78744e 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -660,9 +660,8 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username, bool isOpti // even if we have a public key for them right now, request a new one in case it has just changed JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; + callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "publicKeyJSONCallback"; - callbackParams.errorCallbackReceiver = this; callbackParams.errorCallbackMethod = "publicKeyJSONErrorCallback"; @@ -675,19 +674,19 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username, bool isOpti QNetworkAccessManager::GetOperation, callbackParams); } -QString extractUsernameFromPublicKeyRequest(QNetworkReply& requestReply) { +QString extractUsernameFromPublicKeyRequest(QNetworkReply* requestReply) { // extract the username from the request url QString username; const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key"; QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING); - if (usernameRegex.indexIn(requestReply.url().toString()) != -1) { + if (usernameRegex.indexIn(requestReply->url().toString()) != -1) { username = usernameRegex.cap(1); } return username.toLower(); } -void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) { - QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); +void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply* requestReply) { + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply->readAll()).object(); QString username = extractUsernameFromPublicKeyRequest(requestReply); bool isOptimisticKey = _inFlightPublicKeyRequests.take(username); @@ -707,8 +706,8 @@ void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) { } } -void DomainGatekeeper::publicKeyJSONErrorCallback(QNetworkReply& requestReply) { - qDebug() << "publicKey api call failed:" << requestReply.error(); +void DomainGatekeeper::publicKeyJSONErrorCallback(QNetworkReply* requestReply) { + qDebug() << "publicKey api call failed:" << requestReply->error(); QString username = extractUsernameFromPublicKeyRequest(requestReply); _inFlightPublicKeyRequests.remove(username); } @@ -893,9 +892,8 @@ void DomainGatekeeper::getGroupMemberships(const QString& username) { JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; + callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "getIsGroupMemberJSONCallback"; - callbackParams.errorCallbackReceiver = this; callbackParams.errorCallbackMethod = "getIsGroupMemberErrorCallback"; const QString GET_IS_GROUP_MEMBER_PATH = "api/v1/groups/members/%2"; @@ -906,18 +904,18 @@ void DomainGatekeeper::getGroupMemberships(const QString& username) { } -QString extractUsernameFromGroupMembershipsReply(QNetworkReply& requestReply) { +QString extractUsernameFromGroupMembershipsReply(QNetworkReply* requestReply) { // extract the username from the request url QString username; const QString GROUP_MEMBERSHIPS_URL_REGEX_STRING = "api\\/v1\\/groups\\/members\\/([A-Za-z0-9_\\.]+)"; QRegExp usernameRegex(GROUP_MEMBERSHIPS_URL_REGEX_STRING); - if (usernameRegex.indexIn(requestReply.url().toString()) != -1) { + if (usernameRegex.indexIn(requestReply->url().toString()) != -1) { username = usernameRegex.cap(1); } return username.toLower(); } -void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply& requestReply) { +void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply* requestReply) { // { // "data":{ // "username":"sethalves", @@ -934,7 +932,7 @@ void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply& requestReply) // "status":"success" // } - QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply->readAll()).object(); if (jsonObject["status"].toString() == "success") { QJsonObject data = jsonObject["data"].toObject(); QJsonObject groups = data["groups"].toObject(); @@ -953,16 +951,15 @@ void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply& requestReply) _inFlightGroupMembershipsRequests.remove(extractUsernameFromGroupMembershipsReply(requestReply)); } -void DomainGatekeeper::getIsGroupMemberErrorCallback(QNetworkReply& requestReply) { - qDebug() << "getIsGroupMember api call failed:" << requestReply.error(); +void DomainGatekeeper::getIsGroupMemberErrorCallback(QNetworkReply* requestReply) { + qDebug() << "getIsGroupMember api call failed:" << requestReply->error(); _inFlightGroupMembershipsRequests.remove(extractUsernameFromGroupMembershipsReply(requestReply)); } void DomainGatekeeper::getDomainOwnerFriendsList() { JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; + callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "getDomainOwnerFriendsListJSONCallback"; - callbackParams.errorCallbackReceiver = this; callbackParams.errorCallbackMethod = "getDomainOwnerFriendsListErrorCallback"; const QString GET_FRIENDS_LIST_PATH = "api/v1/user/friends"; @@ -974,7 +971,7 @@ void DomainGatekeeper::getDomainOwnerFriendsList() { } -void DomainGatekeeper::getDomainOwnerFriendsListJSONCallback(QNetworkReply& requestReply) { +void DomainGatekeeper::getDomainOwnerFriendsListJSONCallback(QNetworkReply* requestReply) { // { // status: "success", // data: { @@ -991,7 +988,7 @@ void DomainGatekeeper::getDomainOwnerFriendsListJSONCallback(QNetworkReply& requ // ] // } // } - QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply->readAll()).object(); if (jsonObject["status"].toString() == "success") { _domainOwnerFriends.clear(); QJsonArray friends = jsonObject["data"].toObject()["friends"].toArray(); @@ -1003,8 +1000,8 @@ void DomainGatekeeper::getDomainOwnerFriendsListJSONCallback(QNetworkReply& requ } } -void DomainGatekeeper::getDomainOwnerFriendsListErrorCallback(QNetworkReply& requestReply) { - qDebug() << "getDomainOwnerFriendsList api call failed:" << requestReply.error(); +void DomainGatekeeper::getDomainOwnerFriendsListErrorCallback(QNetworkReply* requestReply) { + qDebug() << "getDomainOwnerFriendsList api call failed:" << requestReply->error(); } void DomainGatekeeper::refreshGroupsCache() { diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index 2cb9b4c8a9..f8d79179d6 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -51,14 +51,14 @@ public slots: void processICEPingReplyPacket(QSharedPointer message); void processICEPeerInformationPacket(QSharedPointer message); - void publicKeyJSONCallback(QNetworkReply& requestReply); - void publicKeyJSONErrorCallback(QNetworkReply& requestReply); + void publicKeyJSONCallback(QNetworkReply* requestReply); + void publicKeyJSONErrorCallback(QNetworkReply* requestReply); - void getIsGroupMemberJSONCallback(QNetworkReply& requestReply); - void getIsGroupMemberErrorCallback(QNetworkReply& requestReply); + void getIsGroupMemberJSONCallback(QNetworkReply* requestReply); + void getIsGroupMemberErrorCallback(QNetworkReply* requestReply); - void getDomainOwnerFriendsListJSONCallback(QNetworkReply& requestReply); - void getDomainOwnerFriendsListErrorCallback(QNetworkReply& requestReply); + void getDomainOwnerFriendsListJSONCallback(QNetworkReply* requestReply); + void getDomainOwnerFriendsListErrorCallback(QNetworkReply* requestReply); void refreshGroupsCache(); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index a34deebc95..eccd1c1e20 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -514,13 +514,13 @@ void DomainServer::getTemporaryName(bool force) { // request a temporary name from the metaverse auto accountManager = DependencyManager::get(); - JSONCallbackParameters callbackParameters { this, "handleTempDomainSuccess", this, "handleTempDomainError" }; + JSONCallbackParameters callbackParameters { this, "handleTempDomainSuccess", "handleTempDomainError" }; accountManager->sendRequest("/api/v1/domains/temporary", AccountManagerAuth::None, QNetworkAccessManager::PostOperation, callbackParameters); } -void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) { - QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); +void DomainServer::handleTempDomainSuccess(QNetworkReply* requestReply) { + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply->readAll()).object(); // grab the information for the new domain static const QString DATA_KEY = "data"; @@ -565,7 +565,7 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) { } } -void DomainServer::handleTempDomainError(QNetworkReply& requestReply) { +void DomainServer::handleTempDomainError(QNetworkReply* requestReply) { qWarning() << "A temporary name was requested but there was an error creating one. Please try again via domain-server relaunch" << "or from the domain-server settings."; } @@ -1345,7 +1345,7 @@ void DomainServer::sendPendingTransactionsToServer() { JSONCallbackParameters transactionCallbackParams; - transactionCallbackParams.jsonCallbackReceiver = this; + transactionCallbackParams.callbackReceiver = this; transactionCallbackParams.jsonCallbackMethod = "transactionJSONCallback"; while (i != _pendingAssignmentCredits.end()) { @@ -1449,11 +1449,11 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) { DependencyManager::get()->sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(getID())), AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation, - JSONCallbackParameters(nullptr, QString(), this, "handleMetaverseHeartbeatError"), + JSONCallbackParameters(this, QString(), "handleMetaverseHeartbeatError"), domainUpdateJSON.toUtf8()); } -void DomainServer::handleMetaverseHeartbeatError(QNetworkReply& requestReply) { +void DomainServer::handleMetaverseHeartbeatError(QNetworkReply* requestReply) { if (!_metaverseHeartbeatTimer) { // avoid rehandling errors from the same issue return; @@ -1462,13 +1462,13 @@ void DomainServer::handleMetaverseHeartbeatError(QNetworkReply& requestReply) { // only attempt to grab a new temporary name if we're already a temporary domain server if (_type == MetaverseTemporaryDomain) { // check if we need to force a new temporary domain name - switch (requestReply.error()) { + 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(); + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply->readAll()).object(); auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY]; if (!tokenFailure.isNull()) { @@ -1531,9 +1531,8 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() { // make sure we hear about failure so we can retry JSONCallbackParameters callbackParameters; - callbackParameters.errorCallbackReceiver = this; + callbackParameters.callbackReceiver = this; callbackParameters.errorCallbackMethod = "handleFailedICEServerAddressUpdate"; - callbackParameters.jsonCallbackReceiver = this; callbackParameters.jsonCallbackMethod = "handleSuccessfulICEServerAddressUpdate"; static bool printedIceServerMessage = false; @@ -1552,7 +1551,7 @@ void DomainServer::sendICEServerAddressToMetaverseAPI() { domainUpdateJSON.toUtf8()); } -void DomainServer::handleSuccessfulICEServerAddressUpdate(QNetworkReply& requestReply) { +void DomainServer::handleSuccessfulICEServerAddressUpdate(QNetworkReply* requestReply) { _sendICEServerAddressToMetaverseAPIInProgress = false; if (_sendICEServerAddressToMetaverseAPIRedo) { qDebug() << "ice-server address updated with metaverse, but has since changed. redoing update..."; @@ -1563,7 +1562,7 @@ void DomainServer::handleSuccessfulICEServerAddressUpdate(QNetworkReply& request } } -void DomainServer::handleFailedICEServerAddressUpdate(QNetworkReply& requestReply) { +void DomainServer::handleFailedICEServerAddressUpdate(QNetworkReply* requestReply) { _sendICEServerAddressToMetaverseAPIInProgress = false; if (_sendICEServerAddressToMetaverseAPIRedo) { // if we have new data, retry right away, even though the previous attempt didn't go well. @@ -1573,7 +1572,7 @@ void DomainServer::handleFailedICEServerAddressUpdate(QNetworkReply& requestRepl const int ICE_SERVER_UPDATE_RETRY_MS = 2 * 1000; qWarning() << "Failed to update ice-server address with High Fidelity Metaverse - error was" - << requestReply.errorString(); + << requestReply->errorString(); qWarning() << "\tRe-attempting in" << ICE_SERVER_UPDATE_RETRY_MS / 1000 << "seconds"; QTimer::singleShot(ICE_SERVER_UPDATE_RETRY_MS, this, SLOT(sendICEServerAddressToMetaverseAPI())); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 3703877fa1..c69267f379 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -108,10 +108,10 @@ private slots: void sendHeartbeatToIceServer(); void handleConnectedNode(SharedNodePointer newNode); - void handleTempDomainSuccess(QNetworkReply& requestReply); - void handleTempDomainError(QNetworkReply& requestReply); + void handleTempDomainSuccess(QNetworkReply* requestReply); + void handleTempDomainError(QNetworkReply* requestReply); - void handleMetaverseHeartbeatError(QNetworkReply& requestReply); + void handleMetaverseHeartbeatError(QNetworkReply* requestReply); void queuedQuit(QString quitMessage, int exitCode); @@ -121,8 +121,8 @@ private slots: void handleICEHostInfo(const QHostInfo& hostInfo); void sendICEServerAddressToMetaverseAPI(); - void handleSuccessfulICEServerAddressUpdate(QNetworkReply& requestReply); - void handleFailedICEServerAddressUpdate(QNetworkReply& requestReply); + void handleSuccessfulICEServerAddressUpdate(QNetworkReply* requestReply); + void handleFailedICEServerAddressUpdate(QNetworkReply* requestReply); void updateReplicatedNodes(); void updateDownstreamNodes(); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index c46b2eda49..2bcaa8899e 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1815,9 +1815,8 @@ void DomainServerSettingsManager::apiRefreshGroupInformation() { void DomainServerSettingsManager::apiGetGroupID(const QString& groupName) { JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; + callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "apiGetGroupIDJSONCallback"; - callbackParams.errorCallbackReceiver = this; callbackParams.errorCallbackMethod = "apiGetGroupIDErrorCallback"; const QString GET_GROUP_ID_PATH = "api/v1/groups/names/%1"; @@ -1826,7 +1825,7 @@ void DomainServerSettingsManager::apiGetGroupID(const QString& groupName) { QNetworkAccessManager::GetOperation, callbackParams); } -void DomainServerSettingsManager::apiGetGroupIDJSONCallback(QNetworkReply& requestReply) { +void DomainServerSettingsManager::apiGetGroupIDJSONCallback(QNetworkReply* requestReply) { // { // "data":{ // "groups":[{ @@ -1857,7 +1856,7 @@ void DomainServerSettingsManager::apiGetGroupIDJSONCallback(QNetworkReply& reque // }, // "status":"success" // } - QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply->readAll()).object(); if (jsonObject["status"].toString() == "success") { QJsonArray groups = jsonObject["data"].toObject()["groups"].toArray(); for (int i = 0; i < groups.size(); i++) { @@ -1876,15 +1875,14 @@ void DomainServerSettingsManager::apiGetGroupIDJSONCallback(QNetworkReply& reque } } -void DomainServerSettingsManager::apiGetGroupIDErrorCallback(QNetworkReply& requestReply) { - qDebug() << "******************** getGroupID api call failed:" << requestReply.error(); +void DomainServerSettingsManager::apiGetGroupIDErrorCallback(QNetworkReply* requestReply) { + qDebug() << "******************** getGroupID api call failed:" << requestReply->error(); } void DomainServerSettingsManager::apiGetGroupRanks(const QUuid& groupID) { JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; + callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "apiGetGroupRanksJSONCallback"; - callbackParams.errorCallbackReceiver = this; callbackParams.errorCallbackMethod = "apiGetGroupRanksErrorCallback"; const QString GET_GROUP_RANKS_PATH = "api/v1/groups/%1/ranks"; @@ -1893,7 +1891,7 @@ void DomainServerSettingsManager::apiGetGroupRanks(const QUuid& groupID) { QNetworkAccessManager::GetOperation, callbackParams); } -void DomainServerSettingsManager::apiGetGroupRanksJSONCallback(QNetworkReply& requestReply) { +void DomainServerSettingsManager::apiGetGroupRanksJSONCallback(QNetworkReply* requestReply) { // { // "data":{ // "groups":{ @@ -1926,7 +1924,7 @@ void DomainServerSettingsManager::apiGetGroupRanksJSONCallback(QNetworkReply& re // } bool changed = false; - QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply->readAll()).object(); if (jsonObject["status"].toString() == "success") { QJsonObject groups = jsonObject["data"].toObject()["groups"].toObject(); @@ -1972,8 +1970,8 @@ void DomainServerSettingsManager::apiGetGroupRanksJSONCallback(QNetworkReply& re } } -void DomainServerSettingsManager::apiGetGroupRanksErrorCallback(QNetworkReply& requestReply) { - qDebug() << "******************** getGroupRanks api call failed:" << requestReply.error(); +void DomainServerSettingsManager::apiGetGroupRanksErrorCallback(QNetworkReply* requestReply) { + qDebug() << "******************** getGroupRanks api call failed:" << requestReply->error(); } void DomainServerSettingsManager::recordGroupMembership(const QString& name, const QUuid groupID, QUuid rankID) { diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 252ff79ae2..bcd33c2bb0 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -133,10 +133,10 @@ signals: void settingsUpdated(); public slots: - void apiGetGroupIDJSONCallback(QNetworkReply& requestReply); - void apiGetGroupIDErrorCallback(QNetworkReply& requestReply); - void apiGetGroupRanksJSONCallback(QNetworkReply& requestReply); - void apiGetGroupRanksErrorCallback(QNetworkReply& requestReply); + void apiGetGroupIDJSONCallback(QNetworkReply* requestReply); + void apiGetGroupIDErrorCallback(QNetworkReply* requestReply); + void apiGetGroupRanksJSONCallback(QNetworkReply* requestReply); + void apiGetGroupRanksErrorCallback(QNetworkReply* requestReply); private slots: void processSettingsRequestPacket(QSharedPointer message); diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 4708bfdebe..6cbd1c4837 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -126,10 +126,12 @@ Item { activeFocusOnPress: true ShortcutText { + z: 10 anchors { - verticalCenter: usernameField.textFieldLabel.verticalCenter - left: usernameField.textFieldLabel.right - leftMargin: 10 + left: usernameField.left + top: usernameField.top + leftMargin: usernameField.textFieldLabel.contentWidth + 10 + topMargin: -19 } text: "Forgot Username?" @@ -154,10 +156,12 @@ Item { activeFocusOnPress: true ShortcutText { + z: 10 anchors { - verticalCenter: passwordField.textFieldLabel.verticalCenter - left: passwordField.textFieldLabel.right - leftMargin: 10 + left: passwordField.left + top: passwordField.top + leftMargin: passwordField.textFieldLabel.contentWidth + 10 + topMargin: -19 } text: "Forgot Password?" @@ -168,6 +172,7 @@ Item { onLinkActivated: loginDialog.openUrl(link) } + onFocusChanged: { root.text = ""; root.isPassword = true; diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index ba50b7f238..cc1ba49984 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -129,12 +129,10 @@ Rectangle { id: stereoMic spacing: muteMic.spacing; text: qsTr("Enable stereo input"); - checked: AudioScriptingInterface.isStereoInput(); + checked: AudioScriptingInterface.isStereoInput; onClicked: { - var success = AudioScriptingInterface.setStereoInput(checked); - if (!success) { - checked = !checked; - } + AudioScriptingInterface.isStereoInput = checked; + checked = Qt.binding(function() { return AudioScriptingInterface.isStereoInput; }); // restore binding } } } diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index b43372da5c..9f1d307f0e 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -646,6 +646,7 @@ Item { height: 40; enabled: root.hasPermissionToRezThis && MyAvatar.skeletonModelURL !== root.itemHref && + !root.wornEntityID && root.valid; onHoveredChanged: { diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 0d2acf4ec3..8a5b1fb0e7 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -957,7 +957,7 @@ Rectangle { function updateCurrentlyWornWearables(wearables) { for (var i = 0; i < purchasesModel.count; i++) { for (var j = 0; j < wearables.length; j++) { - if (purchasesModel.get(i).itemType === "wearable" && + if (purchasesModel.get(i).item_type === "wearable" && wearables[j].entityCertID === purchasesModel.get(i).certificate_id && wearables[j].entityEdition.toString() === purchasesModel.get(i).edition_number) { purchasesModel.setProperty(i, 'wornEntityID', wearables[j].entityID); diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index d76fe0d8a2..b453509712 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -253,7 +253,11 @@ At the moment, there is currently no way to convert HFC to other currencies. Sta } else if (link === "#blockchain") { Qt.openUrlExternally("https://docs.highfidelity.com/high-fidelity-commerce"); } else if (link === "#bank") { - Qt.openUrlExternally("hifi://BankOfHighFidelity"); + if ((Account.metaverseServerURL).toString().indexOf("staging") >= 0) { + Qt.openUrlExternally("hifi://hifiqa-master-metaverse-staging"); // So that we can test in staging. + } else { + Qt.openUrlExternally("hifi://BankOfHighFidelity"); + } } else if (link === "#support") { Qt.openUrlExternally("mailto:support@highfidelity.com"); } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 354673a3c2..3e0a56b4c5 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -252,64 +252,6 @@ Item { anchors.bottom: parent.bottom; anchors.left: parent.left; anchors.right: parent.right; - - Item { - // On empty history. We don't want to flash and then replace, so don't show until we know we should. - // The history is empty when it contains 1 item (the pending item count) AND there are no pending items. - visible: transactionHistoryModel.count === 1 && - transactionHistoryModel.retrievedAtLeastOnePage && - transactionHistoryModel.get(0).count === 0; - anchors.centerIn: parent; - width: parent.width - 12; - height: parent.height; - - HifiControlsUit.Separator { - colorScheme: 1; - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: parent.top; - } - - RalewayRegular { - id: noActivityText; - text: "Congrats! Your wallet is all set!

" + - "Where's my HFC?
" + - "High Fidelity commerce is in open beta right now. Want more HFC? Get it by meeting with a banker at " + - "BankOfHighFidelity!" - // Text size - size: 22; - // Style - color: hifi.colors.blueAccent; - anchors.top: parent.top; - anchors.topMargin: 36; - anchors.left: parent.left; - anchors.leftMargin: 12; - anchors.right: parent.right; - anchors.rightMargin: 12; - height: paintedHeight; - wrapMode: Text.WordWrap; - horizontalAlignment: Text.AlignHCenter; - - onLinkActivated: { - sendSignalToWallet({ method: "transactionHistory_goToBank" }); - } - } - - HifiControlsUit.Button { - id: bankButton; - color: hifi.buttons.blue; - colorScheme: hifi.colorSchemes.dark; - anchors.top: noActivityText.bottom; - anchors.topMargin: 30; - anchors.horizontalCenter: parent.horizontalCenter; - width: parent.width/2; - height: 50; - text: "VISIT BANK OF HIGH FIDELITY"; - onClicked: { - sendSignalToWallet({ method: "transactionHistory_goToBank" }); - } - } - } ListView { id: transactionHistory; @@ -411,6 +353,64 @@ Item { } } } + + Item { + // On empty history. We don't want to flash and then replace, so don't show until we know we should. + // The history is empty when it contains 1 item (the pending item count) AND there are no pending items. + visible: transactionHistoryModel.count === 1 && + transactionHistoryModel.retrievedAtLeastOnePage && + transactionHistoryModel.get(0).count === 0; + anchors.centerIn: parent; + width: parent.width - 12; + height: parent.height; + + HifiControlsUit.Separator { + colorScheme: 1; + anchors.left: parent.left; + anchors.right: parent.right; + anchors.top: parent.top; + } + + RalewayRegular { + id: noActivityText; + text: "Congrats! Your wallet is all set!

" + + "Where's my HFC?
" + + "High Fidelity commerce is in open beta right now. Want more HFC? Get it by meeting with a banker at " + + "BankOfHighFidelity!" + // Text size + size: 22; + // Style + color: hifi.colors.blueAccent; + anchors.top: parent.top; + anchors.topMargin: 36; + anchors.left: parent.left; + anchors.leftMargin: 12; + anchors.right: parent.right; + anchors.rightMargin: 12; + height: paintedHeight; + wrapMode: Text.WordWrap; + horizontalAlignment: Text.AlignHCenter; + + onLinkActivated: { + sendSignalToWallet({ method: "transactionHistory_goToBank" }); + } + } + + HifiControlsUit.Button { + id: bankButton; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; + anchors.top: noActivityText.bottom; + anchors.topMargin: 30; + anchors.horizontalCenter: parent.horizontalCenter; + width: parent.width/2; + height: 50; + text: "VISIT BANK OF HIGH FIDELITY"; + onClicked: { + sendSignalToWallet({ method: "transactionHistory_goToBank" }); + } + } + } } } diff --git a/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml index cb4913f999..861de001d8 100644 --- a/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml +++ b/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml @@ -17,7 +17,7 @@ PreferencesDialog { id: root objectName: "GeneralPreferencesDialog" title: "General Settings" - showCategories: ["User Interface", "HMD", "Snapshots", "Privacy"] + showCategories: ["User Interface", "Mouse Sensitivity", "HMD", "Snapshots", "Privacy"] property var settings: Settings { category: root.objectName property alias x: root.x diff --git a/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml b/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml index 63801019b9..4f1100f20b 100644 --- a/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml @@ -32,6 +32,6 @@ StackView { TabletPreferencesDialog { id: root objectName: "TabletGeneralPreferences" - showCategories: ["User Interface", "HMD", "Snapshots", "Privacy"] + showCategories: ["User Interface", "Mouse Sensitivity", "HMD", "Snapshots", "Privacy"] } } diff --git a/interface/src/AndroidHelper.cpp b/interface/src/AndroidHelper.cpp index f0c82929ff..1e73d58939 100644 --- a/interface/src/AndroidHelper.cpp +++ b/interface/src/AndroidHelper.cpp @@ -10,7 +10,6 @@ // #include "AndroidHelper.h" #include -#include #include "Application.h" #if defined(qApp) @@ -22,25 +21,6 @@ AndroidHelper::AndroidHelper() { } AndroidHelper::~AndroidHelper() { - workerThread.quit(); - workerThread.wait(); -} - -void AndroidHelper::init() { - workerThread.start(); - _accountManager = QSharedPointer(new AccountManager, &QObject::deleteLater); - _accountManager->setIsAgent(true); - _accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL()); - _accountManager->setSessionID(DependencyManager::get()->getSessionID()); - connect(_accountManager.data(), &AccountManager::loginComplete, [](const QUrl& authURL) { - DependencyManager::get()->setAccountInfo(AndroidHelper::instance().getAccountManager()->getAccountInfo()); - DependencyManager::get()->setAuthURL(authURL); - }); - - connect(_accountManager.data(), &AccountManager::logoutComplete, [] () { - DependencyManager::get()->logout(); - }); - _accountManager->moveToThread(&workerThread); } void AndroidHelper::requestActivity(const QString &activityName, const bool backToScene, QList args) { diff --git a/interface/src/AndroidHelper.h b/interface/src/AndroidHelper.h index 5db51f5180..2c536268d6 100644 --- a/interface/src/AndroidHelper.h +++ b/interface/src/AndroidHelper.h @@ -13,8 +13,6 @@ #define hifi_Android_Helper_h #include -#include -#include class AndroidHelper : public QObject { Q_OBJECT @@ -23,7 +21,6 @@ public: static AndroidHelper instance; return instance; } - void init(); void requestActivity(const QString &activityName, const bool backToScene, QList args = QList()); void notifyLoadComplete(); void notifyEnterForeground(); @@ -32,7 +29,6 @@ public: void performHapticFeedback(int duration); void processURL(const QString &url); - QSharedPointer getAccountManager() { return _accountManager; } AndroidHelper(AndroidHelper const&) = delete; void operator=(AndroidHelper const&) = delete; @@ -50,8 +46,6 @@ signals: private: AndroidHelper(); ~AndroidHelper(); - QSharedPointer _accountManager; - QThread workerThread; }; #endif diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2033e8ee8e..a60d72073c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1081,6 +1081,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo auto nodeList = DependencyManager::get(); nodeList->startThread(); + // move the AddressManager to the NodeList thread so that domain resets due to domain changes always occur + // before we tell MyAvatar to go to a new location in the new domain + auto addressManager = DependencyManager::get(); + addressManager->moveToThread(nodeList->thread()); + const char** constArgv = const_cast(argv); if (cmdOptionExists(argc, constArgv, "--disableWatchdog")) { DISABLE_WATCHDOG = true; @@ -1231,8 +1236,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo accountManager->setIsAgent(true); accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL()); - auto addressManager = DependencyManager::get(); - // use our MyAvatar position and quat for address manager path addressManager->setPositionGetter([this]{ return getMyAvatar()->getWorldPosition(); }); addressManager->setOrientationGetter([this]{ return getMyAvatar()->getWorldOrientation(); }); @@ -2256,7 +2259,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo qCDebug(interfaceapp) << "Metaverse session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID()); #if defined(Q_OS_ANDROID) - AndroidHelper::instance().init(); connect(&AndroidHelper::instance(), &AndroidHelper::enterBackground, this, &Application::enterBackground); connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground); AndroidHelper::instance().notifyLoadComplete(); @@ -2294,6 +2296,7 @@ void Application::domainConnectionRefused(const QString& reasonMessage, int reas QString message = "Unable to connect to the location you are visiting.\n"; message += reasonMessage; OffscreenUi::asyncWarning("", message); + getMyAvatar()->setWorldVelocity(glm::vec3(0.0f)); break; } default: diff --git a/interface/src/CrashHandler_Breakpad.cpp b/interface/src/CrashHandler_Breakpad.cpp index fe4979853e..f2a174b6ea 100644 --- a/interface/src/CrashHandler_Breakpad.cpp +++ b/interface/src/CrashHandler_Breakpad.cpp @@ -24,6 +24,7 @@ #include #include +#include google_breakpad::ExceptionHandler* gBreakpadHandler; @@ -43,7 +44,23 @@ QString obbDir() { return dataAbsPath; } +void flushAnnotations() { + QSettings settings(obbDir() + "/annotations.json", JSON_FORMAT); + settings.clear(); + settings.beginGroup("Annotations"); + for (auto k : annotations.keys()) { + settings.setValue(k, annotations.value(k)); + } + settings.endGroup(); + settings.sync(); +} + bool startCrashHandler() { + annotations["version"] = BuildInfo::VERSION; + annotations["build_number"] = BuildInfo::BUILD_NUMBER; + annotations["build_type"] = BuildInfo::BUILD_TYPE_STRING; + + flushAnnotations(); gBreakpadHandler = new google_breakpad::ExceptionHandler( google_breakpad::MinidumpDescriptor(obbDir().toStdString()), @@ -56,15 +73,7 @@ void setCrashAnnotation(std::string name, std::string value) { QString qName = QString::fromStdString(name); QString qValue = QString::fromStdString(value); annotations[qName] = qValue; - - QSettings settings(obbDir() + "/annotations.json", JSON_FORMAT); - settings.clear(); - settings.beginGroup("Annotations"); - for (auto k : annotations.keys()) { - settings.setValue(k, annotations.value(k)); - } - settings.endGroup(); - settings.sync(); + flushAnnotations(); } #endif diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp index d39aaa4f1a..e3a99475ef 100644 --- a/interface/src/DiscoverabilityManager.cpp +++ b/interface/src/DiscoverabilityManager.cpp @@ -97,7 +97,7 @@ void DiscoverabilityManager::updateLocation() { locationObject.insert(AVAILABILITY_KEY_IN_LOCATION, findableByString(static_cast(_mode.get()))); JSONCallbackParameters callbackParameters; - callbackParameters.jsonCallbackReceiver = this; + callbackParameters.callbackReceiver = this; callbackParameters.jsonCallbackMethod = "handleHeartbeatResponse"; // figure out if we'll send a fresh location or just a simple heartbeat @@ -121,7 +121,7 @@ void DiscoverabilityManager::updateLocation() { // we still send a heartbeat to the metaverse server for stats collection JSONCallbackParameters callbackParameters; - callbackParameters.jsonCallbackReceiver = this; + callbackParameters.callbackReceiver = this; callbackParameters.jsonCallbackMethod = "handleHeartbeatResponse"; accountManager->sendRequest(API_USER_HEARTBEAT_PATH, AccountManagerAuth::Optional, @@ -136,7 +136,7 @@ void DiscoverabilityManager::updateLocation() { setCrashAnnotation("address", currentAddress.toString().toStdString()); } -void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply& requestReply) { +void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply* requestReply) { auto dataObject = AccountManager::dataObjectFromResponse(requestReply); if (!dataObject.isEmpty()) { diff --git a/interface/src/DiscoverabilityManager.h b/interface/src/DiscoverabilityManager.h index 0c62ad5663..fc0b1fa759 100644 --- a/interface/src/DiscoverabilityManager.h +++ b/interface/src/DiscoverabilityManager.h @@ -51,7 +51,7 @@ public: static QString findableByString(Discoverability::Mode discoverabilityMode); private slots: - void handleHeartbeatResponse(QNetworkReply& requestReply); + void handleHeartbeatResponse(QNetworkReply* requestReply); private: DiscoverabilityManager(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index a102f2a42e..fcf7b53536 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -283,7 +283,7 @@ Menu::Menu() { MenuWrapper* settingsMenu = addMenu("Settings"); // Settings > General... - action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::Preferences, Qt::CTRL | Qt::Key_G, nullptr, nullptr, QAction::PreferencesRole); + action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::Preferences, Qt::CTRL | Qt::Key_G, nullptr, nullptr); connect(action, &QAction::triggered, [] { qApp->showDialog(QString("hifi/dialogs/GeneralPreferencesDialog.qml"), QString("hifi/tablet/TabletGeneralPreferences.qml"), "GeneralPreferencesDialog"); diff --git a/interface/src/SecondaryCamera.cpp b/interface/src/SecondaryCamera.cpp index b9a767f700..559cb140b5 100644 --- a/interface/src/SecondaryCamera.cpp +++ b/interface/src/SecondaryCamera.cpp @@ -11,25 +11,16 @@ #include "SecondaryCamera.h" +#include +#include + #include #include -#include #include "Application.h" using RenderArgsPointer = std::shared_ptr; -void MainRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred) { - task.addJob("RenderShadowTask", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1); - const auto items = task.addJob("FetchCullSort", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1); - assert(items.canCast()); - if (!isDeferred) { - task.addJob("Forward", items); - } else { - task.addJob("RenderDeferredTask", items); - } -} - class SecondaryCameraJob { // Changes renderContext for our framebuffer and view. public: using Config = SecondaryCameraJobConfig; @@ -213,10 +204,10 @@ void SecondaryCameraRenderTask::build(JobModel& task, const render::Varying& inp const auto cachedArg = task.addJob("SecondaryCamera"); const auto items = task.addJob("FetchCullSort", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1); assert(items.canCast()); - if (!isDeferred) { - task.addJob("Forward", items); + if (isDeferred) { + task.addJob("RenderDeferredTask", items, false); } else { - task.addJob("RenderDeferredTask", items); + task.addJob("Forward", items); } task.addJob("EndSecondaryCamera", cachedArg); } \ No newline at end of file diff --git a/interface/src/SecondaryCamera.h b/interface/src/SecondaryCamera.h index 3d9e52617c..3c8540c081 100644 --- a/interface/src/SecondaryCamera.h +++ b/interface/src/SecondaryCamera.h @@ -12,23 +12,11 @@ #pragma once #ifndef hifi_SecondaryCamera_h #define hifi_SecondaryCamera_h - -#include + #include -#include -#include #include #include -class MainRenderTask { -public: - using JobModel = render::Task::Model; - - MainRenderTask() {} - - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred = true); -}; - class SecondaryCameraJobConfig : public render::Task::Config { // Exposes secondary camera parameters to JavaScript. Q_OBJECT Q_PROPERTY(QUuid attachedEntityId MEMBER attachedEntityId NOTIFY dirty) // entity whose properties define camera position and orientation diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 4d133706e6..8a25c21946 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -36,13 +36,13 @@ #include #include #include -#include #include #include "Application.h" #include "InterfaceLogging.h" #include "Menu.h" #include "MyAvatar.h" +#include "OtherAvatar.h" #include "SceneScriptingInterface.h" // 50 times per second - target is 45hz, but this helps account for any small deviations @@ -186,11 +186,21 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { uint64_t updateExpiry = startTime + UPDATE_BUDGET; int numAvatarsUpdated = 0; int numAVatarsNotUpdated = 0; + bool physicsEnabled = qApp->isPhysicsEnabled(); render::Transaction transaction; while (!sortedAvatars.empty()) { const SortableAvatar& sortData = sortedAvatars.top(); const auto avatar = std::static_pointer_cast(sortData.getAvatar()); + const auto otherAvatar = std::static_pointer_cast(sortData.getAvatar()); + + // if the geometry is loaded then turn off the orb + if (avatar->getSkeletonModel()->isLoaded()) { + // remove the orb if it is there + otherAvatar->removeOrb(); + } else { + otherAvatar->updateOrbPosition(); + } bool ignoring = DependencyManager::get()->isPersonalMutingNode(avatar->getID()); if (ignoring) { @@ -202,7 +212,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { if (_shouldRender) { avatar->ensureInScene(avatar, qApp->getMain3DScene()); } - if (!avatar->isInPhysicsSimulation()) { + if (physicsEnabled && !avatar->isInPhysicsSimulation()) { ShapeInfo shapeInfo; avatar->computeShapeInfo(shapeInfo); btCollisionShape* shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index 6fc1bd8196..50c715b14a 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -154,6 +154,10 @@ const QUuid AvatarMotionState::getObjectID() const { return _avatar->getSessionUUID(); } +QString AvatarMotionState::getName() const { + return _avatar->getName(); +} + // virtual QUuid AvatarMotionState::getSimulatorID() const { return _avatar->getSessionUUID(); diff --git a/interface/src/avatar/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h index 2738aba8ee..9228641b25 100644 --- a/interface/src/avatar/AvatarMotionState.h +++ b/interface/src/avatar/AvatarMotionState.h @@ -59,6 +59,7 @@ public: virtual const QUuid getObjectID() const override; + virtual QString getName() const override; virtual QUuid getSimulatorID() const override; void setBoundingBox(const glm::vec3& corner, const glm::vec3& diagonal); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b0369303e9..ed7ce87db2 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -532,10 +532,14 @@ void MyAvatar::forgetChild(SpatiallyNestablePointer newChild) const { SpatiallyNestable::forgetChild(newChild); } -void MyAvatar::updateChildCauterization(SpatiallyNestablePointer object) { +void MyAvatar::recalculateChildCauterization() const { + _cauterizationNeedsUpdate = true; +} + +void MyAvatar::updateChildCauterization(SpatiallyNestablePointer object, bool cauterize) { if (object->getNestableType() == NestableType::Entity) { EntityItemPointer entity = std::static_pointer_cast(object); - entity->setCauterized(!_prevShouldDrawHead); + entity->setCauterized(cauterize); } } @@ -545,17 +549,42 @@ void MyAvatar::simulate(float deltaTime) { animateScaleChanges(deltaTime); if (_cauterizationNeedsUpdate) { - const std::unordered_set& headBoneSet = _skeletonModel->getCauterizeBoneSet(); + _cauterizationNeedsUpdate = false; + + // Redisplay cauterized entities that are no longer children of the avatar. + auto cauterizedChild = _cauterizedChildrenOfHead.begin(); + if (cauterizedChild != _cauterizedChildrenOfHead.end()) { + auto children = getChildren(); + while (cauterizedChild != _cauterizedChildrenOfHead.end()) { + if (!children.contains(*cauterizedChild)) { + updateChildCauterization(*cauterizedChild, false); + cauterizedChild = _cauterizedChildrenOfHead.erase(cauterizedChild); + } else { + ++cauterizedChild; + } + } + } + + // Update cauterization of entities that are children of the avatar. + auto headBoneSet = _skeletonModel->getCauterizeBoneSet(); forEachChild([&](SpatiallyNestablePointer object) { bool isChildOfHead = headBoneSet.find(object->getParentJointIndex()) != headBoneSet.end(); if (isChildOfHead) { - updateChildCauterization(object); + // Cauterize or display children of head per head drawing state. + updateChildCauterization(object, !_prevShouldDrawHead); object->forEachDescendant([&](SpatiallyNestablePointer descendant) { - updateChildCauterization(descendant); + updateChildCauterization(descendant, !_prevShouldDrawHead); }); + _cauterizedChildrenOfHead.insert(object); + } else if (_cauterizedChildrenOfHead.find(object) != _cauterizedChildrenOfHead.end()) { + // Redisplay cauterized children that are not longer children of the head. + updateChildCauterization(object, false); + object->forEachDescendant([&](SpatiallyNestablePointer descendant) { + updateChildCauterization(descendant, false); + }); + _cauterizedChildrenOfHead.erase(object); } }); - _cauterizationNeedsUpdate = false; } { @@ -685,7 +714,8 @@ void MyAvatar::simulate(float deltaTime) { entityTree->recurseTreeWithOperator(&moveOperator); } }); - _characterController.setFlyingAllowed(zoneAllowsFlying && _enableFlying); + bool isPhysicsEnabled = qApp->isPhysicsEnabled(); + _characterController.setFlyingAllowed(zoneAllowsFlying && (_enableFlying || !isPhysicsEnabled)); _characterController.setCollisionlessAllowed(collisionlessAllowed); } @@ -1068,8 +1098,8 @@ void MyAvatar::saveData() { settings.setValue("displayName", _displayName); settings.setValue("collisionSoundURL", _collisionSoundURL); settings.setValue("useSnapTurn", _useSnapTurn); - settings.setValue("clearOverlayWhenMoving", _clearOverlayWhenMoving); settings.setValue("userHeight", getUserHeight()); + settings.setValue("enabledFlying", getFlyingEnabled()); settings.endGroup(); } @@ -1219,11 +1249,10 @@ void MyAvatar::loadData() { settings.remove("avatarEntityData"); } setAvatarEntityDataChanged(true); - + setFlyingEnabled(settings.value("enabledFlying").toBool()); setDisplayName(settings.value("displayName").toString()); setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString()); setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool()); - setClearOverlayWhenMoving(settings.value("clearOverlayWhenMoving", _clearOverlayWhenMoving).toBool()); setDominantHand(settings.value("dominantHand", _dominantHand).toString().toLower()); setUserHeight(settings.value("userHeight", DEFAULT_AVATAR_HEIGHT).toDouble()); settings.endGroup(); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 1a6feb142a..a3b07d400f 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -250,7 +250,7 @@ public: Q_ENUM(DriveKeys) explicit MyAvatar(QThread* thread); - ~MyAvatar(); + virtual ~MyAvatar(); void instantiableAvatar() override {}; void registerMetaTypes(ScriptEnginePointer engine); @@ -469,16 +469,6 @@ public: * @param {boolean} on */ Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; } - /**jsdoc - * @function MyAvatar.getClearOverlayWhenMoving - * @returns {boolean} - */ - Q_INVOKABLE bool getClearOverlayWhenMoving() const { return _clearOverlayWhenMoving; } - /**jsdoc - * @function MyAvatar.setClearOverlayWhenMoving - * @param {boolean} on - */ - Q_INVOKABLE void setClearOverlayWhenMoving(bool on) { _clearOverlayWhenMoving = on; } /**jsdoc @@ -1374,6 +1364,7 @@ private slots: protected: virtual void beParentOfChild(SpatiallyNestablePointer newChild) const override; virtual void forgetChild(SpatiallyNestablePointer newChild) const override; + virtual void recalculateChildCauterization() const override; private: @@ -1433,7 +1424,7 @@ private: std::array _driveKeys; std::bitset _disabledDriveKeys; - bool _enableFlying { true }; + bool _enableFlying { false }; bool _wasPushing { false }; bool _isPushing { false }; bool _isBeingPushed { false }; @@ -1495,7 +1486,6 @@ private: ThreadSafeValueCache _prefOverrideAnimGraphUrl; QUrl _fstAnimGraphOverrideUrl; bool _useSnapTurn { true }; - bool _clearOverlayWhenMoving { true }; QString _dominantHand { DOMINANT_RIGHT_HAND }; const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // degrees @@ -1566,6 +1556,7 @@ private: glm::quat _goToOrientation; std::unordered_set _headBoneSet; + std::unordered_set _cauterizedChildrenOfHead; bool _prevShouldDrawHead; bool _rigEnabled { true }; @@ -1621,7 +1612,7 @@ private: // height of user in sensor space, when standing erect. ThreadSafeValueCache _userHeight { DEFAULT_AVATAR_HEIGHT }; - void updateChildCauterization(SpatiallyNestablePointer object); + void updateChildCauterization(SpatiallyNestablePointer object, bool cauterize); // max unscaled forward movement speed ThreadSafeValueCache _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp new file mode 100644 index 0000000000..5e51658128 --- /dev/null +++ b/interface/src/avatar/OtherAvatar.cpp @@ -0,0 +1,60 @@ +// +// Created by Bradley Austin Davis on 2017/04/27 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "OtherAvatar.h" +#include "Application.h" + +OtherAvatar::OtherAvatar(QThread* thread) : Avatar(thread) { + // give the pointer to our head to inherited _headData variable from AvatarData + _headData = new Head(this); + _skeletonModel = std::make_shared(this, nullptr); + _skeletonModel->setLoadingPriority(OTHERAVATAR_LOADING_PRIORITY); + connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); + connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); + connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset); + + // add the purple orb + createOrb(); +} + +OtherAvatar::~OtherAvatar() { + removeOrb(); +} + +void OtherAvatar::removeOrb() { + if (qApp->getOverlays().isAddedOverlay(_otherAvatarOrbMeshPlaceholderID)) { + qApp->getOverlays().deleteOverlay(_otherAvatarOrbMeshPlaceholderID); + } +} + +void OtherAvatar::updateOrbPosition() { + if (_otherAvatarOrbMeshPlaceholder != nullptr) { + _otherAvatarOrbMeshPlaceholder->setWorldPosition(getHead()->getPosition()); + } +} + +void OtherAvatar::createOrb() { + if (_otherAvatarOrbMeshPlaceholderID == UNKNOWN_OVERLAY_ID || + !qApp->getOverlays().isAddedOverlay(_otherAvatarOrbMeshPlaceholderID)) { + _otherAvatarOrbMeshPlaceholder = std::make_shared(); + _otherAvatarOrbMeshPlaceholder->setAlpha(1.0f); + _otherAvatarOrbMeshPlaceholder->setColor({ 0xFF, 0x00, 0xFF }); + _otherAvatarOrbMeshPlaceholder->setIsSolid(false); + _otherAvatarOrbMeshPlaceholder->setPulseMin(0.5); + _otherAvatarOrbMeshPlaceholder->setPulseMax(1.0); + _otherAvatarOrbMeshPlaceholder->setColorPulse(1.0); + _otherAvatarOrbMeshPlaceholder->setIgnoreRayIntersection(true); + _otherAvatarOrbMeshPlaceholder->setDrawInFront(false); + _otherAvatarOrbMeshPlaceholderID = qApp->getOverlays().addOverlay(_otherAvatarOrbMeshPlaceholder); + // Position focus + _otherAvatarOrbMeshPlaceholder->setWorldOrientation(glm::quat(0.0f, 0.0f, 0.0f, 1.0)); + _otherAvatarOrbMeshPlaceholder->setWorldPosition(getHead()->getPosition()); + _otherAvatarOrbMeshPlaceholder->setDimensions(glm::vec3(0.5f, 0.5f, 0.5f)); + _otherAvatarOrbMeshPlaceholder->setVisible(true); + } +} diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h new file mode 100644 index 0000000000..f33952b78b --- /dev/null +++ b/interface/src/avatar/OtherAvatar.h @@ -0,0 +1,32 @@ +// +// Created by Bradley Austin Davis on 2017/04/27 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_OtherAvatar_h +#define hifi_OtherAvatar_h + +#include +#include "ui/overlays/Overlays.h" +#include "ui/overlays/Sphere3DOverlay.h" +#include "InterfaceLogging.h" + +class OtherAvatar : public Avatar { +public: + explicit OtherAvatar(QThread* thread); + virtual ~OtherAvatar(); + + virtual void instantiableAvatar() override { }; + virtual void createOrb() override; + void updateOrbPosition(); + void removeOrb(); + +protected: + std::shared_ptr _otherAvatarOrbMeshPlaceholder { nullptr }; + OverlayID _otherAvatarOrbMeshPlaceholderID { UNKNOWN_OVERLAY_ID }; +}; + +#endif // hifi_OtherAvatar_h diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 69698e82a6..702251f867 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -28,15 +28,15 @@ // account synthesizes a result {status: 'success', data: {keyStatus: "preexisting"|"conflicting"|"ok"}} -QJsonObject Ledger::apiResponse(const QString& label, QNetworkReply& reply) { - QByteArray response = reply.readAll(); +QJsonObject Ledger::apiResponse(const QString& label, QNetworkReply* reply) { + QByteArray response = reply->readAll(); QJsonObject data = QJsonDocument::fromJson(response).object(); qInfo(commerce) << label << "response" << QJsonDocument(data).toJson(QJsonDocument::Compact); return data; } // Non-200 responses are not json: -QJsonObject Ledger::failResponse(const QString& label, QNetworkReply& reply) { - QString response = reply.readAll(); +QJsonObject Ledger::failResponse(const QString& label, QNetworkReply* reply) { + QString response = reply->readAll(); qWarning(commerce) << "FAILED" << label << response; // tempResult will be NULL if the response isn't valid JSON. @@ -52,8 +52,8 @@ QJsonObject Ledger::failResponse(const QString& label, QNetworkReply& reply) { return tempResult.object(); } } -#define ApiHandler(NAME) void Ledger::NAME##Success(QNetworkReply& reply) { emit NAME##Result(apiResponse(#NAME, reply)); } -#define FailHandler(NAME) void Ledger::NAME##Failure(QNetworkReply& reply) { emit NAME##Result(failResponse(#NAME, reply)); } +#define ApiHandler(NAME) void Ledger::NAME##Success(QNetworkReply* reply) { emit NAME##Result(apiResponse(#NAME, reply)); } +#define FailHandler(NAME) void Ledger::NAME##Failure(QNetworkReply* reply) { emit NAME##Result(failResponse(#NAME, reply)); } #define Handler(NAME) ApiHandler(NAME) FailHandler(NAME) Handler(buy) Handler(receiveAt) @@ -68,7 +68,7 @@ Handler(updateItem) void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) { auto accountManager = DependencyManager::get(); const QString URL = "/api/v1/commerce/"; - JSONCallbackParameters callbackParams(this, success, this, fail); + JSONCallbackParameters callbackParams(this, success, fail); qCInfo(commerce) << "Sending" << endpoint << QJsonDocument(request).toJson(QJsonDocument::Compact); accountManager->sendRequest(URL + endpoint, authType, @@ -223,12 +223,12 @@ QString transactionString(const QJsonObject& valueObject) { } static const QString MARKETPLACE_ITEMS_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/marketplace/items/"; -void Ledger::historySuccess(QNetworkReply& reply) { +void Ledger::historySuccess(QNetworkReply* reply) { // here we send a historyResult with some extra stuff in it // Namely, the styled text we'd like to show. The issue is the // QML cannot do that easily since it doesn't know what the wallet // public key(s) are. Let's keep it that way - QByteArray response = reply.readAll(); + QByteArray response = reply->readAll(); QJsonObject data = QJsonDocument::fromJson(response).object(); qInfo(commerce) << "history" << "response" << QJsonDocument(data).toJson(QJsonDocument::Compact); @@ -262,7 +262,7 @@ void Ledger::historySuccess(QNetworkReply& reply) { emit historyResult(newData); } -void Ledger::historyFailure(QNetworkReply& reply) { +void Ledger::historyFailure(QNetworkReply* reply) { failResponse("history", reply); } @@ -273,10 +273,10 @@ void Ledger::history(const QStringList& keys, const int& pageNumber, const int& keysQuery("history", "historySuccess", "historyFailure", params); } -void Ledger::accountSuccess(QNetworkReply& reply) { +void Ledger::accountSuccess(QNetworkReply* reply) { // lets set the appropriate stuff in the wallet now auto wallet = DependencyManager::get(); - QByteArray response = reply.readAll(); + QByteArray response = reply->readAll(); QJsonObject data = QJsonDocument::fromJson(response).object()["data"].toObject(); auto salt = QByteArray::fromBase64(data["salt"].toString().toUtf8()); @@ -312,7 +312,7 @@ void Ledger::accountSuccess(QNetworkReply& reply) { emit accountResult(responseData); } -void Ledger::accountFailure(QNetworkReply& reply) { +void Ledger::accountFailure(QNetworkReply* reply) { failResponse("account", reply); } void Ledger::account() { @@ -320,8 +320,8 @@ void Ledger::account() { } // The api/failResponse is called just for the side effect of logging. -void Ledger::updateLocationSuccess(QNetworkReply& reply) { apiResponse("updateLocation", reply); } -void Ledger::updateLocationFailure(QNetworkReply& reply) { failResponse("updateLocation", reply); } +void Ledger::updateLocationSuccess(QNetworkReply* reply) { apiResponse("updateLocation", reply); } +void Ledger::updateLocationFailure(QNetworkReply* reply) { failResponse("updateLocation", reply); } void Ledger::updateLocation(const QString& asset_id, const QString& location, const bool& alsoUpdateSiblings, const bool controlledFailure) { auto wallet = DependencyManager::get(); auto walletScriptingInterface = DependencyManager::get(); @@ -349,11 +349,11 @@ void Ledger::updateLocation(const QString& asset_id, const QString& location, co } } -void Ledger::certificateInfoSuccess(QNetworkReply& reply) { +void Ledger::certificateInfoSuccess(QNetworkReply* reply) { auto wallet = DependencyManager::get(); auto accountManager = DependencyManager::get(); - QByteArray response = reply.readAll(); + QByteArray response = reply->readAll(); QJsonObject replyObject = QJsonDocument::fromJson(response).object(); QStringList keys = wallet->listPublicKeys(); @@ -366,7 +366,7 @@ void Ledger::certificateInfoSuccess(QNetworkReply& reply) { qInfo(commerce) << "certificateInfo" << "response" << QJsonDocument(replyObject).toJson(QJsonDocument::Compact); emit certificateInfoResult(replyObject); } -void Ledger::certificateInfoFailure(QNetworkReply& reply) { +void Ledger::certificateInfoFailure(QNetworkReply* reply) { emit certificateInfoResult(failResponse("certificateInfo", reply)); } void Ledger::certificateInfo(const QString& certificateId) { diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 8a8fd2630a..ba2f167f4b 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -65,36 +65,36 @@ signals: void updateCertificateStatus(const QString& certID, uint certStatus); public slots: - void buySuccess(QNetworkReply& reply); - void buyFailure(QNetworkReply& reply); - void receiveAtSuccess(QNetworkReply& reply); - void receiveAtFailure(QNetworkReply& reply); - void balanceSuccess(QNetworkReply& reply); - void balanceFailure(QNetworkReply& reply); - void inventorySuccess(QNetworkReply& reply); - void inventoryFailure(QNetworkReply& reply); - void historySuccess(QNetworkReply& reply); - void historyFailure(QNetworkReply& reply); - void accountSuccess(QNetworkReply& reply); - void accountFailure(QNetworkReply& reply); - void updateLocationSuccess(QNetworkReply& reply); - void updateLocationFailure(QNetworkReply& reply); - void certificateInfoSuccess(QNetworkReply& reply); - void certificateInfoFailure(QNetworkReply& reply); - void transferAssetToNodeSuccess(QNetworkReply& reply); - void transferAssetToNodeFailure(QNetworkReply& reply); - void transferAssetToUsernameSuccess(QNetworkReply& reply); - void transferAssetToUsernameFailure(QNetworkReply& reply); - void alreadyOwnedSuccess(QNetworkReply& reply); - void alreadyOwnedFailure(QNetworkReply& reply); - void availableUpdatesSuccess(QNetworkReply& reply); - void availableUpdatesFailure(QNetworkReply& reply); - void updateItemSuccess(QNetworkReply& reply); - void updateItemFailure(QNetworkReply& reply); + void buySuccess(QNetworkReply* reply); + void buyFailure(QNetworkReply* reply); + void receiveAtSuccess(QNetworkReply* reply); + void receiveAtFailure(QNetworkReply* reply); + void balanceSuccess(QNetworkReply* reply); + void balanceFailure(QNetworkReply* reply); + void inventorySuccess(QNetworkReply* reply); + void inventoryFailure(QNetworkReply* reply); + void historySuccess(QNetworkReply* reply); + void historyFailure(QNetworkReply* reply); + void accountSuccess(QNetworkReply* reply); + void accountFailure(QNetworkReply* reply); + void updateLocationSuccess(QNetworkReply* reply); + void updateLocationFailure(QNetworkReply* reply); + void certificateInfoSuccess(QNetworkReply* reply); + void certificateInfoFailure(QNetworkReply* reply); + void transferAssetToNodeSuccess(QNetworkReply* reply); + void transferAssetToNodeFailure(QNetworkReply* reply); + void transferAssetToUsernameSuccess(QNetworkReply* reply); + void transferAssetToUsernameFailure(QNetworkReply* reply); + void alreadyOwnedSuccess(QNetworkReply* reply); + void alreadyOwnedFailure(QNetworkReply* reply); + void availableUpdatesSuccess(QNetworkReply* reply); + void availableUpdatesFailure(QNetworkReply* reply); + void updateItemSuccess(QNetworkReply* reply); + void updateItemFailure(QNetworkReply* reply); private: - QJsonObject apiResponse(const QString& label, QNetworkReply& reply); - QJsonObject failResponse(const QString& label, QNetworkReply& reply); + QJsonObject apiResponse(const QString& label, QNetworkReply* reply); + QJsonObject failResponse(const QString& label, QNetworkReply* reply); void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request); void keysQuery(const QString& endpoint, const QString& success, const QString& fail, QJsonObject& extraRequestParams); void keysQuery(const QString& endpoint, const QString& success, const QString& fail); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index e003ae88a0..991f1ebf3f 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -127,31 +127,40 @@ EC_KEY* readKeys(const char* filename) { bool Wallet::writeBackupInstructions() { QString inputFilename(PathUtils::resourcesPath() + "html/commerce/backup_instructions.html"); QString outputFilename = PathUtils::getAppDataFilePath(INSTRUCTIONS_FILE); + QFile inputFile(inputFilename); QFile outputFile(outputFilename); bool retval = false; - if (QFile::exists(outputFilename) || getKeyFilePath() == "") + if (getKeyFilePath() == "") { return false; } - QFile::copy(inputFilename, outputFilename); - if (QFile::exists(outputFilename) && outputFile.open(QIODevice::ReadWrite)) { + if (QFile::exists(inputFilename) && inputFile.open(QIODevice::ReadOnly)) { + if (outputFile.open(QIODevice::ReadWrite)) { + // Read the data from the original file, then close it + QByteArray fileData = inputFile.readAll(); + inputFile.close(); - QByteArray fileData = outputFile.readAll(); - QString text(fileData); + // Translate the data from the original file into a QString + QString text(fileData); - text.replace(QString("HIFIKEY_PATH_REPLACEME"), keyFilePath()); + // Replace the necessary string + text.replace(QString("HIFIKEY_PATH_REPLACEME"), keyFilePath()); - outputFile.seek(0); // go to the beginning of the file - outputFile.write(text.toUtf8()); // write the new text back to the file + // Write the new text back to the file + outputFile.write(text.toUtf8()); - outputFile.close(); // close the file handle. + // Close the output file + outputFile.close(); - retval = true; - qCDebug(commerce) << "wrote html file successfully"; + retval = true; + qCDebug(commerce) << "wrote html file successfully"; + } else { + qCDebug(commerce) << "failed to open output html file" << outputFilename; + } } else { - qCDebug(commerce) << "failed to open output html file" << outputFilename; + qCDebug(commerce) << "failed to open input html file" << inputFilename; } return retval; } diff --git a/interface/src/scripting/SettingsScriptingInterface.cpp b/interface/src/scripting/SettingsScriptingInterface.cpp index 2f14c33dc7..afafe1a350 100644 --- a/interface/src/scripting/SettingsScriptingInterface.cpp +++ b/interface/src/scripting/SettingsScriptingInterface.cpp @@ -35,5 +35,8 @@ QVariant SettingsScriptingInterface::getValue(const QString& setting, const QVar } void SettingsScriptingInterface::setValue(const QString& setting, const QVariant& value) { - Setting::Handle(setting).set(value); + // Make a deep-copy of the string. + // Dangling pointers can occur with QStrings that are implicitly shared from a QScriptEngine. + QString deepCopy = QString::fromUtf16(setting.utf16()); + Setting::Handle(deepCopy).set(value); } diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 39a5849d85..4d8592a9d3 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -113,9 +113,8 @@ void LoginDialog::linkSteam() { } JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; + callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "linkCompleted"; - callbackParams.errorCallbackReceiver = this; callbackParams.errorCallbackMethod = "linkFailed"; const QString LINK_STEAM_PATH = "api/v1/user/steam/link"; @@ -141,9 +140,8 @@ void LoginDialog::createAccountFromStream(QString username) { } JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; + callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "createCompleted"; - callbackParams.errorCallbackReceiver = this; callbackParams.errorCallbackMethod = "createFailed"; const QString CREATE_ACCOUNT_FROM_STEAM_PATH = "api/v1/user/steam/create"; @@ -185,28 +183,27 @@ void LoginDialog::openUrl(const QString& url) const { } } -void LoginDialog::linkCompleted(QNetworkReply& reply) { +void LoginDialog::linkCompleted(QNetworkReply* reply) { emit handleLinkCompleted(); } -void LoginDialog::linkFailed(QNetworkReply& reply) { - emit handleLinkFailed(reply.errorString()); +void LoginDialog::linkFailed(QNetworkReply* reply) { + emit handleLinkFailed(reply->errorString()); } -void LoginDialog::createCompleted(QNetworkReply& reply) { +void LoginDialog::createCompleted(QNetworkReply* reply) { emit handleCreateCompleted(); } -void LoginDialog::createFailed(QNetworkReply& reply) { - emit handleCreateFailed(reply.errorString()); +void LoginDialog::createFailed(QNetworkReply* reply) { + emit handleCreateFailed(reply->errorString()); } void LoginDialog::signup(const QString& email, const QString& username, const QString& password) { JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; + callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "signupCompleted"; - callbackParams.errorCallbackReceiver = this; callbackParams.errorCallbackMethod = "signupFailed"; QJsonObject payload; @@ -228,7 +225,7 @@ void LoginDialog::signup(const QString& email, const QString& username, const QS QJsonDocument(payload).toJson()); } -void LoginDialog::signupCompleted(QNetworkReply& reply) { +void LoginDialog::signupCompleted(QNetworkReply* reply) { emit handleSignupCompleted(); } @@ -242,10 +239,10 @@ QString errorStringFromAPIObject(const QJsonValue& apiObject) { } } -void LoginDialog::signupFailed(QNetworkReply& reply) { +void LoginDialog::signupFailed(QNetworkReply* reply) { // parse the returned JSON to see what the problem was - auto jsonResponse = QJsonDocument::fromJson(reply.readAll()); + auto jsonResponse = QJsonDocument::fromJson(reply->readAll()); static const QString RESPONSE_DATA_KEY = "data"; diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h index 5ebf866fbd..ad8cab9699 100644 --- a/interface/src/ui/LoginDialog.h +++ b/interface/src/ui/LoginDialog.h @@ -42,14 +42,14 @@ signals: void handleSignupFailed(QString errorString); public slots: - void linkCompleted(QNetworkReply& reply); - void linkFailed(QNetworkReply& reply); + void linkCompleted(QNetworkReply* reply); + void linkFailed(QNetworkReply* reply); - void createCompleted(QNetworkReply& reply); - void createFailed(QNetworkReply& reply); + void createCompleted(QNetworkReply* reply); + void createFailed(QNetworkReply* reply); - void signupCompleted(QNetworkReply& reply); - void signupFailed(QNetworkReply& reply); + void signupCompleted(QNetworkReply* reply); + void signupFailed(QNetworkReply* reply); protected slots: Q_INVOKABLE bool isSteamRunning() const; diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index d131bb3467..e27001567f 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -88,38 +88,24 @@ void OverlayConductor::update(float dt) { _hmdMode = false; } - bool isAtRest = updateAvatarIsAtRest(); - bool isMoving = !isAtRest; - bool shouldRecenter = false; - if (_flags & SuppressedByMove) { - if (!isMoving) { - _flags &= ~SuppressedByMove; - shouldRecenter = true; - } - } else { - if (myAvatar->getClearOverlayWhenMoving() && isMoving) { - _flags |= SuppressedByMove; - } - } - - if (_flags & SuppressedByHead) { - if (isAtRest) { - _flags &= ~SuppressedByHead; + if (_suppressedByHead) { + if (updateAvatarIsAtRest()) { + _suppressedByHead = false; shouldRecenter = true; } } else { if (_hmdMode && headOutsideOverlay()) { - _flags |= SuppressedByHead; + _suppressedByHead = true; } } - bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask)); + bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && !_suppressedByHead; if (targetVisible != currentVisible) { offscreenUi->setPinned(!targetVisible); } - if (shouldRecenter && !_flags) { + if (shouldRecenter && !_suppressedByHead) { centerUI(); } } diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index cf69c32fc5..b47e23d28a 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -25,13 +25,7 @@ private: bool headOutsideOverlay() const; bool updateAvatarIsAtRest(); - enum SupressionFlags { - SuppressedByMove = 0x01, - SuppressedByHead = 0x02, - SuppressMask = 0x03, - }; - - uint8_t _flags { SuppressedByMove }; + bool _suppressedByHead { false }; bool _hmdMode { false }; // used by updateAvatarIsAtRest diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 3d3c432e92..e7c010fc31 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -161,12 +161,6 @@ void setupPreferences() { preferences->addPreference(new CheckPreference(UI_CATEGORY, "Use reticle cursor instead of arrow", getter, setter)); } - { - auto getter = [=]()->bool { return myAvatar->getClearOverlayWhenMoving(); }; - auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); }; - preferences->addPreference(new CheckPreference(UI_CATEGORY, "Clear overlays when moving", getter, setter)); - } - static const QString VIEW_CATEGORY{ "View" }; { auto getter = [=]()->float { return myAvatar->getRealWorldFieldOfView(); }; diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index efcc85b23e..60c039ce1f 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -493,7 +493,7 @@ void Snapshot::uploadSnapshot(const QString& filename, const QUrl& href) { multiPart->append(imagePart); auto accountManager = DependencyManager::get(); - JSONCallbackParameters callbackParams(uploader, "uploadSuccess", uploader, "uploadFailure"); + JSONCallbackParameters callbackParams(uploader, "uploadSuccess", "uploadFailure"); accountManager->sendRequest(SNAPSHOT_UPLOAD_URL, AccountManagerAuth::Required, QNetworkAccessManager::PostOperation, callbackParams, nullptr, multiPart); diff --git a/interface/src/ui/SnapshotUploader.cpp b/interface/src/ui/SnapshotUploader.cpp index 4613871d25..694d0fa8f8 100644 --- a/interface/src/ui/SnapshotUploader.cpp +++ b/interface/src/ui/SnapshotUploader.cpp @@ -23,11 +23,11 @@ SnapshotUploader::SnapshotUploader(QUrl inWorldLocation, QString pathname) : _pathname(pathname) { } -void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { +void SnapshotUploader::uploadSuccess(QNetworkReply* reply) { const QString STORY_UPLOAD_URL = "/api/v1/user_stories"; // parse the reply for the thumbnail_url - QByteArray contents = reply.readAll(); + QByteArray contents = reply->readAll(); QJsonParseError jsonError; auto doc = QJsonDocument::fromJson(contents, &jsonError); if (jsonError.error == QJsonParseError::NoError) { @@ -60,7 +60,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { rootObject.insert("user_story", userStoryObject); auto accountManager = DependencyManager::get(); - JSONCallbackParameters callbackParams(this, "createStorySuccess", this, "createStoryFailure"); + JSONCallbackParameters callbackParams(this, "createStorySuccess", "createStoryFailure"); accountManager->sendRequest(STORY_UPLOAD_URL, AccountManagerAuth::Required, @@ -74,11 +74,11 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { } } -void SnapshotUploader::uploadFailure(QNetworkReply& reply) { - QString replyString = reply.readAll(); - qDebug() << "Error " << reply.errorString() << " uploading snapshot " << _pathname << " from " << _inWorldLocation; +void SnapshotUploader::uploadFailure(QNetworkReply* reply) { + QString replyString = reply->readAll(); + qDebug() << "Error " << reply->errorString() << " uploading snapshot " << _pathname << " from " << _inWorldLocation; if (replyString.size() == 0) { - replyString = reply.errorString(); + replyString = reply->errorString(); } replyString = replyString.left(1000); // Only print first 1000 characters of error qDebug() << "Snapshot upload reply error (truncated):" << replyString; @@ -86,17 +86,17 @@ void SnapshotUploader::uploadFailure(QNetworkReply& reply) { this->deleteLater(); } -void SnapshotUploader::createStorySuccess(QNetworkReply& reply) { - QString replyString = reply.readAll(); +void SnapshotUploader::createStorySuccess(QNetworkReply* reply) { + QString replyString = reply->readAll(); emit DependencyManager::get()->snapshotShared(false, replyString); this->deleteLater(); } -void SnapshotUploader::createStoryFailure(QNetworkReply& reply) { - QString replyString = reply.readAll(); - qDebug() << "Error " << reply.errorString() << " uploading snapshot story " << _pathname << " from " << _inWorldLocation; +void SnapshotUploader::createStoryFailure(QNetworkReply* reply) { + QString replyString = reply->readAll(); + qDebug() << "Error " << reply->errorString() << " uploading snapshot story " << _pathname << " from " << _inWorldLocation; if (replyString.size() == 0) { - replyString = reply.errorString(); + replyString = reply->errorString(); } replyString = replyString.left(1000); // Only print first 1000 characters of error qDebug() << "Snapshot story upload reply error (truncated):" << replyString; diff --git a/interface/src/ui/SnapshotUploader.h b/interface/src/ui/SnapshotUploader.h index ae6d5d55ca..d8e72730c7 100644 --- a/interface/src/ui/SnapshotUploader.h +++ b/interface/src/ui/SnapshotUploader.h @@ -21,12 +21,12 @@ class SnapshotUploader : public QObject { public: SnapshotUploader(QUrl inWorldLocation, QString pathname); public slots: - void uploadSuccess(QNetworkReply& reply); - void uploadFailure(QNetworkReply& reply); - void createStorySuccess(QNetworkReply& reply); - void createStoryFailure(QNetworkReply& reply); + void uploadSuccess(QNetworkReply* reply); + void uploadFailure(QNetworkReply* reply); + void createStorySuccess(QNetworkReply* reply); + void createStoryFailure(QNetworkReply* reply); private: QUrl _inWorldLocation; QString _pathname; }; -#endif // hifi_SnapshotUploader_h \ No newline at end of file +#endif // hifi_SnapshotUploader_h diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index a5f79290cd..a6f0416a30 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1426,6 +1426,8 @@ bool AudioClient::setIsStereoInput(bool isStereoInput) { // restart the input device switchInputToAudioDevice(_inputDeviceInfo); + + emit isStereoInputChanged(_isStereoInput); } return stereoInputChanged; @@ -1463,6 +1465,8 @@ void AudioClient::outputFormatChanged() { } bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo inputDeviceInfo, bool isShutdownRequest) { + Q_ASSERT_X(QThread::currentThread() == thread(), Q_FUNC_INFO, "Function invoked on wrong thread"); + qCDebug(audioclient) << __FUNCTION__ << "inputDeviceInfo: [" << inputDeviceInfo.deviceName() << "]"; bool supportedFormat = false; @@ -1663,6 +1667,8 @@ void AudioClient::outputNotify() { } bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo outputDeviceInfo, bool isShutdownRequest) { + Q_ASSERT_X(QThread::currentThread() == thread(), Q_FUNC_INFO, "Function invoked on wrong thread"); + qCDebug(audioclient) << "AudioClient::switchOutputToAudioDevice() outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]"; bool supportedFormat = false; @@ -2021,7 +2027,7 @@ void AudioClient::setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 sca void AudioClient::startThread() { - moveToNewNamedThread(this, "Audio Thread", [this] { start(); }); + moveToNewNamedThread(this, "Audio Thread", [this] { start(); }, QThread::TimeCriticalPriority); } void AudioClient::setInputVolume(float volume, bool emitSignal) { diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h index 30cbceeb0e..bbfd79d0aa 100644 --- a/libraries/audio/src/AbstractAudioInterface.h +++ b/libraries/audio/src/AbstractAudioInterface.h @@ -44,6 +44,9 @@ public slots: virtual bool setIsStereoInput(bool stereo) = 0; virtual bool isStereoInput() = 0; + +signals: + void isStereoInputChanged(bool isStereo); }; Q_DECLARE_METATYPE(AbstractAudioInterface*) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index a99838d810..843235c0e1 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1338,6 +1338,9 @@ void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const { } void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { + if (!isMyAvatar()) { + createOrb(); + } AvatarData::setSkeletonModelURL(skeletonModelURL); if (QThread::currentThread() == thread()) { _skeletonModel->setURL(_skeletonModelURL); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 98246330c4..bb9d6d8cc9 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -23,7 +23,6 @@ #include #include - #include "Head.h" #include "SkeletonModel.h" #include "Rig.h" @@ -41,7 +40,6 @@ static const float SCALING_RATIO = .05f; extern const float CHAT_MESSAGE_SCALE; extern const float CHAT_MESSAGE_HEIGHT; - enum ScreenTintLayer { SCREEN_TINT_BEFORE_LANDSCAPE = 0, SCREEN_TINT_BEFORE_AVATARS, @@ -69,7 +67,7 @@ public: static void setShowNamesAboveHeads(bool show); explicit Avatar(QThread* thread); - ~Avatar(); + virtual ~Avatar(); virtual void instantiableAvatar() = 0; @@ -109,6 +107,7 @@ public: float getLODDistance() const; virtual bool isMyAvatar() const override { return false; } + virtual void createOrb() { } virtual QVector getJointRotations() const override; using AvatarData::getJointRotation; @@ -167,8 +166,8 @@ public: virtual int parseDataFromBuffer(const QByteArray& buffer) override; - static void renderJointConnectingCone( gpu::Batch& batch, glm::vec3 position1, glm::vec3 position2, - float radius1, float radius2, const glm::vec4& color); + static void renderJointConnectingCone(gpu::Batch& batch, glm::vec3 position1, glm::vec3 position2, + float radius1, float radius2, const glm::vec4& color); virtual void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) { } @@ -235,7 +234,7 @@ public: /// Scales a world space position vector relative to the avatar position and scale /// \param vector position to be scaled. Will store the result - void scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const; + void scaleVectorRelativeToPosition(glm::vec3& positionToScale) const; void slamPosition(const glm::vec3& position); virtual void updateAttitude(const glm::quat& orientation) override; @@ -254,7 +253,6 @@ public: void setPositionViaScript(const glm::vec3& position) override; void setOrientationViaScript(const glm::quat& orientation) override; - /**jsdoc * @function MyAvatar.getParentID * @returns {Uuid} @@ -283,7 +281,6 @@ public: // This calls through to the SpatiallyNestable versions, but is here to expose these to JavaScript. Q_INVOKABLE virtual void setParentJointIndex(quint16 parentJointIndex) override; - /**jsdoc * Returns an array of joints, where each joint is an object containing name, index, and parentIndex fields. * @function MyAvatar.getSkeleton @@ -349,7 +346,6 @@ public: // not all subclasses of AvatarData have access to this data. virtual bool canMeasureEyeHeight() const override { return true; } - virtual float getModelScale() const { return _modelScale; } virtual void setModelScale(float scale) { _modelScale = scale; } virtual glm::vec3 scaleForChildren() const override { return glm::vec3(getModelScale()); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp deleted file mode 100644 index 7678c03276..0000000000 --- a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// -// Created by Bradley Austin Davis on 2017/04/27 -// Copyright 2013-2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "OtherAvatar.h" - -OtherAvatar::OtherAvatar(QThread* thread) : Avatar(thread) { - // give the pointer to our head to inherited _headData variable from AvatarData - _headData = new Head(this); - _skeletonModel = std::make_shared(this, nullptr); - _skeletonModel->setLoadingPriority(OTHERAVATAR_LOADING_PRIORITY); - connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); - connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); - connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset); -} diff --git a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h deleted file mode 100644 index df09d7fd99..0000000000 --- a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// Created by Bradley Austin Davis on 2017/04/27 -// Copyright 2013-2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_OtherAvatar_h -#define hifi_OtherAvatar_h - -#include "Avatar.h" - -class OtherAvatar : public Avatar { -public: - explicit OtherAvatar(QThread* thread); - virtual void instantiableAvatar() override {}; -}; - -#endif // hifi_OtherAvatar_h diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 6ff2ff576c..e8f57ea834 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -363,6 +363,14 @@ bool EntityRenderer::needsRenderUpdateFromEntity(const EntityItemPointer& entity return false; } +void EntityRenderer::updateModelTransform() { + bool success = false; + auto newModelTransform = _entity->getTransformToCenter(success); + if (success) { + _modelTransform = newModelTransform; + } +} + void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity) { DETAILED_PROFILE_RANGE(simulation_physics, __FUNCTION__); withWriteLock([&] { @@ -419,4 +427,4 @@ void EntityRenderer::addMaterial(graphics::MaterialLayer material, const std::st void EntityRenderer::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { std::lock_guard lock(_materialsLock); _materials[parentMaterialName].remove(material); -} \ No newline at end of file +} diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index e1ce2ed39e..40966c4f41 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -97,6 +97,7 @@ protected: virtual void doRender(RenderArgs* args) = 0; bool isFading() const { return _isFading; } + void updateModelTransform(); virtual bool isTransparent() const { return _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) < 1.0f : false; } inline bool isValidRenderItem() const { return _renderItemID != Item::INVALID_ITEM_ID; } @@ -140,6 +141,7 @@ protected: bool _needsRenderUpdate { false }; // Only touched on the rendering thread bool _renderUpdateQueued{ false }; + Transform _renderTransform; std::unordered_map _materials; std::mutex _materialsLock; diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.h b/libraries/entities-renderer/src/RenderableMaterialEntityItem.h index 96c720f79f..168041a842 100644 --- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.h +++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.h @@ -35,7 +35,6 @@ private: glm::vec2 _materialMappingPos; glm::vec2 _materialMappingScale; float _materialMappingRot; - Transform _renderTransform; std::shared_ptr _drawMaterial; }; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index a7126b847e..d8ac3dc63e 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -279,18 +279,16 @@ EntityItemProperties RenderableModelEntityItem::getProperties(EntityPropertyFlag } bool RenderableModelEntityItem::supportsDetailedRayIntersection() const { - return isModelLoaded(); + return true; } bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { auto model = getModel(); - if (!model) { - return true; + if (!model || !isModelLoaded()) { + return false; } - // qCDebug(entitiesrenderer) << "RenderableModelEntityItem::findDetailedRayIntersection() precisionPicking:" - // << precisionPicking; return model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo, precisionPicking, false); diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 2fb4877ed3..a6a6dc05f2 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -122,6 +122,14 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi }); } } + + void* key = (void*)this; + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this] () { + withWriteLock([&] { + updateModelTransform(); + _renderTransform = getModelTransform(); + }); + }); } void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { @@ -319,7 +327,10 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) { // In trail mode, the particles are created in world space. // so we only set a transform if they're not in trail mode if (!_particleProperties.emission.shouldTrail) { - transform = getModelTransform(); + + withReadLock([&] { + transform = _renderTransform; + }); transform.setScale(vec3(1)); } batch.setModelTransform(transform); diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 69068b81d2..2e0656ab81 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -97,16 +97,25 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce addMaterial(graphics::MaterialLayer(_material, 0), "0"); _shape = entity->getShape(); - _position = entity->getWorldPosition(); - _dimensions = entity->getScaledDimensions(); - _orientation = entity->getWorldOrientation(); - _renderTransform = getModelTransform(); + }); - if (_shape == entity::Sphere) { - _renderTransform.postScale(SPHERE_ENTITY_SCALE); - } + void* key = (void*)this; + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this] () { + withWriteLock([&] { + auto entity = getEntity(); + _position = entity->getWorldPosition(); + _dimensions = entity->getScaledDimensions(); + _orientation = entity->getWorldOrientation(); + bool success = false; + auto newModelTransform = entity->getTransformToCenter(success); + _renderTransform = success ? newModelTransform : getModelTransform(); - _renderTransform.postScale(_dimensions); + if (_shape == entity::Sphere) { + _renderTransform.postScale(SPHERE_ENTITY_SCALE); + } + + _renderTransform.postScale(_dimensions); + });; }); } diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h index 463ef187fc..7700aa6ef0 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.h +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h @@ -40,7 +40,6 @@ private: Procedural _procedural; QString _lastUserData; - Transform _renderTransform; entity::Shape _shape { entity::Sphere }; std::shared_ptr _material; glm::vec3 _position; diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index e58eb540e8..ce4b6d9175 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -20,6 +20,7 @@ #include "GLMHelpers.h" +using namespace render; using namespace render::entities; static const int FIXED_FONT_POINT_SIZE = 40; @@ -64,10 +65,20 @@ bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint return false; } +void TextEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { + void* key = (void*)this; + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] () { + withWriteLock([&] { + _dimensions = entity->getScaledDimensions(); + updateModelTransform(); + _renderTransform = getModelTransform(); + }); + }); +} + void TextEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { _textColor = toGlm(entity->getTextColorX()); _backgroundColor = toGlm(entity->getBackgroundColorX()); - _dimensions = entity->getScaledDimensions(); _faceCamera = entity->getFaceCamera(); _lineHeight = entity->getLineHeight(); _text = entity->getText(); @@ -76,24 +87,28 @@ void TextEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointe void TextEntityRenderer::doRender(RenderArgs* args) { PerformanceTimer perfTimer("RenderableTextEntityItem::render"); - + + Transform modelTransform; + glm::vec3 dimensions; + withReadLock([&] { + modelTransform = _renderTransform; + dimensions = _dimensions; + }); static const float SLIGHTLY_BEHIND = -0.005f; float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; bool transparent = fadeRatio < 1.0f; glm::vec4 textColor = glm::vec4(_textColor, fadeRatio); glm::vec4 backgroundColor = glm::vec4(_backgroundColor, fadeRatio); - const glm::vec3& dimensions = _dimensions; - + // Render background glm::vec3 minCorner = glm::vec3(0.0f, -dimensions.y, SLIGHTLY_BEHIND); glm::vec3 maxCorner = glm::vec3(dimensions.x, 0.0f, SLIGHTLY_BEHIND); - - + + // Batch render calls Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; - const auto& modelTransform = getModelTransform(); auto transformToTopLeft = modelTransform; if (_faceCamera) { //rotate about vertical to face the camera @@ -105,7 +120,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) { } transformToTopLeft.postTranslate(dimensions * glm::vec3(-0.5f, 0.5f, 0.0f)); // Go to the top left transformToTopLeft.setScale(1.0f); // Use a scale of one so that the text is not deformed - + batch.setModelTransform(transformToTopLeft); auto geometryCache = DependencyManager::get(); if (!_geometryID) { @@ -113,11 +128,11 @@ void TextEntityRenderer::doRender(RenderArgs* args) { } geometryCache->bindSimpleProgram(batch, false, transparent, false, false, false); geometryCache->renderQuad(batch, minCorner, maxCorner, backgroundColor, _geometryID); - + float scale = _lineHeight / _textRenderer->getFontSize(); transformToTopLeft.setScale(scale); // Scale to have the correct line height batch.setModelTransform(transformToTopLeft); - + float leftMargin = 0.1f * _lineHeight, topMargin = 0.1f * _lineHeight; glm::vec2 bounds = glm::vec2(dimensions.x - 2.0f * leftMargin, dimensions.y - 2.0f * topMargin); diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.h b/libraries/entities-renderer/src/RenderableTextEntityItem.h index b0a72cf253..ac7f2b620f 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.h +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.h @@ -27,6 +27,7 @@ public: ~TextEntityRenderer(); private: virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; + virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override; virtual void doRender(RenderArgs* args) override; int _geometryID{ 0 }; @@ -39,6 +40,6 @@ private: float _lineHeight; }; -} } +} } #endif // hifi_RenderableTextEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 693e3d0cf4..17d6d58781 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -149,8 +149,8 @@ void WebEntityRenderer::onTimeout() { } void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { - // If the content type has changed, or the old content type was QML, we need to - // destroy the existing surface (because surfaces don't support changing the root + // If the content type has changed, or the old content type was QML, we need to + // destroy the existing surface (because surfaces don't support changing the root // object, so subsequent loads of content just overlap the existing content bool urlChanged = false; { @@ -187,24 +187,30 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene if (!hasWebSurface() && !buildWebSurface(entity)) { return; } - + if (urlChanged && _contentType == ContentType::HtmlContent) { _webSurface->getRootItem()->setProperty(URL_PROPERTY, _lastSourceUrl); } - if (_contextPosition != entity->getWorldPosition()) { - // update globalPosition - _contextPosition = entity->getWorldPosition(); - _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(_contextPosition)); - } + void* key = (void*)this; + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] () { + withWriteLock([&] { + if (_contextPosition != entity->getWorldPosition()) { + // update globalPosition + _contextPosition = entity->getWorldPosition(); + _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(_contextPosition)); + } - _lastDPI = entity->getDPI(); - _lastLocked = entity->getLocked(); + _lastDPI = entity->getDPI(); + _lastLocked = entity->getLocked(); - glm::vec2 windowSize = getWindowSize(entity); - _webSurface->resize(QSize(windowSize.x, windowSize.y)); - _renderTransform = getModelTransform(); - _renderTransform.postScale(entity->getScaledDimensions()); + glm::vec2 windowSize = getWindowSize(entity); + _webSurface->resize(QSize(windowSize.x, windowSize.y)); + updateModelTransform(); + _renderTransform = getModelTransform(); + _renderTransform.postScale(entity->getScaledDimensions()); + }); + }); }); } @@ -297,7 +303,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { if (_contentType == ContentType::HtmlContent) { // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. - // FIXME this doesn't handle redirects or shortened URLs, consider using a signaling method from the + // FIXME this doesn't handle redirects or shortened URLs, consider using a signaling method from the // web entity if (QUrl(_lastSourceUrl).host().endsWith("youtube.com", Qt::CaseInsensitive)) { _webSurface->setMaxFps(YOUTUBE_MAX_FPS); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 3100014e9b..1ba8ed0ec7 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -68,7 +68,6 @@ private: bool _lastLocked; QTimer _timer; uint64_t _lastRenderTime { 0 }; - Transform _renderTransform; }; } } // namespace diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index 2f8fd47b79..c48679e5d4 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -123,7 +123,6 @@ private: bool _pendingSkyboxTexture{ false }; QString _proceduralUserData; - Transform _renderTransform; }; } } // namespace diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index a9dc617a6c..3a11fd821a 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -185,6 +185,7 @@ public: /// Dimensions in meters (0.0 - TREE_SCALE) glm::vec3 getScaledDimensions() const; virtual void setScaledDimensions(const glm::vec3& value); + virtual glm::vec3 getRaycastDimensions() const { return getScaledDimensions(); } inline const glm::vec3 getUnscaledDimensions() const { return _unscaledDimensions; } virtual void setUnscaledDimensions(const glm::vec3& value); @@ -239,7 +240,7 @@ public: // position, size, and bounds related helpers virtual AACube getMaximumAACube(bool& success) const override; AACube getMinimumAACube(bool& success) const; - AABox getAABox(bool& success) const; /// axis aligned bounding box in world-frame (meters) + virtual AABox getAABox(bool& success) const; /// axis aligned bounding box in world-frame (meters) using SpatiallyNestable::getQueryAACube; virtual AACube getQueryAACube(bool& success) const override; diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index cbcddfc57b..bc5bb1e81d 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -214,7 +214,7 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori glm::mat4 entityToWorldMatrix = translation * rotation; glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); - glm::vec3 dimensions = entity->getScaledDimensions(); + glm::vec3 dimensions = entity->getRaycastDimensions(); glm::vec3 registrationPoint = entity->getRegistrationPoint(); glm::vec3 corner = -(dimensions * registrationPoint); @@ -312,7 +312,7 @@ void EntityTreeElement::getEntities(const glm::vec3& searchPosition, float searc glm::vec3 penetration; if (!success || entityBox.findSpherePenetration(searchPosition, searchRadius, penetration)) { - glm::vec3 dimensions = entity->getScaledDimensions(); + glm::vec3 dimensions = entity->getRaycastDimensions(); // FIXME - consider allowing the entity to determine penetration so that // entities could presumably dull actuall hull testing if they wanted to diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index b830313723..d0efed3df4 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1816,7 +1816,6 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } - float clusterScale = extractUniformScale(fbxCluster.inverseBindMatrix); glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform; ShapeVertices& points = shapeVertices.at(jointIndex); @@ -1832,7 +1831,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS if (weight >= EXPANSION_WEIGHT_THRESHOLD) { // transform to joint-frame and save for later const glm::mat4 vertexTransform = meshToJoint * glm::translate(extracted.mesh.vertices.at(newIndex)); - points.push_back(extractTranslation(vertexTransform) * clusterScale); + points.push_back(extractTranslation(vertexTransform)); } // look for an unused slot in the weights vector @@ -1886,12 +1885,11 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS FBXJoint& joint = geometry.joints[jointIndex]; // transform cluster vertices to joint-frame and save for later - float clusterScale = extractUniformScale(firstFBXCluster.inverseBindMatrix); glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform; ShapeVertices& points = shapeVertices.at(jointIndex); foreach (const glm::vec3& vertex, extracted.mesh.vertices) { const glm::mat4 vertexTransform = meshToJoint * glm::translate(vertex); - points.push_back(extractTranslation(vertexTransform) * clusterScale); + points.push_back(extractTranslation(vertexTransform)); } // Apply geometric offset, if present, by transforming the vertices directly diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index 622c8f1081..6faccb1527 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -278,6 +278,7 @@ protected: struct InputStageState { bool _invalidFormat { true }; + bool _lastUpdateStereoState{ false }; bool _hadColorAttribute{ true }; Stream::FormatPointer _format; std::string _formatKey; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp index 4145eb6061..77e1f90f66 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp @@ -156,6 +156,14 @@ void GLBackend::do_setIndirectBuffer(const Batch& batch, size_t paramOffset) { } void GLBackend::updateInput() { + bool isStereoNow = isStereo(); + // track stereo state change potentially happening wihtout changing the input format + // this is a rare case requesting to invalid the format +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + _input._invalidFormat |= (isStereoNow != _input._lastUpdateStereoState); +#endif + _input._lastUpdateStereoState = isStereoNow; + if (_input._invalidFormat) { InputStageState::ActivationCache newActivation; @@ -213,7 +221,7 @@ void GLBackend::updateInput() { (void)CHECK_GL_ERROR(); } #ifdef GPU_STEREO_DRAWCALL_INSTANCED - glVertexBindingDivisor(bufferChannelNum, frequency * (isStereo() ? 2 : 1)); + glVertexBindingDivisor(bufferChannelNum, frequency * (isStereoNow ? 2 : 1)); #else glVertexBindingDivisor(bufferChannelNum, frequency); #endif diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp index 42bd56e6e4..9dcb08f0b7 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp @@ -25,6 +25,14 @@ void GL41Backend::resetInputStage() { } void GL41Backend::updateInput() { + bool isStereoNow = isStereo(); + // track stereo state change potentially happening wihtout changing the input format + // this is a rare case requesting to invalid the format +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + _input._invalidFormat |= (isStereoNow != _input._lastUpdateStereoState); +#endif + _input._lastUpdateStereoState = isStereoNow; + if (_input._invalidFormat || _input._invalidBuffers.any()) { if (_input._invalidFormat) { @@ -111,7 +119,7 @@ void GL41Backend::updateInput() { reinterpret_cast(pointer + perLocationStride * (GLuint)locNum)); } #ifdef GPU_STEREO_DRAWCALL_INSTANCED - glVertexAttribDivisor(slot + (GLuint)locNum, attrib._frequency * (isStereo() ? 2 : 1)); + glVertexAttribDivisor(slot + (GLuint)locNum, attrib._frequency * (isStereoNow ? 2 : 1)); #else glVertexAttribDivisor(slot + (GLuint)locNum, attrib._frequency); #endif diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp index 34bf6774f7..cc3e609bda 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp @@ -27,6 +27,14 @@ void GL45Backend::resetInputStage() { } void GL45Backend::updateInput() { + bool isStereoNow = isStereo(); + // track stereo state change potentially happening wihtout changing the input format + // this is a rare case requesting to invalid the format +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + _input._invalidFormat |= (isStereoNow != _input._lastUpdateStereoState); +#endif + _input._lastUpdateStereoState = isStereoNow; + if (_input._invalidFormat) { InputStageState::ActivationCache newActivation; @@ -84,7 +92,7 @@ void GL45Backend::updateInput() { (void)CHECK_GL_ERROR(); } #ifdef GPU_STEREO_DRAWCALL_INSTANCED - glVertexBindingDivisor(bufferChannelNum, frequency * (isStereo() ? 2 : 1)); + glVertexBindingDivisor(bufferChannelNum, frequency * (isStereoNow ? 2 : 1)); #else glVertexBindingDivisor(bufferChannelNum, frequency); #endif diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 05f0ec12b5..5b3196a2bf 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -46,21 +47,18 @@ Q_DECLARE_METATYPE(JSONCallbackParameters) const QString ACCOUNTS_GROUP = "accounts"; -JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, const QString& jsonCallbackMethod, - QObject* errorCallbackReceiver, const QString& errorCallbackMethod, - QObject* updateReceiver, const QString& updateSlot) : - jsonCallbackReceiver(jsonCallbackReceiver), +JSONCallbackParameters::JSONCallbackParameters(QObject* callbackReceiver, + const QString& jsonCallbackMethod, + const QString& errorCallbackMethod) : + callbackReceiver(callbackReceiver), jsonCallbackMethod(jsonCallbackMethod), - errorCallbackReceiver(errorCallbackReceiver), - errorCallbackMethod(errorCallbackMethod), - updateReciever(updateReceiver), - updateSlot(updateSlot) + errorCallbackMethod(errorCallbackMethod) { } -QJsonObject AccountManager::dataObjectFromResponse(QNetworkReply &requestReply) { - QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); +QJsonObject AccountManager::dataObjectFromResponse(QNetworkReply* requestReply) { + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply->readAll()).object(); static const QString STATUS_KEY = "status"; static const QString DATA_KEY = "data"; @@ -74,8 +72,7 @@ QJsonObject AccountManager::dataObjectFromResponse(QNetworkReply &requestReply) AccountManager::AccountManager(UserAgentGetter userAgentGetter) : _userAgentGetter(userAgentGetter), - _authURL(), - _pendingCallbackMap() + _authURL() { qRegisterMetaType("OAuthAccessToken"); qRegisterMetaTypeStreamOperators("OAuthAccessToken"); @@ -323,75 +320,66 @@ void AccountManager::sendRequest(const QString& path, } } - - if (!callbackParams.isEmpty()) { - // if we have information for a callback, insert the callbackParams into our local map - _pendingCallbackMap.insert(networkReply, callbackParams); - - if (callbackParams.updateReciever && !callbackParams.updateSlot.isEmpty()) { - callbackParams.updateReciever->connect(networkReply, SIGNAL(uploadProgress(qint64, qint64)), - callbackParams.updateSlot.toStdString().c_str()); + connect(networkReply, &QNetworkReply::finished, this, [this, networkReply] { + // double check if the finished network reply had a session ID in the header and make + // sure that our session ID matches that value if so + if (networkReply->hasRawHeader(METAVERSE_SESSION_ID_HEADER)) { + _sessionID = networkReply->rawHeader(METAVERSE_SESSION_ID_HEADER); } + }); + + + if (callbackParams.isEmpty()) { + connect(networkReply, &QNetworkReply::finished, networkReply, &QNetworkReply::deleteLater); + } else { + // There's a cleaner way to fire the JSON/error callbacks below and ensure that deleteLater is called for the + // request reply - unfortunately it requires Qt 5.10 which the Android build does not support as of 06/26/18 + + connect(networkReply, &QNetworkReply::finished, callbackParams.callbackReceiver, + [callbackParams, networkReply] { + if (networkReply->error() == QNetworkReply::NoError) { + if (!callbackParams.jsonCallbackMethod.isEmpty()) { + bool invoked = QMetaObject::invokeMethod(callbackParams.callbackReceiver, + qPrintable(callbackParams.jsonCallbackMethod), + Q_ARG(QNetworkReply*, networkReply)); + + if (!invoked) { + QString error = "Could not invoke " + callbackParams.jsonCallbackMethod + " with QNetworkReply* " + + "on callbackReceiver."; + qCWarning(networking) << error; + Q_ASSERT_X(invoked, "AccountManager::passErrorToCallback", qPrintable(error)); + } + } else { + if (VERBOSE_HTTP_REQUEST_DEBUGGING) { + qCDebug(networking) << "Received JSON response from metaverse API that has no matching callback."; + qCDebug(networking) << QJsonDocument::fromJson(networkReply->readAll()); + } + } + } else { + if (!callbackParams.errorCallbackMethod.isEmpty()) { + bool invoked = QMetaObject::invokeMethod(callbackParams.callbackReceiver, + qPrintable(callbackParams.errorCallbackMethod), + Q_ARG(QNetworkReply*, networkReply)); + + if (!invoked) { + QString error = "Could not invoke " + callbackParams.errorCallbackMethod + " with QNetworkReply* " + + "on callbackReceiver."; + qCWarning(networking) << error; + Q_ASSERT_X(invoked, "AccountManager::passErrorToCallback", qPrintable(error)); + } + + } else { + if (VERBOSE_HTTP_REQUEST_DEBUGGING) { + qCDebug(networking) << "Received error response from metaverse API that has no matching callback."; + qCDebug(networking) << "Error" << networkReply->error() << "-" << networkReply->errorString(); + qCDebug(networking) << networkReply->readAll(); + } + } + } + + networkReply->deleteLater(); + }); } - - // if we ended up firing of a request, hook up to it now - connect(networkReply, SIGNAL(finished()), SLOT(processReply())); - } -} - -void AccountManager::processReply() { - QNetworkReply* requestReply = reinterpret_cast(sender()); - - if (requestReply->error() == QNetworkReply::NoError) { - if (requestReply->hasRawHeader(METAVERSE_SESSION_ID_HEADER)) { - _sessionID = requestReply->rawHeader(METAVERSE_SESSION_ID_HEADER); - } - passSuccessToCallback(requestReply); - } else { - passErrorToCallback(requestReply); - } - requestReply->deleteLater(); -} - -void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) { - JSONCallbackParameters callbackParams = _pendingCallbackMap.value(requestReply); - - if (callbackParams.jsonCallbackReceiver) { - // invoke the right method on the callback receiver - QMetaObject::invokeMethod(callbackParams.jsonCallbackReceiver, qPrintable(callbackParams.jsonCallbackMethod), - Q_ARG(QNetworkReply&, *requestReply)); - - // remove the related reply-callback group from the map - _pendingCallbackMap.remove(requestReply); - - } else { - if (VERBOSE_HTTP_REQUEST_DEBUGGING) { - qCDebug(networking) << "Received JSON response from metaverse API that has no matching callback."; - qCDebug(networking) << QJsonDocument::fromJson(requestReply->readAll()); - } - - requestReply->deleteLater(); - } -} - -void AccountManager::passErrorToCallback(QNetworkReply* requestReply) { - JSONCallbackParameters callbackParams = _pendingCallbackMap.value(requestReply); - - if (callbackParams.errorCallbackReceiver) { - // invoke the right method on the callback receiver - QMetaObject::invokeMethod(callbackParams.errorCallbackReceiver, qPrintable(callbackParams.errorCallbackMethod), - Q_ARG(QNetworkReply&, *requestReply)); - - // remove the related reply-callback group from the map - _pendingCallbackMap.remove(requestReply); - } else { - if (VERBOSE_HTTP_REQUEST_DEBUGGING) { - qCDebug(networking) << "Received error response from metaverse API that has no matching callback."; - qCDebug(networking) << "Error" << requestReply->error() << "-" << requestReply->errorString(); - qCDebug(networking) << requestReply->readAll(); - } - - requestReply->deleteLater(); } } @@ -817,16 +805,15 @@ void AccountManager::processGeneratedKeypair(QByteArray publicKey, QByteArray pr // setup callback parameters so we know once the keypair upload has succeeded or failed JSONCallbackParameters callbackParameters; - callbackParameters.jsonCallbackReceiver = this; + callbackParameters.callbackReceiver = this; callbackParameters.jsonCallbackMethod = "publicKeyUploadSucceeded"; - callbackParameters.errorCallbackReceiver = this; callbackParameters.errorCallbackMethod = "publicKeyUploadFailed"; sendRequest(uploadPath, AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation, callbackParameters, QByteArray(), requestMultiPart); } -void AccountManager::publicKeyUploadSucceeded(QNetworkReply& reply) { +void AccountManager::publicKeyUploadSucceeded(QNetworkReply* reply) { qCDebug(networking) << "Uploaded public key to Metaverse API. RSA keypair generation is completed."; // public key upload complete - store the matching private key and persist the account to settings @@ -838,23 +825,17 @@ void AccountManager::publicKeyUploadSucceeded(QNetworkReply& reply) { _isWaitingForKeypairResponse = false; emit newKeypair(); - - // delete the reply object now that we are done with it - reply.deleteLater(); } -void AccountManager::publicKeyUploadFailed(QNetworkReply& reply) { +void AccountManager::publicKeyUploadFailed(QNetworkReply* reply) { // the public key upload has failed - qWarning() << "Public key upload failed from AccountManager" << reply.errorString(); + qWarning() << "Public key upload failed from AccountManager" << reply->errorString(); // we aren't waiting for a response any longer _isWaitingForKeypairResponse = false; // clear our pending private key _pendingPrivateKey.clear(); - - // delete the reply object now that we are done with it - reply.deleteLater(); } void AccountManager::handleKeypairGenerationError() { diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 9068966512..a79b69fe2b 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -28,18 +28,14 @@ class JSONCallbackParameters { public: - JSONCallbackParameters(QObject* jsonCallbackReceiver = nullptr, const QString& jsonCallbackMethod = QString(), - QObject* errorCallbackReceiver = nullptr, const QString& errorCallbackMethod = QString(), - QObject* updateReceiver = nullptr, const QString& updateSlot = QString()); + JSONCallbackParameters(QObject* callbackReceiver = nullptr, const QString& jsonCallbackMethod = QString(), + const QString& errorCallbackMethod = QString()); - bool isEmpty() const { return !jsonCallbackReceiver && !errorCallbackReceiver; } + bool isEmpty() const { return !callbackReceiver; } - QObject* jsonCallbackReceiver; + QObject* callbackReceiver; QString jsonCallbackMethod; - QObject* errorCallbackReceiver; QString errorCallbackMethod; - QObject* updateReciever; - QString updateSlot; }; namespace AccountManagerAuth { @@ -90,7 +86,7 @@ public: DataServerAccountInfo& getAccountInfo() { return _accountInfo; } void setAccountInfo(const DataServerAccountInfo &newAccountInfo); - static QJsonObject dataObjectFromResponse(QNetworkReply& requestReply); + static QJsonObject dataObjectFromResponse(QNetworkReply* requestReply); QUuid getSessionID() const { return _sessionID; } void setSessionID(const QUuid& sessionID); @@ -126,11 +122,10 @@ signals: void newKeypair(); private slots: - void processReply(); void handleKeypairGenerationError(); void processGeneratedKeypair(QByteArray publicKey, QByteArray privateKey); - void publicKeyUploadSucceeded(QNetworkReply& reply); - void publicKeyUploadFailed(QNetworkReply& reply); + void publicKeyUploadSucceeded(QNetworkReply* reply); + void publicKeyUploadFailed(QNetworkReply* reply); void generateNewKeypair(bool isUserKeypair = true, const QUuid& domainID = QUuid()); private: @@ -146,8 +141,6 @@ private: UserAgentGetter _userAgentGetter; QUrl _authURL; - - QMap _pendingCallbackMap; DataServerAccountInfo _accountInfo; bool _isWaitingForTokenRefresh { false }; diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 317be194b8..3fe75c5495 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -215,9 +215,8 @@ const JSONCallbackParameters& AddressManager::apiCallbackParameters() { static JSONCallbackParameters callbackParams; if (!hasSetupParameters) { - callbackParams.jsonCallbackReceiver = this; + callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "handleAPIResponse"; - callbackParams.errorCallbackReceiver = this; callbackParams.errorCallbackMethod = "handleAPIError"; } @@ -377,8 +376,8 @@ void AddressManager::handleLookupString(const QString& lookupString, bool fromSu const QString DATA_OBJECT_DOMAIN_KEY = "domain"; -void AddressManager::handleAPIResponse(QNetworkReply& requestReply) { - QJsonObject responseObject = QJsonDocument::fromJson(requestReply.readAll()).object(); +void AddressManager::handleAPIResponse(QNetworkReply* requestReply) { + QJsonObject responseObject = QJsonDocument::fromJson(requestReply->readAll()).object(); QJsonObject dataObject = responseObject["data"].toObject(); // Lookup succeeded, don't keep re-trying it (especially on server restarts) @@ -396,7 +395,7 @@ void AddressManager::handleAPIResponse(QNetworkReply& requestReply) { const char OVERRIDE_PATH_KEY[] = "override_path"; const char LOOKUP_TRIGGER_KEY[] = "lookup_trigger"; -void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const QNetworkReply& reply) { +void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const QNetworkReply* reply) { const QString DATA_OBJECT_PLACE_KEY = "place"; const QString DATA_OBJECT_USER_LOCATION_KEY = "location"; @@ -461,7 +460,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const emit possibleDomainChangeRequiredViaICEForID(iceServerAddress, domainID); } - LookupTrigger trigger = (LookupTrigger) reply.property(LOOKUP_TRIGGER_KEY).toInt(); + LookupTrigger trigger = (LookupTrigger) reply->property(LOOKUP_TRIGGER_KEY).toInt(); // set our current root place id to the ID that came back @@ -495,7 +494,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const } // check if we had a path to override the path returned - QString overridePath = reply.property(OVERRIDE_PATH_KEY).toString(); + QString overridePath = reply->property(OVERRIDE_PATH_KEY).toString(); if (!overridePath.isEmpty() && overridePath != "/") { // make sure we don't re-handle an overriden path if this was a refresh of info from API @@ -543,10 +542,10 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const } } -void AddressManager::handleAPIError(QNetworkReply& errorReply) { - qCDebug(networking) << "AddressManager API error -" << errorReply.error() << "-" << errorReply.errorString(); +void AddressManager::handleAPIError(QNetworkReply* errorReply) { + qCDebug(networking) << "AddressManager API error -" << errorReply->error() << "-" << errorReply->errorString(); - if (errorReply.error() == QNetworkReply::ContentNotFoundError) { + if (errorReply->error() == QNetworkReply::ContentNotFoundError) { // if this is a lookup that has no result, don't keep re-trying it _previousLookup.clear(); @@ -874,14 +873,14 @@ QString AddressManager::getDomainID() const { return DependencyManager::get()->getDomainHandler().getUUID().toString(); } -void AddressManager::handleShareableNameAPIResponse(QNetworkReply& requestReply) { +void AddressManager::handleShareableNameAPIResponse(QNetworkReply* requestReply) { // make sure that this response is for the domain we're currently connected to auto domainID = DependencyManager::get()->getDomainHandler().getUUID(); - if (requestReply.url().toString().contains(uuidStringWithoutCurlyBraces(domainID))) { + if (requestReply->url().toString().contains(uuidStringWithoutCurlyBraces(domainID))) { // check for a name or default name in the API response - QJsonObject responseObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + QJsonObject responseObject = QJsonDocument::fromJson(requestReply->readAll()).object(); QJsonObject domainObject = responseObject["domain"].toObject(); const QString DOMAIN_NAME_KEY = "name"; @@ -917,7 +916,7 @@ void AddressManager::lookupShareableNameForDomainID(const QUuid& domainID) { // no error callback handling // in the case of an error we simply assume there is no default place name - callbackParams.jsonCallbackReceiver = this; + callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "handleShareableNameAPIResponse"; DependencyManager::get()->sendRequest(GET_DOMAIN_ID.arg(uuidStringWithoutCurlyBraces(domainID)), diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 38eb7ee670..37b85a9acd 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -417,13 +417,13 @@ signals: void goForwardPossible(bool isPossible); private slots: - void handleAPIResponse(QNetworkReply& requestReply); - void handleAPIError(QNetworkReply& errorReply); + void handleAPIResponse(QNetworkReply* requestReply); + void handleAPIError(QNetworkReply* errorReply); - void handleShareableNameAPIResponse(QNetworkReply& requestReply); + void handleShareableNameAPIResponse(QNetworkReply* requestReply); private: - void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply& reply); + void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply* reply); // Set host and port, and return `true` if it was changed. bool setHost(const QString& host, LookupTrigger trigger, quint16 port = 0); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 4920ea97c7..c5c49f68fe 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -549,7 +549,7 @@ void NodeList::handleICEConnectionToDomainServer() { _domainHandler.getICEClientID(), _domainHandler.getPendingDomainID()); } -} +} void NodeList::pingPunchForDomainServer() { // make sure if we're here that we actually still need to ping the domain-server diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index 7a92d4bad9..a5ee417939 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -65,7 +65,7 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall // if no callbacks specified, call our owns if (params.isEmpty()) { - params.errorCallbackReceiver = this; + params.callbackReceiver = this; params.errorCallbackMethod = "requestError"; } @@ -75,8 +75,8 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall params, NULL, multipart); } -void UserActivityLogger::requestError(QNetworkReply& errorReply) { - qCDebug(networking) << errorReply.error() << "-" << errorReply.errorString(); +void UserActivityLogger::requestError(QNetworkReply* errorReply) { + qCDebug(networking) << errorReply->error() << "-" << errorReply->errorString(); } void UserActivityLogger::launch(QString applicationVersion, bool previousSessionCrashed, int previousSessionRuntime) { diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h index b44c60eba7..e4b91b1e81 100644 --- a/libraries/networking/src/UserActivityLogger.h +++ b/libraries/networking/src/UserActivityLogger.h @@ -50,7 +50,7 @@ public slots: void wentTo(AddressManager::LookupTrigger trigger, QString destinationType, QString destinationName); private slots: - void requestError(QNetworkReply& errorReply); + void requestError(QNetworkReply* errorReply); private: UserActivityLogger(); diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 4d31067c20..b990d3612b 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -312,11 +312,26 @@ void PhysicalEntitySimulation::handleDeactivatedMotionStates(const VectorOfMotio assert(state); if (state->getType() == MOTIONSTATE_TYPE_ENTITY) { EntityMotionState* entityState = static_cast(state); - if (!serverlessMode) { - entityState->handleDeactivation(); - } EntityItemPointer entity = entityState->getEntity(); _entitiesToSort.insert(entity); + if (!serverlessMode) { + if (entity->getClientOnly()) { + switch (entityState->getOwnershipState()) { + case EntityMotionState::OwnershipState::PendingBid: + _bids.removeFirst(entityState); + entityState->clearOwnershipState(); + break; + case EntityMotionState::OwnershipState::LocallyOwned: + _owned.removeFirst(entityState); + entityState->clearOwnershipState(); + break; + default: + break; + } + } else { + entityState->handleDeactivation(); + } + } } } } diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index 3f04347f18..fdf996df25 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -37,6 +37,14 @@ public: } pop_back(); } + void removeFirst(EntityMotionState* state) { + for (uint32_t i = 0; i < size(); ++i) { + if ((*this)[i] == state) { + remove(i); + break; + } + } + } }; class PhysicalEntitySimulation : public EntitySimulation { diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index 688f3fdb5f..f0c3dfdffd 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -286,6 +286,13 @@ void OffscreenSurface::loadInternal(const QUrl& qmlSource, if (QThread::currentThread() != thread()) { qFatal("Called load on a non-surface thread"); } + + // For desktop toolbar mode window: stop script when window is closed. + if (qmlSource.isEmpty()) { + getSurfaceContext()->engine()->quit(); + return; + } + // Synchronous loading may take a while; restart the deadlock timer QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection); diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index a98e0403fa..9223e0fa03 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -472,7 +472,8 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, const graphics::HazePointer& haze, const SurfaceGeometryFramebufferPointer& surfaceGeometryFramebuffer, const AmbientOcclusionFramebufferPointer& ambientOcclusionFramebuffer, - const SubsurfaceScatteringResourcePointer& subsurfaceScatteringResource) { + const SubsurfaceScatteringResourcePointer& subsurfaceScatteringResource, + bool renderShadows) { auto args = renderContext->args; auto& batch = (*args->_batch); @@ -554,7 +555,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, // Check if keylight casts shadows bool keyLightCastShadows { false }; - if (lightStage && lightStage->_currentFrame._sunLights.size()) { + if (renderShadows && lightStage && lightStage->_currentFrame._sunLights.size()) { graphics::LightPointer keyLight = lightStage->getLight(lightStage->_currentFrame._sunLights.front()); if (keyLight) { keyLightCastShadows = keyLight->getCastShadows(); @@ -711,11 +712,6 @@ void RenderDeferredCleanup::run(const render::RenderContextPointer& renderContex } } -RenderDeferred::RenderDeferred() { - -} - - void RenderDeferred::configure(const Config& config) { } @@ -742,7 +738,7 @@ void RenderDeferred::run(const RenderContextPointer& renderContext, const Inputs args->_batch = &batch; _gpuTimer->begin(batch); - setupJob.run(renderContext, deferredTransform, deferredFramebuffer, lightingModel, haze, surfaceGeometryFramebuffer, ssaoFramebuffer, subsurfaceScatteringResource); + setupJob.run(renderContext, deferredTransform, deferredFramebuffer, lightingModel, haze, surfaceGeometryFramebuffer, ssaoFramebuffer, subsurfaceScatteringResource, _renderShadows); lightsJob.run(renderContext, deferredTransform, deferredFramebuffer, lightingModel, surfaceGeometryFramebuffer, lightClusters); diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 3b77b8137e..9b55083ad7 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -127,7 +127,8 @@ public: const graphics::HazePointer& haze, const SurfaceGeometryFramebufferPointer& surfaceGeometryFramebuffer, const AmbientOcclusionFramebufferPointer& ambientOcclusionFramebuffer, - const SubsurfaceScatteringResourcePointer& subsurfaceScatteringResource); + const SubsurfaceScatteringResourcePointer& subsurfaceScatteringResource, + bool renderShadows); }; class RenderDeferredLocals { @@ -166,7 +167,8 @@ public: using Config = RenderDeferredConfig; using JobModel = render::Job::ModelI; - RenderDeferred(); + RenderDeferred() {} + RenderDeferred(bool renderShadows) : _renderShadows(renderShadows) {} void configure(const Config& config); @@ -178,6 +180,9 @@ public: protected: gpu::RangeTimerPointer _gpuTimer; + +private: + bool _renderShadows { false }; }; class DefaultLightingSetup { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 178c00c4c7..dc65863c6e 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -401,26 +401,34 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f)); glm::vec3 meshFrameDirection = glm::vec3(worldToMeshMatrix * glm::vec4(direction, 0.0f)); - for (auto& triangleSet : _modelSpaceMeshTriangleSets) { - float triangleSetDistance = 0.0f; - BoxFace triangleSetFace; - Triangle triangleSetTriangle; - if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) { + int shapeID = 0; + for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) { + int partIndex = 0; + for (auto &partTriangleSet : meshTriangleSets) { + float triangleSetDistance = 0.0f; + BoxFace triangleSetFace; + Triangle triangleSetTriangle; + if (partTriangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) { - glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance); - glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f)); - float worldDistance = glm::distance(origin, worldIntersectionPoint); + glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance); + glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f)); + float worldDistance = glm::distance(origin, worldIntersectionPoint); - if (worldDistance < bestDistance) { - bestDistance = worldDistance; - intersectedSomething = true; - face = triangleSetFace; - bestModelTriangle = triangleSetTriangle; - bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix; - extraInfo["worldIntersectionPoint"] = vec3toVariant(worldIntersectionPoint); - extraInfo["meshIntersectionPoint"] = vec3toVariant(meshIntersectionPoint); - bestSubMeshIndex = subMeshIndex; + if (worldDistance < bestDistance) { + bestDistance = worldDistance; + intersectedSomething = true; + face = triangleSetFace; + bestModelTriangle = triangleSetTriangle; + bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix; + extraInfo["worldIntersectionPoint"] = vec3toVariant(worldIntersectionPoint); + extraInfo["meshIntersectionPoint"] = vec3toVariant(meshIntersectionPoint); + extraInfo["partIndex"] = partIndex; + extraInfo["shapeID"] = shapeID; + bestSubMeshIndex = subMeshIndex; + } } + partIndex++; + shapeID++; } subMeshIndex++; } @@ -485,12 +493,14 @@ bool Model::convexHullContains(glm::vec3 point) { glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix); glm::vec3 meshFramePoint = glm::vec3(worldToMeshMatrix * glm::vec4(point, 1.0f)); - for (const auto& triangleSet : _modelSpaceMeshTriangleSets) { - const AABox& box = triangleSet.getBounds(); - if (box.contains(meshFramePoint)) { - if (triangleSet.convexHullContains(meshFramePoint)) { - // It's inside this mesh, return true. - return true; + for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) { + for (auto &partTriangleSet : meshTriangleSets) { + const AABox& box = partTriangleSet.getBounds(); + if (box.contains(meshFramePoint)) { + if (partTriangleSet.convexHullContains(meshFramePoint)) { + // It's inside this mesh, return true. + return true; + } } } } @@ -653,9 +663,15 @@ void Model::calculateTriangleSets(const FBXGeometry& geometry) { for (int i = 0; i < numberOfMeshes; i++) { const FBXMesh& mesh = geometry.meshes.at(i); - for (int j = 0; j < mesh.parts.size(); j++) { + const int numberOfParts = mesh.parts.size(); + auto& meshTriangleSets = _modelSpaceMeshTriangleSets[i]; + meshTriangleSets.resize(numberOfParts); + + for (int j = 0; j < numberOfParts; j++) { const FBXMeshPart& part = mesh.parts.at(j); + auto& partTriangleSet = meshTriangleSets[j]; + const int INDICES_PER_TRIANGLE = 3; const int INDICES_PER_QUAD = 4; const int TRIANGLES_PER_QUAD = 2; @@ -664,7 +680,7 @@ void Model::calculateTriangleSets(const FBXGeometry& geometry) { int numberOfQuads = part.quadIndices.size() / INDICES_PER_QUAD; int numberOfTris = part.triangleIndices.size() / INDICES_PER_TRIANGLE; int totalTriangles = (numberOfQuads * TRIANGLES_PER_QUAD) + numberOfTris; - _modelSpaceMeshTriangleSets[i].reserve(totalTriangles); + partTriangleSet.reserve(totalTriangles); auto meshTransform = geometry.offset * mesh.modelTransform; @@ -686,8 +702,8 @@ void Model::calculateTriangleSets(const FBXGeometry& geometry) { Triangle tri1 = { v0, v1, v3 }; Triangle tri2 = { v1, v2, v3 }; - _modelSpaceMeshTriangleSets[i].insert(tri1); - _modelSpaceMeshTriangleSets[i].insert(tri2); + partTriangleSet.insert(tri1); + partTriangleSet.insert(tri2); } } @@ -706,7 +722,7 @@ void Model::calculateTriangleSets(const FBXGeometry& geometry) { glm::vec3 v2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f)); Triangle tri = { v0, v1, v2 }; - _modelSpaceMeshTriangleSets[i].insert(tri); + partTriangleSet.insert(tri); } } } @@ -876,56 +892,58 @@ void Model::renderDebugMeshBoxes(gpu::Batch& batch) { DependencyManager::get()->bindSimpleProgram(batch, false, false, false, true, true); - for (const auto& triangleSet : _modelSpaceMeshTriangleSets) { - auto box = triangleSet.getBounds(); + for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) { + for (auto &partTriangleSet : meshTriangleSets) { + auto box = partTriangleSet.getBounds(); - if (_debugMeshBoxesID == GeometryCache::UNKNOWN_ID) { - _debugMeshBoxesID = DependencyManager::get()->allocateID(); + if (_debugMeshBoxesID == GeometryCache::UNKNOWN_ID) { + _debugMeshBoxesID = DependencyManager::get()->allocateID(); + } + QVector points; + + glm::vec3 brn = box.getCorner(); + glm::vec3 bln = brn + glm::vec3(box.getDimensions().x, 0, 0); + glm::vec3 brf = brn + glm::vec3(0, 0, box.getDimensions().z); + glm::vec3 blf = brn + glm::vec3(box.getDimensions().x, 0, box.getDimensions().z); + + glm::vec3 trn = brn + glm::vec3(0, box.getDimensions().y, 0); + glm::vec3 tln = bln + glm::vec3(0, box.getDimensions().y, 0); + glm::vec3 trf = brf + glm::vec3(0, box.getDimensions().y, 0); + glm::vec3 tlf = blf + glm::vec3(0, box.getDimensions().y, 0); + + points << brn << bln; + points << brf << blf; + points << brn << brf; + points << bln << blf; + + points << trn << tln; + points << trf << tlf; + points << trn << trf; + points << tln << tlf; + + points << brn << trn; + points << brf << trf; + points << bln << tln; + points << blf << tlf; + + glm::vec4 color[] = { + { 0.0f, 1.0f, 0.0f, 1.0f }, // green + { 1.0f, 0.0f, 0.0f, 1.0f }, // red + { 0.0f, 0.0f, 1.0f, 1.0f }, // blue + { 1.0f, 0.0f, 1.0f, 1.0f }, // purple + { 1.0f, 1.0f, 0.0f, 1.0f }, // yellow + { 0.0f, 1.0f, 1.0f, 1.0f }, // cyan + { 1.0f, 1.0f, 1.0f, 1.0f }, // white + { 0.0f, 0.5f, 0.0f, 1.0f }, + { 0.0f, 0.0f, 0.5f, 1.0f }, + { 0.5f, 0.0f, 0.5f, 1.0f }, + { 0.5f, 0.5f, 0.0f, 1.0f }, + { 0.0f, 0.5f, 0.5f, 1.0f } }; + + DependencyManager::get()->updateVertices(_debugMeshBoxesID, points, color[colorNdx]); + DependencyManager::get()->renderVertices(batch, gpu::LINES, _debugMeshBoxesID); + colorNdx++; } - QVector points; - - glm::vec3 brn = box.getCorner(); - glm::vec3 bln = brn + glm::vec3(box.getDimensions().x, 0, 0); - glm::vec3 brf = brn + glm::vec3(0, 0, box.getDimensions().z); - glm::vec3 blf = brn + glm::vec3(box.getDimensions().x, 0, box.getDimensions().z); - - glm::vec3 trn = brn + glm::vec3(0, box.getDimensions().y, 0); - glm::vec3 tln = bln + glm::vec3(0, box.getDimensions().y, 0); - glm::vec3 trf = brf + glm::vec3(0, box.getDimensions().y, 0); - glm::vec3 tlf = blf + glm::vec3(0, box.getDimensions().y, 0); - - points << brn << bln; - points << brf << blf; - points << brn << brf; - points << bln << blf; - - points << trn << tln; - points << trf << tlf; - points << trn << trf; - points << tln << tlf; - - points << brn << trn; - points << brf << trf; - points << bln << tln; - points << blf << tlf; - - glm::vec4 color[] = { - { 0.0f, 1.0f, 0.0f, 1.0f }, // green - { 1.0f, 0.0f, 0.0f, 1.0f }, // red - { 0.0f, 0.0f, 1.0f, 1.0f }, // blue - { 1.0f, 0.0f, 1.0f, 1.0f }, // purple - { 1.0f, 1.0f, 0.0f, 1.0f }, // yellow - { 0.0f, 1.0f, 1.0f, 1.0f }, // cyan - { 1.0f, 1.0f, 1.0f, 1.0f }, // white - { 0.0f, 0.5f, 0.0f, 1.0f }, - { 0.0f, 0.0f, 0.5f, 1.0f }, - { 0.5f, 0.0f, 0.5f, 1.0f }, - { 0.5f, 0.5f, 0.0f, 1.0f }, - { 0.0f, 0.5f, 0.5f, 1.0f } }; - - DependencyManager::get()->updateVertices(_debugMeshBoxesID, points, color[colorNdx]); - DependencyManager::get()->renderVertices(batch, gpu::LINES, _debugMeshBoxesID); - colorNdx++; } _mutex.unlock(); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index bc82a0d335..0bddae6a38 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -423,8 +423,7 @@ protected: bool _overrideModelTransform { false }; bool _triangleSetsValid { false }; void calculateTriangleSets(const FBXGeometry& geometry); - QVector _modelSpaceMeshTriangleSets; // model space triangles for all sub meshes - + std::vector> _modelSpaceMeshTriangleSets; // model space triangles for all sub meshes virtual void createRenderItemSet(); diff --git a/libraries/render-utils/src/RenderCommonTask.h b/libraries/render-utils/src/RenderCommonTask.h index 79599d3ab7..65f8cdfbfc 100644 --- a/libraries/render-utils/src/RenderCommonTask.h +++ b/libraries/render-utils/src/RenderCommonTask.h @@ -61,7 +61,6 @@ protected: class DrawOverlay3D { public: using Inputs = render::VaryingSet3; - using Config = DrawOverlay3DConfig; using JobModel = render::Job::ModelI; @@ -73,7 +72,7 @@ public: protected: render::ShapePlumberPointer _shapePlumber; int _maxDrawn; // initialized by Config - bool _opaquePass{ true }; + bool _opaquePass { true }; }; class CompositeHUD { diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index dc12c0f781..32bdad280c 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -64,8 +64,8 @@ void RenderDeferredTask::configure(const Config& config) } const render::Varying RenderDeferredTask::addSelectItemJobs(JobModel& task, const char* selectionName, - const render::Varying& metas, - const render::Varying& opaques, + const render::Varying& metas, + const render::Varying& opaques, const render::Varying& transparents) { const auto selectMetaInput = SelectItems::Inputs(metas, Varying(), std::string()).asVarying(); const auto selectedMetas = task.addJob("MetaSelection", selectMetaInput, selectionName); @@ -75,7 +75,7 @@ const render::Varying RenderDeferredTask::addSelectItemJobs(JobModel& task, cons return task.addJob("TransparentSelection", selectItemInput, selectionName); } -void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { +void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output, bool renderShadows) { const auto& items = input.get(); auto fadeEffect = DependencyManager::get(); @@ -168,7 +168,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto deferredLightingInputs = RenderDeferred::Inputs(deferredFrameTransform, deferredFramebuffer, lightingModel, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, scatteringResource, lightClusters, hazeModel).asVarying(); - task.addJob("RenderDeferred", deferredLightingInputs); + task.addJob("RenderDeferred", deferredLightingInputs, renderShadows); // Similar to light stage, background stage has been filled by several potential render items and resolved for the frame in this job diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 9917058790..ab6ab177d2 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -126,7 +126,7 @@ public: RenderDeferredTask(); void configure(const Config& config); - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, bool renderShadows); private: static const render::Varying addSelectItemJobs(JobModel& task, diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp index 82426a3a1f..6f6a87c222 100644 --- a/libraries/render-utils/src/RenderViewTask.cpp +++ b/libraries/render-utils/src/RenderViewTask.cpp @@ -27,7 +27,7 @@ void RenderViewTask::build(JobModel& task, const render::Varying& input, render: assert(items.canCast()); if (isDeferred) { - task.addJob("RenderDeferredTask", items); + task.addJob("RenderDeferredTask", items, true); } else { task.addJob("Forward", items); } diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp index f248c20d41..be419e8005 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.cpp +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp @@ -23,6 +23,21 @@ void registerAudioMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, soundSharedPointerToScriptValue, soundSharedPointerFromScriptValue); } + +void AudioScriptingInterface::setLocalAudioInterface(AbstractAudioInterface* audioInterface) { + if (_localAudioInterface) { + disconnect(_localAudioInterface, &AbstractAudioInterface::isStereoInputChanged, + this, &AudioScriptingInterface::isStereoInputChanged); + } + + _localAudioInterface = audioInterface; + + if (_localAudioInterface) { + connect(_localAudioInterface, &AbstractAudioInterface::isStereoInputChanged, + this, &AudioScriptingInterface::isStereoInputChanged); + } +} + ScriptAudioInjector* AudioScriptingInterface::playSystemSound(SharedSoundPointer sound, const QVector3D& position) { AudioInjectorOptions options; options.position = glm::vec3(position.x(), position.y(), position.z()); @@ -61,11 +76,10 @@ ScriptAudioInjector* AudioScriptingInterface::playSound(SharedSoundPointer sound } bool AudioScriptingInterface::setStereoInput(bool stereo) { - bool stereoInputChanged = false; if (_localAudioInterface) { - stereoInputChanged = _localAudioInterface->setIsStereoInput(stereo); + QMetaObject::invokeMethod(_localAudioInterface, "setIsStereoInput", Q_ARG(bool, stereo)); } - return stereoInputChanged; + return true; } bool AudioScriptingInterface::isStereoInput() { diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index 36fe29243d..843fa3e8f0 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -23,9 +23,11 @@ class AudioScriptingInterface : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY + Q_PROPERTY(bool isStereoInput READ isStereoInput WRITE setStereoInput NOTIFY isStereoInputChanged) + public: virtual ~AudioScriptingInterface() {} - void setLocalAudioInterface(AbstractAudioInterface* audioInterface) { _localAudioInterface = audioInterface; } + void setLocalAudioInterface(AbstractAudioInterface* audioInterface); protected: AudioScriptingInterface() {} @@ -52,7 +54,7 @@ protected: /**jsdoc * @function Audio.setStereoInput * @param {boolean} stereo - * @returns {boolean} + * @returns {boolean} */ Q_INVOKABLE bool setStereoInput(bool stereo); @@ -114,6 +116,13 @@ signals: */ void inputReceived(const QByteArray& inputSamples); + /**jsdoc + * @function Audio.isStereoInputChanged + * @param {boolean} isStereo + * @returns {Signal} + */ + void isStereoInputChanged(bool isStereo); + private: AbstractAudioInterface* _localAudioInterface { nullptr }; }; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index f8c99b192f..99c02ba1f6 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -1639,22 +1639,24 @@ QVariantMap ScriptEngine::fetchModuleSource(const QString& modulePath, const boo loader->start(MAX_RETRIES); if (!loader->isFinished()) { - QTimer monitor; - QEventLoop loop; - QObject::connect(loader, &BatchLoader::finished, this, [&monitor, &loop]{ - monitor.stop(); - loop.quit(); + // This lambda can get called AFTER this local scope has completed. + // This is why we pass smart ptrs to the lambda instead of references to local variables. + auto monitor = std::make_shared(); + auto loop = std::make_shared(); + QObject::connect(loader, &BatchLoader::finished, this, [monitor, loop] { + monitor->stop(); + loop->quit(); }); // this helps detect the case where stop() is invoked during the download // but not seen in time to abort processing in onload()... - connect(&monitor, &QTimer::timeout, this, [this, &loop]{ + connect(monitor.get(), &QTimer::timeout, this, [this, loop] { if (isStopping()) { - loop.exit(-1); + loop->exit(-1); } }); - monitor.start(500); - loop.exec(); + monitor->start(500); + loop->exec(); } loader->deleteLater(); return req; diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index 0f6ff918b3..d385dcca84 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -191,6 +191,7 @@ void ScriptEngines::shutdownScripting() { // Gracefully stop the engine's scripting thread scriptEngine->stop(); + removeScriptEngine(scriptEngine); // We need to wait for the engine to be done running before we proceed, because we don't // want any of the scripts final "scriptEnding()" or pending "update()" methods from accessing @@ -394,6 +395,7 @@ void ScriptEngines::stopAllScripts(bool restart) { // stop all scripts qCDebug(scriptengine) << "stopping script..." << it.key(); scriptEngine->stop(); + removeScriptEngine(scriptEngine); } // wait for engines to stop (ie: providing time for .scriptEnding cleanup handlers to run) before // triggering reload of any Client scripts / Entity scripts @@ -441,6 +443,7 @@ bool ScriptEngines::stopScript(const QString& rawScriptURL, bool restart) { } } scriptEngine->stop(); + removeScriptEngine(scriptEngine); stoppedScript = true; qCDebug(scriptengine) << "stopping script..." << scriptURL; } diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 324cee3417..ccb275ffc9 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -165,6 +165,10 @@ void SpatiallyNestable::forgetChild(SpatiallyNestablePointer newChild) const { void SpatiallyNestable::setParentJointIndex(quint16 parentJointIndex) { _parentJointIndex = parentJointIndex; + auto parent = _parent.lock(); + if (parent) { + parent->recalculateChildCauterization(); + } } glm::vec3 SpatiallyNestable::worldToLocal(const glm::vec3& position, diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 5d4793ba4e..361f0aaf17 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -218,6 +218,7 @@ protected: virtual void beParentOfChild(SpatiallyNestablePointer newChild) const; virtual void forgetChild(SpatiallyNestablePointer newChild) const; + virtual void recalculateChildCauterization() const { } mutable ReadWriteLockable _childrenLock; mutable QHash _children; diff --git a/libraries/shared/src/TriangleSet.h b/libraries/shared/src/TriangleSet.h index 9f688f9def..2853d0f68e 100644 --- a/libraries/shared/src/TriangleSet.h +++ b/libraries/shared/src/TriangleSet.h @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#pragma once + #include #include "AABox.h" diff --git a/libraries/shared/src/shared/FileLogger.cpp b/libraries/shared/src/shared/FileLogger.cpp index 6a10629ee5..b64d08c0d8 100644 --- a/libraries/shared/src/shared/FileLogger.cpp +++ b/libraries/shared/src/shared/FileLogger.cpp @@ -30,7 +30,7 @@ signals: void rollingLogFile(QString newFilename); protected: - void rollFileIfNecessary(QFile& file, bool notifyListenersIfRolled = true); + void rollFileIfNecessary(QFile& file, bool force = false, bool notifyListenersIfRolled = true); virtual bool processQueueItems(const Queue& messages) override; private: @@ -79,12 +79,12 @@ FilePersistThread::FilePersistThread(const FileLogger& logger) : _logger(logger) // A file may exist from a previous run - if it does, roll the file and suppress notifying listeners. QFile file(_logger._fileName); if (file.exists()) { - rollFileIfNecessary(file, false); + rollFileIfNecessary(file, true, false); } } -void FilePersistThread::rollFileIfNecessary(QFile& file, bool notifyListenersIfRolled) { - if (file.size() > MAX_LOG_SIZE) { +void FilePersistThread::rollFileIfNecessary(QFile& file, bool force, bool notifyListenersIfRolled) { + if (force || (file.size() > MAX_LOG_SIZE)) { QString newFileName = getLogRollerFilename(); if (file.copy(newFileName)) { file.open(QIODevice::WriteOnly | QIODevice::Truncate); diff --git a/libraries/ui/src/Tooltip.cpp b/libraries/ui/src/Tooltip.cpp index c0c015e72f..bd2c4e6d8f 100644 --- a/libraries/ui/src/Tooltip.cpp +++ b/libraries/ui/src/Tooltip.cpp @@ -83,7 +83,7 @@ void Tooltip::requestHyperlinkImage() { auto accountManager = DependencyManager::get(); JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; + callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "handleAPIResponse"; accountManager->sendRequest(GET_PLACE.arg(_title), @@ -94,9 +94,9 @@ void Tooltip::requestHyperlinkImage() { } } -void Tooltip::handleAPIResponse(QNetworkReply& requestReply) { +void Tooltip::handleAPIResponse(QNetworkReply* requestReply) { // did a preview image come back? - QJsonObject responseObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + QJsonObject responseObject = QJsonDocument::fromJson(requestReply->readAll()).object(); QJsonObject dataObject = responseObject["data"].toObject(); const QString PLACE_KEY = "place"; diff --git a/libraries/ui/src/Tooltip.h b/libraries/ui/src/Tooltip.h index 5e884a7aea..b1bf7b7f3e 100644 --- a/libraries/ui/src/Tooltip.h +++ b/libraries/ui/src/Tooltip.h @@ -49,7 +49,7 @@ signals: void imageURLChanged(); private slots: - void handleAPIResponse(QNetworkReply& requestReply); + void handleAPIResponse(QNetworkReply* requestReply); private: void requestHyperlinkImage(); diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 2c52e669a0..6f00e046af 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -648,6 +648,26 @@ void TabletProxy::loadQMLSource(const QVariant& path, bool resizable) { } } +void TabletProxy::stopQMLSource() { + // For desktop toolbar mode dialogs. + if (!_toolbarMode || !_desktopWindow) { + qCDebug(uiLogging) << "tablet cannot clear QML because not desktop toolbar mode"; + return; + } + + auto root = _desktopWindow->asQuickItem(); + if (root) { + QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, "")); + if (!_currentPathLoaded.toString().isEmpty()) { + emit screenChanged(QVariant("QML"), ""); + } + _currentPathLoaded = ""; + _state = State::Home; + } else { + qCDebug(uiLogging) << "tablet cannot clear QML because _desktopWindow is null"; + } +} + bool TabletProxy::pushOntoStack(const QVariant& path) { if (QThread::currentThread() != thread()) { bool result = false; @@ -719,6 +739,7 @@ void TabletProxy::loadHomeScreen(bool forceOntoHomeScreen) { // close desktop window if (_desktopWindow->asQuickItem()) { QMetaObject::invokeMethod(_desktopWindow->asQuickItem(), "setShown", Q_ARG(const QVariant&, QVariant(false))); + stopQMLSource(); // Stop the currently loaded QML running. } } _state = State::Home; diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index 1ab29ca3fd..2d37402d01 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -443,6 +443,9 @@ protected: bool _showRunningScripts { false }; TabletButtonListModel _buttons; + +private: + void stopQMLSource(); }; Q_DECLARE_METATYPE(TabletProxy*); diff --git a/libraries/workload/src/workload/Space.cpp b/libraries/workload/src/workload/Space.cpp index 27a8639f3a..54fad79741 100644 --- a/libraries/workload/src/workload/Space.cpp +++ b/libraries/workload/src/workload/Space.cpp @@ -44,6 +44,11 @@ void Space::processResets(const Transaction::Resets& transactions) { for (auto& reset : transactions) { // Access the true item auto proxyID = std::get<0>(reset); + + // Guard against proxyID being past the end of the list. + if (proxyID < 0 || proxyID >= (int32_t)_proxies.size() || proxyID >= (int32_t)_owners.size()) { + continue; + } auto& item = _proxies[proxyID]; // Reset the item with a new payload diff --git a/scripts/developer/tests/raypickTester.js b/scripts/developer/tests/raypickTester.js new file mode 100644 index 0000000000..cebee4f29a --- /dev/null +++ b/scripts/developer/tests/raypickTester.js @@ -0,0 +1,73 @@ +// raypickTester.js +// +// display intersection details (including material) when hovering over entities/avatars/overlays +// + +/* eslint-disable comma-dangle, no-empty, no-magic-numbers */ + +var PICK_FILTERS = Picks.PICK_ENTITIES | Picks.PICK_OVERLAYS | Picks.PICK_AVATARS | Picks.PICK_INCLUDE_NONCOLLIDABLE; +var HAND_JOINT = '_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND'.replace('RIGHT', MyAvatar.getDominantHand().toUpperCase()); +var JOINT_NAME = HMD.active ? HAND_JOINT : 'Mouse'; +var UPDATE_MS = 1000/30; + +// create tect3d overlay to display hover results +var overlayID = Overlays.addOverlay('text3d', { + text: 'hover', + visible: false, + backgroundAlpha: 0, + isFacingAvatar: true, + lineHeight: 0.05, + dimensions: Vec3.HALF, +}); +Script.scriptEnding.connect(function() { + Overlays.deleteOverlay(overlayID); +}); + +// create raycast picker +var pickID = Picks.createPick(PickType.Ray, { + joint: JOINT_NAME, + filter: PICK_FILTERS, + enabled: true, +}); +var blacklist = [ overlayID ]; // exclude hover text from ray pick results +Picks.setIgnoreItems(pickID, blacklist); +Script.scriptEnding.connect(function() { + Picks.removePick(pickID); +}); + +// query object materials (using the Graphics.* API) +function getSubmeshMaterial(objectID, shapeID) { + try { + var materialLayers = Graphics.getModel(objectID).materialLayers; + var shapeMaterialLayers = materialLayers[shapeID]; + return shapeMaterialLayers[0].material; + } catch (e) { + return { name: '' }; + } +} + +// refresh hover overlay text based on intersection results +function updateOverlay(overlayID, result) { + var material = this.getSubmeshMaterial(result.objectID, result.extraInfo.shapeID); + var position = Vec3.mix(result.searchRay.origin, result.intersection, 0.5); + var extraInfo = result.extraInfo; + var text = [ + 'mesh: ' + extraInfo.subMeshName, + 'materialName: ' + material.name, + 'type: ' + Entities.getNestableType(result.objectID), + 'distance: ' + result.distance.toFixed(2)+'m', + ['submesh: ' + extraInfo.subMeshIndex, 'part: '+extraInfo.partIndex, 'shape: '+extraInfo.shapeID].join(' | '), + ].filter(Boolean).join('\n'); + + Overlays.editOverlay(overlayID, { + text: text, + position: position, + visible: result.intersects, + }); +} + +// monitor for enw results at 30fps +Script.setInterval(function() { + var result = Picks.getPrevPickResult(pickID); + updateOverlay(overlayID, result); +}, UPDATE_MS); diff --git a/scripts/system/+android/audio.js b/scripts/system/+android/audio.js index 50919ed5d1..34dd52604a 100644 --- a/scripts/system/+android/audio.js +++ b/scripts/system/+android/audio.js @@ -46,7 +46,6 @@ function init() { function onMuteClicked() { Audio.muted = !Audio.muted; - Menu.triggerOption("Out of Bounds Vector Access"); } function onMutePressed() { diff --git a/scripts/system/assets/animations/Cheer.fbx b/scripts/system/assets/animations/Cheering.fbx similarity index 100% rename from scripts/system/assets/animations/Cheer.fbx rename to scripts/system/assets/animations/Cheering.fbx diff --git a/scripts/system/assets/animations/Clap.fbx b/scripts/system/assets/animations/Clapping.fbx similarity index 100% rename from scripts/system/assets/animations/Clap.fbx rename to scripts/system/assets/animations/Clapping.fbx diff --git a/scripts/system/assets/animations/Cry.fbx b/scripts/system/assets/animations/Crying.fbx similarity index 67% rename from scripts/system/assets/animations/Cry.fbx rename to scripts/system/assets/animations/Crying.fbx index 7ecd6415a5..2e60ba2450 100644 Binary files a/scripts/system/assets/animations/Cry.fbx and b/scripts/system/assets/animations/Crying.fbx differ diff --git a/scripts/system/assets/animations/Dance.fbx b/scripts/system/assets/animations/Dancing.fbx similarity index 100% rename from scripts/system/assets/animations/Dance.fbx rename to scripts/system/assets/animations/Dancing.fbx diff --git a/scripts/system/assets/animations/Point.fbx b/scripts/system/assets/animations/Pointing.fbx similarity index 100% rename from scripts/system/assets/animations/Point.fbx rename to scripts/system/assets/animations/Pointing.fbx diff --git a/scripts/system/assets/animations/Wave.fbx b/scripts/system/assets/animations/Waving.fbx similarity index 100% rename from scripts/system/assets/animations/Wave.fbx rename to scripts/system/assets/animations/Waving.fbx diff --git a/scripts/system/assets/models/teleport-destination.fbm/Teleportation-Destination-Texture2.png b/scripts/system/assets/models/teleport-destination.fbm/Teleportation-Destination-Texture2.png new file mode 100644 index 0000000000..eb9addcfca Binary files /dev/null and b/scripts/system/assets/models/teleport-destination.fbm/Teleportation-Destination-Texture2.png differ diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index a835373e4d..066ef18c97 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -260,7 +260,7 @@ function Grabber() { this.mouseRayOverlays = Picks.createPick(PickType.Ray, { joint: "Mouse", - filter: Picks.PICK_OVERLAYS, + filter: Picks.PICK_OVERLAYS | Picks.PICK_INCLUDE_NONCOLLIDABLE, enabled: true }); var tabletItems = getMainTabletIDs(); @@ -270,7 +270,7 @@ function Grabber() { var renderStates = [{name: "grabbed", end: beacon}]; this.mouseRayEntities = Pointers.createPointer(PickType.Ray, { joint: "Mouse", - filter: Picks.PICK_ENTITIES, + filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE, faceAvatar: true, scaleWithAvatar: true, enabled: true, @@ -315,7 +315,7 @@ Grabber.prototype.pressEvent = function(event) { return; } - if (event.isLeftButton !== true || event.isRightButton === true || event.isMiddleButton === true) { + if (event.button !== "LEFT") { return; } @@ -419,7 +419,7 @@ Grabber.prototype.pressEvent = function(event) { }; Grabber.prototype.releaseEvent = function(event) { - if ((event.isLeftButton!==true || event.isRightButton===true || event.isMiddleButton===true) && !HMD.active) { + if (event.button !== "LEFT" && !HMD.active) { return; } diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 05f5e3cb19..e9c7a49378 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -475,15 +475,16 @@ var toolBar = (function () { var DELETE_ENTITY_TIMER_TIMEOUT = 100; function checkDeletedEntityAndUpdate(entityID) { - // Allow for multiple entity deletes before updating the entity list. + // Allow for multiple entity deletes before updating the entities selected. entitiesToDelete.push(entityID); if (deletedEntityTimer !== null) { Script.clearTimeout(deletedEntityTimer); } deletedEntityTimer = Script.setTimeout(function () { - selectionManager.removeEntities(entitiesToDelete); - entityListTool.clearEntityList(); - entityListTool.sendUpdate(); + if (entitiesToDelete.length > 0) { + selectionManager.removeEntities(entitiesToDelete); + } + entityListTool.removeEntities(entitiesToDelete, selectionManager.selections); entitiesToDelete = []; deletedEntityTimer = null; }, DELETE_ENTITY_TIMER_TIMEOUT); @@ -2027,12 +2028,8 @@ var PropertiesTool = function (opts) { } else if (data.type === "update") { selectionManager.saveProperties(); if (selectionManager.selections.length > 1) { - properties = { - locked: data.properties.locked, - visible: data.properties.visible - }; for (i = 0; i < selectionManager.selections.length; i++) { - Entities.editEntity(selectionManager.selections[i], properties); + Entities.editEntity(selectionManager.selections[i], data.properties); } } else if (data.properties) { if (data.properties.dynamic === false) { diff --git a/scripts/system/emote.js b/scripts/system/emote.js index 87fc86d569..b99972253f 100644 --- a/scripts/system/emote.js +++ b/scripts/system/emote.js @@ -16,7 +16,8 @@ (function() { // BEGIN LOCAL_SCOPE -var EMOTE_ANIMATIONS = ['Cry', 'Surprised', 'Dance', 'Cheer', 'Wave', 'Fall', 'Point', 'Clap', 'Sit1', 'Sit2', 'Sit3', 'Love']; +var EMOTE_ANIMATIONS = + ['Crying', 'Surprised', 'Dancing', 'Cheering', 'Waving', 'Fall', 'Pointing', 'Clapping', 'Sit1', 'Sit2', 'Sit3', 'Love']; var ANIMATIONS = Array(); var eventMappingName = "io.highfidelity.away"; // restoreAnimation on hand controller button events, too @@ -36,6 +37,7 @@ var EMOTE_LABEL = "EMOTE"; var EMOTE_APP_SORT_ORDER = 12; var FPS = 60; var MSEC_PER_SEC = 1000; +var FINISHED = 3; // see ScriptableResource::State var onEmoteScreen = false; var button; @@ -73,63 +75,65 @@ function onWebEventReceived(event) { if (event.type === "click") { + // Allow for a random sitting animation when a user selects sit + var randSit = Math.floor(Math.random() * 3) + 1; + var emoteName = event.data; - if (activeTimer !== false) { - Script.clearTimeout(activeTimer); + if (emoteName === "Sit"){ + emoteName = event.data + randSit; // Sit1, Sit2, Sit3 } - // If the activeEmote is different from the chosen emote, then play the new emote. Other wise, - // This is a second click on the same emote as the activeEmote, and we will just stop it. - if (activeEmote !== emoteName) { - activeEmote = emoteName; + if (ANIMATIONS[emoteName].resource.state === FINISHED) { - // Allow for a random sitting animation when a user selects sit - var randSit = Math.floor(Math.random() * 3) + 1; - if (emoteName === "Sit"){ - emoteName = event.data + randSit; // "Sit1, Sit2, Sit3" + if (activeTimer !== false) { + Script.clearTimeout(activeTimer); } - var frameCount = ANIMATIONS[emoteName].animation.frames.length; - - // Three types of emotes (non-looping end, non-looping return, looping) - if (emoteName.match(/^Sit.*$/) || emoteName === "Fall") { // non-looping end - - MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount); - - // Non-looping return - } else if (emoteName === "Love" || emoteName === "Surprised" || emoteName === "Cry" || emoteName === "Point"){ - - MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount); - var timeOut = MSEC_PER_SEC * frameCount / FPS; - activeTimer = Script.setTimeout(function () { - MyAvatar.restoreAnimation(); - activeTimer = false; - activeEmote = false; - }, timeOut); + // If the activeEmote is different from the chosen emote, then play the new emote + // This is a second click on the same emote as the activeEmote, and we will just stop it + if (activeEmote !== emoteName) { + activeEmote = emoteName; - } else { // Looping - - MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, true, 0, frameCount); + + // Sit is the only animation currently that plays and then ends at the last frame + if (emoteName.match(/^Sit.*$/)) { - } - - } else { - activeEmote = false; - MyAvatar.restoreAnimation(); - } + // If user provides input during a sit, the avatar animation state should be restored + Controller.keyPressEvent.connect(restoreAnimation); + MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount); + + } else { + + activeEmote = emoteName; + var frameCount = ANIMATIONS[emoteName].animation.frames.length; + MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount); - + var timeOut = MSEC_PER_SEC * frameCount / FPS; + activeTimer = Script.setTimeout(function () { + MyAvatar.restoreAnimation(); + activeTimer = false; + activeEmote = false; + }, timeOut); + + } + + } else { + activeEmote = false; + MyAvatar.restoreAnimation(); + } + } } } -// If a user provides input, end the emote animation and restore the navigation animation states (idle, walk, run) +// Restore the navigation animation states (idle, walk, run) function restoreAnimation() { MyAvatar.restoreAnimation(); + + // Make sure the input is disconnected after animations are restored so it doesn't affect any emotes other than sit + Controller.keyPressEvent.disconnect(restoreAnimation); } - -Controller.keyPressEvent.connect(restoreAnimation); - + // Note peek() so as to not interfere with other mappings. eventMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(restoreAnimation); eventMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(restoreAnimation); @@ -137,6 +141,10 @@ eventMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(restoreAnima eventMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(restoreAnimation); eventMapping.from(Controller.Standard.LB).peek().to(restoreAnimation); eventMapping.from(Controller.Standard.LS).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RY).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RX).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LY).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LX).peek().to(restoreAnimation); eventMapping.from(Controller.Standard.LeftGrip).peek().to(restoreAnimation); eventMapping.from(Controller.Standard.RB).peek().to(restoreAnimation); eventMapping.from(Controller.Standard.RS).peek().to(restoreAnimation); diff --git a/scripts/system/html/EmoteApp.html b/scripts/system/html/EmoteApp.html index 16dee490a9..6b42fb8dc8 100644 --- a/scripts/system/html/EmoteApp.html +++ b/scripts/system/html/EmoteApp.html @@ -98,14 +98,14 @@

Choose an emote:

-

+

-

-

-

+

+

+

-

-

+

+

diff --git a/scripts/system/html/js/colpick.js b/scripts/system/html/js/colpick.js index 199c624bc5..505dd294d6 100644 --- a/scripts/system/html/js/colpick.js +++ b/scripts/system/html/js/colpick.js @@ -269,6 +269,9 @@ For usage and examples: colpick.com/plugin }, // Show/hide the color picker show = function (ev) { + if ($(this).attr('disabled')) { + return; + } // Prevent the trigger of any direct parent ev.stopPropagation(); var cal = $('#' + $(this).data('colpickId')); diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 88b3ccbf7c..a8c0e22ae6 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -184,17 +184,27 @@ function loaded() { currentElement.onclick = onRowClicked; currentElement.ondblclick = onRowDoubleClicked; }); - - if (refreshEntityListTimer) { - clearTimeout(refreshEntityListTimer); - } - refreshEntityListTimer = setTimeout(refreshEntityListObject, 50); } else { var item = entities[id].item; item.values({ name: name, url: filename, locked: locked, visible: visible }); } } + function removeEntities(deletedIDs) { + for (i = 0, length = deletedIDs.length; i < length; i++) { + delete entities[deletedIDs[i]]; + entityList.remove("id", deletedIDs[i]); + } + } + + function scheduleRefreshEntityList() { + var REFRESH_DELAY = 50; + if (refreshEntityListTimer) { + clearTimeout(refreshEntityListTimer); + } + refreshEntityListTimer = setTimeout(refreshEntityListObject, REFRESH_DELAY); + } + function clearEntities() { entities = {}; entityList.clear(); @@ -346,7 +356,7 @@ function loaded() { if (notFound) { refreshEntities(); } - } else if (data.type == "update") { + } else if (data.type === "update" && data.selectedIDs !== undefined) { var newEntities = data.entities; if (newEntities && newEntities.length == 0) { elNoEntitiesMessage.style.display = "block"; @@ -365,13 +375,15 @@ function loaded() { newEntities[i].hasScript ? SCRIPT_GLYPH : null); } updateSelectedEntities(data.selectedIDs); + scheduleRefreshEntityList(); resize(); } - } else if (data.type === "deleted") { - for (i = 0, length = data.ids.length; i < length; i++) { - delete entities[data.ids[i]]; - entityList.remove("id", data.ids[i]); - } + } else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) { + removeEntities(data.deletedIDs); + updateSelectedEntities(data.selectedIDs); + scheduleRefreshEntityList(); + } else if (data.type === "deleted" && data.ids) { + removeEntities(data.ids); refreshFooter(); } }); diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 4271aa9b09..a6a781b35f 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -893,9 +893,9 @@ function loaded() { } else { elServerScriptStatus.innerText = "Not running"; } - } else if (data.type === "update") { + } else if (data.type === "update" && data.selections) { - if (!data.selections || data.selections.length === 0) { + if (data.selections.length === 0) { if (lastEntityID !== null) { if (editor !== null) { saveJSONUserData(true); @@ -911,7 +911,7 @@ function loaded() { elID.value = ""; elPropertiesList.className = ''; disableProperties(); - } else if (data.selections && data.selections.length > 1) { + } else if (data.selections.length > 1) { deleteJSONEditor(); deleteJSONMaterialEditor(); var selections = data.selections; diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 611bd4d84c..3fda7588df 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -57,6 +57,15 @@ EntityListTool = function(opts) { webView.emitScriptEvent(JSON.stringify(data)); }; + that.removeEntities = function (deletedIDs, selectedIDs) { + var data = { + type: 'removeEntities', + deletedIDs: deletedIDs, + selectedIDs: selectedIDs + }; + webView.emitScriptEvent(JSON.stringify(data)); + }; + function valueIfDefined(value) { return value !== undefined ? value : ""; } diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 33e67f7ebf..cd3c9fe418 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -13,11 +13,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global SPACE_LOCAL, SelectionManager */ +/* global SelectionManager, grid, rayPlaneIntersection, rayPlaneIntersection2, pushCommandForSelections, + getMainTabletIDs, getControllerWorldLocation */ -SPACE_LOCAL = "local"; -SPACE_WORLD = "world"; -HIGHLIGHT_LIST_NAME = "editHandleHighlightList"; +var SPACE_LOCAL = "local"; +var SPACE_WORLD = "world"; +var HIGHLIGHT_LIST_NAME = "editHandleHighlightList"; Script.include([ "./controllers.js", @@ -84,7 +85,7 @@ SelectionManager = (function() { outlineWidth: 3, isOutlineSmooth: true }; - //disabling this for now as it is causing rendering issues with the other handle overlays + // disabling this for now as it is causing rendering issues with the other handle overlays //Selection.enableListHighlight(HIGHLIGHT_LIST_NAME, editHandleOutlineStyle); that.savedProperties = {}; @@ -192,7 +193,7 @@ SelectionManager = (function() { } }); return duplicatedEntityIDs; - } + }; that._update = function(selectionUpdated) { var properties = null; @@ -270,11 +271,12 @@ SelectionManager = (function() { // Normalize degrees to be in the range (-180, 180) function normalizeDegrees(degrees) { - degrees = ((degrees + 180) % 360) - 180; - if (degrees <= -180) { - degrees += 360; + var maxDegrees = 360; + var halfMaxDegrees = maxDegrees / 2; + degrees = ((degrees + halfMaxDegrees) % maxDegrees) - halfMaxDegrees; + if (degrees <= -halfMaxDegrees) { + degrees += maxDegrees; } - return degrees; } @@ -284,14 +286,14 @@ SelectionDisplay = (function() { var NEGATE_VECTOR = -1; - var COLOR_GREEN = { red:31, green:198, blue:166 }; - var COLOR_BLUE = { red:0, green:147, blue:197 }; - var COLOR_RED = { red:226, green:51, blue:77 }; - var COLOR_HOVER = { red:227, green:227, blue:227 }; + var COLOR_GREEN = { red: 31, green: 198, blue: 166 }; + var COLOR_BLUE = { red: 0, green: 147, blue: 197 }; + var COLOR_RED = { red: 226, green: 51, blue: 77 }; + var COLOR_HOVER = { red: 227, green: 227, blue: 227 }; var COLOR_ROTATE_CURRENT_RING = { red: 255, green: 99, blue: 9 }; - var COLOR_SCALE_EDGE = { red:87, green:87, blue:87 }; - var COLOR_SCALE_CUBE = { red:106, green:106, blue:106 }; - var COLOR_SCALE_CUBE_SELECTED = { red:18, green:18, blue:18 }; + var COLOR_SCALE_EDGE = { red: 87, green: 87, blue: 87 }; + var COLOR_SCALE_CUBE = { red: 106, green: 106, blue: 106 }; + var COLOR_SCALE_CUBE_SELECTED = { red: 18, green: 18, blue: 18 }; var TRANSLATE_ARROW_CYLINDER_OFFSET = 0.1; var TRANSLATE_ARROW_CYLINDER_CAMERA_DISTANCE_MULTIPLE = 0.005; @@ -320,41 +322,41 @@ SelectionDisplay = (function() { var STRETCH_PANEL_WIDTH = 0.01; var SCALE_CUBE_OFFSET = 0.5; - var SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.015; + var SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.0125; - var CLONER_OFFSET = { x:0.9, y:-0.9, z:0.9 }; + var CLONER_OFFSET = { x: 0.9, y: -0.9, z: 0.9 }; var CTRL_KEY_CODE = 16777249; var TRANSLATE_DIRECTION = { - X : 0, - Y : 1, - Z : 2 - } + X: 0, + Y: 1, + Z: 2 + }; var STRETCH_DIRECTION = { - X : 0, - Y : 1, - Z : 2, - ALL : 3 - } + X: 0, + Y: 1, + Z: 2, + ALL: 3 + }; var SCALE_DIRECTION = { - LBN : 0, - RBN : 1, - LBF : 2, - RBF : 3, - LTN : 4, - RTN : 5, - LTF : 6, - RTF : 7 - } + LBN: 0, + RBN: 1, + LBF: 2, + RBF: 3, + LTN: 4, + RTN: 5, + LTF: 6, + RTF: 7 + }; var ROTATE_DIRECTION = { - PITCH : 0, - YAW : 1, - ROLL : 2 - } + PITCH: 0, + YAW: 1, + ROLL: 2 + }; var spaceMode = SPACE_LOCAL; var overlayNames = []; @@ -397,16 +399,16 @@ SelectionDisplay = (function() { }; var handleTranslateXCone = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCones); var handleTranslateXCylinder = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCylinders); - Overlays.editOverlay(handleTranslateXCone, { color : COLOR_RED }); - Overlays.editOverlay(handleTranslateXCylinder, { color : COLOR_RED }); + Overlays.editOverlay(handleTranslateXCone, { color: COLOR_RED }); + Overlays.editOverlay(handleTranslateXCylinder, { color: COLOR_RED }); var handleTranslateYCone = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCones); var handleTranslateYCylinder = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCylinders); - Overlays.editOverlay(handleTranslateYCone, { color : COLOR_GREEN }); - Overlays.editOverlay(handleTranslateYCylinder, { color : COLOR_GREEN }); + Overlays.editOverlay(handleTranslateYCone, { color: COLOR_GREEN }); + Overlays.editOverlay(handleTranslateYCylinder, { color: COLOR_GREEN }); var handleTranslateZCone = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCones); var handleTranslateZCylinder = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCylinders); - Overlays.editOverlay(handleTranslateZCone, { color : COLOR_BLUE }); - Overlays.editOverlay(handleTranslateZCylinder, { color : COLOR_BLUE }); + Overlays.editOverlay(handleTranslateZCone, { color: COLOR_BLUE }); + Overlays.editOverlay(handleTranslateZCylinder, { color: COLOR_BLUE }); var handlePropertiesRotateRings = { alpha: 1, @@ -422,18 +424,18 @@ SelectionDisplay = (function() { }; var handleRotatePitchRing = Overlays.addOverlay("circle3d", handlePropertiesRotateRings); Overlays.editOverlay(handleRotatePitchRing, { - color : COLOR_RED, - majorTickMarksColor: COLOR_RED, + color: COLOR_RED, + majorTickMarksColor: COLOR_RED }); var handleRotateYawRing = Overlays.addOverlay("circle3d", handlePropertiesRotateRings); Overlays.editOverlay(handleRotateYawRing, { - color : COLOR_GREEN, - majorTickMarksColor: COLOR_GREEN, + color: COLOR_GREEN, + majorTickMarksColor: COLOR_GREEN }); var handleRotateRollRing = Overlays.addOverlay("circle3d", handlePropertiesRotateRings); Overlays.editOverlay(handleRotateRollRing, { - color : COLOR_BLUE, - majorTickMarksColor: COLOR_BLUE, + color: COLOR_BLUE, + majorTickMarksColor: COLOR_BLUE }); var handleRotateCurrentRing = Overlays.addOverlay("circle3d", { @@ -472,11 +474,11 @@ SelectionDisplay = (function() { drawInFront: true }; var handleStretchXSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres); - Overlays.editOverlay(handleStretchXSphere, { color : COLOR_RED }); + Overlays.editOverlay(handleStretchXSphere, { color: COLOR_RED }); var handleStretchYSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres); - Overlays.editOverlay(handleStretchYSphere, { color : COLOR_GREEN }); + Overlays.editOverlay(handleStretchYSphere, { color: COLOR_GREEN }); var handleStretchZSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres); - Overlays.editOverlay(handleStretchZSphere, { color : COLOR_BLUE }); + Overlays.editOverlay(handleStretchZSphere, { color: COLOR_BLUE }); var handlePropertiesStretchPanel = { shape: "Quad", @@ -485,13 +487,13 @@ SelectionDisplay = (function() { visible: false, ignoreRayIntersection: true, drawInFront: true - } + }; var handleStretchXPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel); - Overlays.editOverlay(handleStretchXPanel, { color : COLOR_RED }); + Overlays.editOverlay(handleStretchXPanel, { color: COLOR_RED }); var handleStretchYPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel); - Overlays.editOverlay(handleStretchYPanel, { color : COLOR_GREEN }); + Overlays.editOverlay(handleStretchYPanel, { color: COLOR_GREEN }); var handleStretchZPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel); - Overlays.editOverlay(handleStretchZPanel, { color : COLOR_BLUE }); + Overlays.editOverlay(handleStretchZPanel, { color: COLOR_BLUE }); var handlePropertiesScaleCubes = { size: 0.025, @@ -517,7 +519,7 @@ SelectionDisplay = (function() { ignoreRayIntersection: true, drawInFront: true, lineWidth: 0.2 - } + }; var handleScaleTREdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); var handleScaleTLEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); var handleScaleTFEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); @@ -785,11 +787,11 @@ SelectionDisplay = (function() { }; that.resetPreviousHandleColor = function() { - if (previousHandle != null) { + if (previousHandle !== null) { Overlays.editOverlay(previousHandle, { color: previousHandleColor }); previousHandle = null; } - if (previousHandleHelper != null) { + if (previousHandleHelper !== null) { Overlays.editOverlay(previousHandleHelper, { color: previousHandleColor }); previousHandleHelper = null; } @@ -886,7 +888,7 @@ SelectionDisplay = (function() { Overlays.editOverlay(result.overlayID, { color: COLOR_HOVER }); previousHandle = result.overlayID; previousHandleHelper = that.getHandleHelper(result.overlayID); - if (previousHandleHelper != null) { + if (previousHandleHelper !== null) { Overlays.editOverlay(previousHandleHelper, { color: COLOR_HOVER }); } previousHandleColor = pickedColor; @@ -944,7 +946,7 @@ SelectionDisplay = (function() { ctrlPressed = false; that.updateActiveRotateRing(); } - } + }; // Triggers notification on specific key driven events that.keyPressEvent = function(key) { @@ -952,7 +954,7 @@ SelectionDisplay = (function() { ctrlPressed = true; that.updateActiveRotateRing(); } - } + }; // NOTE: mousePressEvent and mouseMoveEvent from the main script should call us., so we don't hook these: // Controller.mousePressEvent.connect(that.mousePressEvent); @@ -994,6 +996,11 @@ SelectionDisplay = (function() { var toCameraDistance = Vec3.length(Vec3.subtract(cameraPosition, position)); return toCameraDistance; } + + function usePreviousPickRay(pickRayDirection, previousPickRayDirection, normal) { + return (Vec3.dot(pickRayDirection, normal) > 0 && Vec3.dot(previousPickRayDirection, normal) < 0) || + (Vec3.dot(pickRayDirection, normal) < 0 && Vec3.dot(previousPickRayDirection, normal) > 0); + } // @return string - The mode of the currently active tool; // otherwise, "UNKNOWN" if there's no active tool. @@ -1014,8 +1021,6 @@ SelectionDisplay = (function() { lastCameraOrientation = Camera.getOrientation(); if (event !== false) { - pickRay = generalComputePickRay(event.x, event.y); - var wantDebug = false; if (wantDebug) { print("select() with EVENT...... "); @@ -1041,7 +1046,8 @@ SelectionDisplay = (function() { spaceMode = newSpaceMode; that.updateHandles(); } else if (wantDebug) { - print("WARNING: entitySelectionTool.setSpaceMode - Can't update SpaceMode. CurrentMode: " + spaceMode + " DesiredMode: " + newSpaceMode); + print("WARNING: entitySelectionTool.setSpaceMode - Can't update SpaceMode. CurrentMode: " + + spaceMode + " DesiredMode: " + newSpaceMode); } if (wantDebug) { print("====== SetSpaceMode called. <========"); @@ -1091,7 +1097,8 @@ SelectionDisplay = (function() { } if (!handleTools.hasOwnProperty(toolHandle)) { - print("WARNING: entitySelectionTool.isActiveTool - Encountered unknown grabberToolHandle: " + toolHandle + ". Tools should be registered via addHandleTool."); + print("WARNING: entitySelectionTool.isActiveTool - Encountered unknown grabberToolHandle: " + + toolHandle + ". Tools should be registered via addHandleTool."); // EARLY EXIT return false; } @@ -1121,13 +1128,14 @@ SelectionDisplay = (function() { var rotationInverse = Quat.inverse(rotation); var toCameraDistance = getDistanceToCamera(position); - var localRotationX = Quat.fromPitchYawRollDegrees(0, 0, -90); + var rotationDegrees = 90; + var localRotationX = Quat.fromPitchYawRollDegrees(0, 0, -rotationDegrees); var rotationX = Quat.multiply(rotation, localRotationX); worldRotationX = rotationX; - var localRotationY = Quat.fromPitchYawRollDegrees(0, 90, 0); + var localRotationY = Quat.fromPitchYawRollDegrees(0, rotationDegrees, 0); var rotationY = Quat.multiply(rotation, localRotationY); worldRotationY = rotationY; - var localRotationZ = Quat.fromPitchYawRollDegrees(90, 0, 0); + var localRotationZ = Quat.fromPitchYawRollDegrees(rotationDegrees, 0, 0); var rotationZ = Quat.multiply(rotation, localRotationZ); worldRotationZ = rotationZ; @@ -1140,7 +1148,7 @@ SelectionDisplay = (function() { // UPDATE ROTATION RINGS // rotateDimension is used as the base dimension for all overlays var rotateDimension = Math.max(maxHandleDimension, toCameraDistance * ROTATE_RING_CAMERA_DISTANCE_MULTIPLE); - var rotateDimensions = { x:rotateDimension, y:rotateDimension, z:rotateDimension }; + var rotateDimensions = { x: rotateDimension, y: rotateDimension, z: rotateDimension }; if (!isActiveTool(handleRotatePitchRing)) { Overlays.editOverlay(handleRotatePitchRing, { position: position, @@ -1172,16 +1180,16 @@ SelectionDisplay = (function() { var arrowCylinderDimension = rotateDimension * TRANSLATE_ARROW_CYLINDER_CAMERA_DISTANCE_MULTIPLE / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; var arrowCylinderDimensions = { - x:arrowCylinderDimension, - y:arrowCylinderDimension * TRANSLATE_ARROW_CYLINDER_Y_MULTIPLE, - z:arrowCylinderDimension + x: arrowCylinderDimension, + y: arrowCylinderDimension * TRANSLATE_ARROW_CYLINDER_Y_MULTIPLE, + z: arrowCylinderDimension }; var arrowConeDimension = rotateDimension * TRANSLATE_ARROW_CONE_CAMERA_DISTANCE_MULTIPLE / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; - var arrowConeDimensions = { x:arrowConeDimension, y:arrowConeDimension, z:arrowConeDimension }; + var arrowConeDimensions = { x: arrowConeDimension, y: arrowConeDimension, z: arrowConeDimension }; var arrowCylinderOffset = rotateDimension * TRANSLATE_ARROW_CYLINDER_OFFSET / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; var arrowConeOffset = arrowCylinderDimensions.y * TRANSLATE_ARROW_CONE_OFFSET_CYLINDER_DIMENSION_MULTIPLE; - var cylinderXPosition = { x:arrowCylinderOffset, y:0, z:0 }; + var cylinderXPosition = { x: arrowCylinderOffset, y: 0, z: 0 }; cylinderXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, cylinderXPosition)); Overlays.editOverlay(handleTranslateXCylinder, { position: cylinderXPosition, @@ -1195,7 +1203,7 @@ SelectionDisplay = (function() { rotation: rotationX, dimensions: arrowConeDimensions }); - var cylinderYPosition = { x:0, y:arrowCylinderOffset, z:0 }; + var cylinderYPosition = { x: 0, y: arrowCylinderOffset, z: 0 }; cylinderYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, cylinderYPosition)); Overlays.editOverlay(handleTranslateYCylinder, { position: cylinderYPosition, @@ -1209,7 +1217,7 @@ SelectionDisplay = (function() { rotation: rotationY, dimensions: arrowConeDimensions }); - var cylinderZPosition = { x:0, y:0, z:arrowCylinderOffset }; + var cylinderZPosition = { x: 0, y: 0, z: arrowCylinderOffset }; cylinderZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, cylinderZPosition)); Overlays.editOverlay(handleTranslateZCylinder, { position: cylinderZPosition, @@ -1228,61 +1236,73 @@ SelectionDisplay = (function() { var scaleCubeOffsetX = SCALE_CUBE_OFFSET * dimensions.x; var scaleCubeOffsetY = SCALE_CUBE_OFFSET * dimensions.y; var scaleCubeOffsetZ = SCALE_CUBE_OFFSET * dimensions.z; - var scaleCubeDimension = rotateDimension * SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE / - ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; - var scaleCubeDimensions = { x:scaleCubeDimension, y:scaleCubeDimension, z:scaleCubeDimension }; var scaleCubeRotation = spaceMode === SPACE_LOCAL ? rotation : Quat.IDENTITY; - var scaleLBNCubePosition = { x:-scaleCubeOffsetX, y:-scaleCubeOffsetY, z:-scaleCubeOffsetZ }; + var scaleLBNCubePosition = { x: -scaleCubeOffsetX, y: -scaleCubeOffsetY, z: -scaleCubeOffsetZ }; scaleLBNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLBNCubePosition)); + var scaleLBNCubeToCamera = getDistanceToCamera(scaleLBNCubePosition); + var scaleRBNCubePosition = { x: scaleCubeOffsetX, y: -scaleCubeOffsetY, z: -scaleCubeOffsetZ }; + scaleRBNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRBNCubePosition)); + var scaleRBNCubeToCamera = getDistanceToCamera(scaleRBNCubePosition); + var scaleLBFCubePosition = { x: -scaleCubeOffsetX, y: -scaleCubeOffsetY, z: scaleCubeOffsetZ }; + scaleLBFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLBFCubePosition)); + var scaleLBFCubeToCamera = getDistanceToCamera(scaleLBFCubePosition); + var scaleRBFCubePosition = { x: scaleCubeOffsetX, y: -scaleCubeOffsetY, z: scaleCubeOffsetZ }; + scaleRBFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRBFCubePosition)); + var scaleRBFCubeToCamera = getDistanceToCamera(scaleRBFCubePosition); + var scaleLTNCubePosition = { x: -scaleCubeOffsetX, y: scaleCubeOffsetY, z: -scaleCubeOffsetZ }; + scaleLTNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLTNCubePosition)); + var scaleLTNCubeToCamera = getDistanceToCamera(scaleLTNCubePosition); + var scaleRTNCubePosition = { x: scaleCubeOffsetX, y: scaleCubeOffsetY, z: -scaleCubeOffsetZ }; + scaleRTNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRTNCubePosition)); + var scaleRTNCubeToCamera = getDistanceToCamera(scaleRTNCubePosition); + var scaleLTFCubePosition = { x: -scaleCubeOffsetX, y: scaleCubeOffsetY, z: scaleCubeOffsetZ }; + scaleLTFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLTFCubePosition)); + var scaleLTFCubeToCamera = getDistanceToCamera(scaleLTFCubePosition); + var scaleRTFCubePosition = { x: scaleCubeOffsetX, y: scaleCubeOffsetY, z: scaleCubeOffsetZ }; + scaleRTFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRTFCubePosition)); + var scaleRTFCubeToCamera = getDistanceToCamera(scaleRTFCubePosition); + + var scaleCubeToCamera = Math.min(scaleLBNCubeToCamera, scaleRBNCubeToCamera, scaleLBFCubeToCamera, + scaleRBFCubeToCamera, scaleLTNCubeToCamera, scaleRTNCubeToCamera, + scaleLTFCubeToCamera, scaleRTFCubeToCamera); + var scaleCubeDimension = scaleCubeToCamera * SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE; + var scaleCubeDimensions = { x: scaleCubeDimension, y: scaleCubeDimension, z: scaleCubeDimension }; + Overlays.editOverlay(handleScaleLBNCube, { position: scaleLBNCubePosition, rotation: scaleCubeRotation, dimensions: scaleCubeDimensions }); - var scaleRBNCubePosition = { x:scaleCubeOffsetX, y:-scaleCubeOffsetY, z:-scaleCubeOffsetZ }; - scaleRBNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRBNCubePosition)); Overlays.editOverlay(handleScaleRBNCube, { position: scaleRBNCubePosition, rotation: scaleCubeRotation, dimensions: scaleCubeDimensions }); - var scaleLBFCubePosition = { x:-scaleCubeOffsetX, y:-scaleCubeOffsetY, z:scaleCubeOffsetZ }; - scaleLBFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLBFCubePosition)); Overlays.editOverlay(handleScaleLBFCube, { position: scaleLBFCubePosition, rotation: scaleCubeRotation, dimensions: scaleCubeDimensions }); - var scaleRBFCubePosition = { x:scaleCubeOffsetX, y:-scaleCubeOffsetY, z:scaleCubeOffsetZ }; - scaleRBFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRBFCubePosition)); Overlays.editOverlay(handleScaleRBFCube, { position: scaleRBFCubePosition, rotation: scaleCubeRotation, dimensions: scaleCubeDimensions }); - var scaleLTNCubePosition = { x:-scaleCubeOffsetX, y:scaleCubeOffsetY, z:-scaleCubeOffsetZ }; - scaleLTNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLTNCubePosition)); Overlays.editOverlay(handleScaleLTNCube, { position: scaleLTNCubePosition, rotation: scaleCubeRotation, dimensions: scaleCubeDimensions }); - var scaleRTNCubePosition = { x:scaleCubeOffsetX, y:scaleCubeOffsetY, z:-scaleCubeOffsetZ }; - scaleRTNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRTNCubePosition)); Overlays.editOverlay(handleScaleRTNCube, { position: scaleRTNCubePosition, rotation: scaleCubeRotation, dimensions: scaleCubeDimensions }); - var scaleLTFCubePosition = { x:-scaleCubeOffsetX, y:scaleCubeOffsetY, z:scaleCubeOffsetZ }; - scaleLTFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLTFCubePosition)); Overlays.editOverlay(handleScaleLTFCube, { position: scaleLTFCubePosition, rotation: scaleCubeRotation, dimensions: scaleCubeDimensions }); - var scaleRTFCubePosition = { x:scaleCubeOffsetX, y:scaleCubeOffsetY, z:scaleCubeOffsetZ }; - scaleRTFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRTFCubePosition)); Overlays.editOverlay(handleScaleRTFCube, { position: scaleRTFCubePosition, rotation: scaleCubeRotation, @@ -1306,21 +1326,21 @@ SelectionDisplay = (function() { // UPDATE STRETCH SPHERES var stretchSphereDimension = rotateDimension * STRETCH_SPHERE_CAMERA_DISTANCE_MULTIPLE / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; - var stretchSphereDimensions = { x:stretchSphereDimension, y:stretchSphereDimension, z:stretchSphereDimension }; + var stretchSphereDimensions = { x: stretchSphereDimension, y: stretchSphereDimension, z: stretchSphereDimension }; var stretchSphereOffset = rotateDimension * STRETCH_SPHERE_OFFSET / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; - var stretchXPosition = { x:stretchSphereOffset, y:0, z:0 }; + var stretchXPosition = { x: stretchSphereOffset, y: 0, z: 0 }; stretchXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchXPosition)); Overlays.editOverlay(handleStretchXSphere, { position: stretchXPosition, dimensions: stretchSphereDimensions }); - var stretchYPosition = { x:0, y:stretchSphereOffset, z:0 }; + var stretchYPosition = { x: 0, y: stretchSphereOffset, z: 0 }; stretchYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchYPosition)); Overlays.editOverlay(handleStretchYSphere, { position: stretchYPosition, dimensions: stretchSphereDimensions }); - var stretchZPosition = { x:0, y:0, z:stretchSphereOffset }; + var stretchZPosition = { x: 0, y: 0, z: stretchSphereOffset }; stretchZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchZPosition)); Overlays.editOverlay(handleStretchZSphere, { position: stretchZPosition, @@ -1337,27 +1357,29 @@ SelectionDisplay = (function() { stretchPanelXDimensions.x = STRETCH_PANEL_WIDTH; stretchPanelXDimensions.y = Math.abs(stretchPanelXDimensions.z); stretchPanelXDimensions.z = tempY; - var stretchPanelXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:dimensions.x / 2, y:0, z:0 })); + var stretchPanelXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x: dimensions.x / 2, y: 0, z: 0 })); Overlays.editOverlay(handleStretchXPanel, { position: stretchPanelXPosition, rotation: rotationZ, dimensions: stretchPanelXDimensions }); var stretchPanelYDimensions = Vec3.subtract(scaleLTNCubePositionRotated, scaleRTFCubePositionRotated); + var tempX = Math.abs(stretchPanelYDimensions.x); stretchPanelYDimensions.x = Math.abs(stretchPanelYDimensions.z); stretchPanelYDimensions.y = STRETCH_PANEL_WIDTH; - stretchPanelYDimensions.z = Math.abs(stretchPanelYDimensions.x); - var stretchPanelYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:dimensions.y / 2, z:0 })); + stretchPanelYDimensions.z = tempX; + var stretchPanelYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x: 0, y: dimensions.y / 2, z: 0 })); Overlays.editOverlay(handleStretchYPanel, { position: stretchPanelYPosition, rotation: rotationY, dimensions: stretchPanelYDimensions }); var stretchPanelZDimensions = Vec3.subtract(scaleLTNCubePositionRotated, scaleRBFCubePositionRotated); + tempX = Math.abs(stretchPanelZDimensions.x); stretchPanelZDimensions.x = Math.abs(stretchPanelZDimensions.y); - stretchPanelZDimensions.y = Math.abs(stretchPanelZDimensions.x); + stretchPanelZDimensions.y = tempX; stretchPanelZDimensions.z = STRETCH_PANEL_WIDTH; - var stretchPanelZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:0, z:dimensions.z / 2 })); + var stretchPanelZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x: 0, y: 0, z: dimensions.z / 2 })); Overlays.editOverlay(handleStretchZPanel, { position: stretchPanelZPosition, rotation: rotationX, @@ -1390,10 +1412,10 @@ SelectionDisplay = (function() { } // UPDATE CLONER (CURRENTLY HIDDEN FOR NOW) - var handleClonerOffset = { - x:CLONER_OFFSET.x * dimensions.x, - y:CLONER_OFFSET.y * dimensions.y, - z:CLONER_OFFSET.z * dimensions.z + var handleClonerOffset = { + x: CLONER_OFFSET.x * dimensions.x, + y: CLONER_OFFSET.y * dimensions.y, + z: CLONER_OFFSET.z * dimensions.z }; var handleClonerPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, handleClonerOffset)); Overlays.editOverlay(handleCloner, { @@ -1431,9 +1453,9 @@ SelectionDisplay = (function() { !isActiveTool(handleRotateYawRing) && !isActiveTool(handleRotateRollRing))); - //keep cloner always hidden for now since you can hold Alt to clone while - //translating an entity - we may bring cloner back for HMD only later - //that.setHandleClonerVisible(!activeTool || isActiveTool(handleCloner)); + // keep cloner always hidden for now since you can hold Alt to clone while + // translating an entity - we may bring cloner back for HMD only later + // that.setHandleClonerVisible(!activeTool || isActiveTool(handleCloner)); if (wantDebug) { print("====== Update Handles <======="); @@ -1451,8 +1473,8 @@ SelectionDisplay = (function() { } else if (isActiveTool(handleRotateRollRing)) { activeRotateRing = handleRotateRollRing; } - if (activeRotateRing != null) { - var tickMarksAngle = ctrlPressed ? ROTATE_CTRL_SNAP_ANGLE : ROTATE_DEFAULT_TICK_MARKS_ANGLE; + if (activeRotateRing !== null) { + var tickMarksAngle = ctrlPressed ? ROTATE_CTRL_SNAP_ANGLE : ROTATE_DEFAULT_TICK_MARKS_ANGLE; Overlays.editOverlay(activeRotateRing, { majorTickMarksAngle: tickMarksAngle }); } }; @@ -1633,7 +1655,7 @@ SelectionDisplay = (function() { }, onMove: function(event) { var wantDebug = false; - pickRay = generalComputePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); var pick = rayPlaneIntersection2(pickRay, translateXZTool.pickPlanePosition, { x: 0, @@ -1699,7 +1721,8 @@ SelectionDisplay = (function() { } constrainMajorOnly = event.isControl; - var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(-0.5, SelectionManager.worldDimensions)); + var negateAndHalve = -0.5; + var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(negateAndHalve, SelectionManager.worldDimensions)); vector = Vec3.subtract( grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), cornerPosition); @@ -1745,15 +1768,16 @@ SelectionDisplay = (function() { var pickNormal = null; var lastPick = null; var projectionVector = null; + var previousPickRay = null; addHandleTool(overlay, { mode: mode, onBegin: function(event, pickRay, pickResult) { if (direction === TRANSLATE_DIRECTION.X) { - pickNormal = { x:0, y:1, z:1 }; + pickNormal = { x: 0, y: 1, z: 1 }; } else if (direction === TRANSLATE_DIRECTION.Y) { - pickNormal = { x:1, y:0, z:1 }; + pickNormal = { x: 1, y: 0, z: 1 }; } else if (direction === TRANSLATE_DIRECTION.Z) { - pickNormal = { x:1, y:1, z:0 }; + pickNormal = { x: 1, y: 1, z: 0 }; } var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; @@ -1780,22 +1804,29 @@ SelectionDisplay = (function() { } else { duplicatedEntityIDs = null; } + + previousPickRay = pickRay; }, onEnd: function(event, reason) { pushCommandForSelections(duplicatedEntityIDs); }, onMove: function(event) { - pickRay = generalComputePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); + + // Use previousPickRay if new pickRay will cause resulting rayPlaneIntersection values to wrap around + if (usePreviousPickRay(pickRay.direction, previousPickRay.direction, pickNormal)) { + pickRay = previousPickRay; + } var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal); var vector = Vec3.subtract(newIntersection, lastPick); if (direction === TRANSLATE_DIRECTION.X) { - projectionVector = { x:1, y:0, z:0 }; + projectionVector = { x: 1, y: 0, z: 0 }; } else if (direction === TRANSLATE_DIRECTION.Y) { - projectionVector = { x:0, y:1, z:0 }; + projectionVector = { x: 0, y: 1, z: 0 }; } else if (direction === TRANSLATE_DIRECTION.Z) { - projectionVector = { x:0, y:0, z:1 }; + projectionVector = { x: 0, y: 0, z: 1 }; } var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; @@ -1829,6 +1860,8 @@ SelectionDisplay = (function() { var newPosition = Vec3.sum(properties.position, vector); Entities.editEntity(id, { position: newPosition }); } + + previousPickRay = pickRay; SelectionManager._update(); } @@ -1869,7 +1902,6 @@ SelectionDisplay = (function() { var lastPick3D = null; var initialPosition = null; var initialDimensions = null; - var initialIntersection = null; var initialProperties = null; var registrationPoint = null; var deltaPivot = null; @@ -1877,6 +1909,7 @@ SelectionDisplay = (function() { var pickRayPosition = null; var pickRayPosition3D = null; var rotation = null; + var previousPickRay = null; var onBegin = function(event, pickRay, pickResult) { var properties = Entities.getEntityProperties(SelectionManager.selections[0]); @@ -1909,7 +1942,7 @@ SelectionDisplay = (function() { var scaledOffset = Vec3.multiply(0.5, offset); // Offset from the registration point - offsetRP = Vec3.subtract(scaledOffset, centeredRP); + var offsetRP = Vec3.subtract(scaledOffset, centeredRP); // Scaled offset in world coordinates var scaledOffsetWorld = vec3Mult(initialDimensions, offsetRP); @@ -1919,57 +1952,10 @@ SelectionDisplay = (function() { if (directionFor3DStretch) { // pivot, offset and pickPlanePosition for 3D manipulation var scaledPivot3D = Vec3.multiply(0.5, Vec3.multiply(1.0, directionFor3DStretch)); - deltaPivot3D = Vec3.subtract(centeredRP, scaledPivot3D); - - var scaledOffsetWorld3D = vec3Mult(initialDimensions, - Vec3.subtract(Vec3.multiply(0.5, Vec3.multiply(-1.0, directionFor3DStretch)), centeredRP)); - + deltaPivot3D = Vec3.subtract(centeredRP, scaledPivot3D); pickRayPosition3D = Vec3.sum(initialPosition, Vec3.multiplyQbyV(rotation, scaledOffsetWorld)); } - var start = null; - var end = null; - if ((numDimensions === 1) && mask.x) { - start = Vec3.multiplyQbyV(rotation, { - x: -10000, - y: 0, - z: 0 - }); - start = Vec3.sum(start, properties.position); - end = Vec3.multiplyQbyV(rotation, { - x: 10000, - y: 0, - z: 0 - }); - end = Vec3.sum(end, properties.position); - } - if ((numDimensions === 1) && mask.y) { - start = Vec3.multiplyQbyV(rotation, { - x: 0, - y: -10000, - z: 0 - }); - start = Vec3.sum(start, properties.position); - end = Vec3.multiplyQbyV(rotation, { - x: 0, - y: 10000, - z: 0 - }); - end = Vec3.sum(end, properties.position); - } - if ((numDimensions === 1) && mask.z) { - start = Vec3.multiplyQbyV(rotation, { - x: 0, - y: 0, - z: -10000 - }); - start = Vec3.sum(start, properties.position); - end = Vec3.multiplyQbyV(rotation, { - x: 0, - y: 0, - z: 10000 - }); - end = Vec3.sum(end, properties.position); - } + if (numDimensions === 1) { if (mask.x === 1) { planeNormal = { @@ -2040,10 +2026,10 @@ SelectionDisplay = (function() { SelectionManager.saveProperties(); that.resetPreviousHandleColor(); - if (stretchPanel != null) { + if (stretchPanel !== null) { Overlays.editOverlay(stretchPanel, { visible: true }); } - if (scaleHandle != null) { + if (scaleHandle !== null) { Overlays.editOverlay(scaleHandle, { color: COLOR_SCALE_CUBE_SELECTED }); } @@ -2053,13 +2039,15 @@ SelectionDisplay = (function() { Entities.editEntity(SelectionManager.selections[0], {collidesWith: newCollidesWith}); that.replaceCollisionsAfterStretch = true; } + + previousPickRay = pickRay; }; var onEnd = function(event, reason) { - if (stretchPanel != null) { + if (stretchPanel !== null) { Overlays.editOverlay(stretchPanel, { visible: false }); } - if (scaleHandle != null) { + if (scaleHandle !== null) { Overlays.editOverlay(scaleHandle, { color: COLOR_SCALE_CUBE }); } @@ -2075,14 +2063,12 @@ SelectionDisplay = (function() { var onMove = function(event) { var proportional = (spaceMode === SPACE_WORLD) || directionEnum === STRETCH_DIRECTION.ALL; - var position, dimensions, rotation; + var position, rotation; if (spaceMode === SPACE_LOCAL) { position = SelectionManager.localPosition; - dimensions = SelectionManager.localDimensions; rotation = SelectionManager.localRotation; } else { position = SelectionManager.worldPosition; - dimensions = SelectionManager.worldDimensions; rotation = SelectionManager.worldRotation; } @@ -2090,9 +2076,15 @@ SelectionDisplay = (function() { var localSigns = signs; var pickRay = generalComputePickRay(event.x, event.y); + // Use previousPickRay if new pickRay will cause resulting rayPlaneIntersection values to wrap around + if (usePreviousPickRay(pickRay.direction, previousPickRay.direction, planeNormal)) { + pickRay = previousPickRay; + } + // Are we using handControllers or Mouse - only relevant for 3D tools var controllerPose = getControllerWorldLocation(activeHand, true); var vector = null; + var newPick = null; if (HMD.isHMDAvailable() && HMD.isHandControllerAvailable() && controllerPose.valid && that.triggered && directionFor3DStretch) { localDeltaPivot = deltaPivot3D; @@ -2143,14 +2135,23 @@ SelectionDisplay = (function() { } var minimumDimension = directionEnum === STRETCH_DIRECTION.ALL ? STRETCH_ALL_MINIMUM_DIMENSION : - STRETCH_MINIMUM_DIMENSION; - newDimensions.x = Math.max(newDimensions.x, minimumDimension); - newDimensions.y = Math.max(newDimensions.y, minimumDimension); - newDimensions.z = Math.max(newDimensions.z, minimumDimension); + STRETCH_MINIMUM_DIMENSION; + if (newDimensions.x < minimumDimension) { + newDimensions.x = minimumDimension; + changeInDimensions.x = minimumDimension - initialDimensions.x; + } + if (newDimensions.y < minimumDimension) { + newDimensions.y = minimumDimension; + changeInDimensions.y = minimumDimension - initialDimensions.y; + } + if (newDimensions.z < minimumDimension) { + newDimensions.z = minimumDimension; + changeInDimensions.z = minimumDimension - initialDimensions.z; + } var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(localDeltaPivot, changeInDimensions)); if (directionEnum === STRETCH_DIRECTION.ALL) { - changeInPosition = { x:0, y:0, z:0 }; + changeInPosition = { x: 0, y: 0, z: 0 }; } var newPosition = Vec3.sum(initialPosition, changeInPosition); @@ -2168,6 +2169,8 @@ SelectionDisplay = (function() { Vec3.print(" changeInPosition:", changeInPosition); Vec3.print(" newPosition:", newPosition); } + + previousPickRay = pickRay; SelectionManager._update(); };// End of onMove def @@ -2184,13 +2187,13 @@ SelectionDisplay = (function() { var directionVector, offset, stretchPanel; if (directionEnum === STRETCH_DIRECTION.X) { stretchPanel = handleStretchXPanel; - directionVector = { x:-1, y:0, z:0 }; + directionVector = { x: -1, y: 0, z: 0 }; } else if (directionEnum === STRETCH_DIRECTION.Y) { stretchPanel = handleStretchYPanel; - directionVector = { x:0, y:-1, z:0 }; + directionVector = { x: 0, y: -1, z: 0 }; } else if (directionEnum === STRETCH_DIRECTION.Z) { - stretchPanel = handleStretchZPanel - directionVector = { x:0, y:0, z:-1 }; + stretchPanel = handleStretchZPanel; + directionVector = { x: 0, y: 0, z: -1 }; } offset = Vec3.multiply(directionVector, NEGATE_VECTOR); var tool = makeStretchTool(mode, directionEnum, directionVector, directionVector, offset, stretchPanel, null); @@ -2201,28 +2204,28 @@ SelectionDisplay = (function() { function addHandleScaleTool(overlay, mode, directionEnum) { var directionVector, offset, selectedHandle; if (directionEnum === SCALE_DIRECTION.LBN) { - directionVector = { x:1, y:1, z:1 }; + directionVector = { x: 1, y: 1, z: 1 }; selectedHandle = handleScaleLBNCube; } else if (directionEnum === SCALE_DIRECTION.RBN) { - directionVector = { x:-1, y:1, z:1 }; + directionVector = { x: -1, y: 1, z: 1 }; selectedHandle = handleScaleRBNCube; } else if (directionEnum === SCALE_DIRECTION.LBF) { - directionVector = { x:1, y:1, z:-1 }; + directionVector = { x: 1, y: 1, z: -1 }; selectedHandle = handleScaleLBFCube; } else if (directionEnum === SCALE_DIRECTION.RBF) { - directionVector = { x:-1, y:1, z:-1 }; + directionVector = { x: -1, y: 1, z: -1 }; selectedHandle = handleScaleRBFCube; } else if (directionEnum === SCALE_DIRECTION.LTN) { - directionVector = { x:1, y:-1, z:1 }; + directionVector = { x: 1, y: -1, z: 1 }; selectedHandle = handleScaleLTNCube; } else if (directionEnum === SCALE_DIRECTION.RTN) { - directionVector = { x:-1, y:-1, z:1 }; + directionVector = { x: -1, y: -1, z: 1 }; selectedHandle = handleScaleRTNCube; } else if (directionEnum === SCALE_DIRECTION.LTF) { - directionVector = { x:1, y:-1, z:-1 }; + directionVector = { x: 1, y: -1, z: -1 }; selectedHandle = handleScaleLTFCube; } else if (directionEnum === SCALE_DIRECTION.RTF) { - directionVector = { x:-1, y:-1, z:-1 }; + directionVector = { x: -1, y: -1, z: -1 }; selectedHandle = handleScaleRTFCube; } offset = Vec3.multiply(directionVector, NEGATE_VECTOR); @@ -2233,7 +2236,6 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE ROTATION DEGREES OVERLAY function updateRotationDegreesOverlay(angleFromZero, position) { - var angle = angleFromZero * (Math.PI / 180); var toCameraDistance = getDistanceToCamera(position); var overlayProps = { position: position, @@ -2424,9 +2426,10 @@ SelectionDisplay = (function() { var startAtCurrent = 0; var endAtCurrent = angleFromZero; + var maxDegrees = 360; if (angleFromZero < 0) { - startAtCurrent = 360 + angleFromZero; - endAtCurrent = 360; + startAtCurrent = maxDegrees + angleFromZero; + endAtCurrent = maxDegrees; } Overlays.editOverlay(handleRotateCurrentRing, { startAt: startAtCurrent, @@ -2438,8 +2441,9 @@ SelectionDisplay = (function() { if (spaceMode === SPACE_LOCAL) { Overlays.editOverlay(handleRotateCurrentRing, { rotation: worldRotationZ }); } else { + var rotationDegrees = 90; Overlays.editOverlay(handleRotateCurrentRing, { - rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0) + rotation: Quat.fromPitchYawRollDegrees(-rotationDegrees, 0, 0) }); } } diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index d9003ffeaa..8642fc5ce6 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -88,8 +88,8 @@ "polarFinish": 1, "radiusFinish": 0.3, "radiusStart": 0.04, - "speedSpread": 0.01, - "radiusSpread": 0.9, + "speedSpread": 0.00, + "radiusSpread": 0.0, "textures": "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle.png", "color": {"red": 200, "green": 170, "blue": 255}, "colorFinish": {"red": 0, "green": 134, "blue": 255}, diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 9b540aefc8..e265ddb621 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -17,7 +17,6 @@ var SNAPSHOT_DELAY = 500; // 500ms var FINISH_SOUND_DELAY = 350; var resetOverlays; var reticleVisible; -var clearOverlayWhenMoving; var buttonName = "SNAP"; var buttonConnected = false; @@ -286,6 +285,7 @@ function printToPolaroid(image_url) { var polaroid_url = image_url; var model_pos = Vec3.sum(MyAvatar.position, Vec3.multiply(1.25, Quat.getForward(MyAvatar.orientation))); + model_pos.y += 0.2; // Print a bit closer to the head var model_q1 = MyAvatar.orientation; var model_q2 = Quat.angleAxis(90, Quat.getRight(model_q1)); @@ -295,11 +295,11 @@ function printToPolaroid(image_url) { "type": 'Model', "shapeType": 'box', - "name": "New Snapshot", - "description": "Printed from Snaps", + "name": "Snapshot by " + MyAvatar.sessionDisplayName, + "description": "Printed from SNAP app", "modelURL": POLAROID_MODEL_URL, - "dimensions": { "x": 0.5667, "y": 0.0212, "z": 0.4176 }, + "dimensions": { "x": 0.5667, "y": 0.042, "z": 0.4176 }, "position": model_pos, "rotation": model_rot, @@ -307,9 +307,9 @@ function printToPolaroid(image_url) { "density": 200, "restitution": 0.15, - "gravity": { "x": 0, "y": -4.5, "z": 0 }, + "gravity": { "x": 0, "y": -2.5, "z": 0 }, - "velocity": { "x": 0, "y": 3.5, "z": 0 }, + "velocity": { "x": 0, "y": 1.95, "z": 0 }, "angularVelocity": { "x": -1.0, "y": 0, "z": -1.3 }, "dynamic": true, @@ -438,11 +438,6 @@ function takeSnapshot() { isUploadingPrintableStill = true; updatePrintPermissions(); - // Raising the desktop for the share dialog at end will interact badly with clearOverlayWhenMoving. - // Turn it off now, before we start futzing with things (and possibly moving). - clearOverlayWhenMoving = MyAvatar.getClearOverlayWhenMoving(); // Do not use Settings. MyAvatar keeps a separate copy. - MyAvatar.setClearOverlayWhenMoving(false); - // We will record snapshots based on the starting location. That could change, e.g., when recording a .gif. // Even the domainID could change (e.g., if the user falls into a teleporter while recording). href = location.href; @@ -544,9 +539,6 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { // last element in data array tells dialog whether we can share or not Settings.setValue("previousStillSnapPath", pathStillSnapshot); - if (clearOverlayWhenMoving) { - MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog - } HMD.openTablet(); isDomainOpen(domainID, function (canShare) { @@ -590,9 +582,6 @@ function processingGifStarted(pathStillSnapshot) { } Settings.setValue("previousStillSnapPath", pathStillSnapshot); - if (clearOverlayWhenMoving) { - MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog - } HMD.openTablet(); isDomainOpen(domainID, function (canShare) { diff --git a/tools/auto-tester/src/ImageComparer.cpp b/tools/auto-tester/src/ImageComparer.cpp index 3afdeaec0a..fa73f97887 100644 --- a/tools/auto-tester/src/ImageComparer.cpp +++ b/tools/auto-tester/src/ImageComparer.cpp @@ -15,13 +15,8 @@ // Computes SSIM - see https://en.wikipedia.org/wiki/Structural_similarity // The value is computed for the luminance component and the average value is returned double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) const { - // Make sure the image is 8 bits per colour - QImage::Format format = expectedImage.format(); - if (format != QImage::Format::Format_ARGB32) { - throw -1; - } - const int L = 255; // (2^number of bits per pixel) - 1 + const double K1 { 0.01 }; const double K2 { 0.03 }; const double c1 = pow((K1 * L), 2); diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 1a25cc5c0e..4f02544c12 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -25,6 +25,11 @@ extern AutoTester* autoTester; Test::Test() { mismatchWindow.setModal(true); + + if (autoTester) { + autoTester->setUserText("highfidelity"); + autoTester->setBranchText("master"); + } } bool Test::createTestResultsFolderPath(const QString& directory) { @@ -63,7 +68,6 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) // Loop over both lists and compare each pair of images // Quit loop if user has aborted due to a failed test. - const double THRESHOLD { 0.9995 }; bool success{ true }; bool keepOn{ true }; for (int i = 0; keepOn && i < expectedImagesFullFilenames.length(); ++i) { @@ -71,17 +75,14 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) QImage resultImage(resultImagesFullFilenames[i]); QImage expectedImage(expectedImagesFullFilenames[i]); + double similarityIndex; // in [-1.0 .. 1.0], where 1.0 means images are identical + + // similarityIndex is set to -100.0 to indicate images are not the same size if (isInteractiveMode && (resultImage.width() != expectedImage.width() || resultImage.height() != expectedImage.height())) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Images are not the same size"); - exit(-1); - } - - double similarityIndex; // in [-1.0 .. 1.0], where 1.0 means images are identical - try { + similarityIndex = -100.0; + } else { similarityIndex = imageComparer.compareImages(resultImage, expectedImage); - } catch (...) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Image not in expected format"); - exit(-1); } if (similarityIndex < THRESHOLD) { @@ -176,20 +177,25 @@ void Test::appendTestResultsToFile(const QString& testResultsFolderPath, TestFai comparisonImage.save(failureFolderPath + "/" + "Difference Image.png"); } -void Test::startTestsEvaluation(const QString& testFolder) { - // Get list of JPEG images in folder, sorted by name - QString previousSelection = snapshotDirectory; - QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); - if (!parent.isNull() && parent.right(1) != "/") { - parent += "/"; - } - snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", parent, - QFileDialog::ShowDirsOnly); +void Test::startTestsEvaluation(const QString& testFolder, const QString& branchFromCommandLine, const QString& userFromCommandLine) { + if (testFolder.isNull()) { + // Get list of JPEG images in folder, sorted by name + QString previousSelection = snapshotDirectory; + QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); + if (!parent.isNull() && parent.right(1) != "/") { + parent += "/"; + } + snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", parent, + QFileDialog::ShowDirsOnly); - // If user cancelled then restore previous selection and return - if (snapshotDirectory == "") { - snapshotDirectory = previousSelection; - return; + // If user cancelled then restore previous selection and return + if (snapshotDirectory == "") { + snapshotDirectory = previousSelection; + return; + } + } else { + snapshotDirectory = testFolder; + exitWhenComplete = true; } // Quit if test results folder could not be created @@ -197,17 +203,6 @@ void Test::startTestsEvaluation(const QString& testFolder) { return; } - // Before any processing - all images are converted to PNGs, as this is the format stored on GitHub - QStringList sortedSnapshotFilenames = createListOfAll_imagesInDirectory("jpg", snapshotDirectory); - foreach(QString filename, sortedSnapshotFilenames) { - QString filenameWithoutExtension = filename.left(filename.length() - 4); - copyJPGtoPNG(snapshotDirectory + "/" + filenameWithoutExtension + ".jpg", - snapshotDirectory + "/" + filenameWithoutExtension + ".png" - ); - - QFile::remove(snapshotDirectory + "/" + filenameWithoutExtension + ".jpg"); - } - // Create two lists. The first is the test results, the second is the expected images // The expected images are represented as a URL to enable download from GitHub // Images that are in the wrong format are ignored. @@ -219,6 +214,9 @@ void Test::startTestsEvaluation(const QString& testFolder) { expectedImagesFilenames.clear(); expectedImagesFullFilenames.clear(); + QString branch = (branchFromCommandLine.isNull()) ? autoTester->getSelectedBranch() : branchFromCommandLine; + QString user = (userFromCommandLine.isNull()) ? autoTester->getSelectedUser() : userFromCommandLine; + foreach(QString currentFilename, sortedTestResultsFilenames) { QString fullCurrentFilename = snapshotDirectory + "/" + currentFilename; if (isInSnapshotFilenameFormat("png", currentFilename)) { @@ -231,7 +229,7 @@ void Test::startTestsEvaluation(const QString& testFolder) { QString expectedImageFilenameTail = currentFilename.left(currentFilename.length() - 4).right(NUM_DIGITS); QString expectedImageStoredFilename = EXPECTED_IMAGE_PREFIX + expectedImageFilenameTail + ".png"; - QString imageURLString("https://raw.githubusercontent.com/" + GIT_HUB_USER + "/hifi_tests/" + GIT_HUB_BRANCH + "/" + + QString imageURLString("https://raw.githubusercontent.com/" + user + "/" + GIT_HUB_REPOSITORY + "/" + branch + "/" + expectedImagePartialSourceDirectory + "/" + expectedImageStoredFilename); expectedImagesURLs << imageURLString; @@ -259,6 +257,10 @@ void Test::finishTestsEvaluation(bool isRunningFromCommandline, bool interactive } zipAndDeleteTestResultsFolder(); + + if (exitWhenComplete) { + exit(0); + } } bool Test::isAValidDirectory(const QString& pathname) { @@ -299,10 +301,9 @@ QString Test::extractPathFromTestsDown(const QString& fullPath) { void Test::includeTest(QTextStream& textStream, const QString& testPathname) { QString partialPath = extractPathFromTestsDown(testPathname); - textStream << "Script.include(\"" - << "https://github.com/" << GIT_HUB_USER << "/hifi_tests/blob/" << GIT_HUB_BRANCH - << partialPath + "?raw=true\");" - << endl; + QString partialPathWithoutTests = partialPath.right(partialPath.length() - 7); + + textStream << "Script.include(testsRootPath + \"" << partialPathWithoutTests + "\");" << endl; } // Creates a single script in a user-selected folder. @@ -397,12 +398,20 @@ void Test::createRecursiveScript(const QString& topLevelDirectory, bool interact const QString DATE_TIME_FORMAT("MMM d yyyy, h:mm"); textStream << "// This is an automatically generated file, created by auto-tester on " << QDateTime::currentDateTime().toString(DATE_TIME_FORMAT) << endl << endl; - textStream << "user = \"" + GIT_HUB_USER + "/\";" << endl; - textStream << "repository = \"" + GIT_HUB_REPOSITORY + "/\";" << endl; - textStream << "branch = \"" + GIT_HUB_BRANCH + "/\";" << endl << endl; + // Include 'autoTest.js' + QString branch = autoTester->getSelectedBranch(); + QString user = autoTester->getSelectedUser(); - textStream << "var autoTester = Script.require(\"https://github.com/" + GIT_HUB_USER + "/hifi_tests/blob/" - + GIT_HUB_BRANCH + "/tests/utils/autoTester.js?raw=true\");" << endl << endl; + textStream << "PATH_TO_THE_REPO_PATH_UTILS_FILE = \"https://raw.githubusercontent.com/" + user + "/hifi_tests/" + branch + "/tests/utils/branchUtils.js\";" << endl; + textStream << "Script.include(PATH_TO_THE_REPO_PATH_UTILS_FILE);" << endl; + textStream << "var autoTester = createAutoTester(Script.resolvePath(\".\"));" << endl << endl; + + textStream << "var testsRootPath = autoTester.getTestsRootPath();" << endl << endl; + + // Wait 10 seconds before starting + textStream << "if (typeof Test !== 'undefined') {" << endl; + textStream << " Test.wait(10000);" << endl; + textStream << "};" << endl << endl; textStream << "autoTester.enableRecursive();" << endl; textStream << "autoTester.enableAuto();" << endl << endl; @@ -498,13 +507,13 @@ void Test::createTests() { return; } - QStringList sortedImageFilenames = createListOfAll_imagesInDirectory("jpg", snapshotDirectory); + QStringList sortedImageFilenames = createListOfAll_imagesInDirectory("png", snapshotDirectory); int i = 1; const int maxImages = pow(10, NUM_DIGITS); foreach (QString currentFilename, sortedImageFilenames) { QString fullCurrentFilename = snapshotDirectory + "/" + currentFilename; - if (isInSnapshotFilenameFormat("jpg", currentFilename)) { + if (isInSnapshotFilenameFormat("png", currentFilename)) { if (i >= maxImages) { QMessageBox::critical(0, "Error", "More than " + QString::number(maxImages) + " images not supported"); exit(-1); @@ -528,9 +537,12 @@ void Test::createTests() { fullNewFileName += "/" + newFilename; try { - copyJPGtoPNG(fullCurrentFilename, fullNewFileName); + if (QFile::exists(fullNewFileName)) { + QFile::remove(fullNewFileName); + } + QFile::copy(fullCurrentFilename, fullNewFileName); } catch (...) { - QMessageBox::critical(0, "Error", "Could not delete existing file: " + currentFilename + "\nTest creation aborted"); + QMessageBox::critical(0, "Error", "Could not copy file: " + fullCurrentFilename + " to " + fullNewFileName + "\n"); exit(-1); } ++i; @@ -560,9 +572,7 @@ ExtractedText Test::getTestScriptLines(QString testFileName) { const QString ws("\\h*"); //white-space character const QString functionPerformName(ws + "autoTester" + ws + "\\." + ws + "perform"); const QString quotedString("\\\".+\\\""); - const QString ownPath("Script" + ws + "\\." + ws + "resolvePath" + ws + "\\(" + ws + "\\\"\\.\\\"" + ws + "\\)"); - const QString functionParameter("function" + ws + "\\(testType" + ws + "\\)"); - QString regexTestTitle(ws + functionPerformName + "\\(" + quotedString + "\\," + ws + ownPath + "\\," + ws + functionParameter + ws + "{" + ".*"); + QString regexTestTitle(ws + functionPerformName + "\\(" + quotedString); QRegularExpression lineContainingTitle = QRegularExpression(regexTestTitle); @@ -811,19 +821,6 @@ void Test::createTestsOutline() { QMessageBox::information(0, "Success", "Test outline file " + testsOutlineFilename + " has been created"); } -void Test::copyJPGtoPNG(const QString& sourceJPGFullFilename, const QString& destinationPNGFullFilename) { - QFile::remove(destinationPNGFullFilename); - - QImageReader reader; - reader.setFileName(sourceJPGFullFilename); - - QImage image = reader.read(); - - QImageWriter writer; - writer.setFileName(destinationPNGFullFilename); - writer.write(image); -} - QStringList Test::createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory) { imageDirectory = QDir(pathToImageDirectory); QStringList nameFilters; diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 32564035e7..5c6d3e5686 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -37,7 +37,7 @@ class Test { public: Test(); - void startTestsEvaluation(const QString& testFolder = QString()); + void startTestsEvaluation(const QString& testFolder = QString(), const QString& branchFromCommandLine = QString(), const QString& userFromCommandLine = QString()); void finishTestsEvaluation(bool isRunningFromCommandline, bool interactiveMode, QProgressBar* progressBar); void createRecursiveScript(); @@ -69,13 +69,13 @@ public: QString getExpectedImageDestinationDirectory(const QString& filename); QString getExpectedImagePartialSourceDirectory(const QString& filename); - void copyJPGtoPNG(const QString& sourceJPGFullFilename, const QString& destinationPNGFullFilename); - private: const QString TEST_FILENAME { "test.js" }; const QString TEST_RESULTS_FOLDER { "TestResults" }; const QString TEST_RESULTS_FILENAME { "TestResults.txt" }; + const double THRESHOLD{ 0.96 }; + QDir imageDirectory; MismatchWindow mismatchWindow; @@ -102,9 +102,7 @@ private: QStringList resultImagesFullFilenames; // Used for accessing GitHub - const QString GIT_HUB_USER{ "highfidelity" }; const QString GIT_HUB_REPOSITORY{ "hifi_tests" }; - const QString GIT_HUB_BRANCH{ "master" }; const QString DATETIME_FORMAT{ "yyyy-MM-dd_hh-mm-ss" }; @@ -115,6 +113,8 @@ private: // var pathSeparator = "."; const QString ADVANCE_KEY{ "n" }; const QString PATH_SEPARATOR{ "." }; + + bool exitWhenComplete{ false }; }; #endif // hifi_test_h \ No newline at end of file diff --git a/tools/auto-tester/src/main.cpp b/tools/auto-tester/src/main.cpp index ffa7a0b237..03b8cf51ff 100644 --- a/tools/auto-tester/src/main.cpp +++ b/tools/auto-tester/src/main.cpp @@ -10,23 +10,63 @@ #include #include "ui/AutoTester.h" +#include + AutoTester* autoTester; int main(int argc, char *argv[]) { - // Only parameter is "--testFolder" + // If no parameters then run in interactive mode + // Parameter --testFolder + // Parameter --branch + // default is "master" + // Parameter --user + // default is "highfidelity" + // Parameter --repository + // default is "highfidelity" + QString testFolder; - if (argc == 3) { - if (QString(argv[1]) == "--testFolder") { - testFolder = QString(argv[2]); + + QString branch{ "master" }; + QString user{ "highfidelity" }; + + for (int i = 1; i < argc - 1; ++i) { + if (QString(argv[i]) == "--testFolder") { + ++i; + if (i < argc) { + testFolder = QString(argv[i]); + } else { + std::cout << "Missing parameter after --testFolder" << std::endl; + exit(-1); + } + } else if (QString(argv[i]) == "--branch") { + ++i; + if (i < argc) { + branch = QString(argv[i]); + } else { + std::cout << "Missing parameter after --branch" << std::endl; + exit(-1); + } + } else if (QString(argv[i]) == "--user") { + ++i; + if (i < argc) { + user = QString(argv[i]); + } else { + std::cout << "Missing parameter after --user" << std::endl; + exit(-1); + } + } else { + std::cout << "Unknown parameter" << std::endl; + exit(-1); } } QApplication application(argc, argv); autoTester = new AutoTester(); + autoTester->setup(); if (!testFolder.isNull()) { - autoTester->runFromCommandLine(testFolder); + autoTester->runFromCommandLine(testFolder, branch, user); } else { autoTester->show(); } diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 14329e36c2..079fa63a9d 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -29,13 +29,15 @@ AutoTester::AutoTester(QWidget *parent) : QMainWindow(parent) { ui.hideTaskbarButton->setVisible(false); ui.showTaskbarButton->setVisible(false); #endif +} +void AutoTester::setup() { test = new Test(); } -void AutoTester::runFromCommandLine(const QString& testFolder) { +void AutoTester::runFromCommandLine(const QString& testFolder, const QString& branch, const QString& user) { isRunningFromCommandline = true; - test->startTestsEvaluation(testFolder); + test->startTestsEvaluation(testFolder, branch, user); } void AutoTester::on_evaluateTestsButton_clicked() { @@ -116,6 +118,7 @@ void AutoTester::downloadImages(const QStringList& URLs, const QString& director ui.progressBar->setValue(0); ui.progressBar->setVisible(true); + downloaders.clear(); for (int i = 0; i < _numberOfImagesToDownload; ++i) { QUrl imageURL(URLs[i]); downloadImage(imageURL); @@ -125,14 +128,12 @@ void AutoTester::downloadImages(const QStringList& URLs, const QString& director } void AutoTester::saveImage(int index) { - QPixmap pixmap; - pixmap.loadFromData(downloaders[index]->downloadedData()); - - QImage image = pixmap.toImage(); - image = image.convertToFormat(QImage::Format_ARGB32); - - QString fullPathname = _directoryName + "/" + _filenames[index]; - if (!image.save(fullPathname, 0, 100)) { + try { + QFile file(_directoryName + "/" + _filenames[index]); + file.open(QIODevice::WriteOnly); + file.write(downloaders[index]->downloadedData()); + file.close(); + } catch (...) { QMessageBox::information(0, "Test Aborted", "Failed to save image: " + _filenames[index]); ui.progressBar->setVisible(false); return; @@ -141,6 +142,7 @@ void AutoTester::saveImage(int index) { ++_numberOfImagesDownloaded; if (_numberOfImagesDownloaded == _numberOfImagesToDownload) { + disconnect(signalMapper, SIGNAL (mapped(int)), this, SLOT (saveImage(int))); test->finishTestsEvaluation(isRunningFromCommandline, ui.checkBoxInteractiveMode->isChecked(), ui.progressBar); } else { ui.progressBar->setValue(_numberOfImagesDownloaded); @@ -150,3 +152,20 @@ void AutoTester::saveImage(int index) { void AutoTester::about() { QMessageBox::information(0, "About", QString("Built ") + __DATE__ + " : " + __TIME__); } + +void AutoTester::setUserText(const QString& user) { + ui.userTextEdit->setText(user); +} + +QString AutoTester::getSelectedUser() +{ + return ui.userTextEdit->toPlainText(); +} + +void AutoTester::setBranchText(const QString& branch) { + ui.branchTextEdit->setText(branch); +} + +QString AutoTester::getSelectedBranch() { + return ui.branchTextEdit->toPlainText(); +} diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index 7b419a9af8..d47c4929c4 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -12,6 +12,7 @@ #include #include +#include #include "ui_AutoTester.h" #include "../Downloader.h" @@ -23,11 +24,19 @@ class AutoTester : public QMainWindow { public: AutoTester(QWidget *parent = Q_NULLPTR); - void runFromCommandLine(const QString& testFolder); + void setup(); + + void runFromCommandLine(const QString& testFolder, const QString& branch, const QString& user); void downloadImage(const QUrl& url); void downloadImages(const QStringList& URLs, const QString& directoryName, const QStringList& filenames); + void setUserText(const QString& user); + QString getSelectedUser(); + + void setBranchText(const QString& branch); + QString getSelectedBranch(); + private slots: void on_evaluateTestsButton_clicked(); void on_createRecursiveScriptButton_clicked(); diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index 236138acf4..e12fc70e3f 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -6,8 +6,8 @@ 0 0 - 607 - 514 + 612 + 537 @@ -17,9 +17,9 @@ - 360 - 400 - 220 + 380 + 430 + 101 40 @@ -43,9 +43,9 @@ - 20 - 285 - 220 + 430 + 270 + 101 40 @@ -56,8 +56,8 @@ - 360 - 35 + 330 + 110 220 40 @@ -69,8 +69,8 @@ - 23 - 250 + 320 + 280 131 20 @@ -85,8 +85,8 @@ - 20 - 340 + 320 + 330 255 23 @@ -98,8 +98,8 @@ - 360 - 100 + 330 + 170 220 40 @@ -112,7 +112,7 @@ 20 - 80 + 110 220 40 @@ -125,7 +125,7 @@ 20 - 130 + 160 220 40 @@ -138,7 +138,7 @@ 20 - 180 + 250 220 40 @@ -150,27 +150,83 @@ - 490 - 280 - 91 + 10 + 440 + 211 40 - Show Taskbar + Show Windows Taskbar - 490 - 230 - 91 + 10 + 390 + 211 40 - Hide Taskbar + Hide Windows Taskbar + + + + + + 330 + 55 + 81 + 16 + + + + + 10 + + + + GitHub Branch + + + + + + 330 + 15 + 81 + 16 + + + + + 10 + + + + GitHub User + + + + + + 420 + 12 + 140 + 24 + + + + + + + 420 + 50 + 140 + 24 + @@ -179,7 +235,7 @@ 0 0 - 607 + 612 21