diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index af5f2c904e..db97da751f 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -935,39 +935,7 @@ void OctreeServer::handleOctreeFileReplacement(QSharedPointer m // so here we just store a special file at our persist path // and then force a stop of the server so that it can pick it up when it relaunches if (!_persistAbsoluteFilePath.isEmpty()) { - - // before we restart the server and make it try and load this data, let's make sure it is valid - auto compressedOctree = message->getMessage(); - QByteArray jsonOctree; - - // assume we have GZipped content - bool wasCompressed = gunzip(compressedOctree, jsonOctree); - if (!wasCompressed) { - // the source was not compressed, assume we were sent regular JSON data - jsonOctree = compressedOctree; - } - - // check the JSON data to verify it is an object - if (QJsonDocument::fromJson(jsonOctree).isObject()) { - if (!wasCompressed) { - // source was not compressed, we compress it before we write it locally - gzip(jsonOctree, compressedOctree); - } - - // write the compressed octree data to a special file - auto replacementFilePath = _persistAbsoluteFilePath.append(OctreePersistThread::REPLACEMENT_FILE_EXTENSION); - QFile replacementFile(replacementFilePath); - if (replacementFile.open(QIODevice::WriteOnly) && replacementFile.write(compressedOctree) != -1) { - // we've now written our replacement file, time to take the server down so it can - // process it when it comes back up - qInfo() << "Wrote octree replacement file to" << replacementFilePath << "- stopping server"; - setFinished(true); - } else { - qWarning() << "Could not write replacement octree data to file - refusing to process"; - } - } else { - qDebug() << "Received replacement octree file that is invalid - refusing to process"; - } + replaceContentFromMessageData(message->getMessage()); } else { qDebug() << "Cannot perform octree file replacement since current persist file path is not yet known"; } @@ -977,6 +945,68 @@ void OctreeServer::handleOctreeFileReplacement(QSharedPointer m } } +// Message->getMessage() contains a QByteArray representation of the URL to download from +void OctreeServer::handleOctreeFileReplacementFromURL(QSharedPointer message) { + qInfo() << "Received request to replace content from a url"; + if (!_isFinished && !_isShuttingDown) { + // This call comes from Interface, so we skip our domain server check + // but confirm that we have permissions to replace content sets + if (DependencyManager::get()->getThisNodeCanReplaceContent()) { + if (!_persistAbsoluteFilePath.isEmpty()) { + // Convert message data into our URL + QString url(message->getMessage()); + QUrl modelsURL = QUrl(url, QUrl::StrictMode); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest request(modelsURL); + QNetworkReply* reply = networkAccessManager.get(request); + connect(reply, &QNetworkReply::finished, [this, reply, modelsURL]() { + QNetworkReply::NetworkError networkError = reply->error(); + if (networkError == QNetworkReply::NoError) { + QByteArray contents = reply->readAll(); + replaceContentFromMessageData(contents); + } else { + qDebug() << "Error downloading JSON from specified file"; + } + }); + } else { + qDebug() << "Cannot perform octree file replacement since current persist file path is not yet known"; + } + } + } +} + +void OctreeServer::replaceContentFromMessageData(QByteArray content) { + //Assume we have compressed data + auto compressedOctree = content; + QByteArray jsonOctree; + + bool wasCompressed = gunzip(compressedOctree, jsonOctree); + if (!wasCompressed) { + // the source was not compressed, assume we were sent regular JSON data + jsonOctree = compressedOctree; + } + // check the JSON data to verify it is an object + if (QJsonDocument::fromJson(jsonOctree).isObject()) { + if (!wasCompressed) { + // source was not compressed, we compress it before we write it locally + gzip(jsonOctree, compressedOctree); + } + // write the compressed octree data to a special file + auto replacementFilePath = _persistAbsoluteFilePath.append(OctreePersistThread::REPLACEMENT_FILE_EXTENSION); + QFile replacementFile(replacementFilePath); + if (replacementFile.open(QIODevice::WriteOnly) && replacementFile.write(compressedOctree) != -1) { + // we've now written our replacement file, time to take the server down so it can + // process it when it comes back up + qInfo() << "Wrote octree replacement file to" << replacementFilePath << "- stopping server"; + setFinished(true); + } else { + qWarning() << "Could not write replacement octree data to file - refusing to process"; + } + } else { + qDebug() << "Received replacement octree file that is invalid - refusing to process"; + } +} + bool OctreeServer::readOptionBool(const QString& optionName, const QJsonObject& settingsSectionObject, bool& result) { result = false; // assume it doesn't exist bool optionAvailable = false; @@ -1202,6 +1232,7 @@ void OctreeServer::domainSettingsRequestComplete() { packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket"); packetReceiver.registerListener(PacketType::JurisdictionRequest, this, "handleJurisdictionRequestPacket"); packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacement"); + packetReceiver.registerListener(PacketType::OctreeFileReplacementFromUrl, this, "handleOctreeFileReplacementFromURL"); readConfiguration(); diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index 8db8d845de..5043ea681c 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -137,6 +137,7 @@ private slots: void handleOctreeDataNackPacket(QSharedPointer message, SharedNodePointer senderNode); void handleJurisdictionRequestPacket(QSharedPointer message, SharedNodePointer senderNode); void handleOctreeFileReplacement(QSharedPointer message); + void handleOctreeFileReplacementFromURL(QSharedPointer message); void removeSendThread(); protected: @@ -161,6 +162,8 @@ protected: UniqueSendThread createSendThread(const SharedNodePointer& node); virtual UniqueSendThread newSendThread(const SharedNodePointer& node); + void replaceContentFromMessageData(QByteArray content); + int _argc; const char** _argv; char** _parsedArgV; diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index c1eff76d78..8d0e949ff3 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1,5 +1,5 @@ { - "version": 1.7, + "version": 1.8, "settings": [ { "name": "metaverse", @@ -112,7 +112,6 @@ "label": "Adult (18+)" } ] - }, { "name": "hosts", @@ -161,15 +160,15 @@ "label": "HTTP Password", "type": "password", "help": "Password used for basic HTTP authentication. Leave this alone if you do not want to change it.", - "password_placeholder" : "******", + "password_placeholder": "******", "value-hidden": true }, { - "name": "verify_http_password", - "label": "Verify HTTP Password", - "type": "password", - "help": "Must match the password entered above for change to be saved.", - "value-hidden": true + "name": "verify_http_password", + "label": "Verify HTTP Password", + "type": "password", + "help": "Must match the password entered above for change to be saved.", + "value-hidden": true }, { "name": "maximum_user_capacity", @@ -208,21 +207,19 @@ "name": "standard_permissions", "type": "table", "label": "Domain-Wide User Permissions", - "help": "Indicate which types of users can have which domain-wide permissions.", + "help": "Indicate which types of users can have which domain-wide permissions.", "caption": "Standard Permissions", "can_add_new_rows": false, - "groups": [ { "label": "Type of User", "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -276,9 +273,15 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ], - "non-deletable-row-key": "permissions_id", "non-deletable-row-values": ["localhost", "anonymous", "logged-in"] }, @@ -291,18 +294,16 @@ "can_add_new_rows": false, "new_category_placeholder": "Add Group", "new_category_message": "Save and reload to see ranks", - "groups": [ { "label": "Rank", "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -381,6 +382,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -393,18 +401,16 @@ "can_add_new_rows": false, "new_category_placeholder": "Add Blacklist Group", "new_category_message": "Save and reload to see ranks", - "groups": [ { "label": "Rank", "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -480,6 +486,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -488,18 +501,16 @@ "type": "table", "caption": "Permissions for Specific Users", "can_add_new_rows": true, - "groups": [ { "label": "User", "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -553,6 +564,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -567,11 +585,10 @@ "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -625,6 +642,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -639,11 +663,10 @@ "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -697,6 +720,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -711,11 +741,10 @@ "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -769,6 +798,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] } @@ -784,7 +820,6 @@ "label": "Persistent Scripts", "help": "Add the URLs for scripts that you would like to ensure are always running in your domain.", "can_add_new_rows": true, - "columns": [ { "name": "url", @@ -946,7 +981,6 @@ "help": "In this table you can define a set of zones in which you can specify various audio properties.", "numbered": false, "can_add_new_rows": true, - "key": { "name": "name", "label": "Name", @@ -999,7 +1033,6 @@ "numbered": true, "can_order": true, "can_add_new_rows": true, - "columns": [ { "name": "source", @@ -1028,7 +1061,6 @@ "help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.", "numbered": true, "can_add_new_rows": true, - "columns": [ { "name": "zone", @@ -1063,7 +1095,9 @@ { "name": "audio_buffer", "label": "Audio Buffers", - "assignment-types": [0], + "assignment-types": [ + 0 + ], "settings": [ { "name": "dynamic_jitter_buffer", @@ -1082,35 +1116,37 @@ "advanced": true }, { - "name": "max_frames_over_desired", - "deprecated": true + "name": "max_frames_over_desired", + "deprecated": true }, { - "name": "window_starve_threshold", - "deprecated": true + "name": "window_starve_threshold", + "deprecated": true }, { - "name": "window_seconds_for_desired_calc_on_too_many_starves", - "deprecated": true + "name": "window_seconds_for_desired_calc_on_too_many_starves", + "deprecated": true }, { - "name": "window_seconds_for_desired_reduction", - "deprecated": true + "name": "window_seconds_for_desired_reduction", + "deprecated": true }, { - "name": "use_stdev_for_desired_calc", - "deprecated": true + "name": "use_stdev_for_desired_calc", + "deprecated": true }, { - "name": "repetition_with_fade", - "deprecated": true + "name": "repetition_with_fade", + "deprecated": true } ] }, { "name": "entity_server_settings", "label": "Entity Server Settings", - "assignment-types": [6], + "assignment-types": [ + 6 + ], "settings": [ { "name": "maxTmpLifetime", @@ -1167,13 +1203,32 @@ "help": "In this table you can define a set of rules for how frequently to backup copies of your entites content file.", "numbered": false, "can_add_new_rows": true, - - "default": [ - {"Name":"Half Hourly Rolling","backupInterval":1800,"format":".backup.halfhourly.%N","maxBackupVersions":5}, - {"Name":"Daily Rolling","backupInterval":86400,"format":".backup.daily.%N","maxBackupVersions":7}, - {"Name":"Weekly Rolling","backupInterval":604800,"format":".backup.weekly.%N","maxBackupVersions":4}, - {"Name":"Thirty Day Rolling","backupInterval":2592000,"format":".backup.thirtyday.%N","maxBackupVersions":12} - ], + "default": [ + { + "Name": "Half Hourly Rolling", + "backupInterval": 1800, + "format": ".backup.halfhourly.%N", + "maxBackupVersions": 5 + }, + { + "Name": "Daily Rolling", + "backupInterval": 86400, + "format": ".backup.daily.%N", + "maxBackupVersions": 7 + }, + { + "Name": "Weekly Rolling", + "backupInterval": 604800, + "format": ".backup.weekly.%N", + "maxBackupVersions": 4 + }, + { + "Name": "Thirty Day Rolling", + "backupInterval": 2592000, + "format": ".backup.thirtyday.%N", + "maxBackupVersions": 12 + } + ], "columns": [ { "name": "Name", @@ -1309,7 +1364,9 @@ { "name": "avatar_mixer", "label": "Avatar Mixer", - "assignment-types": [1], + "assignment-types": [ + 1 + ], "settings": [ { "name": "max_node_send_bandwidth", @@ -1362,7 +1419,10 @@ { "name": "downstream_servers", "label": "Receiving Servers", - "assignment-types": [0,1], + "assignment-types": [ + 0, + 1 + ], "type": "table", "advanced": true, "can_add_new_rows": true, @@ -1402,7 +1462,10 @@ { "name": "upstream_servers", "label": "Broadcasting Servers", - "assignment-types": [0,1], + "assignment-types": [ + 0, + 1 + ], "type": "table", "advanced": true, "can_add_new_rows": true, @@ -1442,4 +1505,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index fc595be67e..6951a90261 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -269,6 +269,7 @@ void DomainGatekeeper::updateNodePermissions() { userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities; userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities; userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer; + userPerms.permissions |= NodePermissions::Permission::canReplaceDomainContent; } else { // this node is an agent const QHostAddress& addr = node->getLocalSocket().getAddress(); @@ -357,6 +358,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities; userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities; userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer; + userPerms.permissions |= NodePermissions::Permission::canReplaceDomainContent; newNode->setPermissions(userPerms); return newNode; } diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 7a2cfa645a..d93126f2c7 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -112,6 +112,7 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList const QString RESTRICTED_ACCESS_SETTINGS_KEYPATH = "security.restricted_access"; const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors"; const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers"; + const QString EDITORS_CAN_REPLACE_CONTENT_KEYPATH = "security.editors_can_replace_content"; qDebug() << "Previous domain-server settings version was" << QString::number(oldVersion, 'g', 8) << "and the new version is" @@ -294,6 +295,13 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList // persist the new config so the user config file has the correctly merged config persistToFile(); } + + if (oldVersion < 1.8) { + unpackPermissions(); + // This was prior to addition of domain content replacement, add that to localhost permissions by default + _standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canReplaceDomainContent); + packPermissions(); + } } unpackPermissions(); diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 81c8a44baf..4ed95b59f7 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -183,6 +183,12 @@ if (WIN32) add_dependency_external_projects(steamworks) endif() +# include OPENSSL +include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") + +# append OpenSSL to our list of libraries to link +target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES}) + # disable /OPT:REF and /OPT:ICF for the Debug builds # This will prevent the following linker warnings # LINK : warning LNK4075: ignoring '/INCREMENTAL' due to '/OPT:ICF' specification diff --git a/interface/resources/qml/Web3DOverlay.qml b/interface/resources/qml/Web3DOverlay.qml index e0689e614d..a1fa2d2641 100644 --- a/interface/resources/qml/Web3DOverlay.qml +++ b/interface/resources/qml/Web3DOverlay.qml @@ -15,6 +15,11 @@ import "controls" as Controls Controls.WebView { + // This is for JS/QML communication, which is unused in a Web3DOverlay, + // but not having this here results in spurious warnings about a + // missing signal + signal sendToScript(var message); + function onWebEventReceived(event) { if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") { ApplicationInterface.addAssetToWorldFromURL(event.slice(18)); diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 3dcf747113..ca7226a6ab 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -41,6 +41,11 @@ FocusScope { // when they're opened. signal showDesktop(); + // This is for JS/QML communication, which is unused in the Desktop, + // but not having this here results in spurious warnings about a + // missing signal + signal sendToScript(var message); + // Allows QML/JS to find the desktop through the parent chain property bool desktopRoot: true diff --git a/interface/resources/qml/hifi/commerce/Checkout.qml b/interface/resources/qml/hifi/commerce/Checkout.qml index 865bb72921..b9d15b61e4 100644 --- a/interface/resources/qml/hifi/commerce/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/Checkout.qml @@ -66,6 +66,14 @@ Rectangle { } } } + onSecurityImageResult: { + securityImage.source = securityImageSelection.getImagePathFromImageID(imageID); + } + } + + SecurityImageSelection { + id: securityImageSelection; + referrerURL: checkoutRoot.itemHref; } // @@ -80,6 +88,20 @@ Rectangle { anchors.left: parent.left; anchors.top: parent.top; + // Security Image + Image { + id: securityImage; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + height: parent.height - 5; + width: height; + anchors.verticalCenter: parent.verticalCenter; + fillMode: Image.PreserveAspectFit; + mipmap: true; + } + // Title Bar text RalewaySemiBold { id: titleBarText; @@ -87,8 +109,11 @@ Rectangle { // Text size size: hifi.fontSizes.overlayTitle; // Anchors - anchors.fill: parent; + anchors.top: parent.top; + anchors.left: securityImage.right; anchors.leftMargin: 16; + anchors.bottom: parent.bottom; + width: paintedWidth; // Style color: hifi.colors.lightGrayText; // Alignment @@ -381,7 +406,7 @@ Rectangle { // "Buy" button HifiControlsUit.Button { id: buyButton; - enabled: balanceAfterPurchase >= 0 && !alreadyOwned && inventoryReceived && balanceReceived; + enabled: balanceAfterPurchase >= 0 && inventoryReceived && balanceReceived; color: hifi.buttons.black; colorScheme: hifi.colorSchemes.dark; anchors.top: parent.top; @@ -391,9 +416,16 @@ Rectangle { anchors.right: parent.right; anchors.rightMargin: 20; width: parent.width/2 - anchors.rightMargin*2; - text: (inventoryReceived && balanceReceived) ? (alreadyOwned ? "Already Owned" : "Buy") : "--"; + text: (inventoryReceived && balanceReceived) ? (alreadyOwned ? "Already Owned: Get Item" : "Buy") : "--"; onClicked: { - commerce.buy(itemId, parseInt(itemPriceText.text)); + if (!alreadyOwned) { + commerce.buy(itemId, parseInt(itemPriceText.text)); + } else { + if (urlHandler.canHandleUrl(itemHref)) { + urlHandler.handleUrl(itemHref); + } + sendToScript({method: 'checkout_buySuccess', itemId: itemId}); + } } } } @@ -427,6 +459,7 @@ Rectangle { itemHref = message.params.itemHref; commerce.balance(); commerce.inventory(); + commerce.getSecurityImage(); break; default: console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); diff --git a/interface/resources/qml/hifi/commerce/Inventory.qml b/interface/resources/qml/hifi/commerce/Inventory.qml index 298abebdab..d7ffae7c3c 100644 --- a/interface/resources/qml/hifi/commerce/Inventory.qml +++ b/interface/resources/qml/hifi/commerce/Inventory.qml @@ -43,6 +43,14 @@ Rectangle { inventoryContentsList.model = inventory.assets; } } + onSecurityImageResult: { + securityImage.source = securityImageSelection.getImagePathFromImageID(imageID); + } + } + + SecurityImageSelection { + id: securityImageSelection; + referrerURL: inventoryRoot.referrerURL; } // @@ -51,12 +59,26 @@ Rectangle { Item { id: titleBarContainer; // Size - width: inventoryRoot.width; + width: parent.width; height: 50; // Anchors anchors.left: parent.left; anchors.top: parent.top; + // Security Image + Image { + id: securityImage; + // Anchors + anchors.top: parent.top; + anchors.left: parent.left; + anchors.leftMargin: 16; + height: parent.height - 5; + width: height; + anchors.verticalCenter: parent.verticalCenter; + fillMode: Image.PreserveAspectFit; + mipmap: true; + } + // Title Bar text RalewaySemiBold { id: titleBarText; @@ -64,8 +86,11 @@ Rectangle { // Text size size: hifi.fontSizes.overlayTitle; // Anchors - anchors.fill: parent; + anchors.top: parent.top; + anchors.left: securityImage.right; anchors.leftMargin: 16; + anchors.bottom: parent.bottom; + width: paintedWidth; // Style color: hifi.colors.lightGrayText; // Alignment @@ -73,6 +98,25 @@ Rectangle { verticalAlignment: Text.AlignVCenter; } + // "Change Security Image" button + HifiControlsUit.Button { + id: changeSecurityImageButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.right: parent.right; + anchors.rightMargin: 20; + width: 200; + text: "Change Security Image" + onClicked: { + securityImageSelection.isManuallyChangingSecurityImage = true; + securityImageSelection.visible = true; + } + } + // Separator HifiControlsUit.Separator { anchors.left: parent.left; @@ -166,6 +210,7 @@ Rectangle { } ListView { id: inventoryContentsList; + clip: true; // Anchors anchors.top: inventoryContentsLabel.bottom; anchors.topMargin: 8; @@ -262,6 +307,7 @@ Rectangle { referrerURL = message.referrerURL; commerce.balance(); commerce.inventory(); + commerce.getSecurityImage(); break; default: console.log('Unrecognized message from marketplaces.js:', JSON.stringify(message)); diff --git a/interface/resources/qml/hifi/commerce/SecurityImageModel.qml b/interface/resources/qml/hifi/commerce/SecurityImageModel.qml new file mode 100644 index 0000000000..2fbf28683f --- /dev/null +++ b/interface/resources/qml/hifi/commerce/SecurityImageModel.qml @@ -0,0 +1,42 @@ +// +// SecurityImageModel.qml +// qml/hifi/commerce +// +// SecurityImageModel +// +// Created by Zach Fox on 2017-08-15 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 + +ListModel { + id: root; + ListElement{ + sourcePath: "images/01cat.jpg" + securityImageEnumValue: 1; + } + ListElement{ + sourcePath: "images/02car.jpg" + securityImageEnumValue: 2; + } + ListElement{ + sourcePath: "images/03dog.jpg" + securityImageEnumValue: 3; + } + ListElement{ + sourcePath: "images/04stars.jpg" + securityImageEnumValue: 4; + } + ListElement{ + sourcePath: "images/05plane.jpg" + securityImageEnumValue: 5; + } + ListElement{ + sourcePath: "images/06gingerbread.jpg" + securityImageEnumValue: 6; + } +} diff --git a/interface/resources/qml/hifi/commerce/SecurityImageSelection.qml b/interface/resources/qml/hifi/commerce/SecurityImageSelection.qml new file mode 100644 index 0000000000..7775f1ff9c --- /dev/null +++ b/interface/resources/qml/hifi/commerce/SecurityImageSelection.qml @@ -0,0 +1,271 @@ +// +// SecurityImageSelection.qml +// qml/hifi/commerce +// +// SecurityImageSelection +// +// Created by Zach Fox on 2017-08-15 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit +import "../../controls" as HifiControls + +// references XXX from root context + +Rectangle { + HifiConstants { id: hifi; } + + id: securityImageSelectionRoot; + property string referrerURL: ""; + property bool isManuallyChangingSecurityImage: false; + anchors.fill: parent; + // Style + color: hifi.colors.baseGray; + z:999; // On top of everything else + visible: false; + + Hifi.QmlCommerce { + id: commerce; + onSecurityImageResult: { + if (!isManuallyChangingSecurityImage) { + securityImageSelectionRoot.visible = (imageID == 0); + } + if (imageID > 0) { + for (var itr = 0; itr < gridModel.count; itr++) { + var thisValue = gridModel.get(itr).securityImageEnumValue; + if (thisValue === imageID) { + securityImageGrid.currentIndex = itr; + break; + } + } + } + } + } + + Component.onCompleted: { + commerce.getSecurityImage(); + } + + // + // TITLE BAR START + // + Item { + id: titleBarContainer; + // Size + width: securityImageSelectionRoot.width; + height: 30; + // Anchors + anchors.left: parent.left; + anchors.top: parent.top; + + // Title Bar text + RalewaySemiBold { + id: titleBarText; + text: "Select a Security Image"; + // Text size + size: hifi.fontSizes.overlayTitle; + // Anchors + anchors.fill: parent; + anchors.leftMargin: 16; + // Style + color: hifi.colors.lightGrayText; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // Separator + HifiControlsUit.Separator { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + } + } + // + // TITLE BAR END + // + + // + // EXPLANATION START + // + Item { + id: explanationContainer; + // Size + width: securityImageSelectionRoot.width; + height: 85; + // Anchors + anchors.top: titleBarContainer.bottom; + anchors.left: parent.left; + anchors.right: parent.right; + + RalewayRegular { + id: explanationText; + text: "This image will be displayed on secure Inventory and Marketplace Checkout dialogs.
If you don't see your selected image on these dialogs, do not use them!
"; + // Text size + size: 16; + // Anchors + anchors.top: parent.top; + anchors.topMargin: 4; + anchors.left: parent.left; + anchors.leftMargin: 16; + anchors.right: parent.right; + anchors.rightMargin: 16; + // Style + color: hifi.colors.lightGrayText; + wrapMode: Text.WordWrap; + // Alignment + horizontalAlignment: Text.AlignHLeft; + verticalAlignment: Text.AlignVCenter; + } + + // Separator + HifiControlsUit.Separator { + anchors.left: parent.left; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + } + } + // + // EXPLANATION END + // + + // + // SECURITY IMAGE GRID START + // + Item { + id: securityImageGridContainer; + // Anchors + anchors.left: parent.left; + anchors.leftMargin: 8; + anchors.right: parent.right; + anchors.rightMargin: 8; + anchors.top: explanationContainer.bottom; + anchors.topMargin: 8; + anchors.bottom: actionButtonsContainer.top; + anchors.bottomMargin: 8; + + SecurityImageModel { + id: gridModel; + } + + GridView { + id: securityImageGrid; + clip: true; + // Anchors + anchors.fill: parent; + currentIndex: -1; + cellWidth: width / 2; + cellHeight: height / 3; + model: gridModel; + delegate: Item { + width: securityImageGrid.cellWidth; + height: securityImageGrid.cellHeight; + Item { + anchors.fill: parent; + Image { + width: parent.width - 8; + height: parent.height - 8; + source: sourcePath; + anchors.horizontalCenter: parent.horizontalCenter; + anchors.verticalCenter: parent.verticalCenter; + fillMode: Image.PreserveAspectFit; + mipmap: true; + } + } + MouseArea { + anchors.fill: parent; + onClicked: { + securityImageGrid.currentIndex = index; + } + } + } + highlight: Rectangle { + width: securityImageGrid.cellWidth; + height: securityImageGrid.cellHeight; + color: hifi.colors.blueHighlight; + } + } + } + // + // SECURITY IMAGE GRID END + // + + + // + // ACTION BUTTONS START + // + Item { + id: actionButtonsContainer; + // Size + width: securityImageSelectionRoot.width; + height: 40; + // Anchors + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + + // "Cancel" button + HifiControlsUit.Button { + id: cancelButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.left: parent.left; + anchors.leftMargin: 20; + width: parent.width/2 - anchors.leftMargin*2; + text: "Cancel" + onClicked: { + if (!securityImageSelectionRoot.isManuallyChangingSecurityImage) { + sendToScript({method: 'securityImageSelection_cancelClicked', referrerURL: securityImageSelectionRoot.referrerURL}); + } else { + securityImageSelectionRoot.visible = false; + } + } + } + + // "Confirm" button + HifiControlsUit.Button { + id: confirmButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.top: parent.top; + anchors.topMargin: 3; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 3; + anchors.right: parent.right; + anchors.rightMargin: 20; + width: parent.width/2 - anchors.rightMargin*2; + text: "Confirm"; + onClicked: { + securityImageSelectionRoot.isManuallyChangingSecurityImage = false; + commerce.chooseSecurityImage(gridModel.get(securityImageGrid.currentIndex).securityImageEnumValue); + } + } + } + // + // ACTION BUTTONS END + // + + // + // FUNCTION DEFINITIONS START + // + signal sendToScript(var message); + + function getImagePathFromImageID(imageID) { + return (imageID ? gridModel.get(imageID - 1).sourcePath : ""); + } + // + // FUNCTION DEFINITIONS END + // +} diff --git a/interface/resources/qml/hifi/commerce/images/01cat.jpg b/interface/resources/qml/hifi/commerce/images/01cat.jpg new file mode 100644 index 0000000000..6e7897cb82 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/01cat.jpg differ diff --git a/interface/resources/qml/hifi/commerce/images/02car.jpg b/interface/resources/qml/hifi/commerce/images/02car.jpg new file mode 100644 index 0000000000..5dd8091e57 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/02car.jpg differ diff --git a/interface/resources/qml/hifi/commerce/images/03dog.jpg b/interface/resources/qml/hifi/commerce/images/03dog.jpg new file mode 100644 index 0000000000..4a85b80c0c Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/03dog.jpg differ diff --git a/interface/resources/qml/hifi/commerce/images/04stars.jpg b/interface/resources/qml/hifi/commerce/images/04stars.jpg new file mode 100644 index 0000000000..8f2bf62f83 Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/04stars.jpg differ diff --git a/interface/resources/qml/hifi/commerce/images/05plane.jpg b/interface/resources/qml/hifi/commerce/images/05plane.jpg new file mode 100644 index 0000000000..6504459d8b Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/05plane.jpg differ diff --git a/interface/resources/qml/hifi/commerce/images/06gingerbread.jpg b/interface/resources/qml/hifi/commerce/images/06gingerbread.jpg new file mode 100644 index 0000000000..54c37faa2f Binary files /dev/null and b/interface/resources/qml/hifi/commerce/images/06gingerbread.jpg differ diff --git a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml index 12e8de3bfc..76484cf8c7 100644 --- a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml +++ b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml @@ -28,6 +28,11 @@ ScrollingWindow { HifiConstants { id: hifi } + // This is for JS/QML communication, which is unused in the AttachmentsDialog, + // but not having this here results in spurious warnings about a + // missing signal + signal sendToScript(var message); + property var settings: Settings { category: "AttachmentsDialog" property alias x: root.x diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2cf57600e8..358362abd8 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -225,6 +225,7 @@ static const int MIN_PROCESSING_THREAD_POOL_SIZE = 1; static const QString SNAPSHOT_EXTENSION = ".jpg"; static const QString SVO_EXTENSION = ".svo"; static const QString SVO_JSON_EXTENSION = ".svo.json"; +static const QString JSON_GZ_EXTENSION = ".json.gz"; static const QString JSON_EXTENSION = ".json"; static const QString JS_EXTENSION = ".js"; static const QString FST_EXTENSION = ".fst"; @@ -258,6 +259,8 @@ static const QString DESKTOP_DISPLAY_PLUGIN_NAME = "Desktop"; static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system"; +static const QString DOMAIN_SPAWNING_POINT = "/0, -10, 0"; + const QHash Application::_acceptedExtensions { { SVO_EXTENSION, &Application::importSVOFromURL }, { SVO_JSON_EXTENSION, &Application::importSVOFromURL }, @@ -265,6 +268,7 @@ const QHash Application::_acceptedExtensi { JSON_EXTENSION, &Application::importJSONFromURL }, { JS_EXTENSION, &Application::askToLoadScript }, { FST_EXTENSION, &Application::askToSetAvatarUrl }, + { JSON_GZ_EXTENSION, &Application::askToReplaceDomainContent }, { ZIP_EXTENSION, &Application::importFromZIP } }; @@ -2811,7 +2815,6 @@ 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)) { @@ -6198,6 +6201,55 @@ bool Application::askToWearAvatarAttachmentUrl(const QString& url) { return true; } +bool Application::askToReplaceDomainContent(const QString& url) { + QString methodDetails; + if (DependencyManager::get()->getThisNodeCanReplaceContent()) { + QUrl originURL { url }; + if (originURL.host().endsWith(MARKETPLACE_CDN_HOSTNAME)) { + // Create a confirmation dialog when this call is made + const int MAX_CHARACTERS_PER_LINE = 90; + static const QString infoText = simpleWordWrap("Your domain's content will be replaced with a new content set. " + "If you want to save what you have now, create a backup before proceeding. For more information about backing up " + "and restoring content, visit the documentation page at: ", MAX_CHARACTERS_PER_LINE) + + "\nhttps://docs.highfidelity.com/create-and-explore/start-working-in-your-sandbox/restoring-sandbox-content"; + + bool agreeToReplaceContent = false; // assume false + agreeToReplaceContent = QMessageBox::Yes == OffscreenUi::question("Are you sure you want to replace this domain's content set?", + infoText, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + + if (agreeToReplaceContent) { + // Given confirmation, send request to domain server to replace content + qCDebug(interfaceapp) << "Attempting to replace domain content: " << url; + QByteArray urlData(url.toUtf8()); + auto limitedNodeList = DependencyManager::get(); + limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) { + return node->getType() == NodeType::EntityServer && node->getActiveSocket(); + }, [&urlData, limitedNodeList](const SharedNodePointer& octreeNode) { + auto octreeFilePacket = NLPacket::create(PacketType::OctreeFileReplacementFromUrl, urlData.size(), true); + octreeFilePacket->write(urlData); + limitedNodeList->sendPacket(std::move(octreeFilePacket), *octreeNode); + }); + DependencyManager::get()->handleLookupString(DOMAIN_SPAWNING_POINT); + methodDetails = "SuccessfulRequestToReplaceContent"; + } else { + methodDetails = "UserDeclinedToReplaceContent"; + } + } else { + methodDetails = "ContentSetDidNotOriginateFromMarketplace"; + } + } else { + methodDetails = "UserDoesNotHavePermissionToReplaceContent"; + OffscreenUi::warning("Unable to replace content", "You do not have permissions to replace domain content", + QMessageBox::Ok, QMessageBox::Ok); + } + QJsonObject messageProperties = { + { "status", methodDetails }, + { "content_set_url", url } + }; + UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties); + return true; +} + void Application::displayAvatarAttachmentWarning(const QString& message) const { auto avatarAttachmentWarningTitle = tr("Avatar Attachment Failure"); OffscreenUi::warning(avatarAttachmentWarningTitle, message); @@ -6886,6 +6938,12 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa }); } +void Application::takeSecondaryCameraSnapshot() { + postLambdaEvent([this] { + Snapshot::saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot()); + }); +} + void Application::shareSnapshot(const QString& path, const QUrl& href) { postLambdaEvent([path, href] { // not much to do here, everything is done in snapshot code... diff --git a/interface/src/Application.h b/interface/src/Application.h index 06670775fd..e1157c7b04 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -274,6 +274,7 @@ public: float getAverageSimsPerSecond() const { return _simCounter.rate(); } void takeSnapshot(bool notify, bool includeAnimated = false, float aspectRatio = 0.0f); + void takeSecondaryCameraSnapshot(); void shareSnapshot(const QString& filename, const QUrl& href = QUrl("")); model::SkyboxPointer getDefaultSkybox() const { return _defaultSkybox; } @@ -431,6 +432,8 @@ private slots: void displayAvatarAttachmentWarning(const QString& message) const; bool displayAvatarAttachmentConfirmationDialog(const QString& name) const; + bool askToReplaceDomainContent(const QString& url); + void setSessionUUID(const QUuid& sessionUUID) const; void domainChanged(const QString& domainHostname); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 56bd3eb749..2c4a515736 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -555,6 +555,8 @@ Menu::Menu() { avatar.get(), SLOT(setEnableDebugDrawIKConstraints(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderIKChains, 0, false, avatar.get(), SLOT(setEnableDebugDrawIKChains(bool))); + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderDetailedCollision, 0, false, + avatar.get(), SLOT(setEnableDebugDrawDetailedCollision(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ActionMotorControl, Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar.get(), SLOT(updateMotionBehaviorFromMenu()), diff --git a/interface/src/Menu.h b/interface/src/Menu.h index a81ef9ac86..4e21cfa4ac 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -162,6 +162,7 @@ namespace MenuOption { const QString RenderIKTargets = "Show IK Targets"; const QString RenderIKConstraints = "Show IK Constraints"; const QString RenderIKChains = "Show IK Chains"; + const QString RenderDetailedCollision = "Show Detailed Collision"; const QString ResetAvatarSize = "Reset Avatar Size"; const QString ResetSensors = "Reset Sensors"; const QString RunningScripts = "Running Scripts..."; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 7f46a9e3bf..30c6a85470 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1060,6 +1060,10 @@ void MyAvatar::setEnableDebugDrawIKConstraints(bool isEnabled) { _enableDebugDrawIKConstraints = isEnabled; } +void MyAvatar::setEnableDebugDrawDetailedCollision(bool isEnabled) { + _enableDebugDrawDetailedCollision = isEnabled; +} + void MyAvatar::setEnableDebugDrawIKChains(bool isEnabled) { _enableDebugDrawIKChains = isEnabled; } @@ -1805,6 +1809,37 @@ void MyAvatar::postUpdate(float deltaTime) { AnimPose postUpdateRoomPose(_sensorToWorldMatrix); updateHoldActions(_prePhysicsRoomPose, postUpdateRoomPose); + + if (_enableDebugDrawDetailedCollision) { + AnimPose rigToWorldPose(glm::vec3(1.0f), getRotation() * Quaternions::Y_180, getPosition()); + const int NUM_DEBUG_COLORS = 8; + const glm::vec4 DEBUG_COLORS[NUM_DEBUG_COLORS] = { + glm::vec4(1.0f, 1.0f, 1.0f, 1.0f), + glm::vec4(1.0f, 0.0f, 0.0f, 1.0f), + glm::vec4(0.0f, 1.0f, 0.0f, 1.0f), + glm::vec4(0.25f, 0.25f, 1.0f, 1.0f), + glm::vec4(1.0f, 1.0f, 0.0f, 1.0f), + glm::vec4(0.25f, 1.0f, 1.0f, 1.0f), + glm::vec4(1.0f, 0.25f, 1.0f, 1.0f), + glm::vec4(1.0f, 0.65f, 0.0f, 1.0f) // Orange you glad I added this color? + }; + + if (_skeletonModel && _skeletonModel->isLoaded()) { + const Rig& rig = _skeletonModel->getRig(); + const FBXGeometry& geometry = _skeletonModel->getFBXGeometry(); + for (int i = 0; i < rig.getJointStateCount(); i++) { + AnimPose jointPose; + rig.getAbsoluteJointPoseInRigFrame(i, jointPose); + const FBXJointShapeInfo& shapeInfo = geometry.joints[i].shapeInfo; + const AnimPose pose = rigToWorldPose * jointPose; + for (size_t j = 0; j < shapeInfo.debugLines.size() / 2; j++) { + glm::vec3 pointA = pose.xformPoint(shapeInfo.debugLines[2 * j]); + glm::vec3 pointB = pose.xformPoint(shapeInfo.debugLines[2 * j + 1]); + DebugDraw::getInstance().drawRay(pointA, pointB, DEBUG_COLORS[i % NUM_DEBUG_COLORS]); + } + } + } + } } void MyAvatar::preDisplaySide(RenderArgs* renderArgs) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index dc4357be52..c7fe309894 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -553,6 +553,7 @@ public slots: void setEnableDebugDrawIKTargets(bool isEnabled); void setEnableDebugDrawIKConstraints(bool isEnabled); void setEnableDebugDrawIKChains(bool isEnabled); + void setEnableDebugDrawDetailedCollision(bool isEnabled); bool getEnableMeshVisible() const { return _skeletonModel->isVisible(); } void setEnableMeshVisible(bool isEnabled); @@ -757,6 +758,7 @@ private: bool _enableDebugDrawIKTargets { false }; bool _enableDebugDrawIKConstraints { false }; bool _enableDebugDrawIKChains { false }; + bool _enableDebugDrawDetailedCollision { false }; AudioListenerMode _audioListenerMode; glm::vec3 _customListenPosition; diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 6d468c3f30..89e4368515 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -124,12 +124,26 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { } } - params.bodyCapsuleRadius = myAvatar->getCharacterController()->getCapsuleRadius(); - params.bodyCapsuleHalfHeight = myAvatar->getCharacterController()->getCapsuleHalfHeight(); - params.bodyCapsuleLocalOffset = myAvatar->getCharacterController()->getCapsuleLocalOffset(); - params.isTalking = head->getTimeWithoutTalking() <= 1.5f; + // pass detailed torso k-dops to rig. + int hipsJoint = _rig.indexOfJoint("Hips"); + if (hipsJoint >= 0) { + params.hipsShapeInfo = geometry.joints[hipsJoint].shapeInfo; + } + int spineJoint = _rig.indexOfJoint("Spine"); + if (spineJoint >= 0) { + params.spineShapeInfo = geometry.joints[spineJoint].shapeInfo; + } + int spine1Joint = _rig.indexOfJoint("Spine1"); + if (spine1Joint >= 0) { + params.spine1ShapeInfo = geometry.joints[spine1Joint].shapeInfo; + } + int spine2Joint = _rig.indexOfJoint("Spine2"); + if (spine2Joint >= 0) { + params.spine2ShapeInfo = geometry.joints[spine2Joint].shapeInfo; + } + _rig.updateFromControllerParameters(params, deltaTime); Rig::CharacterControllerState ccState = convertCharacterControllerState(myAvatar->getCharacterController()->getState()); diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index ad79a836ad..8d7d47aca0 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -24,12 +24,12 @@ void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, cons transaction["inventory_key"] = inventory_key; transaction["inventory_buyer_username"] = buyerUsername; QJsonDocument transactionDoc{ transaction }; - QString transactionString = transactionDoc.toJson(QJsonDocument::Compact); - + auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); + auto wallet = DependencyManager::get(); QString signature = wallet->signWithKey(transactionString, hfc_key); QJsonObject request; - request["transaction"] = transactionString; + request["transaction"] = QString(transactionString); request["signature"] = signature; qCInfo(commerce) << "Transaction:" << QJsonDocument(request).toJson(QJsonDocument::Compact); @@ -78,4 +78,4 @@ void Ledger::inventory(const QStringList& keys) { inventoryObject.insert("assets", _inventory); qCInfo(commerce) << "Inventory:" << inventoryObject; emit inventoryResult(inventoryObject, ""); -} \ No newline at end of file +} diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 63bfca4f31..573740727f 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -19,9 +19,11 @@ HIFI_QML_DEF(QmlCommerce) QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { auto ledger = DependencyManager::get(); + auto wallet = DependencyManager::get(); connect(ledger.data(), &Ledger::buyResult, this, &QmlCommerce::buyResult); connect(ledger.data(), &Ledger::balanceResult, this, &QmlCommerce::balanceResult); connect(ledger.data(), &Ledger::inventoryResult, this, &QmlCommerce::inventoryResult); + connect(wallet.data(), &Wallet::securityImageResult, this, &QmlCommerce::securityImageResult); } void QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUsername) { @@ -48,4 +50,13 @@ void QmlCommerce::inventory() { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); ledger->inventory(wallet->listPublicKeys()); - } \ No newline at end of file +} + +void QmlCommerce::chooseSecurityImage(uint imageID) { + auto wallet = DependencyManager::get(); + wallet->chooseSecurityImage(imageID); +} +void QmlCommerce::getSecurityImage() { + auto wallet = DependencyManager::get(); + wallet->getSecurityImage(); +} \ No newline at end of file diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 0b1d232fd7..5b702bfeff 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -30,11 +30,14 @@ signals: // because we can't scalably know of out-of-band changes (e.g., another machine interacting with the block chain). void balanceResult(int balance, const QString& failureMessage); void inventoryResult(QJsonObject inventory, const QString& failureMessage); + void securityImageResult(uint imageID); protected: Q_INVOKABLE void buy(const QString& assetId, int cost, const QString& buyerUsername = ""); Q_INVOKABLE void balance(); Q_INVOKABLE void inventory(); + Q_INVOKABLE void chooseSecurityImage(uint imageID); + Q_INVOKABLE void getSecurityImage(); }; #endif // hifi_QmlCommerce_h diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 34d89b54b0..f47b174d88 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -9,30 +9,223 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include #include "CommerceLogging.h" #include "Ledger.h" #include "Wallet.h" +#include + +#include + +#include +#include +#include +#include +#include +#include + +static const char* KEY_FILE = "hifikey"; + +void initialize() { + static bool initialized = false; + if (!initialized) { + SSL_load_error_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); + initialized = true; + } +} + +QString keyFilePath() { + return PathUtils::getAppDataFilePath(KEY_FILE); +} + +// for now the callback function just returns the same string. Later we can hook +// this to the gui (some thought required) +int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) { + // just return a hardcoded pwd for now + static const char* pwd = "pwd"; + strcpy(password, pwd); + return static_cast(strlen(pwd)); +} + +// BEGIN copied code - this will be removed/changed at some point soon +// copied (without emits for various signals) from libraries/networking/src/RSAKeypairGenerator.cpp. +// We will have a different implementation in practice, but this gives us a start for now +QPair generateRSAKeypair() { + + RSA* keyPair = RSA_new(); + BIGNUM* exponent = BN_new(); + QPair retval; + + const unsigned long RSA_KEY_EXPONENT = 65537; + BN_set_word(exponent, RSA_KEY_EXPONENT); + + // seed the random number generator before we call RSA_generate_key_ex + srand(time(NULL)); + + const int RSA_KEY_BITS = 2048; + + if (!RSA_generate_key_ex(keyPair, RSA_KEY_BITS, exponent, NULL)) { + qCDebug(commerce) << "Error generating 2048-bit RSA Keypair -" << ERR_get_error(); + + // we're going to bust out of here but first we cleanup the BIGNUM + BN_free(exponent); + return retval; + } + + // we don't need the BIGNUM anymore so clean that up + BN_free(exponent); + + // grab the public key and private key from the file + unsigned char* publicKeyDER = NULL; + int publicKeyLength = i2d_RSAPublicKey(keyPair, &publicKeyDER); + + unsigned char* privateKeyDER = NULL; + int privateKeyLength = i2d_RSAPrivateKey(keyPair, &privateKeyDER); + + if (publicKeyLength <= 0 || privateKeyLength <= 0) { + qCDebug(commerce) << "Error getting DER public or private key from RSA struct -" << ERR_get_error(); + + + // cleanup the RSA struct + RSA_free(keyPair); + + // cleanup the public and private key DER data, if required + if (publicKeyLength > 0) { + OPENSSL_free(publicKeyDER); + } + + if (privateKeyLength > 0) { + OPENSSL_free(privateKeyDER); + } + + return retval; + } + + + + // now lets persist them to files + // TODO: figure out a scheme for multiple keys, etc... + FILE* fp; + if ((fp = fopen(keyFilePath().toStdString().c_str(), "wt"))) { + if (!PEM_write_RSAPublicKey(fp, keyPair)) { + fclose(fp); + qCDebug(commerce) << "failed to write public key"; + return retval; + } + + if (!PEM_write_RSAPrivateKey(fp, keyPair, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) { + fclose(fp); + qCDebug(commerce) << "failed to write private key"; + return retval; + } + fclose(fp); + } + + RSA_free(keyPair); + + // prepare the return values + retval.first = new QByteArray(reinterpret_cast(publicKeyDER), publicKeyLength ), + retval.second = new QByteArray(reinterpret_cast(privateKeyDER), privateKeyLength ); + + // cleanup the publicKeyDER and publicKeyDER data + OPENSSL_free(publicKeyDER); + OPENSSL_free(privateKeyDER); + return retval; +} +// END copied code (which will soon change) + +// the public key can just go into a byte array +QByteArray readPublicKey(const char* filename) { + FILE* fp; + RSA* key = NULL; + if ((fp = fopen(filename, "r"))) { + // file opened successfully + qCDebug(commerce) << "opened key file" << filename; + if ((key = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL))) { + // file read successfully + unsigned char* publicKeyDER = NULL; + int publicKeyLength = i2d_RSAPublicKey(key, &publicKeyDER); + // TODO: check for 0 length? + + // cleanup + RSA_free(key); + fclose(fp); + + qCDebug(commerce) << "parsed public key file successfully"; + + QByteArray retval((char*)publicKeyDER, publicKeyLength); + OPENSSL_free(publicKeyDER); + return retval; + } else { + qCDebug(commerce) << "couldn't parse" << filename; + } + fclose(fp); + } else { + qCDebug(commerce) << "couldn't open" << filename; + } + return QByteArray(); +} + +// the private key should be read/copied into heap memory. For now, we need the RSA struct +// so I'll return that. Note we need to RSA_free(key) later!!! +RSA* readPrivateKey(const char* filename) { + FILE* fp; + RSA* key = NULL; + if ((fp = fopen(filename, "r"))) { + // file opened successfully + qCDebug(commerce) << "opened key file" << filename; + if ((key = PEM_read_RSAPrivateKey(fp, &key, passwordCallback, NULL))) { + // cleanup + fclose(fp); + + qCDebug(commerce) << "parsed private key file successfully"; + + } else { + qCDebug(commerce) << "couldn't parse" << filename; + } + fclose(fp); + } else { + qCDebug(commerce) << "couldn't open" << filename; + } + return key; +} + bool Wallet::createIfNeeded() { - // FIXME: persist in file between sessions. if (_publicKeys.count() > 0) return false; + + // FIXME: initialize OpenSSL elsewhere soon + initialize(); + + // try to read existing keys if they exist... + auto publicKey = readPublicKey(keyFilePath().toStdString().c_str()); + if (publicKey.size() > 0) { + if (auto key = readPrivateKey(keyFilePath().toStdString().c_str()) ) { + qCDebug(commerce) << "read private key"; + RSA_free(key); + // K -- add the public key since we have a legit private key associated with it + _publicKeys.push_back(QUrl::toPercentEncoding(publicKey.toBase64())); + return false; + } + } qCInfo(commerce) << "Creating wallet."; return generateKeyPair(); } bool Wallet::generateKeyPair() { - // FIXME: need private key, too, and persist in file. qCInfo(commerce) << "Generating keypair."; - QString key = QUuid::createUuid().toString(); + auto keyPair = generateRSAKeypair(); + + _publicKeys.push_back(QUrl::toPercentEncoding(keyPair.first->toBase64())); + qCDebug(commerce) << "public key:" << _publicKeys.last(); - _publicKeys.push_back(key); // Keep in memory for synchronous speed. // It's arguable whether we want to change the receiveAt every time, but: // 1. It's certainly needed the first time, when createIfNeeded answers true. // 2. It is maximally private, and we can step back from that later if desired. // 3. It maximally exercises all the machinery, so we are most likely to surface issues now. auto ledger = DependencyManager::get(); - return ledger->receiveAt(key); + return ledger->receiveAt(_publicKeys.last()); } QStringList Wallet::listPublicKeys() { qCInfo(commerce) << "Enumerating public keys."; @@ -40,7 +233,44 @@ QStringList Wallet::listPublicKeys() { return _publicKeys; } -QString Wallet::signWithKey(const QString& text, const QString& key) { +// for now a copy of how we sign in libraries/networking/src/DataServerAccountInfo - +// we sha256 the text, read the private key from disk (for now!), and return the signed +// sha256. Note later with multiple keys, we may need the key parameter (or something +// similar) so I left it alone for now. Also this will probably change when we move +// away from RSA keys anyways. Note that since this returns a QString, we better avoid +// the horror of code pages and so on (changing the bytes) by just returning a base64 +// encoded string representing the signature (suitable for http, etc...) +QString Wallet::signWithKey(const QByteArray& text, const QString& key) { qCInfo(commerce) << "Signing text."; - return "fixme signed"; -} \ No newline at end of file + RSA* rsaPrivateKey = NULL; + if ((rsaPrivateKey = readPrivateKey(keyFilePath().toStdString().c_str()))) { + QByteArray signature(RSA_size(rsaPrivateKey), 0); + unsigned int signatureBytes = 0; + + QByteArray hashedPlaintext = QCryptographicHash::hash(text, QCryptographicHash::Sha256); + + int encryptReturn = RSA_sign(NID_sha256, + reinterpret_cast(hashedPlaintext.constData()), + hashedPlaintext.size(), + reinterpret_cast(signature.data()), + &signatureBytes, + rsaPrivateKey); + + // free the private key RSA struct now that we are done with it + RSA_free(rsaPrivateKey); + + if (encryptReturn != -1) { + return QUrl::toPercentEncoding(signature.toBase64()); + } + } + return QString(); +} + + +void Wallet::chooseSecurityImage(uint imageID) { + _chosenSecurityImage = (SecurityImage)imageID; + emit securityImageResult(imageID); +} +void Wallet::getSecurityImage() { + emit securityImageResult(_chosenSecurityImage); +} diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index 79de5e81da..a1c7c7752b 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -25,10 +25,30 @@ public: bool createIfNeeded(); bool generateKeyPair(); QStringList listPublicKeys(); - QString signWithKey(const QString& text, const QString& key); + QString signWithKey(const QByteArray& text, const QString& key); + void chooseSecurityImage(uint imageID); + void getSecurityImage(); + +signals: + void securityImageResult(uint imageID); + +protected: + // ALWAYS add SecurityImage enum values to the END of the enum. + // They must be in the same order as the images are listed in + // SecurityImageSelection.qml + enum SecurityImage { + NONE = 0, + Cat, + Car, + Dog, + Stars, + Plane, + Gingerbread + }; private: QStringList _publicKeys{}; + SecurityImage _chosenSecurityImage = SecurityImage::NONE; }; #endif // hifi_Wallet_h diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 84f4cbbbd8..277989439c 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -294,6 +294,10 @@ void WindowScriptingInterface::takeSnapshot(bool notify, bool includeAnimated, f qApp->takeSnapshot(notify, includeAnimated, aspectRatio); } +void WindowScriptingInterface::takeSecondaryCameraSnapshot() { + qApp->takeSecondaryCameraSnapshot(); +} + void WindowScriptingInterface::shareSnapshot(const QString& path, const QUrl& href) { qApp->shareSnapshot(path, href); } diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index f8ed20f42f..28f1bafa5d 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -60,6 +60,7 @@ public slots: void showAssetServer(const QString& upload = ""); void copyToClipboard(const QString& text); void takeSnapshot(bool notify = true, bool includeAnimated = false, float aspectRatio = 0.0f); + void takeSecondaryCameraSnapshot(); void makeConnection(bool success, const QString& userNameOrError); void displayAnnouncement(const QString& message); void shareSnapshot(const QString& path, const QUrl& href = QUrl("")); diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 21f98d3e01..1b91737364 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -168,6 +168,8 @@ void Rig::destroyAnimGraph() { void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOffset) { _geometryOffset = AnimPose(geometry.offset); _invGeometryOffset = _geometryOffset.inverse(); + _geometryToRigTransform = modelOffset * geometry.offset; + _rigToGeometryTransform = glm::inverse(_geometryToRigTransform); setModelOffset(modelOffset); _animSkeleton = std::make_shared(geometry); @@ -1099,35 +1101,139 @@ void Rig::updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headPos } } -void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool leftArmEnabled, bool rightArmEnabled, float dt, - const AnimPose& leftHandPose, const AnimPose& rightHandPose, - float bodyCapsuleRadius, float bodyCapsuleHalfHeight, const glm::vec3& bodyCapsuleLocalOffset) { +const float INV_SQRT_3 = 1.0f / sqrtf(3.0f); +const int DOP14_COUNT = 14; +const glm::vec3 DOP14_NORMALS[DOP14_COUNT] = { + Vectors::UNIT_X, + -Vectors::UNIT_X, + Vectors::UNIT_Y, + -Vectors::UNIT_Y, + Vectors::UNIT_Z, + -Vectors::UNIT_Z, + glm::vec3(INV_SQRT_3, INV_SQRT_3, INV_SQRT_3), + -glm::vec3(INV_SQRT_3, INV_SQRT_3, INV_SQRT_3), + glm::vec3(INV_SQRT_3, -INV_SQRT_3, INV_SQRT_3), + -glm::vec3(INV_SQRT_3, -INV_SQRT_3, INV_SQRT_3), + glm::vec3(INV_SQRT_3, INV_SQRT_3, -INV_SQRT_3), + -glm::vec3(INV_SQRT_3, INV_SQRT_3, -INV_SQRT_3), + glm::vec3(INV_SQRT_3, -INV_SQRT_3, -INV_SQRT_3), + -glm::vec3(INV_SQRT_3, -INV_SQRT_3, -INV_SQRT_3) +}; - // Use this capsule to represent the avatar body. - int hipsIndex = indexOfJoint("Hips"); - glm::vec3 hipsTrans; - if (hipsIndex >= 0) { - hipsTrans = _internalPoseSet._absolutePoses[hipsIndex].trans(); +// returns true if the given point lies inside of the k-dop, specified by shapeInfo & shapePose. +// if the given point does lie within the k-dop, it also returns the amount of displacement necessary to push that point outward +// such that it lies on the surface of the kdop. +static bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& shapePose, const FBXJointShapeInfo& shapeInfo, glm::vec3& displacementOut) { + + // transform point into local space of jointShape. + glm::vec3 localPoint = shapePose.inverse().xformPoint(point); + + // Only works for 14-dop shape infos. + assert(shapeInfo.dots.size() == DOP14_COUNT); + if (shapeInfo.dots.size() != DOP14_COUNT) { + return false; } - const glm::vec3 bodyCapsuleCenter = hipsTrans - bodyCapsuleLocalOffset; - const glm::vec3 bodyCapsuleStart = bodyCapsuleCenter - glm::vec3(0, bodyCapsuleHalfHeight, 0); - const glm::vec3 bodyCapsuleEnd = bodyCapsuleCenter + glm::vec3(0, bodyCapsuleHalfHeight, 0); + glm::vec3 minDisplacement(FLT_MAX); + float minDisplacementLen = FLT_MAX; + glm::vec3 p = localPoint - shapeInfo.avgPoint; + float pLen = glm::length(p); + if (pLen > 0.0f) { + int slabCount = 0; + for (int i = 0; i < DOP14_COUNT; i++) { + float dot = glm::dot(p, DOP14_NORMALS[i]); + if (dot > 0.0f && dot < shapeInfo.dots[i]) { + slabCount++; + float distToPlane = pLen * (shapeInfo.dots[i] / dot); + float displacementLen = distToPlane - pLen; + + // keep track of the smallest displacement + if (displacementLen < minDisplacementLen) { + minDisplacementLen = displacementLen; + minDisplacement = (p / pLen) * displacementLen; + } + } + } + if (slabCount == (DOP14_COUNT / 2) && minDisplacementLen != FLT_MAX) { + // we are within the k-dop so push the point along the minimum displacement found + displacementOut = shapePose.xformVectorFast(minDisplacement); + return true; + } else { + // point is outside of kdop + return false; + } + } else { + // point is directly on top of shapeInfo.avgPoint. + // push the point out along the x axis. + displacementOut = shapePose.xformVectorFast(shapeInfo.points[0]); + return true; + } +} + +glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, + const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) const { + glm::vec3 position = handPosition; + glm::vec3 displacement; + int hipsJoint = indexOfJoint("Hips"); + if (hipsJoint >= 0) { + AnimPose hipsPose; + if (getAbsoluteJointPoseInRigFrame(hipsJoint, hipsPose)) { + if (findPointKDopDisplacement(position, hipsPose, hipsShapeInfo, displacement)) { + position += displacement; + } + } + } + + int spineJoint = indexOfJoint("Spine"); + if (spineJoint >= 0) { + AnimPose spinePose; + if (getAbsoluteJointPoseInRigFrame(spineJoint, spinePose)) { + if (findPointKDopDisplacement(position, spinePose, spineShapeInfo, displacement)) { + position += displacement; + } + } + } + + int spine1Joint = indexOfJoint("Spine1"); + if (spine1Joint >= 0) { + AnimPose spine1Pose; + if (getAbsoluteJointPoseInRigFrame(spine1Joint, spine1Pose)) { + if (findPointKDopDisplacement(position, spine1Pose, spine1ShapeInfo, displacement)) { + position += displacement; + } + } + } + + int spine2Joint = indexOfJoint("Spine2"); + if (spine2Joint >= 0) { + AnimPose spine2Pose; + if (getAbsoluteJointPoseInRigFrame(spine2Joint, spine2Pose)) { + if (findPointKDopDisplacement(position, spine2Pose, spine2ShapeInfo, displacement)) { + position += displacement; + } + } + } + + return position; +} + +void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool leftArmEnabled, bool rightArmEnabled, float dt, + const AnimPose& leftHandPose, const AnimPose& rightHandPose, + const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, + const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) { - const float HAND_RADIUS = 0.05f; const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.95f; + int hipsIndex = indexOfJoint("Hips"); + if (leftHandEnabled) { glm::vec3 handPosition = leftHandPose.trans(); glm::quat handRotation = leftHandPose.rot(); if (!hipsEnabled) { - // prevent the hand IK targets from intersecting the body capsule - glm::vec3 displacement; - if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) { - handPosition -= displacement; - } + // prevent the hand IK targets from intersecting the torso + handPosition = deflectHandFromTorso(handPosition, hipsShapeInfo, spineShapeInfo, spine1ShapeInfo, spine2ShapeInfo); } _animVars.set("leftHandPosition", handPosition); @@ -1173,11 +1279,8 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab glm::quat handRotation = rightHandPose.rot(); if (!hipsEnabled) { - // prevent the hand IK targets from intersecting the body capsule - glm::vec3 displacement; - if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) { - handPosition -= displacement; - } + // prevent the hand IK targets from intersecting the torso + handPosition = deflectHandFromTorso(handPosition, hipsShapeInfo, spineShapeInfo, spine1ShapeInfo, spine2ShapeInfo); } _animVars.set("rightHandPosition", handPosition); @@ -1414,7 +1517,7 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, leftArmEnabled, rightArmEnabled, dt, params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], - params.bodyCapsuleRadius, params.bodyCapsuleHalfHeight, params.bodyCapsuleLocalOffset); + params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo); updateFeet(leftFootEnabled, rightFootEnabled, params.primaryControllerPoses[PrimaryControllerType_LeftFoot], params.primaryControllerPoses[PrimaryControllerType_RightFoot]); @@ -1660,59 +1763,11 @@ void Rig::computeAvatarBoundingCapsule( return; } - AnimInverseKinematics ikNode("boundingShape"); - ikNode.setSkeleton(_animSkeleton); - - ikNode.setTargetVars("LeftHand", "leftHandPosition", "leftHandRotation", - "leftHandType", "leftHandWeight", 1.0f, {}, - QString(), QString(), QString()); - ikNode.setTargetVars("RightHand", "rightHandPosition", "rightHandRotation", - "rightHandType", "rightHandWeight", 1.0f, {}, - QString(), QString(), QString()); - ikNode.setTargetVars("LeftFoot", "leftFootPosition", "leftFootRotation", - "leftFootType", "leftFootWeight", 1.0f, {}, - QString(), QString(), QString()); - ikNode.setTargetVars("RightFoot", "rightFootPosition", "rightFootRotation", - "rightFootType", "rightFootWeight", 1.0f, {}, - QString(), QString(), QString()); glm::vec3 hipsPosition(0.0f); int hipsIndex = indexOfJoint("Hips"); if (hipsIndex >= 0) { hipsPosition = transformPoint(_geometryToRigTransform, _animSkeleton->getAbsoluteDefaultPose(hipsIndex).trans()); } - AnimVariantMap animVars; - animVars.setRigToGeometryTransform(_rigToGeometryTransform); - glm::quat handRotation = glm::angleAxis(PI, Vectors::UNIT_X); - animVars.set("leftHandPosition", hipsPosition); - animVars.set("leftHandRotation", handRotation); - animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition); - animVars.set("rightHandPosition", hipsPosition); - animVars.set("rightHandRotation", handRotation); - animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition); - - int rightFootIndex = indexOfJoint("RightFoot"); - int leftFootIndex = indexOfJoint("LeftFoot"); - if (rightFootIndex != -1 && leftFootIndex != -1) { - glm::vec3 geomFootPosition = glm::vec3(0.0f, _animSkeleton->getAbsoluteDefaultPose(rightFootIndex).trans().y, 0.0f); - glm::vec3 footPosition = transformPoint(_geometryToRigTransform, geomFootPosition); - glm::quat footRotation = glm::angleAxis(0.5f * PI, Vectors::UNIT_X); - animVars.set("leftFootPosition", footPosition); - animVars.set("leftFootRotation", footRotation); - animVars.set("leftFootType", (int)IKTarget::Type::RotationAndPosition); - animVars.set("rightFootPosition", footPosition); - animVars.set("rightFootRotation", footRotation); - animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition); - } - - // call overlay twice: once to verify AnimPoseVec joints and again to do the IK - AnimNode::Triggers triggersOut; - AnimContext context(false, false, false, _geometryToRigTransform, _rigToGeometryTransform); - float dt = 1.0f; // the value of this does not matter - ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses()); - AnimPoseVec finalPoses = ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses()); - - // convert relative poses to absolute - _animSkeleton->convertRelativePosesToAbsolute(finalPoses); // compute bounding box that encloses all points Extents totalExtents; @@ -1723,15 +1778,15 @@ void Rig::computeAvatarBoundingCapsule( // even if they do not have legs (default robot) totalExtents.addPoint(glm::vec3(0.0f)); - // HACK to reduce the radius of the bounding capsule to be tight with the torso, we only consider joints + // To reduce the radius of the bounding capsule to be tight with the torso, we only consider joints // from the head to the hips when computing the rest of the bounding capsule. int index = indexOfJoint("Head"); while (index != -1) { const FBXJointShapeInfo& shapeInfo = geometry.joints.at(index).shapeInfo; - AnimPose pose = finalPoses[index]; + AnimPose pose = _animSkeleton->getAbsoluteDefaultPose(index); if (shapeInfo.points.size() > 0) { - for (int j = 0; j < shapeInfo.points.size(); ++j) { - totalExtents.addPoint((pose * shapeInfo.points[j])); + for (auto& point : shapeInfo.points) { + totalExtents.addPoint((pose * point)); } } index = _animSkeleton->getParentIndex(index); @@ -1745,7 +1800,6 @@ void Rig::computeAvatarBoundingCapsule( radiusOut = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z)); heightOut = diagonal.y - 2.0f * radiusOut; - glm::vec3 rootPosition = finalPoses[geometry.rootJointIndex].trans(); - glm::vec3 rigCenter = transformPoint(_geometryToRigTransform, (0.5f * (totalExtents.maximum + totalExtents.minimum))); - localOffsetOut = rigCenter - transformPoint(_geometryToRigTransform, rootPosition); + glm::vec3 capsuleCenter = transformPoint(_geometryToRigTransform, (0.5f * (totalExtents.maximum + totalExtents.minimum))); + localOffsetOut = capsuleCenter - hipsPosition; } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 7e1504e461..ca55635250 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -75,9 +75,10 @@ public: AnimPose secondaryControllerPoses[NumSecondaryControllerTypes]; // rig space bool secondaryControllerActiveFlags[NumSecondaryControllerTypes]; bool isTalking; - float bodyCapsuleRadius; - float bodyCapsuleHalfHeight; - glm::vec3 bodyCapsuleLocalOffset; + FBXJointShapeInfo hipsShapeInfo; + FBXJointShapeInfo spineShapeInfo; + FBXJointShapeInfo spine1ShapeInfo; + FBXJointShapeInfo spine2ShapeInfo; }; struct EyeParameters { @@ -249,7 +250,8 @@ protected: void updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headMatrix); void updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnabled, bool leftArmEnabled, bool rightArmEnabled, float dt, const AnimPose& leftHandPose, const AnimPose& rightHandPose, - float bodyCapsuleRadius, float bodyCapsuleHalfHeight, const glm::vec3& bodyCapsuleLocalOffset); + const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, + const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo); void updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose); void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAt, const glm::vec3& saccade); @@ -257,6 +259,8 @@ protected: glm::vec3 calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int hipsIndex, bool isLeft) const; glm::vec3 calculateKneePoleVector(int footJointIndex, int kneeJoint, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const; + glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, + const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) const; AnimPose _modelOffset; // model to rig space AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets) diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp index a4777b087b..6a19a34727 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp @@ -34,3 +34,7 @@ void NullDisplayPlugin::submitFrame(const gpu::FramePointer& frame) { QImage NullDisplayPlugin::getScreenshot(float aspectRatio) const { return QImage(); } + +QImage NullDisplayPlugin::getSecondaryCameraScreenshot() const { + return QImage(); +} diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h index 8d01539e8a..97b71b5780 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h @@ -19,6 +19,7 @@ public: bool hasFocus() const override; void submitFrame(const gpu::FramePointer& newFrame) override; QImage getScreenshot(float aspectRatio = 0.0f) const override; + QImage getSecondaryCameraScreenshot() const override; void copyTextureToQuickFramebuffer(NetworkTexturePointer source, QOpenGLFramebufferObject* target, GLsync* fenceSync) override {}; private: static const QString NAME; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index e1259fc5fc..7144e401e4 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -775,6 +775,19 @@ QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const { return screenshot.mirrored(false, true); } +QImage OpenGLDisplayPlugin::getSecondaryCameraScreenshot() const { + auto textureCache = DependencyManager::get(); + auto secondaryCameraFramebuffer = textureCache->getSpectatorCameraFramebuffer(); + gpu::Vec4i region(0, 0, secondaryCameraFramebuffer->getWidth(), secondaryCameraFramebuffer->getHeight()); + + auto glBackend = const_cast(*this).getGLBackend(); + QImage screenshot(region.z, region.w, QImage::Format_ARGB32); + withMainThreadContext([&] { + glBackend->downloadFramebuffer(secondaryCameraFramebuffer, region, screenshot); + }); + return screenshot.mirrored(false, true); +} + glm::uvec2 OpenGLDisplayPlugin::getSurfacePixels() const { uvec2 result; auto window = _container->getPrimaryWidget(); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 2f93fa630d..2080fa5ea6 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -60,6 +60,7 @@ public: virtual bool setDisplayTexture(const QString& name) override; virtual bool onDisplayTextureReset() { return false; }; QImage getScreenshot(float aspectRatio = 0.0f) const override; + QImage getSecondaryCameraScreenshot() const override; float presentRate() const override; diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 6d4f586c52..cd313dbd05 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1682,8 +1682,8 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS int newIndex = it.value(); // remember vertices with at least 1/4 weight - const float EXPANSION_WEIGHT_THRESHOLD = 0.99f; - if (weight > EXPANSION_WEIGHT_THRESHOLD) { + const float EXPANSION_WEIGHT_THRESHOLD = 0.25f; + if (weight >= EXPANSION_WEIGHT_THRESHOLD) { // transform to joint-frame and save for later const glm::mat4 vertexTransform = meshToJoint * glm::translate(extracted.mesh.vertices.at(newIndex)); points.push_back(extractTranslation(vertexTransform) * clusterScale); @@ -1788,6 +1788,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS avgPoint += points[j]; } avgPoint /= (float)points.size(); + joint.shapeInfo.avgPoint = avgPoint; // compute a k-Dop bounding volume for (uint32_t j = 0; j < cardinalDirections.size(); ++j) { @@ -1803,8 +1804,11 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } joint.shapeInfo.points.push_back(avgPoint + maxDot * cardinalDirections[j]); + joint.shapeInfo.dots.push_back(maxDot); joint.shapeInfo.points.push_back(avgPoint + minDot * cardinalDirections[j]); + joint.shapeInfo.dots.push_back(-minDot); } + generateBoundryLinesForDop14(joint.shapeInfo.dots, joint.shapeInfo.avgPoint, joint.shapeInfo.debugLines); } } geometry.palmDirection = parseVec3(mapping.value("palmDirection", "0, -1, 0").toString()); diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index f73088e7a1..170bbbf366 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -56,7 +56,10 @@ public: struct FBXJointShapeInfo { // same units and frame as FBXJoint.translation - QVector points; + glm::vec3 avgPoint; + std::vector dots; + std::vector points; + std::vector debugLines; }; /// A single joint (transformation node) extracted from an FBX document. diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index e03ec5e771..1d094d923c 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -167,6 +167,10 @@ void LimitedNodeList::setPermissions(const NodePermissions& newPermissions) { newPermissions.can(NodePermissions::Permission::canKick)) { emit canKickChanged(_permissions.can(NodePermissions::Permission::canKick)); } + if (originalPermissions.can(NodePermissions::Permission::canReplaceDomainContent) != + newPermissions.can(NodePermissions::Permission::canReplaceDomainContent)) { + emit canReplaceContentChanged(_permissions.can(NodePermissions::Permission::canReplaceDomainContent)); + } } void LimitedNodeList::setSocketLocalPort(quint16 socketLocalPort) { diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 5d602cc0c0..f4ec47636b 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -115,7 +115,8 @@ public: bool getThisNodeCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); } bool getThisNodeCanWriteAssets() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); } bool getThisNodeCanKick() const { return _permissions.can(NodePermissions::Permission::canKick); } - + bool getThisNodeCanReplaceContent() const { return _permissions.can(NodePermissions::Permission::canReplaceDomainContent); } + quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); } Q_INVOKABLE void setSocketLocalPort(quint16 socketLocalPort); @@ -329,6 +330,7 @@ signals: void canRezTmpChanged(bool canRezTmp); void canWriteAssetsChanged(bool canWriteAssets); void canKickChanged(bool canKick); + void canReplaceContentChanged(bool canReplaceContent); protected slots: void connectedForLocalSocketTest(); diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 4d09f077bd..4451ba4abe 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -74,6 +74,7 @@ public: bool getCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); } bool getCanWriteToAssetServer() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); } bool getCanKick() const { return _permissions.can(NodePermissions::Permission::canKick); } + bool getCanReplaceContent() const { return _permissions.can(NodePermissions::Permission::canReplaceDomainContent); } void parseIgnoreRequestMessage(QSharedPointer message); void addIgnoredNode(const QUuid& otherNodeID); diff --git a/libraries/networking/src/NodePermissions.cpp b/libraries/networking/src/NodePermissions.cpp index a1d4fc182e..cc5df515aa 100644 --- a/libraries/networking/src/NodePermissions.cpp +++ b/libraries/networking/src/NodePermissions.cpp @@ -45,6 +45,7 @@ NodePermissions::NodePermissions(QMap perms) { permissions |= perms["id_can_connect_past_max_capacity"].toBool() ? Permission::canConnectPastMaxCapacity : Permission::none; permissions |= perms["id_can_kick"].toBool() ? Permission::canKick : Permission::none; + permissions |= perms["id_can_replace_content"].toBool() ? Permission::canReplaceDomainContent : Permission::none; } QVariant NodePermissions::toVariant(QHash groupRanks) { @@ -65,6 +66,7 @@ QVariant NodePermissions::toVariant(QHash groupRanks) { values["id_can_write_to_asset_server"] = can(Permission::canWriteToAssetServer); values["id_can_connect_past_max_capacity"] = can(Permission::canConnectPastMaxCapacity); values["id_can_kick"] = can(Permission::canKick); + values["id_can_replace_content"] = can(Permission::canReplaceDomainContent); return QVariant(values); } @@ -128,6 +130,9 @@ QDebug operator<<(QDebug debug, const NodePermissions& perms) { if (perms.can(NodePermissions::Permission::canKick)) { debug << " kick"; } + if (perms.can(NodePermissions::Permission::canReplaceDomainContent)) { + debug << " can_replace_content"; + } debug.nospace() << "]"; return debug.nospace(); } diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index 6fa005e360..129d7e5c08 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -77,7 +77,8 @@ public: canRezTemporaryEntities = 8, canWriteToAssetServer = 16, canConnectPastMaxCapacity = 32, - canKick = 64 + canKick = 64, + canReplaceDomainContent = 128 }; Q_DECLARE_FLAGS(Permissions, Permission) Permissions permissions; diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index e2304e62f7..3314e69d78 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -121,6 +121,7 @@ public: ReplicatedAvatarIdentity, ReplicatedKillAvatar, ReplicatedBulkAvatarData, + OctreeFileReplacementFromUrl, NUM_PACKET_TYPE }; diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 9e18ee534d..d7531e66a7 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -183,6 +183,7 @@ public: // Fetch the most recently displayed image as a QImage virtual QImage getScreenshot(float aspectRatio = 0.0f) const = 0; + virtual QImage getSecondaryCameraScreenshot() const = 0; // will query the underlying hmd api to compute the most recent head pose virtual bool beginFrameRender(uint32_t frameIndex) { return true; } diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 4ae907eb3b..6b9718fbb8 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -17,6 +17,7 @@ #include #include "NumericalConstants.h" +#include "GLMHelpers.h" glm::vec3 computeVectorFromPointToSegment(const glm::vec3& point, const glm::vec3& start, const glm::vec3& end) { // compute the projection of the point vector onto the segment vector @@ -657,3 +658,150 @@ bool findPlaneFromPoints(const glm::vec3* points, size_t numPoints, glm::vec3& p planeNormalOut = glm::normalize(dir); return true; } + +bool findIntersectionOfThreePlanes(const glm::vec4& planeA, const glm::vec4& planeB, const glm::vec4& planeC, glm::vec3& intersectionPointOut) { + glm::vec3 normalA(planeA); + glm::vec3 normalB(planeB); + glm::vec3 normalC(planeC); + glm::vec3 u = glm::cross(normalB, normalC); + float denom = glm::dot(normalA, u); + if (fabsf(denom) < EPSILON) { + return false; // planes do not intersect in a point. + } else { + intersectionPointOut = (planeA.w * u + glm::cross(normalA, planeC.w * normalB - planeB.w * normalC)) / denom; + return true; + } +} + +const float INV_SQRT_3 = 1.0f / sqrtf(3.0f); +const int DOP14_COUNT = 14; +const glm::vec3 DOP14_NORMALS[DOP14_COUNT] = { + Vectors::UNIT_X, + -Vectors::UNIT_X, + Vectors::UNIT_Y, + -Vectors::UNIT_Y, + Vectors::UNIT_Z, + -Vectors::UNIT_Z, + glm::vec3(INV_SQRT_3, INV_SQRT_3, INV_SQRT_3), + -glm::vec3(INV_SQRT_3, INV_SQRT_3, INV_SQRT_3), + glm::vec3(INV_SQRT_3, -INV_SQRT_3, INV_SQRT_3), + -glm::vec3(INV_SQRT_3, -INV_SQRT_3, INV_SQRT_3), + glm::vec3(INV_SQRT_3, INV_SQRT_3, -INV_SQRT_3), + -glm::vec3(INV_SQRT_3, INV_SQRT_3, -INV_SQRT_3), + glm::vec3(INV_SQRT_3, -INV_SQRT_3, -INV_SQRT_3), + -glm::vec3(INV_SQRT_3, -INV_SQRT_3, -INV_SQRT_3) +}; + +typedef std::tuple Int3Tuple; +const std::tuple DOP14_PLANE_COMBINATIONS[] = { + Int3Tuple(0, 2, 4), Int3Tuple(0, 2, 5), Int3Tuple(0, 2, 6), Int3Tuple(0, 2, 7), Int3Tuple(0, 2, 8), Int3Tuple(0, 2, 9), Int3Tuple(0, 2, 10), Int3Tuple(0, 2, 11), Int3Tuple(0, 2, 12), Int3Tuple(0, 2, 13), + Int3Tuple(0, 3, 4), Int3Tuple(0, 3, 5), Int3Tuple(0, 3, 6), Int3Tuple(0, 3, 7), Int3Tuple(0, 3, 8), Int3Tuple(0, 3, 9), Int3Tuple(0, 3, 10), Int3Tuple(0, 3, 11), Int3Tuple(0, 3, 12), Int3Tuple(0, 3, 13), + Int3Tuple(0, 4, 6), Int3Tuple(0, 4, 7), Int3Tuple(0, 4, 8), Int3Tuple(0, 4, 9), Int3Tuple(0, 4, 10), Int3Tuple(0, 4, 11), Int3Tuple(0, 4, 12), Int3Tuple(0, 4, 13), + Int3Tuple(0, 5, 6), Int3Tuple(0, 5, 7), Int3Tuple(0, 5, 8), Int3Tuple(0, 5, 9), Int3Tuple(0, 5, 10), Int3Tuple(0, 5, 11), Int3Tuple(0, 5, 12), Int3Tuple(0, 5, 13), + Int3Tuple(0, 6, 8), Int3Tuple(0, 6, 9), Int3Tuple(0, 6, 10), Int3Tuple(0, 6, 11), Int3Tuple(0, 6, 12), Int3Tuple(0, 6, 13), + Int3Tuple(0, 7, 8), Int3Tuple(0, 7, 9), Int3Tuple(0, 7, 10), Int3Tuple(0, 7, 11), Int3Tuple(0, 7, 12), Int3Tuple(0, 7, 13), + Int3Tuple(0, 8, 10), Int3Tuple(0, 8, 11), Int3Tuple(0, 8, 12), Int3Tuple(0, 8, 13), Int3Tuple(0, 9, 10), + Int3Tuple(0, 9, 11), Int3Tuple(0, 9, 12), Int3Tuple(0, 9, 13), + Int3Tuple(0, 10, 12), Int3Tuple(0, 10, 13), + Int3Tuple(0, 11, 12), Int3Tuple(0, 11, 13), + Int3Tuple(1, 2, 4), Int3Tuple(1, 2, 5), Int3Tuple(1, 2, 6), Int3Tuple(1, 2, 7), Int3Tuple(1, 2, 8), Int3Tuple(1, 2, 9), Int3Tuple(1, 2, 10), Int3Tuple(1, 2, 11), Int3Tuple(1, 2, 12), Int3Tuple(1, 2, 13), + Int3Tuple(1, 3, 4), Int3Tuple(1, 3, 5), Int3Tuple(1, 3, 6), Int3Tuple(1, 3, 7), Int3Tuple(1, 3, 8), Int3Tuple(1, 3, 9), Int3Tuple(1, 3, 10), Int3Tuple(1, 3, 11), Int3Tuple(1, 3, 12), Int3Tuple(1, 3, 13), + Int3Tuple(1, 4, 6), Int3Tuple(1, 4, 7), Int3Tuple(1, 4, 8), Int3Tuple(1, 4, 9), Int3Tuple(1, 4, 10), Int3Tuple(1, 4, 11), Int3Tuple(1, 4, 12), Int3Tuple(1, 4, 13), + Int3Tuple(1, 5, 6), Int3Tuple(1, 5, 7), Int3Tuple(1, 5, 8), Int3Tuple(1, 5, 9), Int3Tuple(1, 5, 10), Int3Tuple(1, 5, 11), Int3Tuple(1, 5, 12), Int3Tuple(1, 5, 13), + Int3Tuple(1, 6, 8), Int3Tuple(1, 6, 9), Int3Tuple(1, 6, 10), Int3Tuple(1, 6, 11), Int3Tuple(1, 6, 12), Int3Tuple(1, 6, 13), + Int3Tuple(1, 7, 8), Int3Tuple(1, 7, 9), Int3Tuple(1, 7, 10), Int3Tuple(1, 7, 11), Int3Tuple(1, 7, 12), Int3Tuple(1, 7, 13), + Int3Tuple(1, 8, 10), Int3Tuple(1, 8, 11), Int3Tuple(1, 8, 12), Int3Tuple(1, 8, 13), + Int3Tuple(1, 9, 10), Int3Tuple(1, 9, 11), Int3Tuple(1, 9, 12), Int3Tuple(1, 9, 13), + Int3Tuple(1, 10, 12), Int3Tuple(1, 10, 13), + Int3Tuple(1, 11, 12), Int3Tuple(1, 11, 13), + Int3Tuple(2, 4, 6), Int3Tuple(2, 4, 7), Int3Tuple(2, 4, 8), Int3Tuple(2, 4, 9), Int3Tuple(2, 4, 10), Int3Tuple(2, 4, 11), Int3Tuple(2, 4, 12), Int3Tuple(2, 4, 13), + Int3Tuple(2, 5, 6), Int3Tuple(2, 5, 7), Int3Tuple(2, 5, 8), Int3Tuple(2, 5, 9), Int3Tuple(2, 5, 10), Int3Tuple(2, 5, 11), Int3Tuple(2, 5, 12), Int3Tuple(2, 5, 13), + Int3Tuple(2, 6, 8), Int3Tuple(2, 6, 9), Int3Tuple(2, 6, 10), Int3Tuple(2, 6, 11), Int3Tuple(2, 6, 12), Int3Tuple(2, 6, 13), + Int3Tuple(2, 7, 8), Int3Tuple(2, 7, 9), Int3Tuple(2, 7, 10), Int3Tuple(2, 7, 11), Int3Tuple(2, 7, 12), Int3Tuple(2, 7, 13), + Int3Tuple(2, 8, 10), Int3Tuple(2, 8, 11), Int3Tuple(2, 8, 12), Int3Tuple(2, 8, 13), + Int3Tuple(2, 9, 10), Int3Tuple(2, 9, 11), Int3Tuple(2, 9, 12), Int3Tuple(2, 9, 13), + Int3Tuple(2, 10, 12), Int3Tuple(2, 10, 13), + Int3Tuple(2, 11, 12), Int3Tuple(2, 11, 13), + Int3Tuple(3, 4, 6), Int3Tuple(3, 4, 7), Int3Tuple(3, 4, 8), Int3Tuple(3, 4, 9), Int3Tuple(3, 4, 10), Int3Tuple(3, 4, 11), Int3Tuple(3, 4, 12), Int3Tuple(3, 4, 13), + Int3Tuple(3, 5, 6), Int3Tuple(3, 5, 7), Int3Tuple(3, 5, 8), Int3Tuple(3, 5, 9), Int3Tuple(3, 5, 10), Int3Tuple(3, 5, 11), Int3Tuple(3, 5, 12), Int3Tuple(3, 5, 13), + Int3Tuple(3, 6, 8), Int3Tuple(3, 6, 9), Int3Tuple(3, 6, 10), Int3Tuple(3, 6, 11), Int3Tuple(3, 6, 12), Int3Tuple(3, 6, 13), + Int3Tuple(3, 7, 8), Int3Tuple(3, 7, 9), Int3Tuple(3, 7, 10), Int3Tuple(3, 7, 11), Int3Tuple(3, 7, 12), Int3Tuple(3, 7, 13), + Int3Tuple(3, 8, 10), Int3Tuple(3, 8, 11), Int3Tuple(3, 8, 12), Int3Tuple(3, 8, 13), + Int3Tuple(3, 9, 10), Int3Tuple(3, 9, 11), Int3Tuple(3, 9, 12), Int3Tuple(3, 9, 13), + Int3Tuple(3, 10, 12), Int3Tuple(3, 10, 13), + Int3Tuple(3, 11, 12), Int3Tuple(3, 11, 13), + Int3Tuple(4, 6, 8), Int3Tuple(4, 6, 9), Int3Tuple(4, 6, 10), Int3Tuple(4, 6, 11), Int3Tuple(4, 6, 12), Int3Tuple(4, 6, 13), + Int3Tuple(4, 7, 8), Int3Tuple(4, 7, 9), Int3Tuple(4, 7, 10), Int3Tuple(4, 7, 11), Int3Tuple(4, 7, 12), Int3Tuple(4, 7, 13), + Int3Tuple(4, 8, 10), Int3Tuple(4, 8, 11), Int3Tuple(4, 8, 12), Int3Tuple(4, 8, 13), + Int3Tuple(4, 9, 10), Int3Tuple(4, 9, 11), Int3Tuple(4, 9, 12), Int3Tuple(4, 9, 13), + Int3Tuple(4, 10, 12), Int3Tuple(4, 10, 13), + Int3Tuple(4, 11, 12), Int3Tuple(4, 11, 13), + Int3Tuple(5, 6, 8), Int3Tuple(5, 6, 9), Int3Tuple(5, 6, 10), Int3Tuple(5, 6, 11), Int3Tuple(5, 6, 12), Int3Tuple(5, 6, 13), + Int3Tuple(5, 7, 8), Int3Tuple(5, 7, 9), Int3Tuple(5, 7, 10), Int3Tuple(5, 7, 11), Int3Tuple(5, 7, 12), Int3Tuple(5, 7, 13), + Int3Tuple(5, 8, 10), Int3Tuple(5, 8, 11), Int3Tuple(5, 8, 12), Int3Tuple(5, 8, 13), + Int3Tuple(5, 9, 10), Int3Tuple(5, 9, 11), Int3Tuple(5, 9, 12), Int3Tuple(5, 9, 13), + Int3Tuple(5, 10, 12), Int3Tuple(5, 10, 13), + Int3Tuple(5, 11, 12), Int3Tuple(5, 11, 13), + Int3Tuple(6, 8, 10), Int3Tuple(6, 8, 11), Int3Tuple(6, 8, 12), Int3Tuple(6, 8, 13), + Int3Tuple(6, 9, 10), Int3Tuple(6, 9, 11), Int3Tuple(6, 9, 12), Int3Tuple(6, 9, 13), + Int3Tuple(6, 10, 12), Int3Tuple(6, 10, 13), + Int3Tuple(6, 11, 12), Int3Tuple(6, 11, 13), + Int3Tuple(7, 8, 10), Int3Tuple(7, 8, 11), Int3Tuple(7, 8, 12), Int3Tuple(7, 8, 13), + Int3Tuple(7, 9, 10), Int3Tuple(7, 9, 11), Int3Tuple(7, 9, 12), Int3Tuple(7, 9, 13), + Int3Tuple(7, 10, 12), Int3Tuple(7, 10, 13), + Int3Tuple(7, 11, 12), Int3Tuple(7, 11, 13), + Int3Tuple(8, 10, 12), Int3Tuple(8, 10, 13), + Int3Tuple(8, 11, 12), Int3Tuple(8, 11, 13), + Int3Tuple(9, 10, 12), Int3Tuple(9, 10, 13), + Int3Tuple(9, 11, 12), Int3Tuple(9, 11, 13) +}; + +void generateBoundryLinesForDop14(const std::vector& dots, const glm::vec3& center, std::vector& linesOut) { + if (dots.size() != DOP14_COUNT) { + return; + } + + // iterate over all purmutations of non-parallel planes. + // find all the vertices that lie on the surface of the k-dop + std::vector vertices; + for (auto& tuple : DOP14_PLANE_COMBINATIONS) { + int i = std::get<0>(tuple); + int j = std::get<1>(tuple); + int k = std::get<2>(tuple); + glm::vec4 planeA(DOP14_NORMALS[i], dots[i]); + glm::vec4 planeB(DOP14_NORMALS[j], dots[j]); + glm::vec4 planeC(DOP14_NORMALS[k], dots[k]); + glm::vec3 intersectionPoint; + const float IN_FRONT_MARGIN = 0.01f; + if (findIntersectionOfThreePlanes(planeA, planeB, planeC, intersectionPoint)) { + bool inFront = false; + for (int p = 0; p < DOP14_COUNT; p++) { + if (glm::dot(DOP14_NORMALS[p], intersectionPoint) > dots[p] + IN_FRONT_MARGIN) { + inFront = true; + } + } + if (!inFront) { + vertices.push_back(intersectionPoint); + } + } + } + + // build a set of lines between these vertices, that also lie on the surface of the k-dop. + for (size_t i = 0; i < vertices.size(); i++) { + for (size_t j = i; j < vertices.size(); j++) { + glm::vec3 midPoint = (vertices[i] + vertices[j]) * 0.5f; + int onSurfaceCount = 0; + const float SURFACE_MARGIN = 0.01f; + for (int p = 0; p < DOP14_COUNT; p++) { + float d = glm::dot(DOP14_NORMALS[p], midPoint); + if (d > dots[p] - SURFACE_MARGIN && d < dots[p] + SURFACE_MARGIN) { + onSurfaceCount++; + } + } + if (onSurfaceCount > 1) { + linesOut.push_back(vertices[i] + center); + linesOut.push_back(vertices[j] + center); + } + } + } +} diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index a5ee67748b..eb9424d938 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -13,6 +13,7 @@ #define hifi_GeometryUtil_h #include +#include glm::vec3 computeVectorFromPointToSegment(const glm::vec3& point, const glm::vec3& start, const glm::vec3& end); @@ -166,4 +167,10 @@ private: // given a set of points, compute a best fit plane that passes as close as possible through all the points. bool findPlaneFromPoints(const glm::vec3* points, size_t numPoints, glm::vec3& planeNormalOut, glm::vec3& pointOnPlaneOut); +// plane equation is specified by ax + by + cz + d = 0. +// the coefficents are passed in as a vec4. (a, b, c, d) +bool findIntersectionOfThreePlanes(const glm::vec4& planeA, const glm::vec4& planeB, const glm::vec4& planeC, glm::vec3& intersectionPointOut); + +void generateBoundryLinesForDop14(const std::vector& dots, const glm::vec3& center, std::vector& linesOut); + #endif // hifi_GeometryUtil_h diff --git a/scripts/system/edit.js b/scripts/system/edit.js index c141c7cd52..7ee6c64858 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -1228,7 +1228,7 @@ Script.scriptEnding.connect(function () { Controller.mouseMoveEvent.disconnect(mouseMoveEventBuffered); Controller.mouseReleaseEvent.disconnect(mouseReleaseEvent); - Messages.messageReceived.disconnect(handleOverlaySelectionToolUpdates); + Messages.messageReceived.disconnect(handleMessagesReceived); Messages.unsubscribe("entityToolUpdates"); Messages.unsubscribe("Toolbar-DomainChanged"); createButton = null; diff --git a/scripts/system/help.js b/scripts/system/help.js index 1265a5597b..9ab7fa3fb3 100644 --- a/scripts/system/help.js +++ b/scripts/system/help.js @@ -61,6 +61,7 @@ tablet.gotoHomeScreen(); } button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); Script.clearInterval(interval); if (tablet) { tablet.removeButton(button); diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 77b62913bf..44f3c9e041 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -30,11 +30,13 @@ function objectTranslationPlanePoint(position, dimensions) { SelectionManager = (function() { var that = {}; + // FUNCTION: SUBSCRIBE TO UPDATE MESSAGES function subscribeToUpdateMessages() { Messages.subscribe("entityToolUpdates"); Messages.messageReceived.connect(handleEntitySelectionToolUpdates); } + // FUNCTION: HANDLE ENTITY SELECTION TOOL UDPATES function handleEntitySelectionToolUpdates(channel, message, sender) { if (channel !== 'entityToolUpdates') { return; @@ -238,6 +240,7 @@ function normalizeDegrees(degrees) { return degrees; } +// FUNCTION: getRelativeCenterPosition // Return the enter position of an entity relative to it's registrationPoint // A registration point of (0.5, 0.5, 0.5) will have an offset of (0, 0, 0) // A registration point of (1.0, 1.0, 1.0) will have an offset of (-dimensions.x / 2, -dimensions.y / 2, -dimensions.z / 2) @@ -249,6 +252,7 @@ function getRelativeCenterPosition(dimensions, registrationPoint) { }; } +// SELECTION DISPLAY DEFINITION SelectionDisplay = (function() { var that = {}; @@ -1152,6 +1156,7 @@ SelectionDisplay = (function() { that.updateHandles(); }; + // FUNCTION: UPDATE ROTATION HANDLES that.updateRotationHandles = function() { var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); @@ -1545,11 +1550,6 @@ SelectionDisplay = (function() { translateHandlesVisible = false; } - var rotation = selectionManager.worldRotation; - var dimensions = selectionManager.worldDimensions; - var position = selectionManager.worldPosition; - - Overlays.editOverlay(rotateOverlayTarget, { visible: rotationOverlaysVisible }); @@ -1577,6 +1577,7 @@ SelectionDisplay = (function() { }); }; + // FUNCTION: SET SPACE MODE that.setSpaceMode = function(newSpaceMode) { if (spaceMode != newSpaceMode) { spaceMode = newSpaceMode; @@ -1584,6 +1585,7 @@ SelectionDisplay = (function() { } }; + // 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"); @@ -1593,8 +1595,11 @@ SelectionDisplay = (function() { that.updateHandles(); }; + // FUNCTION: UNSELECT ALL + // TODO?: Needs implementation that.unselectAll = function() {}; + // FUNCTION: UPDATE HANDLES that.updateHandles = function() { if (SelectionManager.selections.length === 0) { that.setOverlaysVisible(false); @@ -2168,10 +2173,10 @@ SelectionDisplay = (function() { position: EdgeTR }); - var boxPosition = Vec3.multiplyQbyV(rotation, center); - boxPosition = Vec3.sum(position, boxPosition); + var selectionBoxPosition = Vec3.multiplyQbyV(rotation, center); + selectionBoxPosition = Vec3.sum(position, selectionBoxPosition); Overlays.editOverlay(selectionBox, { - position: boxPosition, + position: selectionBoxPosition, dimensions: dimensions, rotation: rotation, visible: !(mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL"), @@ -2218,13 +2223,13 @@ SelectionDisplay = (function() { var offset = vec3Mult(props.dimensions, centeredRP); offset = Vec3.multiply(-1, offset); offset = Vec3.multiplyQbyV(props.rotation, offset); - var boxPosition = Vec3.sum(props.position, offset); + 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}; Overlays.editOverlay(selectionBoxes[i], { - position: boxPosition, + position: curBoxPosition, color: color, rotation: props.rotation, dimensions: props.dimensions, @@ -2305,9 +2310,9 @@ SelectionDisplay = (function() { x: position.x, y: position.y + worldTop + grabberMoveUpOffset, z: position.z - } + }; Overlays.editOverlay(grabberMoveUp, { - visible: activeTool == null || mode == "TRANSLATE_UP_DOWN" + visible: (activeTool === null) || (mode == "TRANSLATE_UP_DOWN") }); Overlays.editOverlay(baseOfEntityProjectionOverlay, { @@ -2327,21 +2332,24 @@ SelectionDisplay = (function() { }; + // FUNCTION: SET OVERLAYS VISIBLE that.setOverlaysVisible = function(isVisible) { var length = allOverlays.length; - for (var i = 0; i < length; i++) { - Overlays.editOverlay(allOverlays[i], { + for (var overlayIndex = 0; overlayIndex < length; overlayIndex++) { + Overlays.editOverlay(allOverlays[overlayIndex], { visible: isVisible }); } length = selectionBoxes.length; - for (var i = 0; i < length; i++) { - Overlays.editOverlay(selectionBoxes[i], { + for (var boxIndex = 0; boxIndex < length; boxIndex++) { + Overlays.editOverlay(selectionBoxes[boxIndex], { visible: isVisible }); } }; + // FUNCTION: UNSELECT + // TODO?: Needs implementation that.unselect = function(entityID) {}; var initialXZPick = null; @@ -2350,6 +2358,7 @@ SelectionDisplay = (function() { var startPosition = null; var duplicatedEntityIDs = null; + // TOOL DEFINITION: TRANSLATE XZ TOOL var translateXZTool = { mode: 'TRANSLATE_XZ', pickPlanePosition: { x: 0, y: 0, z: 0 }, @@ -2538,7 +2547,8 @@ SelectionDisplay = (function() { } }; - var lastXYPick = null + // GRABBER TOOL: GRABBER MOVE UP + var lastXYPick = null; var upDownPickNormal = null; addGrabberTool(grabberMoveUp, { mode: "TRANSLATE_UP_DOWN", @@ -2594,7 +2604,7 @@ 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]; @@ -2612,6 +2622,7 @@ SelectionDisplay = (function() { }, }); + // GRABBER TOOL: GRABBER CLONER addGrabberTool(grabberCloner, { mode: "CLONE", onBegin: function(event) { @@ -2639,7 +2650,7 @@ SelectionDisplay = (function() { - + // FUNCTION: VEC 3 MULT var vec3Mult = function(v1, v2) { return { x: v1.x * v2.x, @@ -2647,6 +2658,8 @@ SelectionDisplay = (function() { z: v1.z * v2.z }; }; + + // FUNCTION: MAKE STRETCH TOOL // stretchMode - name of mode // direction - direction to stretch in // pivot - point to use as a pivot @@ -2898,13 +2911,14 @@ SelectionDisplay = (function() { // Are we using handControllers or Mouse - only relevant for 3D tools var controllerPose = getControllerWorldLocation(activeHand, true); - if (HMD.isHMDAvailable() - && HMD.isHandControllerAvailable() && controllerPose.valid && that.triggered && directionFor3DStretch) { + var vector = null; + if (HMD.isHMDAvailable() && HMD.isHandControllerAvailable() && + controllerPose.valid && that.triggered && directionFor3DStretch) { localDeltaPivot = deltaPivot3D; newPick = pickRay.origin; - var vector = Vec3.subtract(newPick, lastPick3D); + vector = Vec3.subtract(newPick, lastPick3D); vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector); @@ -2919,7 +2933,7 @@ SelectionDisplay = (function() { newPick = rayPlaneIntersection(pickRay, pickRayPosition, planeNormal); - var vector = Vec3.subtract(newPick, lastPick); + vector = Vec3.subtract(newPick, lastPick); vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector); @@ -2955,41 +2969,39 @@ SelectionDisplay = (function() { } else { newDimensions = Vec3.sum(initialDimensions, changeInDimensions); } - } - - - newDimensions.x = Math.max(newDimensions.x, MINIMUM_DIMENSION); - newDimensions.y = Math.max(newDimensions.y, MINIMUM_DIMENSION); - newDimensions.z = Math.max(newDimensions.z, MINIMUM_DIMENSION); - var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(localDeltaPivot, changeInDimensions)); - var newPosition = Vec3.sum(initialPosition, changeInPosition); + newDimensions.x = Math.max(newDimensions.x, MINIMUM_DIMENSION); + newDimensions.y = Math.max(newDimensions.y, MINIMUM_DIMENSION); + newDimensions.z = Math.max(newDimensions.z, MINIMUM_DIMENSION); - for (var i = 0; i < SelectionManager.selections.length; i++) { - Entities.editEntity(SelectionManager.selections[i], { - position: newPosition, - dimensions: newDimensions, - }); - } - + var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(localDeltaPivot, changeInDimensions)); + var newPosition = Vec3.sum(initialPosition, changeInPosition); - var wantDebug = false; - if (wantDebug) { - print(stretchMode); - //Vec3.print(" newIntersection:", newIntersection); - Vec3.print(" vector:", vector); - //Vec3.print(" oldPOS:", oldPOS); - //Vec3.print(" newPOS:", newPOS); - Vec3.print(" changeInDimensions:", changeInDimensions); - Vec3.print(" newDimensions:", newDimensions); + for (var i = 0; i < SelectionManager.selections.length; i++) { + Entities.editEntity(SelectionManager.selections[i], { + position: newPosition, + dimensions: newDimensions, + }); + } + - Vec3.print(" changeInPosition:", changeInPosition); - Vec3.print(" newPosition:", newPosition); + var wantDebug = false; + if (wantDebug) { + print(stretchMode); + //Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + //Vec3.print(" oldPOS:", oldPOS); + //Vec3.print(" newPOS:", newPOS); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } } SelectionManager._update(); - - }; + };//--End of onMove def return { mode: stretchMode, @@ -3042,7 +3054,8 @@ SelectionDisplay = (function() { z: -1 } }; - + + // FUNCTION: GET DIRECTION FOR 3D STRETCH // Returns a vector with directions for the stretch tool in 3D using hand controllers function getDirectionsFor3DStretch(mode) { if (mode === "STRETCH_LBN") { @@ -3067,7 +3080,7 @@ SelectionDisplay = (function() { } - + // FUNCTION: ADD STRETCH TOOL function addStretchTool(overlay, mode, pivot, direction, offset, handleMove) { if (!pivot) { pivot = direction; @@ -3077,6 +3090,7 @@ SelectionDisplay = (function() { addGrabberTool(overlay, tool); } + // FUNCTION: CUTOFF STRETCH FUNC function cutoffStretchFunc(vector, change) { vector = change; Vec3.print("Radius stretch: ", vector); @@ -3097,6 +3111,7 @@ SelectionDisplay = (function() { SelectionManager._update(); } + // FUNCTION: RADIUS STRETCH FUNC function radiusStretchFunc(vector, change) { var props = selectionManager.savedProperties[selectionManager.selections[0]]; @@ -3123,6 +3138,7 @@ SelectionDisplay = (function() { SelectionManager._update(); } + // STRETCH TOOL DEF SECTION addStretchTool(grabberNEAR, "STRETCH_NEAR", { x: 0, y: 0, @@ -3529,6 +3545,7 @@ SelectionDisplay = (function() { z: 1 }); + // FUNCTION: UPDATE ROTATION DEGREES OVERLAY function updateRotationDegreesOverlay(angleFromZero, handleRotation, centerPosition) { var angle = angleFromZero * (Math.PI / 180); var position = { @@ -3549,6 +3566,7 @@ SelectionDisplay = (function() { }); } + // YAW GRABBER TOOL DEFINITION var initialPosition = SelectionManager.worldPosition; addGrabberTool(yawHandle, { mode: "ROTATE_YAW", @@ -3625,10 +3643,10 @@ SelectionDisplay = (function() { if (result.intersects) { var center = yawCenter; var zero = yawZero; - // TODO: these vectors are backwards to their names, doesn't matter for this use case (inverted vectors still give same angle) - var centerToZero = Vec3.subtract(center, zero); - var centerToIntersect = Vec3.subtract(center, result.intersection); - // TODO: orientedAngle wants normalized centerToZero and centerToIntersect + 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; @@ -3717,6 +3735,7 @@ SelectionDisplay = (function() { } }); + // PITCH GRABBER TOOL DEFINITION addGrabberTool(pitchHandle, { mode: "ROTATE_PITCH", onBegin: function(event) { @@ -3789,11 +3808,12 @@ SelectionDisplay = (function() { var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); if (result.intersects) { - var properties = Entities.getEntityProperties(selectionManager.selections[0]); var center = pitchCenter; var zero = pitchZero; - var centerToZero = Vec3.subtract(center, zero); - var centerToIntersect = Vec3.subtract(center, result.intersection); + 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); @@ -3809,7 +3829,6 @@ SelectionDisplay = (function() { 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 dPos = Vec3.subtract(initialProperties.position, initialPosition); dPos = Vec3.multiplyQbyV(pitchChange, dPos); @@ -3874,6 +3893,7 @@ SelectionDisplay = (function() { } }); + // ROLL GRABBER TOOL DEFINITION addGrabberTool(rollHandle, { mode: "ROTATE_ROLL", onBegin: function(event) { @@ -3946,11 +3966,12 @@ SelectionDisplay = (function() { var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); if (result.intersects) { - var properties = Entities.getEntityProperties(selectionManager.selections[0]); var center = rollCenter; var zero = rollZero; - var centerToZero = Vec3.subtract(center, zero); - var centerToIntersect = Vec3.subtract(center, result.intersection); + 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); @@ -3965,7 +3986,6 @@ SelectionDisplay = (function() { }); 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 dPos = Vec3.subtract(initialProperties.position, initialPosition); dPos = Vec3.multiplyQbyV(rollChange, dPos); @@ -4030,6 +4050,7 @@ SelectionDisplay = (function() { } }); + // FUNCTION: CHECK MOVE that.checkMove = function() { if (SelectionManager.hasSelection()) { @@ -4044,6 +4065,7 @@ SelectionDisplay = (function() { } }; + // FUNCTION: MOUSE PRESS EVENT that.mousePressEvent = function(event) { var wantDebug = false; if (!event.isLeftButton && !that.triggered) { @@ -4167,7 +4189,7 @@ SelectionDisplay = (function() { // Only intersect versus yaw/pitch/roll. - var result = Overlays.findRayIntersection(pickRay, true, [ yawHandle, pitchHandle, rollHandle ] ); + result = Overlays.findRayIntersection(pickRay, true, [ yawHandle, pitchHandle, rollHandle ] ); var overlayOrientation; var overlayCenter; @@ -4184,10 +4206,10 @@ SelectionDisplay = (function() { originalRoll = roll; if (result.intersects) { - var tool = grabberTools[result.overlayID]; - if (tool) { - activeTool = tool; - mode = tool.mode; + var resultTool = grabberTools[result.overlayID]; + if (resultTool) { + activeTool = resultTool; + mode = resultTool.mode; somethingClicked = 'tool'; if (activeTool && activeTool.onBegin) { activeTool.onBegin(event); @@ -4370,7 +4392,7 @@ SelectionDisplay = (function() { if (!somethingClicked) { // Only intersect versus selectionBox. - var result = Overlays.findRayIntersection(pickRay, true, [selectionBox]); + result = Overlays.findRayIntersection(pickRay, true, [selectionBox]); if (result.intersects) { switch (result.overlayID) { case selectionBox: @@ -4425,6 +4447,7 @@ SelectionDisplay = (function() { return somethingClicked; }; + // FUNCTION: MOUSE MOVE EVENT that.mouseMoveEvent = function(event) { if (activeTool) { activeTool.onMove(event); @@ -4554,7 +4577,7 @@ SelectionDisplay = (function() { return false; }; - + // FUNCTION: UPDATE HANDLE SIZES that.updateHandleSizes = function() { if (selectionManager.hasSelection()) { var diff = Vec3.subtract(selectionManager.worldPosition, Camera.getPosition()); @@ -4593,6 +4616,7 @@ SelectionDisplay = (function() { }; Script.update.connect(that.updateHandleSizes); + // FUNCTION: MOUSE RELEASE EVENT that.mouseReleaseEvent = function(event) { var showHandles = false; if (activeTool && activeTool.onEnd) { diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 9378a1d95b..84d7d44689 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -21,6 +21,7 @@ var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); var MARKETPLACE_CHECKOUT_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/Checkout.qml"; var MARKETPLACE_INVENTORY_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/Inventory.qml"; + var MARKETPLACE_SECURITY_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/SecurityImageSelection.qml"; var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; @@ -87,7 +88,7 @@ function onScreenChanged(type, url) { onMarketplaceScreen = type === "Web" && url === MARKETPLACE_URL_INITIAL; - wireEventBridge(type === "QML" && (url === MARKETPLACE_CHECKOUT_QML_PATH || url === MARKETPLACE_INVENTORY_QML_PATH)); + wireEventBridge(type === "QML" && (url === MARKETPLACE_CHECKOUT_QML_PATH || url === MARKETPLACE_INVENTORY_QML_PATH || url === MARKETPLACE_SECURITY_QML_PATH)); // for toolbar mode: change button to active when window is first openend, false otherwise. marketplaceButton.editProperties({ isActive: onMarketplaceScreen }); if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) { @@ -217,8 +218,11 @@ case 'inventory_backClicked': tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); break; + case 'securityImageSelection_cancelClicked': + tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); + break; default: - print('Unrecognized message from Checkout.qml or Inventory.qml: ' + JSON.stringify(message)); + print('Unrecognized message from Checkout.qml, Inventory.qml, or SecurityImageSelection.qml: ' + JSON.stringify(message)); } } diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 37618253ee..df5ed45fed 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -764,8 +764,8 @@ Script.scriptEnding.connect(function () { } Window.snapshotShared.disconnect(snapshotUploaded); Snapshot.snapshotLocationSet.disconnect(snapshotLocationSet); - Entities.canRezChanged.disconnect(processRezPermissionChange); - Entities.canRezTmpChanged.disconnect(processRezPermissionChange); + Entities.canRezChanged.disconnect(updatePrintPermissions); + Entities.canRezTmpChanged.disconnect(updatePrintPermissions); }); }()); // END LOCAL_SCOPE