diff --git a/.gitignore b/.gitignore index 8aa82865a4..072e6001da 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,15 @@ Makefile local.properties android/libraries +# VSCode +# List taken from Github Global Ignores master@435c4d92 +# https://github.com/github/gitignore/commits/master/Global/VisualStudioCode.gitignore +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + # Xcode *.xcodeproj *.xcworkspace diff --git a/interface/resources/html/commerce/backup_instructions.html b/interface/resources/html/commerce/backup_instructions.html new file mode 100644 index 0000000000..560894e33d --- /dev/null +++ b/interface/resources/html/commerce/backup_instructions.html @@ -0,0 +1,609 @@ + + + + + +Backing Up Your Private Keys | High Fidelity + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +

Backing Up Your Private Keys

+
+ + +

What are private keys?

+

A private key is a secret piece of text that is used to prove ownership, unlock confidential information and sign transactions.

+

In High Fidelity, your private keys are used to securely access the contents of your Wallet and Purchases.

+ +
+

Where are my private keys stored?"

+

By default, your private keys are only stored on your hard drive in High Fidelity Interface's AppData directory.

+

Here is the file path of your hifikey - you can browse to it using your file explorer.

+
HIFIKEY_PATH_REPLACEME
+ +
+

How should I make a backup of my private keys?

+

You should backup your .hifikey file above by copying it to a USB flash drive, or to a service like Dropbox or Google Drive. Restore your backup by replacing the file in Interface's AppData directory with your backed-up copy.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

What happens if I lose my passphrase?

+

Your passphrase is used to encrypt your private keys. If you lose your passphrase, you will no longer be able to decrypt your private key file nor have access to the contents of your Wallet or My Purchases.

+

In order to guarantee your privacy, nobody can help you recover your passphrase, including High Fidelity. + +

Please write it down and store it securely.

+

 

+
+ +

Want to learn more?

+

You can find out much more about the blockchain and about commerce in High Fidelity by visiting our Docs site:

+

Visit High Fidelity's Docs

+
+ +
+
+ +
+ + diff --git a/interface/resources/qml/hifi/audio/PlaySampleSound.qml b/interface/resources/qml/hifi/audio/PlaySampleSound.qml index 99f3648ec3..fdf579420d 100644 --- a/interface/resources/qml/hifi/audio/PlaySampleSound.qml +++ b/interface/resources/qml/hifi/audio/PlaySampleSound.qml @@ -29,12 +29,22 @@ RowLayout { function playSound() { // FIXME: MyAvatar is not properly exposed to QML; MyAvatar.qmlPosition is a stopgap // FIXME: Audio.playSystemSound should not require position - sample = Audio.playSystemSound(sound, MyAvatar.qmlPosition); - isPlaying = true; - sample.finished.connect(function() { isPlaying = false; sample = null; }); + if (sample === null && !isPlaying) { + sample = Audio.playSystemSound(sound, MyAvatar.qmlPosition); + isPlaying = true; + sample.finished.connect(reset); + } } function stopSound() { - sample && sample.stop(); + if (sample && isPlaying) { + sample.stop(); + } + } + + function reset() { + sample.finished.disconnect(reset); + isPlaying = false; + sample = null; } Component.onCompleted: createSampleSound(); diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 2ab8e90e9d..8d94e284ed 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -79,6 +79,7 @@ Rectangle { failureErrorText.text = result.message; root.activeView = "checkoutFailure"; } else { + root.itemHref = result.data.download_url; root.activeView = "checkoutSuccess"; } } @@ -114,7 +115,7 @@ Rectangle { } onItemHrefChanged: { - itemIsJson = root.itemHref.indexOf('.json') !== -1; + itemIsJson = root.itemHref.endsWith('.json'); } onItemPriceChanged: { @@ -574,8 +575,8 @@ Rectangle { anchors.right: parent.right; text: "Rez It" onClicked: { - if (urlHandler.canHandleUrl(itemHref)) { - urlHandler.handleUrl(itemHref); + if (urlHandler.canHandleUrl(root.itemHref)) { + urlHandler.handleUrl(root.itemHref); } rezzedNotifContainer.visible = true; rezzedNotifContainerTimer.start(); diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 42708a80c5..ea32c139d4 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -426,7 +426,7 @@ Rectangle { itemName: title; itemId: id; itemPreviewImageUrl: preview; - itemHref: root_file_url; + itemHref: download_url; purchaseStatus: status; purchaseStatusChanged: statusChanged; itemEdition: model.edition_number; diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index 21548ea788..65c06994f8 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -30,12 +30,14 @@ Item { id: commerce; onKeyFilePathIfExistsResult: { - keyFilePath = path; + root.keyFilePath = path; } } - Component.onCompleted: { - commerce.getKeyFilePathIfExists(); + onVisibleChanged: { + if (visible) { + commerce.getKeyFilePathIfExists(); + } } RalewaySemiBold { @@ -103,7 +105,7 @@ Item { ListElement { isExpanded: false; question: "What is a 'Security Pic'?" - answer: qsTr("Your Security Pic is an encrypted image that you selected during Wallet Setup. It acts as an extra layer of Wallet security.

When you see your Security Pic, you know that your actions and data are securely making use of your private keys.

If you don't see your Security Pic on a page that is asking you for your Wallet passphrase, someone untrustworthy may be trying to gain access to your Wallet.

The Pic is stored on your hard drive inside the same file as your private keys."); + answer: qsTr("Your Security Pic is an encrypted image that you selected during Wallet Setup. It acts as an extra layer of Wallet security.

When you see your Security Pic, you know that your actions and data are securely making use of your private keys.

If you don't see your Security Pic on a page that is asking you for your Wallet passphrase, someone untrustworthy may be trying to gain access to your Wallet.

The encrypted Pic is stored on your hard drive inside the same file as your private keys."); } ListElement { isExpanded: false; diff --git a/interface/resources/qml/hifi/commerce/wallet/Security.qml b/interface/resources/qml/hifi/commerce/wallet/Security.qml index 9b70bb1f71..0f2edbe913 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Security.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Security.qml @@ -25,13 +25,13 @@ Item { HifiConstants { id: hifi; } id: root; - property string keyFilePath: ""; + property string keyFilePath; Hifi.QmlCommerce { id: commerce; onKeyFilePathIfExistsResult: { - keyFilePath = path; + root.keyFilePath = path; } } @@ -232,6 +232,12 @@ Item { anchors.rightMargin: 55; anchors.bottom: parent.bottom; + onVisibleChanged: { + if (visible) { + commerce.getKeyFilePathIfExists(); + } + } + HiFiGlyphs { id: yourPrivateKeysImage; text: hifi.glyphs.walletKey; @@ -320,8 +326,9 @@ Item { height: 40; onClicked: { - Qt.openUrlExternally("https://www.highfidelity.com/"); - Qt.openUrlExternally("file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/'))); + var keyPath = "file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/')); + Qt.openUrlExternally(keyPath + "/backup_instructions.html"); + Qt.openUrlExternally(keyPath); removeHmdContainer.visible = true; removeHmdContainerTimer.start(); } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml index 898cdf0ef2..0075e86bdc 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml @@ -30,6 +30,7 @@ Item { property string lastPage; property bool hasShownSecurityImageTip: false; property string referrer; + property string keyFilePath; Image { anchors.fill: parent; @@ -58,7 +59,7 @@ Item { } onKeyFilePathIfExistsResult: { - keyFilePath.text = path; + root.keyFilePath = path; } } @@ -608,7 +609,7 @@ Item { anchors.fill: parent; RalewaySemiBold { - id: keyFilePathText; + id: keyFilePathHelperText; text: "Private Key File Location:"; size: 18; anchors.top: parent.top; @@ -627,7 +628,7 @@ Item { colorScheme: hifi.colorSchemes.dark; anchors.left: parent.left; anchors.leftMargin: 30; - anchors.top: keyFilePathText.bottom; + anchors.top: keyFilePathHelperText.bottom; anchors.topMargin: 8; height: 24; width: height; @@ -643,11 +644,12 @@ Item { } onClicked: { - Qt.openUrlExternally("file:///" + keyFilePath.text.substring(0, keyFilePath.text.lastIndexOf('/'))); + Qt.openUrlExternally("file:///" + keyFilePath.substring(0, keyFilePath.lastIndexOf('/'))); } } RalewayRegular { - id: keyFilePath; + id: keyFilePathText; + text: root.keyFilePath; size: 18; anchors.top: clipboardButton.top; anchors.left: clipboardButton.right; @@ -670,7 +672,7 @@ Item { id: openInstructionsButton; color: hifi.buttons.blue; colorScheme: hifi.colorSchemes.dark; - anchors.top: keyFilePath.bottom; + anchors.top: keyFilePathText.bottom; anchors.topMargin: 30; anchors.left: parent.left; anchors.leftMargin: 30; @@ -682,8 +684,9 @@ Item { instructions01Container.visible = false; instructions02Container.visible = true; keysReadyPageFinishButton.visible = true; - Qt.openUrlExternally("https://www.highfidelity.com/"); - Qt.openUrlExternally("file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/'))); + var keyPath = "file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/')); + Qt.openUrlExternally(keyPath + "/backup_instructions.html"); + Qt.openUrlExternally(keyPath); } } } diff --git a/interface/resources/qml/windows/TabletModalWindow.qml b/interface/resources/qml/windows/TabletModalWindow.qml index 05f192f7a7..e21cb6b224 100644 --- a/interface/resources/qml/windows/TabletModalWindow.qml +++ b/interface/resources/qml/windows/TabletModalWindow.qml @@ -15,7 +15,7 @@ import "." Rectangle { id: modalWindow layer.enabled: true - property var title: "Modal" + property var title: "Open" width: tabletRoot.width height: tabletRoot.height color: "#80000000" diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 6608cfe5fc..9f9973e7d0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1639,12 +1639,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo properties["throttled"] = _displayPlugin ? _displayPlugin->isThrottled() : false; QJsonObject bytesDownloaded; - bytesDownloaded["atp"] = statTracker->getStat(STAT_ATP_RESOURCE_TOTAL_BYTES).toInt(); - bytesDownloaded["http"] = statTracker->getStat(STAT_HTTP_RESOURCE_TOTAL_BYTES).toInt(); - bytesDownloaded["file"] = statTracker->getStat(STAT_FILE_RESOURCE_TOTAL_BYTES).toInt(); - bytesDownloaded["total"] = bytesDownloaded["atp"].toInt() + bytesDownloaded["http"].toInt() - + bytesDownloaded["file"].toInt(); - properties["bytesDownloaded"] = bytesDownloaded; + auto atpBytes = statTracker->getStat(STAT_ATP_RESOURCE_TOTAL_BYTES).toLongLong(); + auto httpBytes = statTracker->getStat(STAT_HTTP_RESOURCE_TOTAL_BYTES).toLongLong(); + auto fileBytes = statTracker->getStat(STAT_FILE_RESOURCE_TOTAL_BYTES).toLongLong(); + bytesDownloaded["atp"] = atpBytes; + bytesDownloaded["http"] = httpBytes; + bytesDownloaded["file"] = fileBytes; + bytesDownloaded["total"] = atpBytes + httpBytes + fileBytes; + properties["bytes_downloaded"] = bytesDownloaded; auto myAvatar = getMyAvatar(); glm::vec3 avatarPosition = myAvatar->getPosition(); @@ -2690,15 +2692,10 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { bool Application::importJSONFromURL(const QString& urlString) { // we only load files that terminate in just .json (not .svo.json and not .ava.json) - // if they come from the High Fidelity Marketplace Assets CDN QUrl jsonURL { urlString }; - if (jsonURL.host().endsWith(MARKETPLACE_CDN_HOSTNAME)) { - emit svoImportRequested(urlString); - return true; - } else { - return false; - } + emit svoImportRequested(urlString); + return true; } bool Application::importSVOFromURL(const QString& urlString) { @@ -5640,14 +5637,37 @@ bool Application::nearbyEntitiesAreReadyForPhysics() { return false; } + // We don't want to use EntityTree::findEntities(AABox, ...) method because that scan will snarf parented entities + // whose bounding boxes cannot be computed (it is too loose for our purposes here). Instead we manufacture + // custom filters and use the general-purpose EntityTree::findEntities(filter, ...) QVector entities; + AABox avatarBox(getMyAvatar()->getPosition() - glm::vec3(PHYSICS_READY_RANGE), glm::vec3(2 * PHYSICS_READY_RANGE)); + // create two functions that use avatarBox (entityScan and elementScan), the second calls the first + std::function entityScan = [=](EntityItemPointer& entity) { + if (entity->shouldBePhysical()) { + bool success = false; + AABox entityBox = entity->getAABox(success); + // important: bail for entities that cannot supply a valid AABox + return success && avatarBox.touches(entityBox); + } + return false; + }; + std::function elementScan = [&](const OctreeElementPointer& element, void* unused) { + if (element->getAACube().touches(avatarBox)) { + EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); + entityTreeElement->getEntities(entityScan, entities); + return true; + } + return false; + }; + entityTree->withReadLock([&] { - AABox box(getMyAvatar()->getPosition() - glm::vec3(PHYSICS_READY_RANGE), glm::vec3(2 * PHYSICS_READY_RANGE)); - entityTree->findEntities(box, entities); + // Pass the second function to the general-purpose EntityTree::findEntities() + // which will traverse the tree, apply the two filter functions (to element, then to entities) + // as it traverses. The end result will be a list of entities that match. + entityTree->findEntities(elementScan, entities); }); - // For reasons I haven't found, we don't necessarily have the full scene when we receive a stats packet. Apply - // a heuristic to try to decide when we actually know about all of the nearby entities. uint32_t nearbyCount = entities.size(); if (nearbyCount == _nearbyEntitiesCountAtLastPhysicsCheck) { _nearbyEntitiesStabilityCount++; diff --git a/interface/src/assets/ATPAssetMigrator.cpp b/interface/src/assets/ATPAssetMigrator.cpp index 8de40865b7..45ac80b054 100644 --- a/interface/src/assets/ATPAssetMigrator.cpp +++ b/interface/src/assets/ATPAssetMigrator.cpp @@ -78,7 +78,7 @@ void ATPAssetMigrator::loadEntityServerFile() { request->send(); } else { ++_errorCount; - qWarning() << "Count not create request for asset at" << migrationURL.toString(); + qWarning() << "Could not create request for asset at" << migrationURL.toString(); } }; diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index e80475f2cc..dd926d00d4 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -41,6 +41,7 @@ #endif static const char* KEY_FILE = "hifikey"; +static const char* INSTRUCTIONS_FILE = "backup_instructions.html"; static const char* IMAGE_HEADER = "-----BEGIN SECURITY IMAGE-----\n"; static const char* IMAGE_FOOTER = "-----END SECURITY IMAGE-----\n"; @@ -104,6 +105,38 @@ RSA* readKeys(const char* filename) { return key; } +bool writeBackupInstructions() { + QString inputFilename(PathUtils::resourcesPath() + "html/commerce/backup_instructions.html"); + QString filename = PathUtils::getAppDataFilePath(INSTRUCTIONS_FILE); + QFile outputFile(filename); + bool retval = false; + + if (QFile::exists(filename)) + { + QFile::remove(filename); + } + QFile::copy(inputFilename, filename); + + if (QFile::exists(filename) && outputFile.open(QIODevice::ReadWrite)) { + + QByteArray fileData = outputFile.readAll(); + QString text(fileData); + + 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 + + outputFile.close(); // close the file handle. + + retval = true; + qCDebug(commerce) << "wrote html file successfully"; + } else { + qCDebug(commerce) << "failed to open output html file" << filename; + } + return retval; +} + bool writeKeys(const char* filename, RSA* keys) { FILE* fp; bool retval = false; @@ -121,6 +154,8 @@ bool writeKeys(const char* filename, RSA* keys) { QFile(QString(filename)).remove(); return retval; } + + writeBackupInstructions(); retval = true; qCDebug(commerce) << "wrote keys successfully"; @@ -303,6 +338,11 @@ Wallet::Wallet() { walletScriptingInterface->setWalletStatus(status); emit walletStatusResult(status); }); + + auto accountManager = DependencyManager::get(); + connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { + getWalletStatus(); + }); } Wallet::~Wallet() { @@ -488,7 +528,6 @@ bool Wallet::generateKeyPair() { // TODO: redo this soon -- need error checking and so on writeSecurityImage(_securityImage, keyFilePath()); - emit keyFilePathIfExistsResult(getKeyFilePath()); QString oldKey = _publicKeys.count() == 0 ? "" : _publicKeys.last(); QString key = keyPair.first->toBase64(); _publicKeys.push_back(key); @@ -646,6 +685,7 @@ bool Wallet::writeWallet(const QString& newPassphrase) { QFile(QString(keyFilePath())).remove(); QFile(tempFileName).rename(QString(keyFilePath())); qCDebug(commerce) << "wallet written successfully"; + emit keyFilePathIfExistsResult(getKeyFilePath()); return true; } else { qCDebug(commerce) << "couldn't write security image to temp wallet"; diff --git a/interface/src/scripting/AssetMappingsScriptingInterface.cpp b/interface/src/scripting/AssetMappingsScriptingInterface.cpp index 5308be59bf..a1b2a9ccfc 100644 --- a/interface/src/scripting/AssetMappingsScriptingInterface.cpp +++ b/interface/src/scripting/AssetMappingsScriptingInterface.cpp @@ -21,6 +21,7 @@ #include #include #include +#include static const int AUTO_REFRESH_INTERVAL = 1000; @@ -104,6 +105,21 @@ void AssetMappingsScriptingInterface::uploadFile(QString path, QString mapping, startedCallback.call(); + QFileInfo fileInfo { path }; + int64_t size { fileInfo.size() }; + + QString extension = ""; + auto idx = path.lastIndexOf("."); + if (idx >= 0) { + extension = path.mid(idx + 1); + } + + UserActivityLogger::getInstance().logAction("uploading_asset", { + { "size", (qint64)size }, + { "mapping", mapping }, + { "extension", extension} + }); + auto upload = DependencyManager::get()->createUpload(path); QObject::connect(upload, &AssetUpload::finished, this, [=](AssetUpload* upload, const QString& hash) mutable { if (upload->getError() != AssetUpload::NoError) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 064eacdb35..9120cd1788 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1240,6 +1240,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce mapJoints(entity, model->getJointNames()); } animate(entity); + emit requestRenderUpdate(); } } diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 56a92fb20c..b8293a117d 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -895,6 +895,12 @@ void EntityTree::findEntities(const ViewFrustum& frustum, QVector& foundEntities) { + recurseTreeWithOperation(elementFilter, nullptr); +} + EntityItemPointer EntityTree::findEntityByID(const QUuid& id) { EntityItemID entityID(id); return findEntityByEntityItemID(entityID); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index d6a35ca2aa..693c8c5b56 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -171,6 +171,11 @@ public: /// \param foundEntities[out] vector of EntityItemPointer void findEntities(const ViewFrustum& frustum, QVector& foundEntities); + /// finds all entities that match scanOperator + /// \parameter scanOperator function that scans entities that match criteria + /// \parameter foundEntities[out] vector of EntityItemPointer + void findEntities(RecurseOctreeOperation& scanOperator, QVector& foundEntities); + void addNewlyCreatedHook(NewlyCreatedEntityHook* hook); void removeNewlyCreatedHook(NewlyCreatedEntityHook* hook); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 0c33855a61..2696377028 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -869,6 +869,14 @@ void EntityTreeElement::getEntities(const ViewFrustum& frustum, QVector& foundEntities) { + forEachEntity([&](EntityItemPointer entity) { + if (filter(entity)) { + foundEntities.push_back(entity); + } + }); +} + EntityItemPointer EntityTreeElement::getEntityWithEntityItemID(const EntityItemID& id) const { EntityItemPointer foundEntity = NULL; withReadLock([&] { diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index c7fb80c330..cafae9941a 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -27,6 +27,7 @@ class EntityTreeElement; using EntityItems = QVector; using EntityTreeElementWeakPointer = std::weak_ptr; using EntityTreeElementPointer = std::shared_ptr; +using EntityItemFilter = std::function; class EntityTreeUpdateArgs { public: @@ -199,6 +200,11 @@ public: /// \param entities[out] vector of non-const EntityItemPointer void getEntities(const ViewFrustum& frustum, QVector& foundEntities); + /// finds all entities that match filter + /// \param filter function that adds matching entities to foundEntities + /// \param entities[out] vector of non-const EntityItemPointer + void getEntities(EntityItemFilter& filter, QVector& foundEntities); + EntityItemPointer getEntityWithID(uint32_t id) const; EntityItemPointer getEntityWithEntityItemID(const EntityItemID& id) const; void getEntitiesInside(const AACube& box, QVector& foundEntities); diff --git a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp index ef9b6c4297..192a82dafc 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLTexelFormat.cpp @@ -19,6 +19,7 @@ bool GLTexelFormat::isCompressed() const { case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: return true; default: return false; @@ -94,6 +95,11 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { result = GL_R11F_G11F_B10F; break; + case gpu::RGB9E5: + // the type should be float + result = GL_RGB9_E5; + break; + case gpu::DEPTH: result = GL_DEPTH_COMPONENT32; switch (dstFormat.getType()) { @@ -244,6 +250,9 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { case gpu::COMPRESSED_BC5_XY: result = GL_COMPRESSED_RG_RGTC2; break; + case gpu::COMPRESSED_BC6_RGB: + result = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT; + break; case gpu::COMPRESSED_BC7_SRGBA: result = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; break; @@ -396,6 +405,9 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::COMPRESSED_BC5_XY: texel.internalFormat = GL_COMPRESSED_RG_RGTC2; break; + case gpu::COMPRESSED_BC6_RGB: + texel.internalFormat = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT; + break; case gpu::COMPRESSED_BC7_SRGBA: texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; break; @@ -495,10 +507,16 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::R11G11B10: texel.format = GL_RGB; - // the type should be float + texel.type = GL_UNSIGNED_INT_10F_11F_11F_REV; texel.internalFormat = GL_R11F_G11F_B10F; break; + case gpu::RGB9E5: + texel.format = GL_RGB; + texel.type = GL_UNSIGNED_INT_5_9_9_9_REV; + texel.internalFormat = GL_RGB9_E5; + break; + case gpu::DEPTH: texel.format = GL_DEPTH_COMPONENT; // It's depth component to load it texel.internalFormat = GL_DEPTH_COMPONENT32; @@ -694,6 +712,9 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E case gpu::COMPRESSED_BC5_XY: texel.internalFormat = GL_COMPRESSED_RG_RGTC2; break; + case gpu::COMPRESSED_BC6_RGB: + texel.internalFormat = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT; + break; case gpu::COMPRESSED_BC7_SRGBA: texel.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; break; diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp index 5998aebb33..cde4ff5f75 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -114,6 +114,7 @@ Size GL41Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: glCompressedTexSubImage2D(_target, mip, 0, yOffset, size.x, size.y, internalFormat, static_cast(sourceSize), sourcePointer); break; @@ -131,6 +132,7 @@ Size GL41Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: glCompressedTexSubImage2D(target, mip, 0, yOffset, size.x, size.y, internalFormat, static_cast(sourceSize), sourcePointer); break; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index d8b3968ed8..795a630ccd 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -143,6 +143,7 @@ Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: glCompressedTextureSubImage2D(_id, mip, 0, yOffset, size.x, size.y, internalFormat, static_cast(sourceSize), sourcePointer); break; @@ -158,6 +159,7 @@ Size GL45Texture::copyMipFaceLinesFromTexture(uint16_t mip, uint8_t face, const case GL_COMPRESSED_RED_RGTC1: case GL_COMPRESSED_RG_RGTC2: case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: if (glCompressedTextureSubImage2DEXT) { auto target = GLTexture::CUBE_FACE_LAYOUT[face]; glCompressedTextureSubImage2DEXT(_id, target, mip, 0, yOffset, size.x, size.y, internalFormat, diff --git a/libraries/gpu/src/gpu/Format.cpp b/libraries/gpu/src/gpu/Format.cpp index b15f8d929f..7efe4d3ed6 100644 --- a/libraries/gpu/src/gpu/Format.cpp +++ b/libraries/gpu/src/gpu/Format.cpp @@ -24,11 +24,13 @@ const Element Element::COLOR_COMPRESSED_SRGB { TILE4x4, COMPRESSED, COMPRESSED_B const Element Element::COLOR_COMPRESSED_SRGBA_MASK { TILE4x4, COMPRESSED, COMPRESSED_BC1_SRGBA }; const Element Element::COLOR_COMPRESSED_SRGBA { TILE4x4, COMPRESSED, COMPRESSED_BC3_SRGBA }; const Element Element::COLOR_COMPRESSED_XY { TILE4x4, COMPRESSED, COMPRESSED_BC5_XY }; -const Element Element::COLOR_COMPRESSED_SRGBA_HIGH { TILE4x4, COMPRESSED, COMPRESSED_BC7_SRGBA }; +const Element Element::COLOR_COMPRESSED_SRGBA_HIGH{ TILE4x4, COMPRESSED, COMPRESSED_BC7_SRGBA }; +const Element Element::COLOR_COMPRESSED_HDR_RGB{ TILE4x4, COMPRESSED, COMPRESSED_BC6_RGB }; const Element Element::VEC2NU8_XY{ VEC2, NUINT8, XY }; const Element Element::COLOR_R11G11B10{ SCALAR, FLOAT, R11G11B10 }; +const Element Element::COLOR_RGB9E5{ SCALAR, FLOAT, RGB9E5 }; const Element Element::VEC4F_COLOR_RGBA{ VEC4, FLOAT, RGBA }; const Element Element::VEC2F_UV{ VEC2, FLOAT, UV }; const Element Element::VEC2F_XY{ VEC2, FLOAT, XY }; diff --git a/libraries/gpu/src/gpu/Format.h b/libraries/gpu/src/gpu/Format.h index c4d88236da..0654b23581 100644 --- a/libraries/gpu/src/gpu/Format.h +++ b/libraries/gpu/src/gpu/Format.h @@ -187,11 +187,13 @@ enum Semantic : uint8_t { COMPRESSED_BC3_SRGBA, COMPRESSED_BC4_RED, COMPRESSED_BC5_XY, + COMPRESSED_BC6_RGB, COMPRESSED_BC7_SRGBA, _LAST_COMPRESSED, R11G11B10, + RGB9E5, UNIFORM, UNIFORM_BUFFER, @@ -240,11 +242,13 @@ static const int SEMANTIC_SIZE_FACTOR[NUM_SEMANTICS] = { 16, //COMPRESSED_BC3_SRGBA, 1 byte/pixel * 4x4 pixels = 16 bytes 8, //COMPRESSED_BC4_RED, 1/2 byte/pixel * 4x4 pixels = 8 bytes 16, //COMPRESSED_BC5_XY, 1 byte/pixel * 4x4 pixels = 16 bytes + 16, //COMPRESSED_BC6_RGB, 1 byte/pixel * 4x4 pixels = 16 bytes 16, //COMPRESSED_BC7_SRGBA, 1 byte/pixel * 4x4 pixels = 16 bytes 1, //_LAST_COMPRESSED, 1, //R11G11B10, + 1, //RGB9E5 1, //UNIFORM, 1, //UNIFORM_BUFFER, @@ -306,12 +310,14 @@ public: static const Element COLOR_BGRA_32; static const Element COLOR_SBGRA_32; static const Element COLOR_R11G11B10; + static const Element COLOR_RGB9E5; static const Element COLOR_COMPRESSED_RED; static const Element COLOR_COMPRESSED_SRGB; static const Element COLOR_COMPRESSED_SRGBA_MASK; static const Element COLOR_COMPRESSED_SRGBA; static const Element COLOR_COMPRESSED_XY; static const Element COLOR_COMPRESSED_SRGBA_HIGH; + static const Element COLOR_COMPRESSED_HDR_RGB; static const Element VEC2NU8_XY; static const Element VEC4F_COLOR_RGBA; static const Element VEC2F_UV; diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 4b836512c4..4a588c3c84 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -683,6 +684,21 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< PROFILE_RANGE(render_gpu, "sphericalHarmonicsFromTexture"); + auto mipFormat = cubeTexture.getStoredMipFormat(); + std::function unpackFunc; + + switch (mipFormat.getSemantic()) { + case gpu::R11G11B10: + unpackFunc = glm::unpackF2x11_1x10; + break; + case gpu::RGB9E5: + unpackFunc = glm::unpackF3x9_E1x5; + break; + default: + assert(false); + break; + } + const uint sqOrder = order*order; // allocate memory for calculations @@ -716,17 +732,7 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) { PROFILE_RANGE(render_gpu, "ProcessFace"); - auto mipFormat = cubeTexture.getStoredMipFormat(); - auto numComponents = mipFormat.getScalarCount(); - int roffset { 0 }; - int goffset { 1 }; - int boffset { 2 }; - if ((mipFormat.getSemantic() == gpu::BGRA) || (mipFormat.getSemantic() == gpu::SBGRA)) { - roffset = 2; - boffset = 0; - } - - auto data = cubeTexture.accessStoredMipFace(0, face)->readData(); + auto data = reinterpret_cast( cubeTexture.accessStoredMipFace(0, face)->readData() ); if (data == nullptr) { continue; } @@ -806,29 +812,24 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< // index of texel in texture - // get color from texture and map to range [0, 1] - float red { 0.0f }; - float green { 0.0f }; - float blue { 0.0f }; + // get color from texture + glm::vec3 color{ 0.0f, 0.0f, 0.0f }; for (int i = 0; i < stride; ++i) { for (int j = 0; j < stride; ++j) { - int k = (int)(x + i - halfStride + (y + j - halfStride) * width) * numComponents; - red += ColorUtils::sRGB8ToLinearFloat(data[k + roffset]); - green += ColorUtils::sRGB8ToLinearFloat(data[k + goffset]); - blue += ColorUtils::sRGB8ToLinearFloat(data[k + boffset]); + int k = (int)(x + i - halfStride + (y + j - halfStride) * width); + color += unpackFunc(data[k]); } } - glm::vec3 clr(red, green, blue); // scale color and add to previously accumulated coefficients // red - sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.r * fDiffSolid); + sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), color.r * fDiffSolid); sphericalHarmonicsAdd(resultR.data(), order, resultR.data(), shBuffB.data()); // green - sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.g * fDiffSolid); + sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), color.g * fDiffSolid); sphericalHarmonicsAdd(resultG.data(), order, resultG.data(), shBuffB.data()); // blue - sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), clr.b * fDiffSolid); + sphericalHarmonicsScale(shBuffB.data(), order, shBuff.data(), color.b * fDiffSolid); sphericalHarmonicsAdd(resultB.data(), order, resultB.data(), shBuffB.data()); } } diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 14fd983ec2..08fc4ec101 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -515,8 +515,6 @@ TexturePointer Texture::build(const ktx::KTXDescriptor& descriptor) { return texture; } - - TexturePointer Texture::unserialize(const cache::FilePointer& cacheEntry) { std::unique_ptr ktxPointer = ktx::KTX::create(std::make_shared(cacheEntry->getFilepath().c_str())); if (!ktxPointer) { @@ -536,7 +534,7 @@ TexturePointer Texture::unserialize(const std::string& ktxfile) { if (!ktxPointer) { return nullptr; } - + auto texture = build(ktxPointer->toDescriptor()); if (texture) { texture->setKtxBacking(ktxfile); @@ -570,6 +568,12 @@ bool Texture::evalKTXFormat(const Element& mipFormat, const Element& texelFormat header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RG_RGTC2, ktx::GLBaseInternalFormat::RG); } else if (texelFormat == Format::COLOR_COMPRESSED_SRGBA_HIGH && mipFormat == Format::COLOR_COMPRESSED_SRGBA_HIGH) { header.setCompressed(ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM, ktx::GLBaseInternalFormat::RGBA); + } else if (texelFormat == Format::COLOR_COMPRESSED_HDR_RGB && mipFormat == Format::COLOR_COMPRESSED_HDR_RGB) { + header.setCompressed(ktx::GLInternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT, ktx::GLBaseInternalFormat::RGB); + } else if (texelFormat == Format::COLOR_RGB9E5 && mipFormat == Format::COLOR_RGB9E5) { + header.setUncompressed(ktx::GLType::UNSIGNED_INT_5_9_9_9_REV, 1, ktx::GLFormat::RGB, ktx::GLInternalFormat::RGB9_E5, ktx::GLBaseInternalFormat::RGB); + } else if (texelFormat == Format::COLOR_R11G11B10 && mipFormat == Format::COLOR_R11G11B10) { + header.setUncompressed(ktx::GLType::UNSIGNED_INT_10F_11F_11F_REV, 1, ktx::GLFormat::RGB, ktx::GLInternalFormat::R11F_G11F_B10F, ktx::GLBaseInternalFormat::RGB); } else { return false; } @@ -612,6 +616,12 @@ bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, E } else { return false; } + } else if (header.getGLFormat() == ktx::GLFormat::RGB && header.getGLType() == ktx::GLType::UNSIGNED_INT_10F_11F_11F_REV) { + mipFormat = Format::COLOR_R11G11B10; + texelFormat = Format::COLOR_R11G11B10; + } else if (header.getGLFormat() == ktx::GLFormat::RGB && header.getGLType() == ktx::GLType::UNSIGNED_INT_5_9_9_9_REV) { + mipFormat = Format::COLOR_RGB9E5; + texelFormat = Format::COLOR_RGB9E5; } else if (header.isCompressed()) { if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_S3TC_DXT1_EXT) { mipFormat = Format::COLOR_COMPRESSED_SRGB; @@ -631,6 +641,9 @@ bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, E } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM) { mipFormat = Format::COLOR_COMPRESSED_SRGBA_HIGH; texelFormat = Format::COLOR_COMPRESSED_SRGBA_HIGH; + } else if (header.getGLInternaFormat() == ktx::GLInternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT) { + mipFormat = Format::COLOR_COMPRESSED_HDR_RGB; + texelFormat = Format::COLOR_COMPRESSED_HDR_RGB; } else { return false; } diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index b4b5bdeae1..a2f5f93e9b 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -11,9 +11,10 @@ #include "Image.h" +#include +#include + #include - - #include #include #include @@ -42,6 +43,8 @@ bool DEV_DECIMATE_TEXTURES = false; std::atomic DECIMATED_TEXTURE_COUNT{ 0 }; std::atomic RECTIFIED_TEXTURE_COUNT{ 0 }; +static const auto HDR_FORMAT = gpu::Element::COLOR_R11G11B10; + static std::atomic compressColorTextures { false }; static std::atomic compressNormalTextures { false }; static std::atomic compressGrayscaleTextures { false }; @@ -71,6 +74,10 @@ glm::uvec2 rectifyToSparseSize(const glm::uvec2& size) { namespace image { +enum { + QIMAGE_HDR_FORMAT = QImage::Format_RGB30 +}; + TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, const QVariantMap& options) { switch (type) { case ALBEDO_TEXTURE: @@ -213,6 +220,25 @@ void setCubeTexturesCompressionEnabled(bool enabled) { compressCubeTextures.store(enabled); } +static float denormalize(float value, const float minValue) { + return value < minValue ? 0.0f : value; +} + +uint32 packR11G11B10F(const glm::vec3& color) { + // Denormalize else unpacking gives high and incorrect values + // See https://www.khronos.org/opengl/wiki/Small_Float_Formats for this min value + static const auto minValue = 6.10e-5f; + static const auto maxValue = 6.50e4f; + glm::vec3 ucolor; + ucolor.r = denormalize(color.r, minValue); + ucolor.g = denormalize(color.g, minValue); + ucolor.b = denormalize(color.b, minValue); + ucolor.r = std::min(ucolor.r, maxValue); + ucolor.g = std::min(ucolor.g, maxValue); + ucolor.b = std::min(ucolor.b, maxValue); + return glm::packF2x11_1x10(ucolor); +} + gpu::TexturePointer processImage(const QByteArray& content, const std::string& filename, int maxNumPixels, TextureUsage::Type textureType, const std::atomic& abortProcessing) { @@ -270,8 +296,6 @@ gpu::TexturePointer processImage(const QByteArray& content, const std::string& f return texture; } - - QImage processSourceImage(const QImage& srcImage, bool cubemap) { PROFILE_RANGE(resource_parse, "processSourceImage"); const glm::uvec2 srcImageSize = toGlm(srcImage.size()); @@ -303,8 +327,8 @@ QImage processSourceImage(const QImage& srcImage, bool cubemap) { } #if defined(NVTT_API) -struct MyOutputHandler : public nvtt::OutputHandler { - MyOutputHandler(gpu::Texture* texture, int face) : _texture(texture), _face(face) {} +struct OutputHandler : public nvtt::OutputHandler { + OutputHandler(gpu::Texture* texture, int face) : _texture(texture), _face(face) {} virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override { _size = size; @@ -313,12 +337,14 @@ struct MyOutputHandler : public nvtt::OutputHandler { _data = static_cast(malloc(size)); _current = _data; } + virtual bool writeData(const void* data, int size) override { assert(_current + size <= _data + _size); memcpy(_current, data, size); _current += size; return true; } + virtual void endImage() override { if (_face >= 0) { _texture->assignStoredMipFace(_miplevel, _face, _size, static_cast(_data)); @@ -336,6 +362,51 @@ struct MyOutputHandler : public nvtt::OutputHandler { int _size = 0; int _face = -1; }; + +struct PackedFloatOutputHandler : public OutputHandler { + PackedFloatOutputHandler(gpu::Texture* texture, int face, gpu::Element format) : OutputHandler(texture, face) { + if (format == gpu::Element::COLOR_RGB9E5) { + _packFunc = glm::packF3x9_E1x5; + } else if (format == gpu::Element::COLOR_R11G11B10) { + _packFunc = packR11G11B10F; + } else { + qCWarning(imagelogging) << "Unknown handler format"; + Q_UNREACHABLE(); + } + } + + virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override { + // Divide by 3 because we will compress from 3*floats to 1 uint32 + OutputHandler::beginImage(size / 3, width, height, depth, face, miplevel); + } + virtual bool writeData(const void* data, int size) override { + // Expecting to write multiple of floats + if (_packFunc) { + assert((size % sizeof(float)) == 0); + auto floatCount = size / sizeof(float); + const float* floatBegin = (const float*)data; + const float* floatEnd = floatBegin + floatCount; + + while (floatBegin < floatEnd) { + _pixel[_coordIndex] = *floatBegin; + floatBegin++; + _coordIndex++; + if (_coordIndex == 3) { + uint32 packedRGB = _packFunc(_pixel); + _coordIndex = 0; + OutputHandler::writeData(&packedRGB, sizeof(packedRGB)); + } + } + return true; + } + return false; + } + + std::function _packFunc; + glm::vec3 _pixel; + int _coordIndex{ 0 }; +}; + struct MyErrorHandler : public nvtt::ErrorHandler { virtual void error(nvtt::Error e) override { qCWarning(imagelogging) << "Texture compression error:" << nvtt::errorString(e); @@ -343,10 +414,115 @@ struct MyErrorHandler : public nvtt::ErrorHandler { }; #endif -void generateMips(gpu::Texture* texture, QImage& image, const std::atomic& abortProcessing = false, int face = -1) { -#if CPU_MIPMAPS - PROFILE_RANGE(resource_parse, "generateMips"); +class SequentialTaskDispatcher : public nvtt::TaskDispatcher { +public: + SequentialTaskDispatcher(const std::atomic& abortProcessing) : _abortProcessing(abortProcessing) {}; + const std::atomic& _abortProcessing; + + virtual void dispatch(nvtt::Task* task, void* context, int count) override { + for (int i = 0; i < count; i++) { + if (!_abortProcessing.load()) { + task(context, i); + } else { + break; + } + } + } +}; + +void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atomic& abortProcessing, int face) { + assert(image.format() == QIMAGE_HDR_FORMAT); + + const int width = image.width(), height = image.height(); + std::vector data; + std::vector::iterator dataIt; + auto mipFormat = texture->getStoredMipFormat(); + std::function unpackFunc; + + nvtt::TextureType textureType = nvtt::TextureType_2D; + nvtt::InputFormat inputFormat = nvtt::InputFormat_RGBA_32F; + nvtt::WrapMode wrapMode = nvtt::WrapMode_Mirror; + nvtt::RoundMode roundMode = nvtt::RoundMode_None; + nvtt::AlphaMode alphaMode = nvtt::AlphaMode_None; + + nvtt::CompressionOptions compressionOptions; + compressionOptions.setQuality(nvtt::Quality_Production); + + if (mipFormat == gpu::Element::COLOR_COMPRESSED_HDR_RGB) { + compressionOptions.setFormat(nvtt::Format_BC6); + } else if (mipFormat == gpu::Element::COLOR_RGB9E5) { + compressionOptions.setFormat(nvtt::Format_RGB); + compressionOptions.setPixelType(nvtt::PixelType_Float); + compressionOptions.setPixelFormat(32, 32, 32, 0); + } else if (mipFormat == gpu::Element::COLOR_R11G11B10) { + compressionOptions.setFormat(nvtt::Format_RGB); + compressionOptions.setPixelType(nvtt::PixelType_Float); + compressionOptions.setPixelFormat(32, 32, 32, 0); + } else { + qCWarning(imagelogging) << "Unknown mip format"; + Q_UNREACHABLE(); + return; + } + + if (HDR_FORMAT == gpu::Element::COLOR_RGB9E5) { + unpackFunc = glm::unpackF3x9_E1x5; + } else if (HDR_FORMAT == gpu::Element::COLOR_R11G11B10) { + unpackFunc = glm::unpackF2x11_1x10; + } else { + qCWarning(imagelogging) << "Unknown HDR encoding format in QImage"; + Q_UNREACHABLE(); + return; + } + + data.resize(width*height); + dataIt = data.begin(); + for (auto lineNb = 0; lineNb < height; lineNb++) { + const uint32* srcPixelIt = reinterpret_cast( image.constScanLine(lineNb) ); + const uint32* srcPixelEnd = srcPixelIt + width; + + while (srcPixelIt < srcPixelEnd) { + *dataIt = glm::vec4(unpackFunc(*srcPixelIt), 1.0f); + ++srcPixelIt; + ++dataIt; + } + } + assert(dataIt == data.end()); + + nvtt::OutputOptions outputOptions; + outputOptions.setOutputHeader(false); + std::unique_ptr outputHandler; + MyErrorHandler errorHandler; + outputOptions.setErrorHandler(&errorHandler); + nvtt::Context context; + int mipLevel = 0; + + if (mipFormat == gpu::Element::COLOR_RGB9E5 || mipFormat == gpu::Element::COLOR_R11G11B10) { + // Don't use NVTT (at least version 2.1) as it outputs wrong RGB9E5 and R11G11B10F values from floats + outputHandler.reset(new PackedFloatOutputHandler(texture, face, mipFormat)); + } else { + outputHandler.reset( new OutputHandler(texture, face) ); + } + + outputOptions.setOutputHandler(outputHandler.get()); + + nvtt::Surface surface; + surface.setImage(inputFormat, width, height, 1, &(*data.begin())); + surface.setAlphaMode(alphaMode); + surface.setWrapMode(wrapMode); + + SequentialTaskDispatcher dispatcher(abortProcessing); + nvtt::Compressor compressor; + context.setTaskDispatcher(&dispatcher); + + context.compress(surface, face, mipLevel++, compressionOptions, outputOptions); + while (surface.canMakeNextMipmap() && !abortProcessing.load()) { + surface.buildNextMipmap(nvtt::MipmapFilter_Box); + context.compress(surface, face, mipLevel++, compressionOptions, outputOptions); + } +} + +void generateLDRMips(gpu::Texture* texture, QImage& image, const std::atomic& abortProcessing, int face) { if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); } @@ -400,10 +576,10 @@ void generateMips(gpu::Texture* texture, QImage& image, const std::atomic& compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); compressionOptions.setPitchAlignment(4); compressionOptions.setPixelFormat(32, - 0x000000FF, - 0x0000FF00, - 0x00FF0000, - 0xFF000000); + 0x000000FF, + 0x0000FF00, + 0x00FF0000, + 0xFF000000); inputGamma = 1.0f; outputGamma = 1.0f; } else if (mipFormat == gpu::Element::COLOR_BGRA_32) { @@ -411,10 +587,10 @@ void generateMips(gpu::Texture* texture, QImage& image, const std::atomic& compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); compressionOptions.setPitchAlignment(4); compressionOptions.setPixelFormat(32, - 0x00FF0000, - 0x0000FF00, - 0x000000FF, - 0xFF000000); + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000); inputGamma = 1.0f; outputGamma = 1.0f; } else if (mipFormat == gpu::Element::COLOR_SRGBA_32) { @@ -422,19 +598,19 @@ void generateMips(gpu::Texture* texture, QImage& image, const std::atomic& compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); compressionOptions.setPitchAlignment(4); compressionOptions.setPixelFormat(32, - 0x000000FF, - 0x0000FF00, - 0x00FF0000, - 0xFF000000); + 0x000000FF, + 0x0000FF00, + 0x00FF0000, + 0xFF000000); } else if (mipFormat == gpu::Element::COLOR_SBGRA_32) { compressionOptions.setFormat(nvtt::Format_RGBA); compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); compressionOptions.setPitchAlignment(4); compressionOptions.setPixelFormat(32, - 0x00FF0000, - 0x0000FF00, - 0x000000FF, - 0xFF000000); + 0x00FF0000, + 0x0000FF00, + 0x000000FF, + 0xFF000000); } else if (mipFormat == gpu::Element::COLOR_R_8) { compressionOptions.setFormat(nvtt::Format_RGB); compressionOptions.setPixelType(nvtt::PixelType_UnsignedNorm); @@ -454,32 +630,26 @@ void generateMips(gpu::Texture* texture, QImage& image, const std::atomic& nvtt::OutputOptions outputOptions; outputOptions.setOutputHeader(false); - MyOutputHandler outputHandler(texture, face); + OutputHandler outputHandler(texture, face); outputOptions.setOutputHandler(&outputHandler); MyErrorHandler errorHandler; outputOptions.setErrorHandler(&errorHandler); - class SequentialTaskDispatcher : public nvtt::TaskDispatcher { - public: - SequentialTaskDispatcher(const std::atomic& abortProcessing) : _abortProcessing(abortProcessing) {}; - - const std::atomic& _abortProcessing; - - virtual void dispatch(nvtt::Task* task, void* context, int count) override { - for (int i = 0; i < count; i++) { - if (!_abortProcessing.load()) { - task(context, i); - } else { - break; - } - } - } - }; - SequentialTaskDispatcher dispatcher(abortProcessing); nvtt::Compressor compressor; compressor.setTaskDispatcher(&dispatcher); compressor.process(inputOptions, compressionOptions, outputOptions); +} + +void generateMips(gpu::Texture* texture, QImage& image, const std::atomic& abortProcessing = false, int face = -1) { +#if CPU_MIPMAPS + PROFILE_RANGE(resource_parse, "generateMips"); + + if (image.format() == QIMAGE_HDR_FORMAT) { + generateHDRMips(texture, image, abortProcessing, face); + } else { + generateLDRMips(texture, image, abortProcessing, face); + } #else texture->setAutoGenerateMips(true); #endif @@ -961,6 +1131,62 @@ const CubeLayout CubeLayout::CUBEMAP_LAYOUTS[] = { }; const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) / sizeof(CubeLayout); +//#define DEBUG_COLOR_PACKING + +QImage convertToHDRFormat(QImage srcImage, gpu::Element format) { + QImage hdrImage(srcImage.width(), srcImage.height(), (QImage::Format)QIMAGE_HDR_FORMAT); + std::function packFunc; +#ifdef DEBUG_COLOR_PACKING + std::function unpackFunc; +#endif + + switch (format.getSemantic()) { + case gpu::R11G11B10: + packFunc = packR11G11B10F; +#ifdef DEBUG_COLOR_PACKING + unpackFunc = glm::unpackF2x11_1x10; +#endif + break; + case gpu::RGB9E5: + packFunc = glm::packF3x9_E1x5; +#ifdef DEBUG_COLOR_PACKING + unpackFunc = glm::unpackF3x9_E1x5; +#endif + break; + default: + qCWarning(imagelogging) << "Unsupported HDR format"; + Q_UNREACHABLE(); + return srcImage; + } + + srcImage = srcImage.convertToFormat(QImage::Format_ARGB32); + for (auto y = 0; y < srcImage.height(); y++) { + const QRgb* srcLineIt = reinterpret_cast( srcImage.constScanLine(y) ); + const QRgb* srcLineEnd = srcLineIt + srcImage.width(); + uint32* hdrLineIt = reinterpret_cast( hdrImage.scanLine(y) ); + glm::vec3 color; + + while (srcLineIt < srcLineEnd) { + color.r = qRed(*srcLineIt); + color.g = qGreen(*srcLineIt); + color.b = qBlue(*srcLineIt); + // Normalize and apply gamma + color /= 255.0f; + color.r = powf(color.r, 2.2f); + color.g = powf(color.g, 2.2f); + color.b = powf(color.b, 2.2f); + *hdrLineIt = packFunc(color); +#ifdef DEBUG_COLOR_PACKING + glm::vec3 ucolor = unpackFunc(*hdrLineIt); + assert(glm::distance(color, ucolor) <= 5e-2); +#endif + ++srcLineIt; + ++hdrLineIt; + } + } + return hdrImage; +} + gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool generateIrradiance, const std::atomic& abortProcessing) { @@ -969,18 +1195,19 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& gpu::TexturePointer theTexture = nullptr; if ((srcImage.width() > 0) && (srcImage.height() > 0)) { QImage image = processSourceImage(srcImage, true); - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); - } gpu::Element formatMip; gpu::Element formatGPU; if (isCubeTexturesCompressionEnabled()) { - formatMip = gpu::Element::COLOR_COMPRESSED_SRGBA_HIGH; - formatGPU = gpu::Element::COLOR_COMPRESSED_SRGBA_HIGH; + formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB; + formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB; } else { - formatMip = gpu::Element::COLOR_SRGBA_32; - formatGPU = gpu::Element::COLOR_SRGBA_32; + formatMip = HDR_FORMAT; + formatGPU = HDR_FORMAT; + } + + if (image.format() != QIMAGE_HDR_FORMAT) { + image = convertToHDRFormat(image, HDR_FORMAT); } // Find the layout of the cubemap in the 2D image @@ -1028,9 +1255,9 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& // Generate irradiance while we are at it if (generateIrradiance) { PROFILE_RANGE(resource_parse, "generateIrradiance"); - auto irradianceTexture = gpu::Texture::createCube(gpu::Element::COLOR_SRGBA_32, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); + auto irradianceTexture = gpu::Texture::createCube(HDR_FORMAT, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); irradianceTexture->setSource(srcImageName); - irradianceTexture->setStoredMipFormat(gpu::Element::COLOR_SBGRA_32); + irradianceTexture->setStoredMipFormat(HDR_FORMAT); for (uint8 face = 0; face < faces.size(); ++face) { irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits()); } diff --git a/libraries/ktx/src/khronos/KHR.h b/libraries/ktx/src/khronos/KHR.h index 98cc1a4736..cda22513ee 100644 --- a/libraries/ktx/src/khronos/KHR.h +++ b/libraries/ktx/src/khronos/KHR.h @@ -209,6 +209,137 @@ namespace khronos { COMPRESSED_SIGNED_RG11_EAC = 0x9273, }; + inline uint8_t evalUncompressedBlockBitSize(InternalFormat format) { + switch (format) { + case InternalFormat::R8: + case InternalFormat::R8_SNORM: + return 8; + case InternalFormat::R16: + case InternalFormat::R16_SNORM: + case InternalFormat::RG8: + case InternalFormat::RG8_SNORM: + return 16; + case InternalFormat::RG16: + case InternalFormat::RG16_SNORM: + return 16; + case InternalFormat::R3_G3_B2: + return 8; + case InternalFormat::RGB4: + return 12; + case InternalFormat::RGB5: + case InternalFormat::RGB565: + return 16; + case InternalFormat::RGB8: + case InternalFormat::RGB8_SNORM: + return 24; + case InternalFormat::RGB10: + // TODO: check if this is really the case + return 32; + case InternalFormat::RGB12: + // TODO: check if this is really the case + return 48; + case InternalFormat::RGB16: + case InternalFormat::RGB16_SNORM: + return 48; + case InternalFormat::RGBA2: + return 8; + case InternalFormat::RGBA4: + case InternalFormat::RGB5_A1: + return 16; + case InternalFormat::RGBA8: + case InternalFormat::RGBA8_SNORM: + case InternalFormat::RGB10_A2: + case InternalFormat::RGB10_A2UI: + return 32; + case InternalFormat::RGBA12: + return 48; + case InternalFormat::RGBA16: + case InternalFormat::RGBA16_SNORM: + return 64; + case InternalFormat::SRGB8: + return 24; + case InternalFormat::SRGB8_ALPHA8: + return 32; + case InternalFormat::R16F: + return 16; + case InternalFormat::RG16F: + return 32; + case InternalFormat::RGB16F: + return 48; + case InternalFormat::RGBA16F: + return 64; + case InternalFormat::R32F: + return 32; + case InternalFormat::RG32F: + return 64; + case InternalFormat::RGB32F: + return 96; + case InternalFormat::RGBA32F: + return 128; + case InternalFormat::R11F_G11F_B10F: + case InternalFormat::RGB9_E5: + return 32; + case InternalFormat::R8I: + case InternalFormat::R8UI: + return 8; + case InternalFormat::R16I: + case InternalFormat::R16UI: + return 16; + case InternalFormat::R32I: + case InternalFormat::R32UI: + return 32; + case InternalFormat::RG8I: + case InternalFormat::RG8UI: + return 16; + case InternalFormat::RG16I: + case InternalFormat::RG16UI: + return 32; + case InternalFormat::RG32I: + case InternalFormat::RG32UI: + return 64; + case InternalFormat::RGB8I: + case InternalFormat::RGB8UI: + return 24; + case InternalFormat::RGB16I: + case InternalFormat::RGB16UI: + return 48; + case InternalFormat::RGB32I: + case InternalFormat::RGB32UI: + return 96; + case InternalFormat::RGBA8I: + case InternalFormat::RGBA8UI: + return 32; + case InternalFormat::RGBA16I: + case InternalFormat::RGBA16UI: + return 64; + case InternalFormat::RGBA32I: + case InternalFormat::RGBA32UI: + return 128; + case InternalFormat::DEPTH_COMPONENT16: + return 16; + case InternalFormat::DEPTH_COMPONENT24: + return 24; + case InternalFormat::DEPTH_COMPONENT32: + case InternalFormat::DEPTH_COMPONENT32F: + case InternalFormat::DEPTH24_STENCIL8: + return 32; + case InternalFormat::DEPTH32F_STENCIL8: + // TODO : check if this true + return 40; + case InternalFormat::STENCIL_INDEX1: + return 1; + case InternalFormat::STENCIL_INDEX4: + return 4; + case InternalFormat::STENCIL_INDEX8: + return 8; + case InternalFormat::STENCIL_INDEX16: + return 16; + + default: + return 0; + } + } + template inline uint32_t evalAlignedCompressedBlockCount(uint32_t value) { enum { val = ALIGNMENT && !(ALIGNMENT & (ALIGNMENT - 1)) }; @@ -225,6 +356,7 @@ namespace khronos { case InternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: // BC3 case InternalFormat::COMPRESSED_RED_RGTC1: // BC4 case InternalFormat::COMPRESSED_RG_RGTC2: // BC5 + case InternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: // BC6 case InternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM: // BC7 return evalAlignedCompressedBlockCount<4>(value); @@ -241,6 +373,7 @@ namespace khronos { return 8; case InternalFormat::COMPRESSED_SRGB_ALPHA_BPTC_UNORM: + case InternalFormat::COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: case InternalFormat::COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: case InternalFormat::COMPRESSED_RG_RGTC2: return 16; @@ -250,6 +383,10 @@ namespace khronos { } } + inline uint8_t evalCompressedBlockBitSize(InternalFormat format) { + return evalCompressedBlockSize(format) * 8; + } + enum class BaseInternalFormat : uint32_t { // GL 4.4 Table 8.11 DEPTH_COMPONENT = 0x1902, diff --git a/libraries/ktx/src/ktx/KTX.cpp b/libraries/ktx/src/ktx/KTX.cpp index 3b1a12cba7..5bd45549c1 100644 --- a/libraries/ktx/src/ktx/KTX.cpp +++ b/libraries/ktx/src/ktx/KTX.cpp @@ -54,15 +54,13 @@ uint32_t Header::evalPixelOrBlockDepth(uint32_t level) const { return evalMipDimension(level, getPixelDepth()); } -size_t Header::evalPixelOrBlockSize() const { +size_t Header::evalPixelOrBlockBitSize() const { size_t result = 0; + auto format = getGLInternaFormat(); if (isCompressed()) { - auto format = getGLInternaFormat(); - result = khronos::gl::texture::evalCompressedBlockSize(format); + result = khronos::gl::texture::evalCompressedBlockBitSize(format); } else { - // FIXME should really be using the internal format, not the base internal format - auto baseFormat = getGLBaseInternalFormat(); - result = khronos::gl::texture::evalComponentCount(baseFormat); + result = khronos::gl::texture::evalUncompressedBlockBitSize(format); } if (0 == result) { @@ -73,11 +71,14 @@ size_t Header::evalPixelOrBlockSize() const { size_t Header::evalRowSize(uint32_t level) const { auto pixWidth = evalPixelOrBlockWidth(level); - auto pixSize = evalPixelOrBlockSize(); + auto pixSize = evalPixelOrBlockBitSize(); if (pixSize == 0) { return 0; } - return evalPaddedSize(pixWidth * pixSize); + auto totalByteSize = pixWidth * pixSize; + // Round to the nearest upper byte size + totalByteSize = (totalByteSize / 8) + (((totalByteSize % 8) != 0) & 1); + return evalPaddedSize(totalByteSize); } size_t Header::evalFaceSize(uint32_t level) const { diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h index 8dc4ec7a47..54a8188a42 100644 --- a/libraries/ktx/src/ktx/KTX.h +++ b/libraries/ktx/src/ktx/KTX.h @@ -170,7 +170,7 @@ namespace ktx { uint32_t evalPixelOrBlockHeight(uint32_t level) const; uint32_t evalPixelOrBlockDepth(uint32_t level) const; - size_t evalPixelOrBlockSize() const; + size_t evalPixelOrBlockBitSize() const; size_t evalRowSize(uint32_t level) const; size_t evalFaceSize(uint32_t level) const; size_t evalImageSize(uint32_t level) const; diff --git a/libraries/networking/src/AssetResourceRequest.cpp b/libraries/networking/src/AssetResourceRequest.cpp index 2dc22a9337..55d0ad4f75 100644 --- a/libraries/networking/src/AssetResourceRequest.cpp +++ b/libraries/networking/src/AssetResourceRequest.cpp @@ -155,6 +155,7 @@ void AssetResourceRequest::requestHash(const AssetHash& hash) { case AssetRequest::Error::NoError: _data = req->getData(); _result = Success; + recordBytesDownloadedInStats(STAT_ATP_RESOURCE_TOTAL_BYTES, _data.size()); break; case AssetRequest::InvalidHash: _result = InvalidURL; @@ -202,9 +203,8 @@ void AssetResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytes emit progress(bytesReceived, bytesTotal); auto now = p_high_resolution_clock::now(); - - // Recording ATP bytes downloaded in stats - DependencyManager::get()->updateStat(STAT_ATP_RESOURCE_TOTAL_BYTES, bytesReceived); + + recordBytesDownloadedInStats(STAT_ATP_RESOURCE_TOTAL_BYTES, bytesReceived); // if we haven't received the full asset check if it is time to output progress to log // we do so every X seconds to assist with ATP download tracking diff --git a/libraries/networking/src/AssetResourceRequest.h b/libraries/networking/src/AssetResourceRequest.h index 18b82f2573..ac36c83985 100644 --- a/libraries/networking/src/AssetResourceRequest.h +++ b/libraries/networking/src/AssetResourceRequest.h @@ -41,6 +41,8 @@ private: AssetRequest* _assetRequest { nullptr }; p_high_resolution_clock::time_point _lastProgressDebug; + + int64_t _lastRecordedBytesDownloaded { 0 }; }; #endif diff --git a/libraries/networking/src/FileResourceRequest.cpp b/libraries/networking/src/FileResourceRequest.cpp index 26857716e1..dfff21dae6 100644 --- a/libraries/networking/src/FileResourceRequest.cpp +++ b/libraries/networking/src/FileResourceRequest.cpp @@ -69,8 +69,7 @@ void FileResourceRequest::doSend() { if (_result == ResourceRequest::Success) { statTracker->incrementStat(STAT_FILE_REQUEST_SUCCESS); - // Recording FILE bytes downloaded in stats - statTracker->updateStat(STAT_FILE_RESOURCE_TOTAL_BYTES,fileSize); + statTracker->updateStat(STAT_FILE_RESOURCE_TOTAL_BYTES, fileSize); } else { statTracker->incrementStat(STAT_FILE_REQUEST_FAILED); } diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp index edba520bd5..a34f92aaa6 100644 --- a/libraries/networking/src/HTTPResourceRequest.cpp +++ b/libraries/networking/src/HTTPResourceRequest.cpp @@ -141,6 +141,8 @@ void HTTPResourceRequest::onRequestFinished() { } } + recordBytesDownloadedInStats(STAT_HTTP_RESOURCE_TOTAL_BYTES, _data.size()); + break; case QNetworkReply::TimeoutError: @@ -201,11 +203,8 @@ void HTTPResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesT _sendTimer->start(); emit progress(bytesReceived, bytesTotal); - - // Recording HTTP bytes downloaded in stats - DependencyManager::get()->updateStat(STAT_HTTP_RESOURCE_TOTAL_BYTES, bytesReceived); - - + + recordBytesDownloadedInStats(STAT_HTTP_RESOURCE_TOTAL_BYTES, bytesReceived); } void HTTPResourceRequest::onTimeout() { diff --git a/libraries/networking/src/ResourceRequest.cpp b/libraries/networking/src/ResourceRequest.cpp index f028535e2f..5d39583c9e 100644 --- a/libraries/networking/src/ResourceRequest.cpp +++ b/libraries/networking/src/ResourceRequest.cpp @@ -11,6 +11,9 @@ #include "ResourceRequest.h" +#include +#include + #include ResourceRequest::ResourceRequest(const QUrl& url) : _url(url) { } @@ -40,3 +43,11 @@ QString ResourceRequest::getResultString() const { default: return "Unspecified Error"; } } + +void ResourceRequest::recordBytesDownloadedInStats(const QString& statName, int64_t bytesReceived) { + auto dBytes = bytesReceived - _lastRecordedBytesDownloaded; + if (dBytes > 0) { + _lastRecordedBytesDownloaded = bytesReceived; + DependencyManager::get()->updateStat(statName, dBytes); + } +} diff --git a/libraries/networking/src/ResourceRequest.h b/libraries/networking/src/ResourceRequest.h index cf852c1e1b..8dd09ccc49 100644 --- a/libraries/networking/src/ResourceRequest.h +++ b/libraries/networking/src/ResourceRequest.h @@ -85,6 +85,7 @@ signals: protected: virtual void doSend() = 0; + void recordBytesDownloadedInStats(const QString& statName, int64_t bytesReceived); QUrl _url; QUrl _relativePathURL; @@ -97,6 +98,7 @@ protected: ByteRange _byteRange; bool _rangeRequestSuccessful { false }; uint64_t _totalSizeOfResource { 0 }; + int64_t _lastRecordedBytesDownloaded { 0 }; }; #endif diff --git a/libraries/shared/src/StatTracker.cpp b/libraries/shared/src/StatTracker.cpp index 0ac9ab0092..adfafd0ea7 100644 --- a/libraries/shared/src/StatTracker.cpp +++ b/libraries/shared/src/StatTracker.cpp @@ -14,15 +14,15 @@ StatTracker::StatTracker() { QVariant StatTracker::getStat(const QString& name) { std::lock_guard lock(_statsLock); - return _stats[name]; + return QVariant::fromValue(_stats[name]); } -void StatTracker::setStat(const QString& name, int value) { +void StatTracker::setStat(const QString& name, int64_t value) { Lock lock(_statsLock); _stats[name] = value; } -void StatTracker::updateStat(const QString& name, int value) { +void StatTracker::updateStat(const QString& name, int64_t value) { Lock lock(_statsLock); auto itr = _stats.find(name); if (_stats.end() == itr) { diff --git a/libraries/shared/src/StatTracker.h b/libraries/shared/src/StatTracker.h index 38afc2c379..4b84bbcb32 100644 --- a/libraries/shared/src/StatTracker.h +++ b/libraries/shared/src/StatTracker.h @@ -24,15 +24,15 @@ class StatTracker : public Dependency { public: StatTracker(); QVariant getStat(const QString& name); - void setStat(const QString& name, int value); - void updateStat(const QString& name, int mod); + void setStat(const QString& name, int64_t value); + void updateStat(const QString& name, int64_t mod); void incrementStat(const QString& name); void decrementStat(const QString& name); private: using Mutex = std::mutex; using Lock = std::lock_guard; Mutex _statsLock; - QHash _stats; + QHash _stats; }; class CounterStat { diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntity.js b/scripts/system/controllers/controllerModules/farActionGrabEntity.js index 0ef0e67471..fb380f992b 100644 --- a/scripts/system/controllers/controllerModules/farActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farActionGrabEntity.js @@ -119,6 +119,7 @@ Script.include("/~/system/libraries/controllers.js"); this.reticleMaxX; this.reticleMinY = MARGIN; this.reticleMaxY; + this.madeDynamic = false; var ACTION_TTL = 15; // seconds @@ -344,6 +345,13 @@ Script.include("/~/system/libraries/controllers.js"); var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.grabbedThingID, "releaseGrab", args); + if (this.madeDynamic) { + var props = {}; + props.dynamic = false; + props.localVelocity = {x: 0, y: 0, z: 0}; + Entities.editEntity(this.grabbedThingID, props); + this.madeDynamic = false; + } this.actionID = null; this.grabbedThingID = null; }; @@ -503,7 +511,13 @@ Script.include("/~/system/libraries/controllers.js"); this.destroyContextOverlay(); } - if (entityIsDistanceGrabbable(targetProps)) { + if (entityIsGrabbable(targetProps)) { + if (!entityIsDistanceGrabbable(targetProps)) { + targetProps.dynamic = true; + Entities.editEntity(entityID, targetProps); + this.madeDynamic = true; + } + if (!this.distanceRotating) { this.grabbedThingID = entityID; this.grabbedDistance = rayPickInfo.distance; diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index 0e9b8569ae..2f046cbce3 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -15,12 +15,13 @@ // /* global MyAvatar, Entities, Script, Camera, Vec3, Reticle, Overlays, getEntityCustomData, Messages, Quat, Controller, - isInEditMode, HMD */ + isInEditMode, HMD entityIsGrabbable*/ (function() { // BEGIN LOCAL_SCOPE -Script.include("/~/system/libraries/utils.js"); + Script.include("/~/system/libraries/utils.js"); + Script.include("/~/system/libraries/controllerDispatcherUtils.js"); var MAX_SOLID_ANGLE = 0.01; // objects that appear smaller than this can't be grabbed var DELAY_FOR_30HZ = 33; // milliseconds @@ -330,9 +331,11 @@ Grabber.prototype.pressEvent = function(event) { return; } - var isDynamic = Entities.getEntityProperties(pickResults.objectID, "dynamic").dynamic; - if (!isDynamic) { - // only grab dynamic objects + var props = Entities.getEntityProperties(pickResults.objectID, ["dynamic", "userData", "locked", "type"]); + var isDynamic = props.dynamic; + var isGrabbable = props.grabbable; + if (!entityIsGrabbable(props)) { + // only grab grabbable objects return; } @@ -350,6 +353,7 @@ Grabber.prototype.pressEvent = function(event) { var entityProperties = Entities.getEntityProperties(clickedEntity); this.startPosition = entityProperties.position; this.lastRotation = entityProperties.rotation; + this.madeDynamic = false; var cameraPosition = Camera.getPosition(); var objectBoundingDiameter = Vec3.length(entityProperties.dimensions); @@ -361,6 +365,11 @@ Grabber.prototype.pressEvent = function(event) { return; } + if (entityIsGrabbable(props) && !isDynamic) { + entityProperties.dynamic = true; + Entities.editEntity(clickedEntity, entityProperties); + this.madeDynamic = true; + } // this.activateEntity(clickedEntity, entityProperties); this.isGrabbing = true; @@ -416,6 +425,14 @@ Grabber.prototype.releaseEvent = function(event) { if (this.actionID) { Entities.deleteAction(this.entityID, this.actionID); } + + if (this.madeDynamic) { + var entityProps = {}; + entityProps.dynamic = false; + entityProps.localVelocity = {x: 0, y: 0, z: 0}; + Entities.editEntity(this.entityID, entityProps); + } + this.actionID = null; LaserPointers.setRenderState(this.mouseRayEntities, ""); diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 41db724c3f..fc16eae8bf 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -26,7 +26,7 @@ var xmlHttpRequest = null; var isPreparing = false; // Explicitly track download request status. - var confirmAllPurchases = false; // Set this to "true" to cause Checkout.qml to popup for all items, even if free + var commerceMode = false; var userIsLoggedIn = false; var walletNeedsSetup = false; @@ -99,7 +99,9 @@ } function maybeAddSetupWalletButton() { - if (userIsLoggedIn && walletNeedsSetup) { + if (!$('body').hasClass("walletsetup-injected") && userIsLoggedIn && walletNeedsSetup) { + $('body').addClass("walletsetup-injected"); + var resultsElement = document.getElementById('results'); var setupWalletElement = document.createElement('div'); setupWalletElement.classList.add("row"); @@ -135,7 +137,8 @@ } function maybeAddLogInButton() { - if (!userIsLoggedIn) { + if (!$('body').hasClass("login-injected") && !userIsLoggedIn) { + $('body').addClass("login-injected"); var resultsElement = document.getElementById('results'); var logInElement = document.createElement('div'); logInElement.classList.add("row"); @@ -300,68 +303,72 @@ } function injectHiFiCode() { - if (!$('body').hasClass("code-injected") && confirmAllPurchases) { - - $('body').addClass("code-injected"); - + if (commerceMode) { maybeAddLogInButton(); maybeAddSetupWalletButton(); - changeDropdownMenu(); - var target = document.getElementById('templated-items'); - // MutationObserver is necessary because the DOM is populated after the page is loaded. - // We're searching for changes to the element whose ID is '#templated-items' - this is - // the element that gets filled in by the AJAX. - var observer = new MutationObserver(function (mutations) { - mutations.forEach(function (mutation) { - injectBuyButtonOnMainPage(); + if (!$('body').hasClass("code-injected")) { + + $('body').addClass("code-injected"); + changeDropdownMenu(); + + var target = document.getElementById('templated-items'); + // MutationObserver is necessary because the DOM is populated after the page is loaded. + // We're searching for changes to the element whose ID is '#templated-items' - this is + // the element that gets filled in by the AJAX. + var observer = new MutationObserver(function (mutations) { + mutations.forEach(function (mutation) { + injectBuyButtonOnMainPage(); + }); + //observer.disconnect(); }); - //observer.disconnect(); - }); - var config = { attributes: true, childList: true, characterData: true }; - observer.observe(target, config); + var config = { attributes: true, childList: true, characterData: true }; + observer.observe(target, config); - // Try this here in case it works (it will if the user just pressed the "back" button, - // since that doesn't trigger another AJAX request. - injectBuyButtonOnMainPage(); - maybeAddPurchasesButton(); + // Try this here in case it works (it will if the user just pressed the "back" button, + // since that doesn't trigger another AJAX request. + injectBuyButtonOnMainPage(); + maybeAddPurchasesButton(); + } } } function injectHiFiItemPageCode() { - if (!$('body').hasClass("code-injected") && confirmAllPurchases) { - - $('body').addClass("code-injected"); - + if (commerceMode) { maybeAddLogInButton(); - changeDropdownMenu(); - var purchaseButton = $('#side-info').find('.btn').first(); + if (!$('body').hasClass("code-injected")) { - var href = purchaseButton.attr('href'); - purchaseButton.attr('href', '#'); - purchaseButton.css({ - "background": "linear-gradient(#00b4ef, #0093C5)", - "color": "#FFF", - "font-weight": "600", - "padding-bottom": "10px" - }); + $('body').addClass("code-injected"); + changeDropdownMenu(); - var cost = $('.item-cost').text(); + var purchaseButton = $('#side-info').find('.btn').first(); - if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { - purchaseButton.html('PURCHASE ' + cost); + var href = purchaseButton.attr('href'); + purchaseButton.attr('href', '#'); + purchaseButton.css({ + "background": "linear-gradient(#00b4ef, #0093C5)", + "color": "#FFF", + "font-weight": "600", + "padding-bottom": "10px" + }); + + var cost = $('.item-cost').text(); + + if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { + purchaseButton.html('PURCHASE ' + cost); + } + + purchaseButton.on('click', function () { + buyButtonClicked(window.location.pathname.split("/")[3], + $('#top-center').find('h1').text(), + $('#creator').find('.value').text(), + cost, + href); + }); + maybeAddPurchasesButton(); } - - purchaseButton.on('click', function () { - buyButtonClicked(window.location.pathname.split("/")[3], - $('#top-center').find('h1').text(), - $('#creator').find('.value').text(), - cost, - href); - }); - maybeAddPurchasesButton(); } } @@ -622,7 +629,7 @@ if (parsedJsonMessage.type === "marketplaces") { if (parsedJsonMessage.action === "commerceSetting") { - confirmAllPurchases = !!parsedJsonMessage.data.commerceMode; + commerceMode = !!parsedJsonMessage.data.commerceMode; userIsLoggedIn = !!parsedJsonMessage.data.userIsLoggedIn; walletNeedsSetup = !!parsedJsonMessage.data.walletNeedsSetup; injectCode(); diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 4df25c41b7..d36b3f28ee 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -45,11 +45,12 @@ SelectionManager = (function() { return; } + var wantDebug = false; var messageParsed; try { messageParsed = JSON.parse(message); } catch (err) { - print("error -- entitySelectionTool got malformed message: " + message); + print("ERROR: entitySelectionTool.handleEntitySelectionToolUpdates - got malformed message: " + message); } // if (message === 'callUpdate') { @@ -57,7 +58,9 @@ SelectionManager = (function() { // } if (messageParsed.method === "selectEntity") { - print("setting selection to " + messageParsed.entityID); + if (wantDebug) { + print("setting selection to " + messageParsed.entityID); + } that.setSelections([messageParsed.entityID]); } } @@ -137,12 +140,12 @@ SelectionManager = (function() { if (entityID) { var idx = -1; for (var i = 0; i < that.selections.length; i++) { - if (entityID == that.selections[i]) { + if (entityID === that.selections[i]) { idx = i; break; } } - if (idx == -1) { + if (idx === -1) { that.selections.push(entityID); } else if (toggleSelection) { that.selections.splice(idx, 1); @@ -172,7 +175,7 @@ SelectionManager = (function() { that.localPosition = null; that.worldDimensions = null; that.worldPosition = null; - } else if (that.selections.length == 1) { + } else if (that.selections.length === 1) { properties = Entities.getEntityProperties(that.selections[0]); that.localDimensions = properties.dimensions; that.localPosition = properties.position; @@ -214,7 +217,7 @@ SelectionManager = (function() { that.worldPosition = { x: brn.x + (that.worldDimensions.x / 2), y: brn.y + (that.worldDimensions.y / 2), - z: brn.z + (that.worldDimensions.z / 2), + z: brn.z + (that.worldDimensions.z / 2) }; // For 1+ selections we can only modify selections in world space @@ -225,7 +228,7 @@ SelectionManager = (function() { try { listeners[j](selectionUpdated === true); } catch (e) { - print("EntitySelectionTool got exception: " + JSON.stringify(e)); + print("ERROR: entitySelectionTool.update got exception: " + JSON.stringify(e)); } } }; @@ -235,8 +238,11 @@ SelectionManager = (function() { // Normalize degrees to be in the range (-180, 180] function normalizeDegrees(degrees) { - while (degrees > 180) degrees -= 360; - while (degrees <= -180) degrees += 360; + degrees = ((degrees + 180) % 360) - 180; + if (degrees <= -180) { + degrees += 360; + } + return degrees; } @@ -272,7 +278,6 @@ SelectionDisplay = (function() { var showExtendedStretchHandles = false; var spaceMode = SPACE_LOCAL; - var mode = "UNKNOWN"; var overlayNames = []; var lastCameraPosition = Camera.getPosition(); var lastCameraOrientation = Camera.getOrientation(); @@ -288,7 +293,6 @@ SelectionDisplay = (function() { }; var handleHoverAlpha = 1.0; - var rotateOverlayTargetSize = 10000; // really big target var innerSnapAngle = 22.5; // the angle which we snap to on the inner rotation tool var innerRadius; var outerRadius; @@ -298,19 +302,9 @@ SelectionDisplay = (function() { var yawCenter; var pitchCenter; var rollCenter; - var yawZero; - var pitchZero; - var rollZero; - var yawNormal; - var pitchNormal; - var rollNormal; + var rotZero; var rotationNormal; - var originalRotation; - var originalPitch; - var originalYaw; - var originalRoll; - var handleColor = { red: 255, @@ -382,7 +376,7 @@ SelectionDisplay = (function() { dashed: false, lineWidth: grabberLineWidth, drawInFront: true, - borderSize: 1.4, + borderSize: 1.4 }; var grabberPropertiesEdge = { @@ -399,7 +393,7 @@ SelectionDisplay = (function() { dashed: false, lineWidth: grabberLineWidth, drawInFront: true, - borderSize: 1.4, + borderSize: 1.4 }; var grabberPropertiesFace = { @@ -416,7 +410,7 @@ SelectionDisplay = (function() { dashed: false, lineWidth: grabberLineWidth, drawInFront: true, - borderSize: 1.4, + borderSize: 1.4 }; var grabberPropertiesCloner = { @@ -433,12 +427,12 @@ SelectionDisplay = (function() { dashed: false, lineWidth: grabberLineWidth, drawInFront: true, - borderSize: 1.4, + borderSize: 1.4 }; var spotLightLineProperties = { color: lightOverlayColor, - lineWidth: 1.5, + lineWidth: 1.5 }; var highlightBox = Overlays.addOverlay("cube", { @@ -478,7 +472,7 @@ SelectionDisplay = (function() { solid: false, visible: false, dashed: false, - lineWidth: 1.0, + lineWidth: 1.0 }); var selectionBoxes = []; @@ -514,7 +508,7 @@ SelectionDisplay = (function() { topMargin: 0, rightMargin: 0, bottomMargin: 0, - leftMargin: 0, + leftMargin: 0 }); var grabberMoveUp = Overlays.addOverlay("image3d", { @@ -530,7 +524,7 @@ SelectionDisplay = (function() { size: 0.1, scale: 0.1, isFacingAvatar: true, - drawInFront: true, + drawInFront: true }); // var normalLine = Overlays.addOverlay("line3d", { @@ -588,6 +582,12 @@ SelectionDisplay = (function() { var grabberSpotLightT = Overlays.addOverlay("cube", grabberPropertiesEdge); var grabberSpotLightB = Overlays.addOverlay("cube", grabberPropertiesEdge); + var spotLightGrabberHandles = [ + grabberSpotLightCircle, grabberSpotLightCenter, grabberSpotLightRadius, + grabberSpotLightLineT, grabberSpotLightLineB, grabberSpotLightLineL, grabberSpotLightLineR, + grabberSpotLightT, grabberSpotLightB, grabberSpotLightL, grabberSpotLightR + ]; + var grabberPointLightCircleX = Overlays.addOverlay("circle3d", { rotation: Quat.fromPitchYawRollDegrees(0, 90, 0), color: lightOverlayColor, @@ -613,6 +613,12 @@ SelectionDisplay = (function() { var grabberPointLightF = Overlays.addOverlay("cube", grabberPropertiesEdge); var grabberPointLightN = Overlays.addOverlay("cube", grabberPropertiesEdge); + var pointLightGrabberHandles = [ + grabberPointLightCircleX, grabberPointLightCircleY, grabberPointLightCircleZ, + grabberPointLightT, grabberPointLightB, grabberPointLightL, + grabberPointLightR, grabberPointLightF, grabberPointLightN + ]; + var grabberCloner = Overlays.addOverlay("cube", grabberPropertiesCloner); var stretchHandles = [ @@ -689,7 +695,7 @@ SelectionDisplay = (function() { width: 300, height: 200, rotation: baseOverlayRotation, - ignoreRayIntersection: true, // always ignore this + ignoreRayIntersection: true // always ignore this }); var yawOverlayAngles = { @@ -790,7 +796,7 @@ SelectionDisplay = (function() { green: 0, blue: 0 }, - ignoreRayIntersection: true, // always ignore this + ignoreRayIntersection: true // always ignore this }); var rotateCurrentOverlay = Overlays.addOverlay("line3d", { @@ -811,28 +817,10 @@ SelectionDisplay = (function() { green: 0, blue: 255 }, - ignoreRayIntersection: true, // always ignore this + ignoreRayIntersection: true // always ignore this }); - var rotateOverlayTarget = Overlays.addOverlay("circle3d", { - position: { - x: 0, - y: 0, - z: 0 - }, - size: rotateOverlayTargetSize, - color: { - red: 0, - green: 0, - blue: 0 - }, - alpha: 0.0, - solid: true, - visible: false, - rotation: yawOverlayRotation, - }); - var rotateOverlayInner = Overlays.addOverlay("circle3d", { position: { x: 0, @@ -864,7 +852,7 @@ SelectionDisplay = (function() { green: 0, blue: 0 }, - ignoreRayIntersection: true, // always ignore this + ignoreRayIntersection: true // always ignore this }); var rotateOverlayOuter = Overlays.addOverlay("circle3d", { @@ -899,7 +887,7 @@ SelectionDisplay = (function() { green: 0, blue: 0 }, - ignoreRayIntersection: true, // always ignore this + ignoreRayIntersection: true // always ignore this }); var rotateOverlayCurrent = Overlays.addOverlay("circle3d", { @@ -929,7 +917,7 @@ SelectionDisplay = (function() { red: 0, green: 0, blue: 0 - }, + } }); var yawHandle = Overlays.addOverlay("image3d", { @@ -945,7 +933,7 @@ SelectionDisplay = (function() { size: 0.1, scale: 0.1, isFacingAvatar: false, - drawInFront: true, + drawInFront: true }); @@ -962,7 +950,7 @@ SelectionDisplay = (function() { size: 0.1, scale: 0.1, isFacingAvatar: false, - drawInFront: true, + drawInFront: true }); @@ -979,7 +967,7 @@ SelectionDisplay = (function() { size: 0.1, scale: 0.1, isFacingAvatar: false, - drawInFront: true, + drawInFront: true }); var allOverlays = [ @@ -989,7 +977,6 @@ SelectionDisplay = (function() { yawHandle, pitchHandle, rollHandle, - rotateOverlayTarget, rotateOverlayInner, rotateOverlayOuter, rotateOverlayCurrent, @@ -1003,7 +990,7 @@ SelectionDisplay = (function() { grabberSpotLightCircle, grabberPointLightCircleX, grabberPointLightCircleY, - grabberPointLightCircleZ, + grabberPointLightCircleZ ].concat(stretchHandles); @@ -1044,7 +1031,6 @@ SelectionDisplay = (function() { overlayNames[pitchHandle] = "pitchHandle"; overlayNames[rollHandle] = "rollHandle"; - overlayNames[rotateOverlayTarget] = "rotateOverlayTarget"; overlayNames[rotateOverlayInner] = "rotateOverlayInner"; overlayNames[rotateOverlayOuter] = "rotateOverlayOuter"; overlayNames[rotateOverlayCurrent] = "rotateOverlayCurrent"; @@ -1059,7 +1045,7 @@ SelectionDisplay = (function() { // But we dont' get mousePressEvents. that.triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); Script.scriptEnding.connect(that.triggerMapping.disable); - that.TRIGGER_GRAB_VALUE = 0.85; // From handControllerGrab/Pointer.js. Should refactor. + that.TRIGGER_GRAB_VALUE = 0.85; // From handControllerGrab/Pointer.js. Should refactor. that.TRIGGER_ON_VALUE = 0.4; that.TRIGGER_OFF_VALUE = 0.15; that.triggered = false; @@ -1100,12 +1086,37 @@ SelectionDisplay = (function() { return controllerComputePickRay() || Camera.computePickRay(x, y); } function addGrabberTool(overlay, tool) { - grabberTools[overlay] = { - mode: tool.mode, - onBegin: tool.onBegin, - onMove: tool.onMove, - onEnd: tool.onEnd, - }; + grabberTools[overlay] = tool; + return tool; + } + + // @param: toolHandle: The overlayID associated with the tool + // that correlates to the tool you wish to query. + // @note: If toolHandle is null or undefined then activeTool + // will be checked against those values as opposed to + // the tool registered under toolHandle. Null & Undefined + // are treated as separate values. + // @return: bool - Indicates if the activeTool is that queried. + function isActiveTool(toolHandle) { + if (!toolHandle) { + // Allow isActiveTool(null) and similar to return true if there's + // no active tool + return (activeTool === toolHandle); + } + + if (!grabberTools.hasOwnProperty(toolHandle)) { + print("WARNING: entitySelectionTool.isActiveTool - Encountered unknown grabberToolHandle: " + toolHandle + ". Tools should be egistered via addGrabberTool."); + // EARLY EXIT + return false; + } + + return (activeTool === grabberTools[ toolHandle ]); + } + + // @return string - The mode of the currently active tool; + // otherwise, "UNKNOWN" if there's no active tool. + function getMode() { + return (activeTool ? activeTool.mode : "UNKNOWN"); } @@ -1162,8 +1173,8 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE ROTATION HANDLES that.updateRotationHandles = function() { - var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; - var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); + var diagonal = (Vec3.length(SelectionManager.worldDimensions) / 2) * 1.1; + var halfDimensions = Vec3.multiply(SelectionManager.worldDimensions, 0.5); var innerActive = false; var innerAlpha = 0.2; var outerAlpha = 0.2; @@ -1178,7 +1189,7 @@ SelectionDisplay = (function() { var top, far, left, bottom, near, right, boundsCenter, objectCenter, BLN, BRN, BLF, TLN, TRN, TLF, TRF; var dimensions, rotation; - if (spaceMode == SPACE_LOCAL) { + if (spaceMode === SPACE_LOCAL) { rotation = SelectionManager.localRotation; } else { rotation = SelectionManager.worldRotation; @@ -1235,22 +1246,6 @@ SelectionDisplay = (function() { z: 0 }); - yawNormal = { - x: 0, - y: 1, - z: 0 - }; - pitchNormal = { - x: 1, - y: 0, - z: 0 - }; - rollNormal = { - x: 0, - y: 0, - z: 1 - }; - yawCorner = { x: left + rotateHandleOffset, y: bottom - rotateHandleOffset, @@ -1312,23 +1307,6 @@ SelectionDisplay = (function() { z: 90 }); - yawNormal = { - x: 0, - y: 1, - z: 0 - }; - pitchNormal = { - x: 1, - y: 0, - z: 0 - }; - rollNormal = { - x: 0, - y: 0, - z: 1 - }; - - yawCorner = { x: left + rotateHandleOffset, y: bottom - rotateHandleOffset, @@ -1392,22 +1370,6 @@ SelectionDisplay = (function() { z: 180 }); - yawNormal = { - x: 0, - y: 1, - z: 0 - }; - pitchNormal = { - x: 1, - y: 0, - z: 0 - }; - rollNormal = { - x: 0, - y: 0, - z: 1 - }; - yawCorner = { x: right - rotateHandleOffset, y: bottom - rotateHandleOffset, @@ -1467,22 +1429,6 @@ SelectionDisplay = (function() { z: 180 }); - yawNormal = { - x: 0, - y: 1, - z: 0 - }; - rollNormal = { - x: 0, - y: 0, - z: 1 - }; - pitchNormal = { - x: 1, - y: 0, - z: 0 - }; - yawCorner = { x: right - rotateHandleOffset, y: bottom - rotateHandleOffset, @@ -1529,34 +1475,31 @@ SelectionDisplay = (function() { var rotateHandlesVisible = true; var rotationOverlaysVisible = false; - var translateHandlesVisible = true; - var stretchHandlesVisible = true; - var selectionBoxVisible = true; + // note: Commented out as these are currently unused here; however, + // leaving them around as they document intent of state as it + // relates to modes that may be useful later. + // var translateHandlesVisible = true; + // var selectionBoxVisible = true; var isPointLight = false; - - if (selectionManager.selections.length == 1) { - var properties = Entities.getEntityProperties(selectionManager.selections[0]); - isPointLight = properties.type == "Light" && !properties.isSpotlight; + if (SelectionManager.selections.length === 1) { + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); + isPointLight = (properties.type === "Light") && !properties.isSpotlight; } - if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL" || mode == "TRANSLATE_X in case they Z") { + if (isActiveTool(yawHandle) || isActiveTool(pitchHandle) || + isActiveTool(rollHandle) || isActiveTool(selectionBox) || isActiveTool(grabberCloner)) { rotationOverlaysVisible = true; rotateHandlesVisible = false; - translateHandlesVisible = false; - stretchHandlesVisible = false; - selectionBoxVisible = false; - } else if (mode == "TRANSLATE_UP_DOWN" || isPointLight) { + // translateHandlesVisible = false; + // selectionBoxVisible = false; + } else if (isActiveTool(grabberMoveUp) || isPointLight) { rotateHandlesVisible = false; - stretchHandlesVisible = false; - } else if (mode != "UNKNOWN") { + } else if (activeTool) { // every other mode is a stretch mode... rotateHandlesVisible = false; - translateHandlesVisible = false; + // translateHandlesVisible = false; } - Overlays.editOverlay(rotateOverlayTarget, { - visible: rotationOverlaysVisible - }); Overlays.editOverlay(rotateZeroOverlay, { visible: rotationOverlaysVisible }); @@ -1581,22 +1524,87 @@ SelectionDisplay = (function() { }); }; + // FUNCTION: UPDATE HANDLE SIZES + that.updateHandleSizes = function() { + if (SelectionManager.hasSelection()) { + var diff = Vec3.subtract(SelectionManager.worldPosition, Camera.getPosition()); + var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 5; + var dimensions = SelectionManager.worldDimensions; + var avgDimension = (dimensions.x + dimensions.y + dimensions.z) / 3; + grabberSize = Math.min(grabberSize, avgDimension / 10); + + for (var i = 0; i < stretchHandles.length; i++) { + Overlays.editOverlay(stretchHandles[i], { + size: grabberSize + }); + } + var handleSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 7; + handleSize = Math.min(handleSize, avgDimension / 3); + + Overlays.editOverlay(yawHandle, { + scale: handleSize + }); + Overlays.editOverlay(pitchHandle, { + scale: handleSize + }); + Overlays.editOverlay(rollHandle, { + scale: handleSize + }); + var pos = Vec3.sum(grabberMoveUpPosition, { + x: 0, + y: Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 3, + z: 0 + }); + Overlays.editOverlay(grabberMoveUp, { + position: pos, + scale: handleSize / 1.25 + }); + } + }; + Script.update.connect(that.updateHandleSizes); + // FUNCTION: SET SPACE MODE that.setSpaceMode = function(newSpaceMode) { - if (spaceMode != newSpaceMode) { + var wantDebug = false; + if (wantDebug) { + print("======> SetSpaceMode called. ========"); + } + + if (spaceMode !== newSpaceMode) { + if (wantDebug) { + print(" Updating SpaceMode From: " + spaceMode + " To: " + newSpaceMode); + } spaceMode = newSpaceMode; that.updateHandles(); + } else if (wantDebug) { + print("WARNING: entitySelectionTool.setSpaceMode - Can't update SpaceMode. CurrentMode: " + spaceMode + " DesiredMode: " + newSpaceMode); + } + if (wantDebug) { + print("====== SetSpaceMode called. <========"); } }; // FUNCTION: TOGGLE SPACE MODE that.toggleSpaceMode = function() { - if (spaceMode == SPACE_WORLD && SelectionManager.selections.length > 1) { - print("Local space editing is not available with multiple selections"); + var wantDebug = false; + if (wantDebug) { + print("========> ToggleSpaceMode called. ========="); + } + if ((spaceMode === SPACE_WORLD) && (SelectionManager.selections.length > 1)) { + if (wantDebug) { + print("Local space editing is not available with multiple selections"); + } return; } - spaceMode = spaceMode == SPACE_LOCAL ? SPACE_WORLD : SPACE_LOCAL; + if (wantDebug) { + print("PreToggle: " + spaceMode); + } + spaceMode = (spaceMode === SPACE_LOCAL) ? SPACE_WORLD : SPACE_LOCAL; that.updateHandles(); + if (wantDebug) { + print("PostToggle: " + spaceMode); + print("======== ToggleSpaceMode called. <========="); + } }; // FUNCTION: UNSELECT ALL @@ -1605,16 +1613,24 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE HANDLES that.updateHandles = function() { + var wantDebug = false; + if (wantDebug) { + print("======> Update Handles ======="); + print(" Selections Count: " + SelectionManager.selections.length); + print(" SpaceMode: " + spaceMode); + print(" DisplayMode: " + getMode()); + } if (SelectionManager.selections.length === 0) { that.setOverlaysVisible(false); return; } + // print(" Triggering updateRotationHandles"); that.updateRotationHandles(); var rotation, dimensions, position, registrationPoint; - if (spaceMode == SPACE_LOCAL) { + if (spaceMode === SPACE_LOCAL) { rotation = SelectionManager.localRotation; dimensions = SelectionManager.localDimensions; position = SelectionManager.localPosition; @@ -1629,11 +1645,11 @@ SelectionDisplay = (function() { var registrationPointDimensions = { x: dimensions.x * registrationPoint.x, y: dimensions.y * registrationPoint.y, - z: dimensions.z * registrationPoint.z, + z: dimensions.z * registrationPoint.z }; // Center of entity, relative to registration point - center = getRelativeCenterPosition(dimensions, registrationPoint); + var center = getRelativeCenterPosition(dimensions, registrationPoint); // Distances in world coordinates relative to the registration point var left = -registrationPointDimensions.x; @@ -1837,265 +1853,164 @@ SelectionDisplay = (function() { EdgeFR = Vec3.sum(position, EdgeFR); EdgeFL = Vec3.sum(position, EdgeFL); - var stretchHandlesVisible = spaceMode == SPACE_LOCAL; - var extendedStretchHandlesVisible = stretchHandlesVisible && showExtendedStretchHandles; - - if (selectionManager.selections.length == 1) { - var properties = Entities.getEntityProperties(selectionManager.selections[0]); - if (properties.type == "Light" && properties.isSpotlight) { - stretchHandlesVisible = false; - extendedStretchHandlesVisible = false; - - Overlays.editOverlay(grabberSpotLightCenter, { - position: position, - visible: false, - }); - Overlays.editOverlay(grabberSpotLightRadius, { - position: NEAR, - rotation: rotation, - visible: true, - }); - var distance = (properties.dimensions.z / 2) * Math.sin(properties.cutoff * (Math.PI / 180)); - - Overlays.editOverlay(grabberSpotLightL, { - position: EdgeNL, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightR, { - position: EdgeNR, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightT, { - position: EdgeTN, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightB, { - position: EdgeBN, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightCircle, { - position: NEAR, - dimensions: { - x: distance, - y: distance, - z: 1 - }, - lineWidth: 1.5, - rotation: rotation, - visible: true, - }); - - Overlays.editOverlay(grabberSpotLightLineT, { - start: position, - end: EdgeTN, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightLineB, { - start: position, - end: EdgeBN, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightLineR, { - start: position, - end: EdgeNR, - visible: true, - }); - Overlays.editOverlay(grabberSpotLightLineL, { - start: position, - end: EdgeNL, - visible: true, - }); - - Overlays.editOverlay(grabberPointLightCircleX, { - visible: false - }); - Overlays.editOverlay(grabberPointLightCircleY, { - visible: false - }); - Overlays.editOverlay(grabberPointLightCircleZ, { - visible: false - }); - Overlays.editOverlay(grabberPointLightT, { - visible: false - }); - Overlays.editOverlay(grabberPointLightB, { - visible: false - }); - Overlays.editOverlay(grabberPointLightL, { - visible: false - }); - Overlays.editOverlay(grabberPointLightR, { - visible: false - }); - Overlays.editOverlay(grabberPointLightF, { - visible: false - }); - Overlays.editOverlay(grabberPointLightN, { - visible: false - }); - } else if (properties.type == "Light" && !properties.isSpotlight) { - stretchHandlesVisible = false; - extendedStretchHandlesVisible = false; - Overlays.editOverlay(grabberPointLightT, { - position: TOP, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberPointLightB, { - position: BOTTOM, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberPointLightL, { - position: LEFT, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberPointLightR, { - position: RIGHT, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberPointLightF, { - position: FAR, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberPointLightN, { - position: NEAR, - rotation: rotation, - visible: true, - }); - Overlays.editOverlay(grabberPointLightCircleX, { - position: position, - rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, 90, 0)), - dimensions: { - x: properties.dimensions.z / 2.0, - y: properties.dimensions.z / 2.0, - z: 1 - }, - visible: true, - }); - Overlays.editOverlay(grabberPointLightCircleY, { - position: position, - rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(90, 0, 0)), - dimensions: { - x: properties.dimensions.z / 2.0, - y: properties.dimensions.z / 2.0, - z: 1 - }, - visible: true, - }); - Overlays.editOverlay(grabberPointLightCircleZ, { - position: position, - rotation: rotation, - dimensions: { - x: properties.dimensions.z / 2.0, - y: properties.dimensions.z / 2.0, - z: 1 - }, - visible: true, - }); - - Overlays.editOverlay(grabberSpotLightRadius, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightL, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightR, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightT, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightB, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightCircle, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineL, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineR, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineT, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineB, { - visible: false - }); - } else { - Overlays.editOverlay(grabberSpotLightCenter, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightRadius, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightL, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightR, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightT, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightB, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightCircle, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineL, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineR, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineT, { - visible: false - }); - Overlays.editOverlay(grabberSpotLightLineB, { - visible: false - }); - - Overlays.editOverlay(grabberPointLightCircleX, { - visible: false - }); - Overlays.editOverlay(grabberPointLightCircleY, { - visible: false - }); - Overlays.editOverlay(grabberPointLightCircleZ, { - visible: false - }); - Overlays.editOverlay(grabberPointLightT, { - visible: false - }); - Overlays.editOverlay(grabberPointLightB, { - visible: false - }); - Overlays.editOverlay(grabberPointLightL, { - visible: false - }); - Overlays.editOverlay(grabberPointLightR, { - visible: false - }); - Overlays.editOverlay(grabberPointLightF, { - visible: false - }); - Overlays.editOverlay(grabberPointLightN, { - visible: false - }); - } + var inModeRotate = (isActiveTool(yawHandle) || isActiveTool(pitchHandle) || isActiveTool(rollHandle)); + var inModeTranslate = (isActiveTool(selectionBox) || isActiveTool(grabberCloner) || isActiveTool(grabberMoveUp)); + var stretchHandlesVisible = !(inModeRotate || inModeTranslate) && (spaceMode === SPACE_LOCAL); + var extendedStretchHandlesVisible = (stretchHandlesVisible && showExtendedStretchHandles); + var cloneHandleVisible = !(inModeRotate || inModeTranslate); + if (wantDebug) { + print(" Set Non-Light Grabbers Visible - Norm: " + stretchHandlesVisible + " Ext: " + extendedStretchHandlesVisible); } + var isSingleSelection = (SelectionManager.selections.length === 1); + if (isSingleSelection) { + var properties = Entities.getEntityProperties(SelectionManager.selections[0]); + var isLightSelection = (properties.type === "Light"); + if (isLightSelection) { + if (wantDebug) { + print(" Light Selection revoking Non-Light Grabbers Visibility!"); + } + stretchHandlesVisible = false; + extendedStretchHandlesVisible = false; + cloneHandleVisible = false; + if (properties.isSpotlight) { + that.setPointLightHandlesVisible(false); + + var distance = (properties.dimensions.z / 2) * Math.sin(properties.cutoff * (Math.PI / 180)); + var showEdgeSpotGrabbers = !(inModeTranslate || inModeRotate); + Overlays.editOverlay(grabberSpotLightCenter, { + position: position, + visible: false + }); + Overlays.editOverlay(grabberSpotLightRadius, { + position: NEAR, + rotation: rotation, + visible: showEdgeSpotGrabbers + }); + + Overlays.editOverlay(grabberSpotLightL, { + position: EdgeNL, + rotation: rotation, + visible: showEdgeSpotGrabbers + }); + Overlays.editOverlay(grabberSpotLightR, { + position: EdgeNR, + rotation: rotation, + visible: showEdgeSpotGrabbers + }); + Overlays.editOverlay(grabberSpotLightT, { + position: EdgeTN, + rotation: rotation, + visible: showEdgeSpotGrabbers + }); + Overlays.editOverlay(grabberSpotLightB, { + position: EdgeBN, + rotation: rotation, + visible: showEdgeSpotGrabbers + }); + Overlays.editOverlay(grabberSpotLightCircle, { + position: NEAR, + dimensions: { + x: distance, + y: distance, + z: 1 + }, + lineWidth: 1.5, + rotation: rotation, + visible: true + }); + + Overlays.editOverlay(grabberSpotLightLineT, { + start: position, + end: EdgeTN, + visible: true + }); + Overlays.editOverlay(grabberSpotLightLineB, { + start: position, + end: EdgeBN, + visible: true + }); + Overlays.editOverlay(grabberSpotLightLineR, { + start: position, + end: EdgeNR, + visible: true + }); + Overlays.editOverlay(grabberSpotLightLineL, { + start: position, + end: EdgeNL, + visible: true + }); + + } else { // ..it's a PointLight + that.setSpotLightHandlesVisible(false); + + var showEdgePointGrabbers = !inModeTranslate; + Overlays.editOverlay(grabberPointLightT, { + position: TOP, + rotation: rotation, + visible: showEdgePointGrabbers + }); + Overlays.editOverlay(grabberPointLightB, { + position: BOTTOM, + rotation: rotation, + visible: showEdgePointGrabbers + }); + Overlays.editOverlay(grabberPointLightL, { + position: LEFT, + rotation: rotation, + visible: showEdgePointGrabbers + }); + Overlays.editOverlay(grabberPointLightR, { + position: RIGHT, + rotation: rotation, + visible: showEdgePointGrabbers + }); + Overlays.editOverlay(grabberPointLightF, { + position: FAR, + rotation: rotation, + visible: showEdgePointGrabbers + }); + Overlays.editOverlay(grabberPointLightN, { + position: NEAR, + rotation: rotation, + visible: showEdgePointGrabbers + }); + Overlays.editOverlay(grabberPointLightCircleX, { + position: position, + rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, 90, 0)), + dimensions: { + x: properties.dimensions.z / 2.0, + y: properties.dimensions.z / 2.0, + z: 1 + }, + visible: true + }); + Overlays.editOverlay(grabberPointLightCircleY, { + position: position, + rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(90, 0, 0)), + dimensions: { + x: properties.dimensions.z / 2.0, + y: properties.dimensions.z / 2.0, + z: 1 + }, + visible: true + }); + Overlays.editOverlay(grabberPointLightCircleZ, { + position: position, + rotation: rotation, + dimensions: { + x: properties.dimensions.z / 2.0, + y: properties.dimensions.z / 2.0, + z: 1 + }, + visible: true + }); + } + } else { // ..it's not a light at all + that.setSpotLightHandlesVisible(false); + that.setPointLightHandlesVisible(false); + } + }// end of isSingleSelection Overlays.editOverlay(grabberLBN, { @@ -2172,7 +2087,7 @@ SelectionDisplay = (function() { }); Overlays.editOverlay(grabberCloner, { - visible: true, + visible: cloneHandleVisible, rotation: rotation, position: EdgeTR }); @@ -2183,11 +2098,11 @@ SelectionDisplay = (function() { position: selectionBoxPosition, dimensions: dimensions, rotation: rotation, - visible: !(mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL"), + visible: !inModeRotate }); // Create more selection box overlays if we don't have enough - var overlaysNeeded = selectionManager.selections.length - selectionBoxes.length; + var overlaysNeeded = SelectionManager.selections.length - selectionBoxes.length; for (var i = 0; i < overlaysNeeded; i++) { selectionBoxes.push( Overlays.addOverlay("cube", { @@ -2207,15 +2122,15 @@ SelectionDisplay = (function() { visible: false, dashed: false, lineWidth: 1.0, - ignoreRayIntersection: true, + ignoreRayIntersection: true })); } i = 0; // Only show individual selections boxes if there is more than 1 selection - if (selectionManager.selections.length > 1) { - for (; i < selectionManager.selections.length; i++) { - var props = Entities.getEntityProperties(selectionManager.selections[i]); + if (SelectionManager.selections.length > 1) { + for (; i < SelectionManager.selections.length; i++) { + var props = Entities.getEntityProperties(SelectionManager.selections[i]); // Adjust overlay position to take registrationPoint into account // centeredRP = registrationPoint with range [-0.5, 0.5] @@ -2230,14 +2145,16 @@ SelectionDisplay = (function() { var curBoxPosition = Vec3.sum(props.position, offset); var color = {red: 255, green: 128, blue: 0}; - if (i >= selectionManager.selections.length - 1) color = {red: 255, green: 255, blue: 64}; + if (i >= SelectionManager.selections.length - 1) { + color = {red: 255, green: 255, blue: 64}; + } Overlays.editOverlay(selectionBoxes[i], { position: curBoxPosition, color: color, rotation: props.rotation, dimensions: props.dimensions, - visible: true, + visible: true }); } } @@ -2316,42 +2233,85 @@ SelectionDisplay = (function() { z: position.z }; Overlays.editOverlay(grabberMoveUp, { - visible: (activeTool === null) || (mode == "TRANSLATE_UP_DOWN") + visible: (!activeTool) || isActiveTool(grabberMoveUp) }); Overlays.editOverlay(baseOfEntityProjectionOverlay, { - visible: mode != "ROTATE_YAW" && mode != "ROTATE_PITCH" && mode != "ROTATE_ROLL", + visible: !inModeRotate, solid: true, position: { - x: selectionManager.worldPosition.x, + x: SelectionManager.worldPosition.x, y: grid.getOrigin().y, - z: selectionManager.worldPosition.z + z: SelectionManager.worldPosition.z }, dimensions: { - x: selectionManager.worldDimensions.x, - y: selectionManager.worldDimensions.z + x: SelectionManager.worldDimensions.x, + y: SelectionManager.worldDimensions.z }, - rotation: Quat.fromPitchYawRollDegrees(90, 0, 0), + rotation: Quat.fromPitchYawRollDegrees(90, 0, 0) }); + if (wantDebug) { + print("====== Update Handles <======="); + } + }; + function helperSetOverlaysVisibility(handleArray, isVisible) { + var numHandles = handleArray.length; + var visibilityUpdate = { visible: isVisible }; + for (var handleIndex = 0; handleIndex < numHandles; ++handleIndex) { + Overlays.editOverlay(handleArray[ handleIndex ], visibilityUpdate); + } + } + // FUNCTION: SET OVERLAYS VISIBLE that.setOverlaysVisible = function(isVisible) { - var length = allOverlays.length; - for (var overlayIndex = 0; overlayIndex < length; overlayIndex++) { - Overlays.editOverlay(allOverlays[overlayIndex], { - visible: isVisible - }); - } - length = selectionBoxes.length; - for (var boxIndex = 0; boxIndex < length; boxIndex++) { - Overlays.editOverlay(selectionBoxes[boxIndex], { - visible: isVisible - }); + helperSetOverlaysVisibility(allOverlays, isVisible); + helperSetOverlaysVisibility(selectionBoxes, isVisible); + }; + + // FUNCTION: SET ROTATION HANDLES VISIBLE + that.setRotationHandlesVisible = function(isVisible) { + var visibilityUpdate = { visible: isVisible }; + Overlays.editOverlay(yawHandle, visibilityUpdate); + Overlays.editOverlay(pitchHandle, visibilityUpdate); + Overlays.editOverlay(rollHandle, visibilityUpdate); + }; + + // FUNCTION: SET STRETCH HANDLES VISIBLE + that.setStretchHandlesVisible = function(isVisible) { + helperSetOverlaysVisibility(stretchHandles, isVisible); + }; + + // FUNCTION: SET GRABBER MOVE UP VISIBLE + that.setGrabberMoveUpVisible = function(isVisible) { + Overlays.editOverlay(grabberMoveUp, { visible: isVisible }); + }; + + // FUNCTION: SET GRABBER TOOLS UP VISIBLE + that.setGrabberToolsVisible = function(isVisible) { + var visibilityUpdate = { visible: isVisible }; + for (var toolKey in grabberTools) { + if (!grabberTools.hasOwnProperty(toolKey)) { + // EARLY ITERATION EXIT--(On to the next one) + continue; + } + + Overlays.editOverlay(toolKey, visibilityUpdate); } }; + // FUNCTION: SET POINT LIGHT HANDLES VISIBLE + that.setPointLightHandlesVisible = function(isVisible) { + helperSetOverlaysVisibility(pointLightGrabberHandles, isVisible); + }; + + // FUNCTION: SET SPOT LIGHT HANDLES VISIBLE + that.setSpotLightHandlesVisible = function(isVisible) { + helperSetOverlaysVisibility(spotLightGrabberHandles, isVisible); + }; + // FUNCTION: UNSELECT // TODO?: Needs implementation that.unselect = function(entityID) {}; @@ -2363,18 +2323,38 @@ SelectionDisplay = (function() { var duplicatedEntityIDs = null; // TOOL DEFINITION: TRANSLATE XZ TOOL - var translateXZTool = { + var translateXZTool = addGrabberTool(selectionBox,{ mode: 'TRANSLATE_XZ', pickPlanePosition: { x: 0, y: 0, z: 0 }, greatestDimension: 0.0, startingDistance: 0.0, startingElevation: 0.0, - onBegin: function(event,isAltFromGrab) { - SelectionManager.saveProperties(); - startPosition = SelectionManager.worldPosition; - var dimensions = SelectionManager.worldDimensions; + onBegin: function(event, pickRay, pickResult, doClone) { + var wantDebug = false; + if (wantDebug) { + print("================== TRANSLATE_XZ(Beg) -> ======================="); + Vec3.print(" pickRay", pickRay); + Vec3.print(" pickRay.origin", pickRay.origin); + Vec3.print(" pickResult.intersection", pickResult.intersection); + } + + SelectionManager.saveProperties(); + that.setRotationHandlesVisible(false); + that.setStretchHandlesVisible(false); + that.setGrabberMoveUpVisible(false); + + startPosition = SelectionManager.worldPosition; + + translateXZTool.pickPlanePosition = pickResult.intersection; + translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), SelectionManager.worldDimensions.z); + translateXZTool.startingDistance = Vec3.distance(pickRay.origin, SelectionManager.position); + translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition); + if (wantDebug) { + print(" longest dimension: " + translateXZTool.greatestDimension); + print(" starting distance: " + translateXZTool.startingDistance); + print(" starting elevation: " + translateXZTool.startingElevation); + } - var pickRay = generalComputePickRay(event.x, event.y); initialXZPick = rayPlaneIntersection(pickRay, translateXZTool.pickPlanePosition, { x: 0, y: 1, @@ -2384,7 +2364,7 @@ SelectionDisplay = (function() { // Duplicate entities if alt is pressed. This will make a // copy of the selected entities and move the _original_ entities, not // the new ones. - if (event.isAlt || isAltFromGrab) { + if (event.isAlt || doClone) { duplicatedEntityIDs = []; for (var otherEntityID in SelectionManager.savedProperties) { var properties = SelectionManager.savedProperties[otherEntityID]; @@ -2392,7 +2372,7 @@ SelectionDisplay = (function() { var entityID = Entities.addEntity(properties); duplicatedEntityIDs.push({ entityID: entityID, - properties: properties, + properties: properties }); } } @@ -2401,6 +2381,9 @@ SelectionDisplay = (function() { } isConstrained = false; + if (wantDebug) { + print("================== TRANSLATE_XZ(End) <- ======================="); + } }, onEnd: function(event, reason) { pushCommandForSelections(duplicatedEntityIDs); @@ -2429,29 +2412,33 @@ SelectionDisplay = (function() { // this will happen when someone drags across the horizon from the side they started on. if (!pick) { if (wantDebug) { - print("Pick ray does not intersect XZ plane."); + print(" "+ translateXZTool.mode + "Pick ray does not intersect XZ plane."); } + + // EARLY EXIT--(Invalid ray detected.) return; } var vector = Vec3.subtract(pick, initialXZPick); // If the mouse is too close to the horizon of the pick plane, stop moving - var MIN_ELEVATION = 0.02; // largest dimension of object divided by distance to it + var MIN_ELEVATION = 0.02; // largest dimension of object divided by distance to it var elevation = translateXZTool.elevation(pickRay.origin, pick); if (wantDebug) { - print("Start Elevation: " + translateXZTool.startingElevation + ", elevation: " + elevation); + print("Start Elevation: " + translateXZTool.startingElevation + ", elevation: " + elevation); } if ((translateXZTool.startingElevation > 0.0 && elevation < MIN_ELEVATION) || (translateXZTool.startingElevation < 0.0 && elevation > -MIN_ELEVATION)) { if (wantDebug) { - print("too close to horizon!"); + print(" "+ translateXZTool.mode + " - too close to horizon!"); } + + // EARLY EXIT--(Don't proceed past the reached limit.) return; } // If the angular size of the object is too small, stop moving - var MIN_ANGULAR_SIZE = 0.01; // Radians + var MIN_ANGULAR_SIZE = 0.01; // Radians if (translateXZTool.greatestDimension > 0) { var angularSize = Math.atan(translateXZTool.greatestDimension / Vec3.distance(pickRay.origin, pick)); if (wantDebug) { @@ -2518,13 +2505,12 @@ SelectionDisplay = (function() { } constrainMajorOnly = event.isControl; - var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(-0.5, selectionManager.worldDimensions)); + var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(-0.5, SelectionManager.worldDimensions)); vector = Vec3.subtract( grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), cornerPosition); - for (var i = 0; i < SelectionManager.selections.length; i++) { var properties = SelectionManager.savedProperties[SelectionManager.selections[i]]; if (!properties) { @@ -2536,7 +2522,7 @@ SelectionDisplay = (function() { z: vector.z }); Entities.editEntity(SelectionManager.selections[i], { - position: newPosition, + position: newPosition }); if (wantDebug) { @@ -2549,23 +2535,24 @@ SelectionDisplay = (function() { SelectionManager._update(); } - }; - + }); + // GRABBER TOOL: GRABBER MOVE UP var lastXYPick = null; var upDownPickNormal = null; addGrabberTool(grabberMoveUp, { mode: "TRANSLATE_UP_DOWN", - onBegin: function(event) { - pickRay = generalComputePickRay(event.x, event.y); - + onBegin: function(event, pickRay, pickResult) { upDownPickNormal = Quat.getForward(lastCameraOrientation); - // Remove y component so the y-axis lies along the plane we picking on - this will + // Remove y component so the y-axis lies along the plane we're picking on - this will // give movements that follow the mouse. upDownPickNormal.y = 0; lastXYPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, upDownPickNormal); SelectionManager.saveProperties(); + that.setGrabberMoveUpVisible(true); + that.setStretchHandlesVisible(false); + that.setRotationHandlesVisible(false); // Duplicate entities if alt is pressed. This will make a // copy of the selected entities and move the _original_ entities, not @@ -2578,7 +2565,7 @@ SelectionDisplay = (function() { var entityID = Entities.addEntity(properties); duplicatedEntityIDs.push({ entityID: entityID, - properties: properties, + properties: properties }); } } @@ -2608,36 +2595,30 @@ SelectionDisplay = (function() { print(" event.y:" + event.y); Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - //Vec3.print(" newPosition:", newPosition); + // Vec3.print(" newPosition:", newPosition); } for (var i = 0; i < SelectionManager.selections.length; i++) { var id = SelectionManager.selections[i]; - var properties = selectionManager.savedProperties[id]; + var properties = SelectionManager.savedProperties[id]; var original = properties.position; var newPosition = Vec3.sum(properties.position, vector); Entities.editEntity(id, { - position: newPosition, + position: newPosition }); } SelectionManager._update(); - }, + } }); // GRABBER TOOL: GRABBER CLONER addGrabberTool(grabberCloner, { mode: "CLONE", - onBegin: function(event) { - - var pickRay = generalComputePickRay(event.x, event.y); - var result = Overlays.findRayIntersection(pickRay); - translateXZTool.pickPlanePosition = result.intersection; - translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), - SelectionManager.worldDimensions.z); - - translateXZTool.onBegin(event,true); + onBegin: function(event, pickRay, pickResult) { + var doClone = true; + translateXZTool.onBegin(event,pickRay,pickResult,doClone); }, elevation: function (event) { translateXZTool.elevation(event); @@ -2653,14 +2634,13 @@ SelectionDisplay = (function() { }); - // FUNCTION: VEC 3 MULT var vec3Mult = function(v1, v2) { - return { - x: v1.x * v2.x, - y: v1.y * v2.y, - z: v1.z * v2.z - }; + return { + x: v1.x * v2.x, + y: v1.y * v2.y, + z: v1.z * v2.z + }; }; // FUNCTION: MAKE STRETCH TOOL @@ -2668,6 +2648,7 @@ SelectionDisplay = (function() { // direction - direction to stretch in // pivot - point to use as a pivot // offset - the position of the overlay tool relative to the selections center position + // @return: tool obj var makeStretchTool = function(stretchMode, direction, pivot, offset, customOnMove) { // directionFor3DStretch - direction and pivot for 3D stretch // distanceFor3DStretch - distance from the intersection point and the handController @@ -2682,16 +2663,15 @@ SelectionDisplay = (function() { var signs = { x: direction.x < 0 ? -1 : (direction.x > 0 ? 1 : 0), y: direction.y < 0 ? -1 : (direction.y > 0 ? 1 : 0), - z: direction.z < 0 ? -1 : (direction.z > 0 ? 1 : 0), + z: direction.z < 0 ? -1 : (direction.z > 0 ? 1 : 0) }; var mask = { x: Math.abs(direction.x) > 0 ? 1 : 0, y: Math.abs(direction.y) > 0 ? 1 : 0, - z: Math.abs(direction.z) > 0 ? 1 : 0, + z: Math.abs(direction.z) > 0 ? 1 : 0 }; - - + var numDimensions = mask.x + mask.y + mask.z; @@ -2709,12 +2689,12 @@ SelectionDisplay = (function() { var pickRayPosition3D = null; var rotation = null; - var onBegin = function(event) { + var onBegin = function(event, pickRay, pickResult) { var properties = Entities.getEntityProperties(SelectionManager.selections[0]); initialProperties = properties; - rotation = spaceMode == SPACE_LOCAL ? properties.rotation : Quat.fromPitchYawRollDegrees(0, 0, 0); + rotation = (spaceMode === SPACE_LOCAL) ? properties.rotation : Quat.fromPitchYawRollDegrees(0, 0, 0); - if (spaceMode == SPACE_LOCAL) { + if (spaceMode === SPACE_LOCAL) { rotation = SelectionManager.localRotation; initialPosition = SelectionManager.localPosition; initialDimensions = SelectionManager.localDimensions; @@ -2753,14 +2733,13 @@ SelectionDisplay = (function() { deltaPivot3D = Vec3.subtract(centeredRP, scaledPivot3D); var scaledOffsetWorld3D = vec3Mult(initialDimensions, - Vec3.subtract(Vec3.multiply(0.5, Vec3.multiply(-1.0, directionFor3DStretch)), - centeredRP)); + Vec3.subtract(Vec3.multiply(0.5, Vec3.multiply(-1.0, directionFor3DStretch)), centeredRP)); pickRayPosition3D = Vec3.sum(initialPosition, Vec3.multiplyQbyV(rotation, scaledOffsetWorld)); } var start = null; var end = null; - if (numDimensions == 1 && mask.x) { + if ((numDimensions === 1) && mask.x) { start = Vec3.multiplyQbyV(rotation, { x: -10000, y: 0, @@ -2776,10 +2755,10 @@ SelectionDisplay = (function() { Overlays.editOverlay(xRailOverlay, { start: start, end: end, - visible: true, + visible: true }); } - if (numDimensions == 1 && mask.y) { + if ((numDimensions === 1) && mask.y) { start = Vec3.multiplyQbyV(rotation, { x: 0, y: -10000, @@ -2795,10 +2774,10 @@ SelectionDisplay = (function() { Overlays.editOverlay(yRailOverlay, { start: start, end: end, - visible: true, + visible: true }); } - if (numDimensions == 1 && mask.z) { + if ((numDimensions === 1) && mask.z) { start = Vec3.multiplyQbyV(rotation, { x: 0, y: 0, @@ -2814,17 +2793,17 @@ SelectionDisplay = (function() { Overlays.editOverlay(zRailOverlay, { start: start, end: end, - visible: true, + visible: true }); } - if (numDimensions == 1) { - if (mask.x == 1) { + if (numDimensions === 1) { + if (mask.x === 1) { planeNormal = { x: 0, y: 1, z: 0 }; - } else if (mask.y == 1) { + } else if (mask.y === 1) { planeNormal = { x: 1, y: 0, @@ -2837,7 +2816,7 @@ SelectionDisplay = (function() { z: 0 }; } - } else if (numDimensions == 2) { + } else if (numDimensions === 2) { if (mask.x === 0) { planeNormal = { x: 1, @@ -2854,13 +2833,12 @@ SelectionDisplay = (function() { planeNormal = { x: 0, y: 0, - z: z + z: 1 }; } } planeNormal = Vec3.multiplyQbyV(rotation, planeNormal); - var pickRay = generalComputePickRay(event.x, event.y); lastPick = rayPlaneIntersection(pickRay, pickRayPosition, planeNormal); @@ -2895,10 +2873,10 @@ SelectionDisplay = (function() { }; var onMove = function(event) { - var proportional = spaceMode == SPACE_WORLD || event.isShifted || activeTool.mode == "STRETCH_RADIUS"; + var proportional = (spaceMode === SPACE_WORLD) || event.isShifted || isActiveTool(grabberSpotLightRadius); var position, dimensions, rotation; - if (spaceMode == SPACE_LOCAL) { + if (spaceMode === SPACE_LOCAL) { position = SelectionManager.localPosition; dimensions = SelectionManager.localDimensions; rotation = SelectionManager.localRotation; @@ -2935,8 +2913,8 @@ SelectionDisplay = (function() { } else { newPick = rayPlaneIntersection(pickRay, - pickRayPosition, - planeNormal); + pickRayPosition, + planeNormal); vector = Vec3.subtract(newPick, lastPick); vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector); @@ -2984,7 +2962,7 @@ SelectionDisplay = (function() { for (var i = 0; i < SelectionManager.selections.length; i++) { Entities.editEntity(SelectionManager.selections[i], { position: newPosition, - dimensions: newDimensions, + dimensions: newDimensions }); } @@ -2992,10 +2970,10 @@ SelectionDisplay = (function() { var wantDebug = false; if (wantDebug) { print(stretchMode); - //Vec3.print(" newIntersection:", newIntersection); + // Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - //Vec3.print(" oldPOS:", oldPOS); - //Vec3.print(" newPOS:", newPOS); + // Vec3.print(" oldPOS:", oldPOS); + // Vec3.print(" newPOS:", newPOS); Vec3.print(" changeInDimensions:", changeInDimensions); Vec3.print(" newDimensions:", newDimensions); @@ -3005,7 +2983,7 @@ SelectionDisplay = (function() { } SelectionManager._update(); - };//--End of onMove def + };// End of onMove def return { mode: stretchMode, @@ -3091,15 +3069,18 @@ SelectionDisplay = (function() { } var tool = makeStretchTool(mode, direction, pivot, offset, handleMove); - addGrabberTool(overlay, tool); + return addGrabberTool(overlay, tool); } // FUNCTION: CUTOFF STRETCH FUNC function cutoffStretchFunc(vector, change) { vector = change; - Vec3.print("Radius stretch: ", vector); + var wantDebug = false; + if (wantDebug) { + Vec3.print("Radius stretch: ", vector); + } var length = vector.x + vector.y + vector.z; - var props = selectionManager.savedProperties[selectionManager.selections[0]]; + var props = SelectionManager.savedProperties[SelectionManager.selections[0]]; var radius = props.dimensions.z / 2; var originalCutoff = props.cutoff; @@ -3108,8 +3089,8 @@ SelectionDisplay = (function() { var newSize = originalSize + length; var cutoff = Math.atan2(newSize, radius) * 180 / Math.PI; - Entities.editEntity(selectionManager.selections[0], { - cutoff: cutoff, + Entities.editEntity(SelectionManager.selections[0], { + cutoff: cutoff }); SelectionManager._update(); @@ -3117,7 +3098,7 @@ SelectionDisplay = (function() { // FUNCTION: RADIUS STRETCH FUNC function radiusStretchFunc(vector, change) { - var props = selectionManager.savedProperties[selectionManager.selections[0]]; + var props = SelectionManager.savedProperties[SelectionManager.selections[0]]; // Find the axis being adjusted var size; @@ -3135,8 +3116,8 @@ SelectionDisplay = (function() { z: size }; - Entities.editEntity(selectionManager.selections[0], { - dimensions: newDimensions, + Entities.editEntity(SelectionManager.selections[0], { + dimensions: newDimensions }); SelectionManager._update(); @@ -3551,506 +3532,326 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE ROTATION DEGREES OVERLAY function updateRotationDegreesOverlay(angleFromZero, handleRotation, centerPosition) { + var wantDebug = false; + if (wantDebug) { + print("---> updateRotationDegreesOverlay ---"); + print(" AngleFromZero: " + angleFromZero); + print(" HandleRotation - X: " + handleRotation.x + " Y: " + handleRotation.y + " Z: " + handleRotation.z); + print(" CenterPos - " + centerPosition.x + " Y: " + centerPosition.y + " Z: " + centerPosition.z); + } + var angle = angleFromZero * (Math.PI / 180); var position = { x: Math.cos(angle) * outerRadius * ROTATION_DISPLAY_DISTANCE_MULTIPLIER, y: Math.sin(angle) * outerRadius * ROTATION_DISPLAY_DISTANCE_MULTIPLIER, - z: 0, + z: 0 }; + if (wantDebug) { + print(" Angle: " + angle); + print(" InitialPos: " + position.x + ", " + position.y + ", " + position.z); + } + position = Vec3.multiplyQbyV(handleRotation, position); position = Vec3.sum(centerPosition, position); - Overlays.editOverlay(rotationDegreesDisplay, { + var overlayProps = { position: position, dimensions: { x: innerRadius * ROTATION_DISPLAY_SIZE_X_MULTIPLIER, y: innerRadius * ROTATION_DISPLAY_SIZE_Y_MULTIPLIER }, lineHeight: innerRadius * ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER, - text: normalizeDegrees(angleFromZero) + "°", - }); + text: normalizeDegrees(angleFromZero) + "°" + }; + if (wantDebug) { + print(" TranslatedPos: " + position.x + ", " + position.y + ", " + position.z); + print(" OverlayDim - X: " + overlayProps.dimensions.x + " Y: " + overlayProps.dimensions.y + " Z: " + overlayProps.dimensions.z); + print(" OverlayLineHeight: " + overlayProps.lineHeight); + print(" OverlayText: " + overlayProps.text); + } + + Overlays.editOverlay(rotationDegreesDisplay, overlayProps); + if (wantDebug) { + print("<--- updateRotationDegreesOverlay ---"); + } } + // FUNCTION DEF: updateSelectionsRotation + // Helper func used by rotation grabber tools + function updateSelectionsRotation(rotationChange) { + if (!rotationChange) { + print("ERROR: entitySelectionTool.updateSelectionsRotation - Invalid arg specified!!"); + + // EARLY EXIT + return; + } + + // Entities should only reposition if we are rotating multiple selections around + // the selections center point. Otherwise, the rotation will be around the entities + // registration point which does not need repositioning. + var reposition = (SelectionManager.selections.length > 1); + for (var i = 0; i < SelectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + var initialProperties = SelectionManager.savedProperties[entityID]; + + var newProperties = { + rotation: Quat.multiply(rotationChange, initialProperties.rotation) + }; + + if (reposition) { + var dPos = Vec3.subtract(initialProperties.position, initialPosition); + dPos = Vec3.multiplyQbyV(rotationChange, dPos); + newProperties.position = Vec3.sum(initialPosition, dPos); + } + + Entities.editEntity(entityID, newProperties); + } + } + + function helperRotationHandleOnBegin(event, pickRay, rotAroundAxis, rotCenter, handleRotation) { + var wantDebug = false; + if (wantDebug) { + print("================== " + getMode() + "(rotation helper onBegin) -> ======================="); + } + + SelectionManager.saveProperties(); + that.setRotationHandlesVisible(false); + that.setStretchHandlesVisible(false); + that.setGrabberMoveUpVisible(false); + + initialPosition = SelectionManager.worldPosition; + rotationNormal = { x: 0, y: 0, z: 0 }; + rotationNormal[rotAroundAxis] = 1; + + // Size the overlays to the current selection size + var diagonal = (Vec3.length(SelectionManager.worldDimensions) / 2) * 1.1; + var halfDimensions = Vec3.multiply(SelectionManager.worldDimensions, 0.5); + innerRadius = diagonal; + outerRadius = diagonal * 1.15; + var innerAlpha = 0.2; + var outerAlpha = 0.2; + Overlays.editOverlay(rotateOverlayInner, { + visible: true, + rotation: handleRotation, + position: rotCenter, + size: innerRadius, + innerRadius: 0.9, + startAt: 0, + endAt: 360, + alpha: innerAlpha + }); + + Overlays.editOverlay(rotateOverlayOuter, { + visible: true, + rotation: handleRotation, + position: rotCenter, + size: outerRadius, + innerRadius: 0.9, + startAt: 0, + endAt: 360, + alpha: outerAlpha + }); + + Overlays.editOverlay(rotateOverlayCurrent, { + visible: true, + rotation: handleRotation, + position: rotCenter, + size: outerRadius, + startAt: 0, + endAt: 0, + innerRadius: 0.9 + }); + + Overlays.editOverlay(rotationDegreesDisplay, { + visible: true + }); + + updateRotationDegreesOverlay(0, handleRotation, rotCenter); + + // editOverlays may not have committed rotation changes. + // Compute zero position based on where the overlay will be eventually. + var result = rayPlaneIntersection(pickRay, rotCenter, rotationNormal); + // In case of a parallel ray, this will be null, which will cause early-out + // in the onMove helper. + rotZero = result; + + if (wantDebug) { + print("================== " + getMode() + "(rotation helper onBegin) <- ======================="); + } + }// End_Function(helperRotationHandleOnBegin) + + function helperRotationHandleOnMove(event, rotAroundAxis, rotCenter, handleRotation) { + + if (!rotZero) { + print("ERROR: entitySelectionTool.handleRotationHandleOnMove - Invalid RotationZero Specified (missed rotation target plane?)"); + + // EARLY EXIT + return; + } + + var wantDebug = false; + if (wantDebug) { + print("================== "+ getMode() + "(rotation helper onMove) -> ======================="); + Vec3.print(" rotZero: ", rotZero); + } + var pickRay = generalComputePickRay(event.x, event.y); + Overlays.editOverlay(selectionBox, { + visible: false + }); + Overlays.editOverlay(baseOfEntityProjectionOverlay, { + visible: false + }); + + var result = rayPlaneIntersection(pickRay, rotCenter, rotationNormal); + if (result) { + var centerToZero = Vec3.subtract(rotZero, rotCenter); + var centerToIntersect = Vec3.subtract(result, rotCenter); + if (wantDebug) { + Vec3.print(" RotationNormal: ", rotationNormal); + Vec3.print(" rotZero: ", rotZero); + Vec3.print(" rotCenter: ", rotCenter); + Vec3.print(" intersect: ", result); + Vec3.print(" centerToZero: ", centerToZero); + Vec3.print(" centerToIntersect: ", centerToIntersect); + } + // Note: orientedAngle which wants normalized centerToZero and centerToIntersect + // handles that internally, so it's to pass unnormalized vectors here. + var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); + + var distanceFromCenter = Vec3.length(centerToIntersect); + var snapToInner = distanceFromCenter < innerRadius; + var snapAngle = snapToInner ? innerSnapAngle : 1.0; + angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; + + var vec3Degrees = { x: 0, y: 0, z: 0 }; + vec3Degrees[rotAroundAxis] = angleFromZero; + var rotChange = Quat.fromVec3Degrees(vec3Degrees); + updateSelectionsRotation(rotChange); + + updateRotationDegreesOverlay(angleFromZero, handleRotation, rotCenter); + + // update the rotation display accordingly... + var startAtCurrent = 0; + var endAtCurrent = angleFromZero; + var startAtRemainder = angleFromZero; + var endAtRemainder = 360; + if (angleFromZero < 0) { + startAtCurrent = 360 + angleFromZero; + endAtCurrent = 360; + startAtRemainder = 0; + endAtRemainder = startAtCurrent; + } + if (snapToInner) { + Overlays.editOverlay(rotateOverlayOuter, { + startAt: 0, + endAt: 360 + }); + Overlays.editOverlay(rotateOverlayInner, { + startAt: startAtRemainder, + endAt: endAtRemainder + }); + Overlays.editOverlay(rotateOverlayCurrent, { + startAt: startAtCurrent, + endAt: endAtCurrent, + size: innerRadius, + majorTickMarksAngle: innerSnapAngle, + minorTickMarksAngle: 0, + majorTickMarksLength: -0.25, + minorTickMarksLength: 0 + }); + } else { + Overlays.editOverlay(rotateOverlayInner, { + startAt: 0, + endAt: 360 + }); + Overlays.editOverlay(rotateOverlayOuter, { + startAt: startAtRemainder, + endAt: endAtRemainder + }); + Overlays.editOverlay(rotateOverlayCurrent, { + startAt: startAtCurrent, + endAt: endAtCurrent, + size: outerRadius, + majorTickMarksAngle: 45.0, + minorTickMarksAngle: 5, + majorTickMarksLength: 0.25, + minorTickMarksLength: 0.1 + }); + } + }// End_If(results.intersects) + + if (wantDebug) { + print("================== "+ getMode() + "(rotation helper onMove) <- ======================="); + } + }// End_Function(helperRotationHandleOnMove) + + function helperRotationHandleOnEnd() { + var wantDebug = false; + if (wantDebug) { + print("================== " + getMode() + "(onEnd) -> ======================="); + } + Overlays.editOverlay(rotateOverlayInner, { + visible: false + }); + Overlays.editOverlay(rotateOverlayOuter, { + visible: false + }); + Overlays.editOverlay(rotateOverlayCurrent, { + visible: false + }); + Overlays.editOverlay(rotationDegreesDisplay, { + visible: false + }); + + pushCommandForSelections(); + + if (wantDebug) { + print("================== " + getMode() + "(onEnd) <- ======================="); + } + }// End_Function(helperRotationHandleOnEnd) + + // YAW GRABBER TOOL DEFINITION var initialPosition = SelectionManager.worldPosition; addGrabberTool(yawHandle, { mode: "ROTATE_YAW", - onBegin: function(event) { - SelectionManager.saveProperties(); - initialPosition = SelectionManager.worldPosition; - - // Size the overlays to the current selection size - var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; - var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); - innerRadius = diagonal; - outerRadius = diagonal * 1.15; - var innerAlpha = 0.2; - var outerAlpha = 0.2; - Overlays.editOverlay(rotateOverlayInner, { - visible: true, - size: innerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: innerAlpha - }); - - Overlays.editOverlay(rotateOverlayOuter, { - visible: true, - size: outerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: outerAlpha, - }); - - Overlays.editOverlay(rotateOverlayCurrent, { - visible: true, - size: outerRadius, - startAt: 0, - endAt: 0, - innerRadius: 0.9, - }); - - Overlays.editOverlay(rotationDegreesDisplay, { - visible: true, - }); - - updateRotationDegreesOverlay(0, yawHandleRotation, yawCenter); + onBegin: function(event, pickRay, pickResult) { + helperRotationHandleOnBegin(event, pickRay, "y", yawCenter, yawHandleRotation); }, onEnd: function(event, reason) { - Overlays.editOverlay(rotateOverlayInner, { - visible: false - }); - Overlays.editOverlay(rotateOverlayOuter, { - visible: false - }); - Overlays.editOverlay(rotateOverlayCurrent, { - visible: false - }); - Overlays.editOverlay(rotationDegreesDisplay, { - visible: false - }); - - pushCommandForSelections(); + helperRotationHandleOnEnd(); }, onMove: function(event) { - var pickRay = generalComputePickRay(event.x, event.y); - Overlays.editOverlay(selectionBox, { - visible: false - }); - Overlays.editOverlay(baseOfEntityProjectionOverlay, { - visible: false - }); - - var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); - - if (result.intersects) { - var center = yawCenter; - var zero = yawZero; - var centerToZero = Vec3.subtract(zero, center); - var centerToIntersect = Vec3.subtract(result.intersection, center); - // Note: orientedAngle which wants normalized centerToZero and centerToIntersect - // handles that internally, so it's to pass unnormalized vectors here. - var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); - var distanceFromCenter = Vec3.distance(center, result.intersection); - var snapToInner = distanceFromCenter < innerRadius; - var snapAngle = snapToInner ? innerSnapAngle : 1.0; - angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; - var yawChange = Quat.fromVec3Degrees({ - x: 0, - y: angleFromZero, - z: 0 - }); - - // Entities should only reposition if we are rotating multiple selections around - // the selections center point. Otherwise, the rotation will be around the entities - // registration point which does not need repositioning. - var reposition = SelectionManager.selections.length > 1; - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - var properties = Entities.getEntityProperties(entityID); - var initialProperties = SelectionManager.savedProperties[entityID]; - - var newProperties = { - rotation: Quat.multiply(yawChange, initialProperties.rotation), - }; - - if (reposition) { - var dPos = Vec3.subtract(initialProperties.position, initialPosition); - dPos = Vec3.multiplyQbyV(yawChange, dPos); - newProperties.position = Vec3.sum(initialPosition, dPos); - } - - Entities.editEntity(entityID, newProperties); - } - - updateRotationDegreesOverlay(angleFromZero, yawHandleRotation, yawCenter); - - // update the rotation display accordingly... - var startAtCurrent = 0; - var endAtCurrent = angleFromZero; - var startAtRemainder = angleFromZero; - var endAtRemainder = 360; - if (angleFromZero < 0) { - startAtCurrent = 360 + angleFromZero; - endAtCurrent = 360; - startAtRemainder = 0; - endAtRemainder = startAtCurrent; - } - if (snapToInner) { - Overlays.editOverlay(rotateOverlayOuter, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayInner, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: innerRadius, - majorTickMarksAngle: innerSnapAngle, - minorTickMarksAngle: 0, - majorTickMarksLength: -0.25, - minorTickMarksLength: 0, - }); - } else { - Overlays.editOverlay(rotateOverlayInner, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayOuter, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: outerRadius, - majorTickMarksAngle: 45.0, - minorTickMarksAngle: 5, - majorTickMarksLength: 0.25, - minorTickMarksLength: 0.1, - }); - } - - } + helperRotationHandleOnMove(event, "y", yawCenter, yawHandleRotation); } }); + // PITCH GRABBER TOOL DEFINITION addGrabberTool(pitchHandle, { mode: "ROTATE_PITCH", - onBegin: function(event) { - SelectionManager.saveProperties(); - initialPosition = SelectionManager.worldPosition; - - // Size the overlays to the current selection size - var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; - var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); - innerRadius = diagonal; - outerRadius = diagonal * 1.15; - var innerAlpha = 0.2; - var outerAlpha = 0.2; - Overlays.editOverlay(rotateOverlayInner, { - visible: true, - size: innerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: innerAlpha - }); - - Overlays.editOverlay(rotateOverlayOuter, { - visible: true, - size: outerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: outerAlpha, - }); - - Overlays.editOverlay(rotateOverlayCurrent, { - visible: true, - size: outerRadius, - startAt: 0, - endAt: 0, - innerRadius: 0.9, - }); - - Overlays.editOverlay(rotationDegreesDisplay, { - visible: true, - }); - - updateRotationDegreesOverlay(0, pitchHandleRotation, pitchCenter); + onBegin: function(event, pickRay, pickResult) { + helperRotationHandleOnBegin(event, pickRay, "x", pitchCenter, pitchHandleRotation); }, onEnd: function(event, reason) { - Overlays.editOverlay(rotateOverlayInner, { - visible: false - }); - Overlays.editOverlay(rotateOverlayOuter, { - visible: false - }); - Overlays.editOverlay(rotateOverlayCurrent, { - visible: false - }); - Overlays.editOverlay(rotationDegreesDisplay, { - visible: false - }); - - pushCommandForSelections(); + helperRotationHandleOnEnd(); }, - onMove: function(event) { - var pickRay = generalComputePickRay(event.x, event.y); - Overlays.editOverlay(selectionBox, { - visible: false - }); - Overlays.editOverlay(baseOfEntityProjectionOverlay, { - visible: false - }); - var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); - - if (result.intersects) { - var center = pitchCenter; - var zero = pitchZero; - var centerToZero = Vec3.subtract(zero, center); - var centerToIntersect = Vec3.subtract(result.intersection, center); - // Note: orientedAngle which wants normalized centerToZero & centerToIntersect, handles - // this internally, so it's fine to pass non-normalized versions here. - var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); - - var distanceFromCenter = Vec3.distance(center, result.intersection); - var snapToInner = distanceFromCenter < innerRadius; - var snapAngle = snapToInner ? innerSnapAngle : 1.0; - angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; - - var pitchChange = Quat.fromVec3Degrees({ - x: angleFromZero, - y: 0, - z: 0 - }); - - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - var initialProperties = SelectionManager.savedProperties[entityID]; - var dPos = Vec3.subtract(initialProperties.position, initialPosition); - dPos = Vec3.multiplyQbyV(pitchChange, dPos); - - Entities.editEntity(entityID, { - position: Vec3.sum(initialPosition, dPos), - rotation: Quat.multiply(pitchChange, initialProperties.rotation), - }); - } - - updateRotationDegreesOverlay(angleFromZero, pitchHandleRotation, pitchCenter); - - // update the rotation display accordingly... - var startAtCurrent = 0; - var endAtCurrent = angleFromZero; - var startAtRemainder = angleFromZero; - var endAtRemainder = 360; - if (angleFromZero < 0) { - startAtCurrent = 360 + angleFromZero; - endAtCurrent = 360; - startAtRemainder = 0; - endAtRemainder = startAtCurrent; - } - if (snapToInner) { - Overlays.editOverlay(rotateOverlayOuter, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayInner, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: innerRadius, - majorTickMarksAngle: innerSnapAngle, - minorTickMarksAngle: 0, - majorTickMarksLength: -0.25, - minorTickMarksLength: 0, - }); - } else { - Overlays.editOverlay(rotateOverlayInner, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayOuter, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: outerRadius, - majorTickMarksAngle: 45.0, - minorTickMarksAngle: 5, - majorTickMarksLength: 0.25, - minorTickMarksLength: 0.1, - }); - } - } + onMove: function (event) { + helperRotationHandleOnMove(event, "x", pitchCenter, pitchHandleRotation); } }); + // ROLL GRABBER TOOL DEFINITION addGrabberTool(rollHandle, { mode: "ROTATE_ROLL", - onBegin: function(event) { - SelectionManager.saveProperties(); - initialPosition = SelectionManager.worldPosition; - - // Size the overlays to the current selection size - var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; - var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); - innerRadius = diagonal; - outerRadius = diagonal * 1.15; - var innerAlpha = 0.2; - var outerAlpha = 0.2; - Overlays.editOverlay(rotateOverlayInner, { - visible: true, - size: innerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: innerAlpha - }); - - Overlays.editOverlay(rotateOverlayOuter, { - visible: true, - size: outerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: outerAlpha, - }); - - Overlays.editOverlay(rotateOverlayCurrent, { - visible: true, - size: outerRadius, - startAt: 0, - endAt: 0, - innerRadius: 0.9, - }); - - Overlays.editOverlay(rotationDegreesDisplay, { - visible: true, - }); - - updateRotationDegreesOverlay(0, rollHandleRotation, rollCenter); + onBegin: function(event, pickRay, pickResult) { + helperRotationHandleOnBegin(event, pickRay, "z", rollCenter, rollHandleRotation); }, - onEnd: function(event, reason) { - Overlays.editOverlay(rotateOverlayInner, { - visible: false - }); - Overlays.editOverlay(rotateOverlayOuter, { - visible: false - }); - Overlays.editOverlay(rotateOverlayCurrent, { - visible: false - }); - Overlays.editOverlay(rotationDegreesDisplay, { - visible: false - }); - - pushCommandForSelections(); + onEnd: function (event, reason) { + helperRotationHandleOnEnd(); }, onMove: function(event) { - var pickRay = generalComputePickRay(event.x, event.y); - Overlays.editOverlay(selectionBox, { - visible: false - }); - Overlays.editOverlay(baseOfEntityProjectionOverlay, { - visible: false - }); - var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); - - if (result.intersects) { - var center = rollCenter; - var zero = rollZero; - var centerToZero = Vec3.subtract(zero, center); - var centerToIntersect = Vec3.subtract(result.intersection, center); - // Note: orientedAngle which wants normalized centerToZero & centerToIntersect, handles - // this internally, so it's fine to pass non-normalized versions here. - var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); - - var distanceFromCenter = Vec3.distance(center, result.intersection); - var snapToInner = distanceFromCenter < innerRadius; - var snapAngle = snapToInner ? innerSnapAngle : 1.0; - angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; - - var rollChange = Quat.fromVec3Degrees({ - x: 0, - y: 0, - z: angleFromZero - }); - for (var i = 0; i < SelectionManager.selections.length; i++) { - var entityID = SelectionManager.selections[i]; - var initialProperties = SelectionManager.savedProperties[entityID]; - var dPos = Vec3.subtract(initialProperties.position, initialPosition); - dPos = Vec3.multiplyQbyV(rollChange, dPos); - - Entities.editEntity(entityID, { - position: Vec3.sum(initialPosition, dPos), - rotation: Quat.multiply(rollChange, initialProperties.rotation), - }); - } - - updateRotationDegreesOverlay(angleFromZero, rollHandleRotation, rollCenter); - - // update the rotation display accordingly... - var startAtCurrent = 0; - var endAtCurrent = angleFromZero; - var startAtRemainder = angleFromZero; - var endAtRemainder = 360; - if (angleFromZero < 0) { - startAtCurrent = 360 + angleFromZero; - endAtCurrent = 360; - startAtRemainder = 0; - endAtRemainder = startAtCurrent; - } - if (snapToInner) { - Overlays.editOverlay(rotateOverlayOuter, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayInner, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: innerRadius, - majorTickMarksAngle: innerSnapAngle, - minorTickMarksAngle: 0, - majorTickMarksLength: -0.25, - minorTickMarksLength: 0, - }); - } else { - Overlays.editOverlay(rotateOverlayInner, { - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayOuter, { - startAt: startAtRemainder, - endAt: endAtRemainder - }); - Overlays.editOverlay(rotateOverlayCurrent, { - startAt: startAtCurrent, - endAt: endAtCurrent, - size: outerRadius, - majorTickMarksAngle: 45.0, - minorTickMarksAngle: 5, - majorTickMarksLength: 0.25, - minorTickMarksLength: 0.1, - }); - } - } + helperRotationHandleOnMove(event, "z", rollCenter, rollHandleRotation); } }); @@ -4059,7 +3860,7 @@ SelectionDisplay = (function() { if (SelectionManager.hasSelection()) { // FIXME - this cause problems with editing in the entity properties window - //SelectionManager._update(); + // SelectionManager._update(); if (!Vec3.equal(Camera.getPosition(), lastCameraPosition) || !Quat.equal(Camera.getOrientation(), lastCameraOrientation)) { @@ -4084,393 +3885,114 @@ SelectionDisplay = (function() { } }; - // FUNCTION: MOUSE PRESS EVENT - that.mousePressEvent = function(event) { + + // FUNCTION DEF(s): Intersection Check Helpers + function testRayIntersect(queryRay, overlayIncludes, overlayExcludes) { var wantDebug = false; - if (!event.isLeftButton && !that.triggered) { - // if another mouse button than left is pressed ignore it - return false; - } - - var somethingClicked = false; - var pickRay = generalComputePickRay(event.x, event.y); - - var result = Overlays.findRayIntersection(pickRay, true, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]); - if (result.intersects) { - // mouse clicks on the tablet should override the edit affordances - return false; - } - - entityIconOverlayManager.setIconsSelectable(selectionManager.selections,true); - - // ignore ray intersection for our selection box and yaw/pitch/roll - result = Overlays.findRayIntersection(pickRay, true, null, [ yawHandle, pitchHandle, rollHandle, selectionBox ] ); - if (result.intersects) { + if ((queryRay === undefined) || (queryRay === null)) { if (wantDebug) { - print("something intersects... "); - print(" result.overlayID:" + result.overlayID + "[" + overlayNames[result.overlayID] + "]"); - print(" result.intersects:" + result.intersects); - print(" result.overlayID:" + result.overlayID); - print(" result.distance:" + result.distance); - print(" result.face:" + result.face); - Vec3.print(" result.intersection:", result.intersection); + print("testRayIntersect - EARLY EXIT -> queryRay is undefined OR null!"); + } + return null; + } + + var intersectObj = Overlays.findRayIntersection(queryRay, true, overlayIncludes, overlayExcludes); + + if (wantDebug) { + if (!overlayIncludes) { + print("testRayIntersect - no overlayIncludes provided."); + } + if (!overlayExcludes) { + print("testRayIntersect - no overlayExcludes provided."); + } + print("testRayIntersect - Hit: " + intersectObj.intersects); + print(" intersectObj.overlayID:" + intersectObj.overlayID + "[" + overlayNames[intersectObj.overlayID] + "]"); + print(" OverlayName: " + overlayNames[intersectObj.overlayID]); + print(" intersectObj.distance:" + intersectObj.distance); + print(" intersectObj.face:" + intersectObj.face); + Vec3.print(" intersectObj.intersection:", intersectObj.intersection); + } + + return intersectObj; + } + + // FUNCTION: MOUSE PRESS EVENT + that.mousePressEvent = function (event) { + var wantDebug = false; + if (wantDebug) { + print("=============== eST::MousePressEvent BEG ======================="); + } + if (!event.isLeftButton && !that.triggered) { + // EARLY EXIT-(if another mouse button than left is pressed ignore it) + return false; + } + + var pickRay = generalComputePickRay(event.x, event.y); + // TODO_Case6491: Move this out to setup just to make it once + var interactiveOverlays = [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID, selectionBox]; + for (var key in grabberTools) { + if (grabberTools.hasOwnProperty(key)) { + interactiveOverlays.push(key); + } + } + + // Start with unknown mode, in case no tool can handle this. + activeTool = null; + + var results = testRayIntersect(pickRay, interactiveOverlays); + if (results.intersects) { + var hitOverlayID = results.overlayID; + if ((hitOverlayID === HMD.tabletID) || (hitOverlayID === HMD.tabletScreenID) || (hitOverlayID === HMD.homeButtonID)) { + // EARLY EXIT-(mouse clicks on the tablet should override the edit affordances) + return false; } - var tool = grabberTools[result.overlayID]; - if (tool) { - activeTool = tool; - mode = tool.mode; - somethingClicked = 'tool'; - if (activeTool && activeTool.onBegin) { - activeTool.onBegin(event); + entityIconOverlayManager.setIconsSelectable(SelectionManager.selections, true); + + var hitTool = grabberTools[ hitOverlayID ]; + if (hitTool) { + activeTool = hitTool; + if (activeTool.onBegin) { + activeTool.onBegin(event, pickRay, results); + } else { + print("ERROR: entitySelectionTool.mousePressEvent - ActiveTool(" + activeTool.mode + ") missing onBegin"); } } else { - switch (result.overlayID) { - case grabberMoveUp: - mode = "TRANSLATE_UP_DOWN"; - somethingClicked = mode; + print("ERROR: entitySelectionTool.mousePressEvent - Hit unexpected object, check interactiveOverlays"); + }// End_if (hitTool) + }// End_If(results.intersects) - // in translate mode, we hide our stretch handles... - for (var i = 0; i < stretchHandles.length; i++) { - Overlays.editOverlay(stretchHandles[i], { - visible: false - }); - } - break; - - - case grabberNEAR: - case grabberEdgeTN: // TODO: maybe this should be TOP+NEAR stretching? - case grabberEdgeBN: // TODO: maybe this should be BOTTOM+FAR stretching? - mode = "STRETCH_NEAR"; - somethingClicked = mode; - break; - - case grabberFAR: - case grabberEdgeTF: // TODO: maybe this should be TOP+FAR stretching? - case grabberEdgeBF: // TODO: maybe this should be BOTTOM+FAR stretching? - mode = "STRETCH_FAR"; - somethingClicked = mode; - break; - case grabberTOP: - mode = "STRETCH_TOP"; - somethingClicked = mode; - break; - case grabberBOTTOM: - mode = "STRETCH_BOTTOM"; - somethingClicked = mode; - break; - case grabberRIGHT: - case grabberEdgeTR: // TODO: maybe this should be TOP+RIGHT stretching? - case grabberEdgeBR: // TODO: maybe this should be BOTTOM+RIGHT stretching? - mode = "STRETCH_RIGHT"; - somethingClicked = mode; - break; - case grabberLEFT: - case grabberEdgeTL: // TODO: maybe this should be TOP+LEFT stretching? - case grabberEdgeBL: // TODO: maybe this should be BOTTOM+LEFT stretching? - mode = "STRETCH_LEFT"; - somethingClicked = mode; - break; - - default: - mode = "UNKNOWN"; - break; - } - } + if (wantDebug) { + print(" DisplayMode: " + getMode()); + print("=============== eST::MousePressEvent END ======================="); } - // if one of the items above was clicked, then we know we are in translate or stretch mode, and we - // should hide our rotate handles... - if (somethingClicked) { - Overlays.editOverlay(yawHandle, { - visible: false - }); - Overlays.editOverlay(pitchHandle, { - visible: false - }); - Overlays.editOverlay(rollHandle, { - visible: false - }); - - if (mode != "TRANSLATE_UP_DOWN") { - Overlays.editOverlay(grabberMoveUp, { - visible: false - }); - } - } - - if (!somethingClicked) { - - if (wantDebug) { - print("rotate handle case..."); - } - - - // Only intersect versus yaw/pitch/roll. - result = Overlays.findRayIntersection(pickRay, true, [ yawHandle, pitchHandle, rollHandle ] ); - - var overlayOrientation; - var overlayCenter; - - var properties = Entities.getEntityProperties(selectionManager.selections[0]); - var angles = Quat.safeEulerAngles(properties.rotation); - var pitch = angles.x; - var yaw = angles.y; - var roll = angles.z; - - originalRotation = properties.rotation; - originalPitch = pitch; - originalYaw = yaw; - originalRoll = roll; - - if (result.intersects) { - var resultTool = grabberTools[result.overlayID]; - if (resultTool) { - activeTool = resultTool; - mode = resultTool.mode; - somethingClicked = 'tool'; - if (activeTool && activeTool.onBegin) { - activeTool.onBegin(event); - } - } - switch (result.overlayID) { - case yawHandle: - mode = "ROTATE_YAW"; - somethingClicked = mode; - overlayOrientation = yawHandleRotation; - overlayCenter = yawCenter; - yawZero = result.intersection; - rotationNormal = yawNormal; - break; - - case pitchHandle: - mode = "ROTATE_PITCH"; - initialPosition = SelectionManager.worldPosition; - somethingClicked = mode; - overlayOrientation = pitchHandleRotation; - overlayCenter = pitchCenter; - pitchZero = result.intersection; - rotationNormal = pitchNormal; - break; - - case rollHandle: - mode = "ROTATE_ROLL"; - somethingClicked = mode; - overlayOrientation = rollHandleRotation; - overlayCenter = rollCenter; - rollZero = result.intersection; - rotationNormal = rollNormal; - break; - - default: - if (wantDebug) { - print("mousePressEvent()...... " + overlayNames[result.overlayID]); - } - mode = "UNKNOWN"; - break; - } - } - if (wantDebug) { - print(" somethingClicked:" + somethingClicked); - print(" mode:" + mode); - } - - if (somethingClicked) { - - Overlays.editOverlay(rotateOverlayTarget, { - visible: true, - rotation: overlayOrientation, - position: overlayCenter - }); - Overlays.editOverlay(rotateOverlayInner, { - visible: true, - rotation: overlayOrientation, - position: overlayCenter - }); - Overlays.editOverlay(rotateOverlayOuter, { - visible: true, - rotation: overlayOrientation, - position: overlayCenter, - startAt: 0, - endAt: 360 - }); - Overlays.editOverlay(rotateOverlayCurrent, { - visible: true, - rotation: overlayOrientation, - position: overlayCenter, - startAt: 0, - endAt: 0 - }); - Overlays.editOverlay(yawHandle, { - visible: false - }); - Overlays.editOverlay(pitchHandle, { - visible: false - }); - Overlays.editOverlay(rollHandle, { - visible: false - }); - - - // TODO: these three duplicate prior three, remove them. - Overlays.editOverlay(yawHandle, { - visible: false - }); - Overlays.editOverlay(pitchHandle, { - visible: false - }); - Overlays.editOverlay(rollHandle, { - visible: false - }); - Overlays.editOverlay(grabberMoveUp, { - visible: false - }); - Overlays.editOverlay(grabberLBN, { - visible: false - }); - Overlays.editOverlay(grabberLBF, { - visible: false - }); - Overlays.editOverlay(grabberRBN, { - visible: false - }); - Overlays.editOverlay(grabberRBF, { - visible: false - }); - Overlays.editOverlay(grabberLTN, { - visible: false - }); - Overlays.editOverlay(grabberLTF, { - visible: false - }); - Overlays.editOverlay(grabberRTN, { - visible: false - }); - Overlays.editOverlay(grabberRTF, { - visible: false - }); - - Overlays.editOverlay(grabberTOP, { - visible: false - }); - Overlays.editOverlay(grabberBOTTOM, { - visible: false - }); - Overlays.editOverlay(grabberLEFT, { - visible: false - }); - Overlays.editOverlay(grabberRIGHT, { - visible: false - }); - Overlays.editOverlay(grabberNEAR, { - visible: false - }); - Overlays.editOverlay(grabberFAR, { - visible: false - }); - - Overlays.editOverlay(grabberEdgeTR, { - visible: false - }); - Overlays.editOverlay(grabberEdgeTL, { - visible: false - }); - Overlays.editOverlay(grabberEdgeTF, { - visible: false - }); - Overlays.editOverlay(grabberEdgeTN, { - visible: false - }); - Overlays.editOverlay(grabberEdgeBR, { - visible: false - }); - Overlays.editOverlay(grabberEdgeBL, { - visible: false - }); - Overlays.editOverlay(grabberEdgeBF, { - visible: false - }); - Overlays.editOverlay(grabberEdgeBN, { - visible: false - }); - Overlays.editOverlay(grabberEdgeNR, { - visible: false - }); - Overlays.editOverlay(grabberEdgeNL, { - visible: false - }); - Overlays.editOverlay(grabberEdgeFR, { - visible: false - }); - Overlays.editOverlay(grabberEdgeFL, { - visible: false - }); - } - } - - if (!somethingClicked) { - // Only intersect versus selectionBox. - result = Overlays.findRayIntersection(pickRay, true, [selectionBox]); - if (result.intersects) { - switch (result.overlayID) { - case selectionBox: - activeTool = translateXZTool; - translateXZTool.pickPlanePosition = result.intersection; - translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y), - SelectionManager.worldDimensions.z); - if (wantDebug) { - print("longest dimension: " + translateXZTool.greatestDimension); - translateXZTool.startingDistance = Vec3.distance(pickRay.origin, SelectionManager.position); - print("starting distance: " + translateXZTool.startingDistance); - translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition); - print(" starting elevation: " + translateXZTool.startingElevation); - } - - mode = translateXZTool.mode; - activeTool.onBegin(event); - somethingClicked = 'selectionBox'; - break; - default: - if (wantDebug) { - print("mousePressEvent()...... " + overlayNames[result.overlayID]); - } - mode = "UNKNOWN"; - break; - } - } - } - - if (somethingClicked) { - pickRay = generalComputePickRay(event.x, event.y); - if (wantDebug) { - print("mousePressEvent()...... " + overlayNames[result.overlayID]); - } - } - - // reset everything as intersectable... - // TODO: we could optimize this since some of these were already flipped back - Overlays.editOverlay(selectionBox, { - ignoreRayIntersection: false - }); - Overlays.editOverlay(yawHandle, { - ignoreRayIntersection: false - }); - Overlays.editOverlay(pitchHandle, { - ignoreRayIntersection: false - }); - Overlays.editOverlay(rollHandle, { - ignoreRayIntersection: false - }); - - return somethingClicked; + // If mode is known then we successfully handled this; + // otherwise, we're missing a tool. + return activeTool; }; // FUNCTION: MOUSE MOVE EVENT that.mouseMoveEvent = function(event) { + var wantDebug = false; + if (wantDebug) { + print("=============== eST::MouseMoveEvent BEG ======================="); + } if (activeTool) { + if (wantDebug) { + print(" Trigger ActiveTool(" + activeTool.mode + ")'s onMove"); + } activeTool.onMove(event); + + if (wantDebug) { + print(" Trigger SelectionManager::update"); + } SelectionManager._update(); + + if (wantDebug) { + print("=============== eST::MouseMoveEvent END ======================="); + } + // EARLY EXIT--(Move handled via active tool) return true; } @@ -4554,7 +4076,7 @@ SelectionDisplay = (function() { pickedAlpha = grabberAlpha; highlightNeeded = true; break; - + default: if (previousHandle) { Overlays.editOverlay(previousHandle, { @@ -4593,60 +4115,35 @@ SelectionDisplay = (function() { } } + if (wantDebug) { + print("=============== eST::MouseMoveEvent END ======================="); + } return false; }; - // FUNCTION: UPDATE HANDLE SIZES - that.updateHandleSizes = function() { - if (selectionManager.hasSelection()) { - var diff = Vec3.subtract(selectionManager.worldPosition, Camera.getPosition()); - var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 5; - var dimensions = SelectionManager.worldDimensions; - var avgDimension = (dimensions.x + dimensions.y + dimensions.z) / 3; - grabberSize = Math.min(grabberSize, avgDimension / 10); - - for (var i = 0; i < stretchHandles.length; i++) { - Overlays.editOverlay(stretchHandles[i], { - size: grabberSize, - }); - } - var handleSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 7; - handleSize = Math.min(handleSize, avgDimension / 3); - - Overlays.editOverlay(yawHandle, { - scale: handleSize, - }); - Overlays.editOverlay(pitchHandle, { - scale: handleSize, - }); - Overlays.editOverlay(rollHandle, { - scale: handleSize, - }); - var pos = Vec3.sum(grabberMoveUpPosition, { - x: 0, - y: Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 3, - z: 0 - }); - Overlays.editOverlay(grabberMoveUp, { - position: pos, - scale: handleSize / 1.25, - }); - } - }; - Script.update.connect(that.updateHandleSizes); - // FUNCTION: MOUSE RELEASE EVENT that.mouseReleaseEvent = function(event) { - var showHandles = false; - if (activeTool && activeTool.onEnd) { - activeTool.onEnd(event); + var wantDebug = false; + if (wantDebug) { + print("=============== eST::MouseReleaseEvent BEG ======================="); } - activeTool = null; + var showHandles = false; + if (activeTool) { + if (activeTool.onEnd) { + if (wantDebug) { + print(" Triggering ActiveTool(" + activeTool.mode + ")'s onEnd"); + } + activeTool.onEnd(event); + } else if (wantDebug) { + print(" ActiveTool(" + activeTool.mode + ")'s missing onEnd"); + } + } + // hide our rotation overlays..., and show our handles - if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL") { - Overlays.editOverlay(rotateOverlayTarget, { - visible: false - }); + if (isActiveTool(yawHandle) || isActiveTool(pitchHandle) || isActiveTool(rollHandle)) { + if (wantDebug) { + print(" Triggering hide of RotateOverlays"); + } Overlays.editOverlay(rotateOverlayInner, { visible: false }); @@ -4656,22 +4153,25 @@ SelectionDisplay = (function() { Overlays.editOverlay(rotateOverlayCurrent, { visible: false }); - showHandles = true; + } - if (mode != "UNKNOWN") { - showHandles = true; - } - - mode = "UNKNOWN"; + showHandles = activeTool; // base on prior tool value + activeTool = null; // if something is selected, then reset the "original" properties for any potential next click+move operation if (SelectionManager.hasSelection()) { if (showHandles) { + if (wantDebug) { + print(" Triggering that.select"); + } that.select(SelectionManager.selections[0], event); } } + if (wantDebug) { + print("=============== eST::MouseReleaseEvent END ======================="); + } }; // NOTE: mousePressEvent and mouseMoveEvent from the main script should call us., so we don't hook these: @@ -4680,7 +4180,6 @@ SelectionDisplay = (function() { Controller.mouseReleaseEvent.connect(that.mouseReleaseEvent); - return that; }()); diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index deeae0d299..bf9822ba19 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -98,6 +98,7 @@ // for toolbar-mode: go back to home screen, this will close the window. tablet.gotoHomeScreen(); } else { + Wallet.refreshWalletStatus(); var entity = HMD.tabletID; Entities.editEntity(entity, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) }); showMarketplace(); @@ -157,11 +158,24 @@ } } + function sendCommerceSettings() { + tablet.emitScriptEvent(JSON.stringify({ + type: "marketplaces", + action: "commerceSetting", + data: { + commerceMode: Settings.getValue("commerce", false), + userIsLoggedIn: Account.loggedIn, + walletNeedsSetup: Wallet.walletStatus === 1 + } + })); + } + marketplaceButton.clicked.connect(onClick); tablet.screenChanged.connect(onScreenChanged); Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged); ContextOverlay.contextOverlayClicked.connect(setCertificateInfo); GlobalServices.myUsernameChanged.connect(onUsernameChanged); + Wallet.walletStatusChanged.connect(sendCommerceSettings); Wallet.refreshWalletStatus(); function onMessage(message) { @@ -203,15 +217,7 @@ canRezCertifiedItems: Entities.canRezCertified || Entities.canRezTmpCertified }); } else if (parsedJsonMessage.type === "REQUEST_SETTING") { - tablet.emitScriptEvent(JSON.stringify({ - type: "marketplaces", - action: "commerceSetting", - data: { - commerceMode: Settings.getValue("commerce", false), - userIsLoggedIn: Account.loggedIn, - walletNeedsSetup: Wallet.walletStatus === 1 - } - })); + sendCommerceSettings(); } else if (parsedJsonMessage.type === "PURCHASES") { referrerURL = parsedJsonMessage.referrerURL; filterText = ""; @@ -244,6 +250,7 @@ tablet.webEventReceived.disconnect(onMessage); Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged); GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); + Wallet.walletStatusChanged.disconnect(sendCommerceSettings); });