diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 3e93656d45..1a33088237 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -3,7 +3,7 @@ This is a stand-alone guide for creating your first High Fidelity build for Wind ## Building High Fidelity Note: We are now using Visual Studio 2017 and Qt 5.9.1. If you are upgrading from Visual Studio 2013 and Qt 5.6.2, do a clean uninstall of those versions before going through this guide. -Note: The prerequisites will require about 10 GB of space on your drive. +Note: The prerequisites will require about 10 GB of space on your drive. You will also need a system with at least 8GB of main memory. ### Step 1. Visual Studio 2017 diff --git a/interface/resources/qml/TabletBrowser.qml b/interface/resources/qml/TabletBrowser.qml index c3d879c513..0b06a6e2a1 100644 --- a/interface/resources/qml/TabletBrowser.qml +++ b/interface/resources/qml/TabletBrowser.qml @@ -39,7 +39,7 @@ Item { width: parent.width height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height - profile: HFTabletWebEngineProfile; + profile: HFWebEngineProfile; property string userScriptUrl: "" diff --git a/interface/resources/qml/controls/TabletWebScreen.qml b/interface/resources/qml/controls/TabletWebScreen.qml index 68f8226e21..1de31aba41 100644 --- a/interface/resources/qml/controls/TabletWebScreen.qml +++ b/interface/resources/qml/controls/TabletWebScreen.qml @@ -31,7 +31,7 @@ Item { width: parent.width height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height - profile: HFTabletWebEngineProfile; + profile: HFWebEngineProfile; property string userScriptUrl: "" diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index 0a5a68717e..8547f67468 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -24,6 +24,7 @@ Item { property alias webView: webview property alias profile: webview.profile property bool remove: false + property bool closeButtonVisible: true // Manage own browse history because WebEngineView history is wiped when a new URL is loaded via // onNewViewRequested, e.g., as happens when a social media share button is clicked. @@ -64,6 +65,7 @@ Item { disabledColor: hifi.colors.lightGrayText enabled: true text: "CLOSE" + visible: closeButtonVisible MouseArea { anchors.fill: parent @@ -142,7 +144,7 @@ Item { width: parent.width height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height - web.headerHeight : parent.height - web.headerHeight anchors.top: buttons.bottom - profile: HFTabletWebEngineProfile; + profile: HFWebEngineProfile; property string userScriptUrl: "" @@ -182,8 +184,6 @@ Item { webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); }); - - webview.profile.httpUserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36"; } onFeaturePermissionRequested: { diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 38136c7eec..3f2ac6363b 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -73,7 +73,6 @@ Item { console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); }); - root.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; } onFeaturePermissionRequested: { diff --git a/interface/resources/qml/hifi/Card.qml b/interface/resources/qml/hifi/Card.qml index fd76c03e45..678245910b 100644 --- a/interface/resources/qml/hifi/Card.qml +++ b/interface/resources/qml/hifi/Card.qml @@ -46,6 +46,8 @@ Item { property int stackShadowNarrowing: 5; property string defaultThumbnail: Qt.resolvedUrl("../../images/default-domain.gif"); property int shadowHeight: 10; + property bool hovered: false + HifiConstants { id: hifi } function pastTime(timestamp) { // Answer a descriptive string @@ -231,6 +233,13 @@ Item { // to that which is being hovered over. property var hoverThunk: function () { }; property var unhoverThunk: function () { }; + Rectangle { + anchors.fill: parent; + visible: root.hovered + color: "transparent"; + border.width: 4; border.color: hifiStyleConstants.colors.primaryHighlight; + z: 1; + } MouseArea { anchors.fill: parent; acceptedButtons: Qt.LeftButton; diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index c1bd35f49d..960b62c169 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -206,9 +206,9 @@ Column { id: scroll; model: suggestions; orientation: ListView.Horizontal; + highlightFollowsCurrentItem: false highlightMoveDuration: -1; highlightMoveVelocity: -1; - highlight: Rectangle { color: "transparent"; border.width: 4; border.color: hifiStyleConstants.colors.primaryHighlight; z: 1; } currentIndex: -1; spacing: 12; @@ -237,9 +237,8 @@ Column { textSizeSmall: root.textSizeSmall; stackShadowNarrowing: root.stackShadowNarrowing; shadowHeight: root.stackedCardShadowHeight; - - hoverThunk: function () { scrollToIndex(index); } - unhoverThunk: function () { scrollToIndex(-1); } + hoverThunk: function () { hovered = true } + unhoverThunk: function () { hovered = false } } } NumberAnimation { diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index f639586668..af54c86bf4 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -147,7 +147,7 @@ Rectangle { width: parent.width; height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight - profile: HFTabletWebEngineProfile; + profile: HFWebEngineProfile; property string userScriptUrl: "" diff --git a/interface/resources/qml/hifi/commerce/Checkout.qml b/interface/resources/qml/hifi/commerce/Checkout.qml index ae85422e78..54a58943cb 100644 --- a/interface/resources/qml/hifi/commerce/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/Checkout.qml @@ -35,8 +35,8 @@ Rectangle { Hifi.QmlCommerce { id: commerce; onBuyResult: { - if (failureMessage.length) { - buyButton.text = "Buy Failed"; + if (result.status !== 'success') { + buyButton.text = result.message; buyButton.enabled = false; } else { if (urlHandler.canHandleUrl(itemHref)) { @@ -46,20 +46,21 @@ Rectangle { } } onBalanceResult: { - if (failureMessage.length) { - console.log("Failed to get balance", failureMessage); + if (result.status !== 'success') { + console.log("Failed to get balance", result.message); } else { balanceReceived = true; - hfcBalanceText.text = balance; - balanceAfterPurchase = balance - parseInt(itemPriceText.text, 10); + hfcBalanceText.text = result.data.balance; + balanceAfterPurchase = result.data.balance - parseInt(itemPriceText.text, 10); } } onInventoryResult: { - if (failureMessage.length) { - console.log("Failed to get inventory", failureMessage); + if (result.status !== 'success') { + console.log("Failed to get inventory", result.message); } else { inventoryReceived = true; - if (inventoryContains(inventory.assets, itemId)) { + console.log('inventory fixme', JSON.stringify(result)); + if (inventoryContains(result.data.assets, itemId)) { alreadyOwned = true; } else { alreadyOwned = false; diff --git a/interface/resources/qml/hifi/commerce/Inventory.qml b/interface/resources/qml/hifi/commerce/Inventory.qml index 6e7eabfb24..5cdc5dab39 100644 --- a/interface/resources/qml/hifi/commerce/Inventory.qml +++ b/interface/resources/qml/hifi/commerce/Inventory.qml @@ -30,17 +30,17 @@ Rectangle { Hifi.QmlCommerce { id: commerce; onBalanceResult: { - if (failureMessage.length) { - console.log("Failed to get balance", failureMessage); + if (result.status !== 'success') { + console.log("Failed to get balance", result.message); } else { - hfcBalanceText.text = balance; + hfcBalanceText.text = result.data.balance; } } onInventoryResult: { - if (failureMessage.length) { - console.log("Failed to get inventory", failureMessage); + if (result.status !== 'success') { + console.log("Failed to get inventory", result.message); } else { - inventoryContentsList.model = inventory.assets; + inventoryContentsList.model = result.data.assets; } } onSecurityImageResult: { diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index c7df6ac64f..bbf56c7827 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -93,6 +93,11 @@ Item { loader.source = ""; loader.source = "TabletWebView.qml"; } + + function loadTabletWebBase() { + loader.source = ""; + loader.source = "../../controls/TabletWebView.qml"; + } function returnToPreviousApp() { tabletApps.remove(currentApp); @@ -121,6 +126,9 @@ Item { loader.item.url = url; loader.item.scriptURL = injectedJavaScriptUrl; tabletApps.append({"appUrl": "TabletWebView.qml", "isWebUrl": true, "scriptUrl": injectedJavaScriptUrl, "appWebUrl": url}); + if (loader.item.hasOwnProperty("closeButtonVisible")) { + loader.item.closeButtonVisible = false; + } } // used to send a message from qml to interface script. diff --git a/interface/resources/qml/hifi/tablet/WindowRoot.qml b/interface/resources/qml/hifi/tablet/WindowRoot.qml index fdfcfcf806..8596007956 100644 --- a/interface/resources/qml/hifi/tablet/WindowRoot.qml +++ b/interface/resources/qml/hifi/tablet/WindowRoot.qml @@ -42,9 +42,17 @@ Windows.ScrollingWindow { loader.source = "WindowWebView.qml"; } + function loadTabletWebBase() { + loader.source = ""; + loader.source = "../../controls/TabletWebView.qml"; + } + function loadWebUrl(url, injectedJavaScriptUrl) { loader.item.url = url; loader.item.scriptURL = injectedJavaScriptUrl; + if (loader.item.hasOwnProperty("closeButtonVisible")) { + loader.item.closeButtonVisible = false; + } } // used to send a message from qml to interface script. diff --git a/interface/resources/shaders/hmd_ui.frag b/interface/resources/shaders/hmd_ui.frag new file mode 100644 index 0000000000..5341ab575d --- /dev/null +++ b/interface/resources/shaders/hmd_ui.frag @@ -0,0 +1,30 @@ +// +// Created by Bradley Austin Davis on 2016/07/11 +// Copyright 2013-2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +uniform sampler2D sampler; + +struct OverlayData { + mat4 mvp; + float alpha; +}; + +layout(std140) uniform overlayBuffer { + OverlayData overlay; +}; + +in vec2 vTexCoord; + +out vec4 FragColor; + +void main() { + FragColor = texture(sampler, vTexCoord); + FragColor.a *= overlay.alpha; + if (FragColor.a <= 0.0) { + discard; + } +} \ No newline at end of file diff --git a/interface/resources/shaders/hmd_ui_glow.vert b/interface/resources/shaders/hmd_ui.vert similarity index 76% rename from interface/resources/shaders/hmd_ui_glow.vert rename to interface/resources/shaders/hmd_ui.vert index 71089d8608..41b9b3666f 100644 --- a/interface/resources/shaders/hmd_ui_glow.vert +++ b/interface/resources/shaders/hmd_ui.vert @@ -8,12 +8,7 @@ struct OverlayData { mat4 mvp; - vec4 glowPoints; - vec4 glowColors[2]; - vec4 resolutionRadiusAlpha; - - vec4 extraGlowColor; - vec2 extraGlowPoint; + float alpha; }; layout(std140) uniform overlayBuffer { @@ -25,11 +20,9 @@ mat4 mvp = overlay.mvp; layout(location = 0) in vec3 Position; layout(location = 3) in vec2 TexCoord; -out vec3 vPosition; out vec2 vTexCoord; void main() { gl_Position = mvp * vec4(Position, 1); vTexCoord = TexCoord; - vPosition = Position; } diff --git a/interface/resources/shaders/hmd_ui_glow.frag b/interface/resources/shaders/hmd_ui_glow.frag deleted file mode 100644 index 5dda76e89d..0000000000 --- a/interface/resources/shaders/hmd_ui_glow.frag +++ /dev/null @@ -1,86 +0,0 @@ -// -// Created by Bradley Austin Davis on 2016/07/11 -// Copyright 2013-2016 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -uniform sampler2D sampler; - -struct OverlayData { - mat4 mvp; - vec4 glowPoints; - vec4 glowColors[2]; - vec4 resolutionRadiusAlpha; - - vec4 extraGlowColor; - vec2 extraGlowPoint; -}; - -layout(std140) uniform overlayBuffer { - OverlayData overlay; -}; - -vec2 resolution = overlay.resolutionRadiusAlpha.xy; -float radius = overlay.resolutionRadiusAlpha.z; -float alpha = overlay.resolutionRadiusAlpha.w; -vec4 glowPoints = overlay.glowPoints; -vec4 glowColors[2] = overlay.glowColors; - -vec2 extraGlowPoint = overlay.extraGlowPoint; -vec4 extraGlowColor = overlay.extraGlowColor; - -in vec3 vPosition; -in vec2 vTexCoord; - -out vec4 FragColor; - -float easeInOutCubic(float f) { - const float d = 1.0; - const float b = 0.0; - const float c = 1.0; - float t = f; - if ((t /= d / 2.0) < 1.0) return c / 2.0 * t * t * t + b; - return c / 2.0 * ((t -= 2.0) * t * t + 2.0) + b; -} - -void main() { - FragColor = texture(sampler, vTexCoord); - - vec2 aspect = resolution; - aspect /= resolution.x; - - float glowIntensity = 0.0; - float dist1 = distance(vTexCoord * aspect, glowPoints.xy * aspect); - float dist2 = distance(vTexCoord * aspect, glowPoints.zw * aspect); - float dist3 = distance(vTexCoord * aspect, extraGlowPoint * aspect); - float distX = min(dist1, dist2); - float dist = min(distX, dist3); - vec3 glowColor = glowColors[0].rgb; - if (dist2 < dist1) { - glowColor = glowColors[1].rgb; - } - if (dist3 < dist2) { - glowColor = extraGlowColor.rgb; - } - - if (dist <= radius) { - glowIntensity = 1.0 - (dist / radius); - glowColor.rgb = pow(glowColor, vec3(1.0 - glowIntensity)); - glowIntensity = easeInOutCubic(glowIntensity); - glowIntensity = pow(glowIntensity, 0.5); - } - - if (alpha <= 0.0) { - if (glowIntensity <= 0.0) { - discard; - } - - FragColor = vec4(glowColor, glowIntensity); - return; - } - - FragColor.rgb = mix(FragColor.rgb, glowColor.rgb, glowIntensity); - FragColor.a *= alpha; -} \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 616b6914e9..398b2dbdb4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -193,6 +193,10 @@ #include #include #include + +#include +#include + #include "commerce/Ledger.h" #include "commerce/Wallet.h" #include "commerce/QmlCommerce.h" @@ -608,6 +612,9 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + return previousSessionCrashed; } @@ -2468,6 +2475,7 @@ void Application::paintGL() { finalFramebuffer = framebufferCache->getFramebuffer(); } + mat4 eyeProjections[2]; { PROFILE_RANGE(render, "/mainRender"); PerformanceTimer perfTimer("mainRender"); @@ -2489,7 +2497,6 @@ void Application::paintGL() { _myCamera.setProjection(displayPlugin->getCullingProjection(_myCamera.getProjection())); renderArgs._context->enableStereo(true); mat4 eyeOffsets[2]; - mat4 eyeProjections[2]; auto baseProjection = renderArgs.getViewFrustum().getProjection(); auto hmdInterface = DependencyManager::get(); float IPDScale = hmdInterface->getIPDScale(); @@ -2520,6 +2527,19 @@ void Application::paintGL() { displaySide(&renderArgs, _myCamera); } + gpu::Batch postCompositeBatch; + { + PROFILE_RANGE(render, "/postComposite"); + PerformanceTimer perfTimer("postComposite"); + renderArgs._batch = &postCompositeBatch; + renderArgs._batch->setViewportTransform(ivec4(0, 0, finalFramebufferSize.width(), finalFramebufferSize.height())); + renderArgs._batch->setViewTransform(renderArgs.getViewFrustum().getView()); + for_each_eye([&](Eye eye) { + renderArgs._batch->setProjectionTransform(eyeProjections[eye]); + _overlays.render3DHUDOverlays(&renderArgs); + }); + } + auto frame = _gpuContext->endFrame(); frame->frameIndex = _frameCount; frame->framebuffer = finalFramebuffer; @@ -2527,6 +2547,7 @@ void Application::paintGL() { DependencyManager::get()->releaseFramebuffer(framebuffer); }; frame->overlay = _applicationOverlay.getOverlayTexture(); + frame->postCompositeBatch = postCompositeBatch; // deliver final scene rendering commands to the display plugin { PROFILE_RANGE(render, "/pluginOutput"); @@ -2830,6 +2851,18 @@ bool Application::importSVOFromURL(const QString& urlString) { return true; } +bool Application::importFromZIP(const QString& filePath) { + qDebug() << "A zip file has been dropped in: " << filePath; + QUrl empty; + // handle Blocks download from Marketplace + if (filePath.contains("vr.google.com/downloads")) { + addAssetToWorldFromURL(filePath); + } else { + qApp->getFileDownloadInterface()->runUnzip(filePath, empty, true, true, false); + } + return true; +} + void Application::onPresent(quint32 frameCount) { if (shouldPaint()) { postEvent(this, new QEvent(static_cast(Idle)), Qt::HighEventPriority); @@ -2837,13 +2870,6 @@ void Application::onPresent(quint32 frameCount) { } } -bool Application::importFromZIP(const QString& filePath) { - qDebug() << "A zip file has been dropped in: " << filePath; - QUrl empty; - qApp->getFileDownloadInterface()->runUnzip(filePath, empty, true, true); - return true; -} - bool _renderRequested { false }; bool Application::event(QEvent* event) { @@ -5078,6 +5104,16 @@ void Application::update(float deltaTime) { _overlays.update(deltaTime); } + { + PROFILE_RANGE(app, "RayPickManager"); + _rayPickManager.update(); + } + + { + PROFILE_RANGE(app, "LaserPointerManager"); + _laserPointerManager.update(); + } + // Update _viewFrustum with latest camera and view frustum data... // NOTE: we get this from the view frustum, to make it simpler, since the // loadViewFrumstum() method will get the correct details from the camera @@ -5939,6 +5975,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerGlobalObject("AvatarBookmarks", DependencyManager::get().data()); scriptEngine->registerGlobalObject("LocationBookmarks", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("RayPick", DependencyManager::get().data()); + scriptEngine->registerGlobalObject("LaserPointers", DependencyManager::get().data()); + // Caches scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get().data()); scriptEngine->registerGlobalObject("TextureCache", DependencyManager::get().data()); @@ -6320,8 +6359,15 @@ void Application::showAssetServerWidget(QString filePath) { void Application::addAssetToWorldFromURL(QString url) { qInfo(interfaceapp) << "Download model and add to world from" << url; - - QString filename = url.section("filename=", 1, 1); // Filename is in "?filename=" parameter at end of URL. + + QString filename; + if (url.contains("filename")) { + filename = url.section("filename=", 1, 1); // Filename is in "?filename=" parameter at end of URL. + } + if (url.contains("vr.google.com/downloads")) { + filename = url.section('/', -1); + filename.remove(".zip?noDownload=false"); + } if (!DependencyManager::get()->getThisNodeCanWriteAssets()) { QString errorInfo = "You do not have permissions to write to the Asset Server."; @@ -6342,7 +6388,17 @@ void Application::addAssetToWorldFromURLRequestFinished() { auto url = request->getUrl().toString(); auto result = request->getResult(); - QString filename = url.section("filename=", 1, 1); // Filename from trailing "?filename=" URL parameter. + QString filename; + bool isBlocks = false; + + if (url.contains("filename")) { + filename = url.section("filename=", 1, 1); // Filename is in "?filename=" parameter at end of URL. + } + if (url.contains("vr.google.com/downloads")) { + filename = url.section('/', -1); + filename.remove(".zip?noDownload=false"); + isBlocks = true; + } if (result == ResourceRequest::Success) { qInfo(interfaceapp) << "Downloaded model from" << url; @@ -6357,7 +6413,7 @@ void Application::addAssetToWorldFromURLRequestFinished() { if (tempFile.open(QIODevice::WriteOnly)) { tempFile.write(request->getData()); addAssetToWorldInfoClear(filename); // Remove message from list; next one added will have a different key. - qApp->getFileDownloadInterface()->runUnzip(downloadPath, url, true, false); + qApp->getFileDownloadInterface()->runUnzip(downloadPath, url, true, false, isBlocks); } else { QString errorInfo = "Couldn't open temporary file for download"; qWarning(interfaceapp) << errorInfo; @@ -6387,7 +6443,7 @@ void Application::addAssetToWorldUnzipFailure(QString filePath) { addAssetToWorldError(filename, "Couldn't unzip file " + filename + "."); } -void Application::addAssetToWorld(QString filePath, QString zipFile, bool isZip) { +void Application::addAssetToWorld(QString filePath, QString zipFile, bool isZip, bool isBlocks) { // Automatically upload and add asset to world as an alternative manual process initiated by showAssetServerWidget(). QString mapping; QString path = filePath; @@ -6396,6 +6452,11 @@ void Application::addAssetToWorld(QString filePath, QString zipFile, bool isZip) QString assetFolder = zipFile.section("/", -1); assetFolder.remove(".zip"); mapping = "/" + assetFolder + "/" + filename; + } else if (isBlocks) { + qCDebug(interfaceapp) << "Path to asset folder: " << zipFile; + QString assetFolder = zipFile.section('/', -1); + assetFolder.remove(".zip?noDownload=false"); + mapping = "/" + assetFolder + "/" + filename; } else { mapping = "/" + filename; } @@ -6775,12 +6836,12 @@ void Application::onAssetToWorldMessageBoxClosed() { } -void Application::handleUnzip(QString zipFile, QStringList unzipFile, bool autoAdd, bool isZip) { +void Application::handleUnzip(QString zipFile, QStringList unzipFile, bool autoAdd, bool isZip, bool isBlocks) { if (autoAdd) { if (!unzipFile.isEmpty()) { for (int i = 0; i < unzipFile.length(); i++) { qCDebug(interfaceapp) << "Preparing file for asset server: " << unzipFile.at(i); - addAssetToWorld(unzipFile.at(i), zipFile, isZip); + addAssetToWorld(unzipFile.at(i), zipFile, isZip, isBlocks); } } else { addAssetToWorldUnzipFailure(zipFile); diff --git a/interface/src/Application.h b/interface/src/Application.h index 752d6657fb..c951b25930 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -71,6 +71,9 @@ #include "ui/overlays/Overlays.h" #include "UndoStackScriptingInterface.h" +#include "raypick/RayPickManager.h" +#include "raypick/LaserPointerManager.h" + #include #include #include @@ -302,6 +305,9 @@ public: QUrl getAvatarOverrideUrl() { return _avatarOverrideUrl; } bool getSaveAvatarOverrideUrl() { return _saveAvatarOverrideUrl; } + LaserPointerManager& getLaserPointerManager() { return _laserPointerManager; } + RayPickManager& getRayPickManager() { return _rayPickManager; } + signals: void svoImportRequested(const QString& url); @@ -333,14 +339,14 @@ public slots: // FIXME: Move addAssetToWorld* methods to own class? void addAssetToWorldFromURL(QString url); void addAssetToWorldFromURLRequestFinished(); - void addAssetToWorld(QString filePath, QString zipFile, bool isZip); + void addAssetToWorld(QString filePath, QString zipFile, bool isZip, bool isBlocks); void addAssetToWorldUnzipFailure(QString filePath); void addAssetToWorldWithNewMapping(QString filePath, QString mapping, int copy); void addAssetToWorldUpload(QString filePath, QString mapping); void addAssetToWorldSetMapping(QString filePath, QString mapping, QString hash); void addAssetToWorldAddEntity(QString filePath, QString mapping); - void handleUnzip(QString sourceFile, QStringList destinationFile, bool autoAdd, bool isZip); + void handleUnzip(QString sourceFile, QStringList destinationFile, bool autoAdd, bool isZip, bool isBlocks); FileScriptingInterface* getFileDownloadInterface() { return _fileDownload; } @@ -701,5 +707,8 @@ private: QUrl _avatarOverrideUrl; bool _saveAvatarOverrideUrl { false }; + LaserPointerManager _laserPointerManager; + RayPickManager _rayPickManager; + }; #endif // hifi_Application_h diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 3d48540a64..e7a83b5abc 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -473,19 +473,25 @@ AvatarSharedPointer AvatarManager::getAvatarBySessionID(const QUuid& sessionID) RayToAvatarIntersectionResult AvatarManager::findRayIntersection(const PickRay& ray, const QScriptValue& avatarIdsToInclude, const QScriptValue& avatarIdsToDiscard) { - RayToAvatarIntersectionResult result; - if (QThread::currentThread() != thread()) { - BLOCKING_INVOKE_METHOD(const_cast(this), "findRayIntersection", - Q_RETURN_ARG(RayToAvatarIntersectionResult, result), - Q_ARG(const PickRay&, ray), - Q_ARG(const QScriptValue&, avatarIdsToInclude), - Q_ARG(const QScriptValue&, avatarIdsToDiscard)); - return result; - } - QVector avatarsToInclude = qVectorEntityItemIDFromScriptValue(avatarIdsToInclude); QVector avatarsToDiscard = qVectorEntityItemIDFromScriptValue(avatarIdsToDiscard); + return findRayIntersectionVector(ray, avatarsToInclude, avatarsToDiscard); +} + +RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const PickRay& ray, + const QVector& avatarsToInclude, + const QVector& avatarsToDiscard) { + RayToAvatarIntersectionResult result; + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(const_cast(this), "findRayIntersectionVector", + Q_RETURN_ARG(RayToAvatarIntersectionResult, result), + Q_ARG(const PickRay&, ray), + Q_ARG(const QVector&, avatarsToInclude), + Q_ARG(const QVector&, avatarsToDiscard)); + return result; + } + glm::vec3 normDirection = glm::normalize(ray.direction); for (auto avatarData : _avatarHash) { diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 30801807d6..810d419a55 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -73,6 +73,9 @@ public: Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray, const QScriptValue& avatarIdsToInclude = QScriptValue(), const QScriptValue& avatarIdsToDiscard = QScriptValue()); + RayToAvatarIntersectionResult findRayIntersectionVector(const PickRay& ray, + const QVector& avatarsToInclude, + const QVector& avatarsToDiscard); // TODO: remove this HACK once we settle on optimal default sort coefficients Q_INVOKABLE float getAvatarSortCoefficient(const QString& name); diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 8d7d47aca0..55ee0e16e9 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -10,72 +10,100 @@ // #include +#include #include #include "AccountManager.h" #include "Wallet.h" #include "Ledger.h" #include "CommerceLogging.h" +// inventory answers {status: 'success', data: {assets: [{id: "guid", title: "name", preview: "url"}....]}} +// balance answers {status: 'success', data: {balance: integer}} +// buy and receive_at answer {status: 'success'} + +QJsonObject Ledger::apiResponse(const QString& label, QNetworkReply& reply) { + QByteArray response = reply.readAll(); + QJsonObject data = QJsonDocument::fromJson(response).object(); + qInfo(commerce) << label << "response" << QJsonDocument(data).toJson(QJsonDocument::Compact); + return data; +} +// Non-200 responses are not json: +QJsonObject Ledger::failResponse(const QString& label, QNetworkReply& reply) { + QString response = reply.readAll(); + qWarning(commerce) << "FAILED" << label << response; + QJsonObject result + { + { "status", "fail" }, + { "message", response } + }; + return result; +} +#define ApiHandler(NAME) void Ledger::NAME##Success(QNetworkReply& reply) { emit NAME##Result(apiResponse(#NAME, reply)); } +#define FailHandler(NAME) void Ledger::NAME##Failure(QNetworkReply& reply) { emit NAME##Result(failResponse(#NAME, reply)); } +#define Handler(NAME) ApiHandler(NAME) FailHandler(NAME) +Handler(buy) +Handler(receiveAt) +Handler(balance) +Handler(inventory) + +void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, QJsonObject request) { + auto accountManager = DependencyManager::get(); + const QString URL = "/api/v1/commerce/"; + JSONCallbackParameters callbackParams(this, success, this, fail); + qCInfo(commerce) << "Sending" << endpoint << QJsonDocument(request).toJson(QJsonDocument::Compact); + accountManager->sendRequest(URL + endpoint, + AccountManagerAuth::Required, + method, + callbackParams, + QJsonDocument(request).toJson()); +} + +void Ledger::signedSend(const QString& propertyName, const QByteArray& text, const QString& key, const QString& endpoint, const QString& success, const QString& fail) { + auto wallet = DependencyManager::get(); + QString signature = key.isEmpty() ? "" : wallet->signWithKey(text, key); + QJsonObject request; + request[propertyName] = QString(text); + request["signature"] = signature; + send(endpoint, success, fail, QNetworkAccessManager::PutOperation, request); +} + +void Ledger::keysQuery(const QString& endpoint, const QString& success, const QString& fail) { + auto wallet = DependencyManager::get(); + QJsonObject request; + request["public_keys"] = QJsonArray::fromStringList(wallet->listPublicKeys()); + send(endpoint, success, fail, QNetworkAccessManager::PostOperation, request); +} + void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const QString& buyerUsername) { QJsonObject transaction; transaction["hfc_key"] = hfc_key; - transaction["hfc"] = cost; + transaction["cost"] = cost; transaction["asset_id"] = asset_id; transaction["inventory_key"] = inventory_key; transaction["inventory_buyer_username"] = buyerUsername; QJsonDocument transactionDoc{ transaction }; auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); - - auto wallet = DependencyManager::get(); - QString signature = wallet->signWithKey(transactionString, hfc_key); - QJsonObject request; - request["transaction"] = QString(transactionString); - request["signature"] = signature; - - qCInfo(commerce) << "Transaction:" << QJsonDocument(request).toJson(QJsonDocument::Compact); - // FIXME: talk to server instead - if (_inventory.contains(asset_id)) { - // This is here more for testing than as a definition of semantics. - // When we have popcerts, you will certainly be able to buy a new instance of an item that you already own a different instance of. - // I'm not sure what the server should do for now in this project's MVP. - return emit buyResult("Already owned."); - } - if (initializedBalance() < cost) { - return emit buyResult("Insufficient funds."); - } - _balance -= cost; - QJsonObject inventoryAdditionObject; - inventoryAdditionObject["id"] = asset_id; - inventoryAdditionObject["title"] = "Test Title"; - inventoryAdditionObject["preview"] = "https://www.aspca.org/sites/default/files/cat-care_cat-nutrition-tips_overweight_body4_left.jpg"; - _inventory.push_back(inventoryAdditionObject); - emit buyResult(""); + signedSend("transaction", transactionString, hfc_key, "buy", "buySuccess", "buyFailure"); } -bool Ledger::receiveAt(const QString& hfc_key) { +bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key) { auto accountManager = DependencyManager::get(); if (!accountManager->isLoggedIn()) { qCWarning(commerce) << "Cannot set receiveAt when not logged in."; - emit receiveAtResult("Not logged in"); + QJsonObject result{ { "status", "fail" }, { "message", "Not logged in" } }; + emit receiveAtResult(result); return false; // We know right away that we will fail, so tell the caller. } - auto username = accountManager->getAccountInfo().getUsername(); - qCInfo(commerce) << "Setting default receiving key for" << username; - emit receiveAtResult(""); // FIXME: talk to server instead. + + signedSend("public_key", hfc_key.toUtf8(), old_key, "receive_at", "receiveAtSuccess", "receiveAtFailure"); return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in. } void Ledger::balance(const QStringList& keys) { - // FIXME: talk to server instead - qCInfo(commerce) << "Balance:" << initializedBalance(); - emit balanceResult(_balance, ""); + keysQuery("balance", "balanceSuccess", "balanceFailure"); } void Ledger::inventory(const QStringList& keys) { - // FIXME: talk to server instead - QJsonObject inventoryObject; - inventoryObject.insert("success", true); - inventoryObject.insert("assets", _inventory); - qCInfo(commerce) << "Inventory:" << inventoryObject; - emit inventoryResult(inventoryObject, ""); + keysQuery("inventory", "inventorySuccess", "inventoryFailure"); } + diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 74ed8c1ab3..e43b8453b9 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -14,9 +14,10 @@ #ifndef hifi_Ledger_h #define hifi_Ledger_h +#include #include -#include -#include +#include + class Ledger : public QObject, public Dependency { Q_OBJECT @@ -24,21 +25,32 @@ class Ledger : public QObject, public Dependency { public: void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const QString& buyerUsername = ""); - bool receiveAt(const QString& hfc_key); + bool receiveAt(const QString& hfc_key, const QString& old_key); void balance(const QStringList& keys); void inventory(const QStringList& keys); signals: - void buyResult(const QString& failureReason); - void receiveAtResult(const QString& failureReason); - void balanceResult(int balance, const QString& failureReason); - void inventoryResult(QJsonObject inventory, const QString& failureReason); + void buyResult(QJsonObject result); + void receiveAtResult(QJsonObject result); + void balanceResult(QJsonObject result); + void inventoryResult(QJsonObject result); + +public slots: + void buySuccess(QNetworkReply& reply); + void buyFailure(QNetworkReply& reply); + void receiveAtSuccess(QNetworkReply& reply); + void receiveAtFailure(QNetworkReply& reply); + void balanceSuccess(QNetworkReply& reply); + void balanceFailure(QNetworkReply& reply); + void inventorySuccess(QNetworkReply& reply); + void inventoryFailure(QNetworkReply& reply); private: - // These in-memory caches is temporary, until we start sending things to the server. - int _balance{ -1 }; - QJsonArray _inventory{}; - int initializedBalance() { if (_balance < 0) _balance = 100; return _balance; } + QJsonObject apiResponse(const QString& label, QNetworkReply& reply); + QJsonObject failResponse(const QString& label, QNetworkReply& reply); + void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, QJsonObject request); + void keysQuery(const QString& endpoint, const QString& success, const QString& fail); + void signedSend(const QString& propertyName, const QByteArray& text, const QString& key, const QString& endpoint, const QString& success, const QString& fail); }; #endif // hifi_Ledger_h diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index b0963fbdab..c50137fe9d 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -1,5 +1,5 @@ // -// Commerce.cpp +// QmlCommerce.cpp // interface/src/commerce // // Created by Howard Stearns on 8/4/17. @@ -31,14 +31,12 @@ void QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUser auto wallet = DependencyManager::get(); QStringList keys = wallet->listPublicKeys(); if (keys.count() == 0) { - return emit buyResult("Uninitialized Wallet."); + QJsonObject result{ { "status", "fail" }, { "message", "Uninitialized Wallet." } }; + return emit buyResult(result); } QString key = keys[0]; // For now, we receive at the same key that pays for it. ledger->buy(key, cost, assetId, key, buyerUsername); - // FIXME: until we start talking to server, report post-transaction balance and inventory so we can see log for testing. - balance(); - inventory(); } void QmlCommerce::balance() { diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 4f86df8299..decc727928 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -1,5 +1,5 @@ // -// Commerce.h +// QmlCommerce.h // interface/src/commerce // // Guard for safe use of Commerce (Wallet, Ledger) by authorized QML. @@ -15,6 +15,7 @@ #ifndef hifi_QmlCommerce_h #define hifi_QmlCommerce_h +#include #include #include @@ -27,11 +28,11 @@ public: QmlCommerce(QQuickItem* parent = nullptr); signals: - void buyResult(const QString& failureMessage); - // Balance and Inventory are NOT properties, because QML can't change them (without risk of failure), and + void buyResult(QJsonObject result); + // Balance and Inventory are NOT properties, because QML can't change them (without risk of failure), and // 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 balanceResult(QJsonObject result); + void inventoryResult(QJsonObject result); void securityImageResult(QPixmap* image); protected: diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 651a395b81..9e7bc58024 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -323,7 +323,6 @@ bool Wallet::createIfNeeded() { 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; } } @@ -334,16 +333,17 @@ bool Wallet::createIfNeeded() { bool Wallet::generateKeyPair() { qCInfo(commerce) << "Generating keypair."; auto keyPair = generateRSAKeypair(); - - _publicKeys.push_back(QUrl::toPercentEncoding(keyPair.first->toBase64())); - qCDebug(commerce) << "public key:" << _publicKeys.last(); + QString oldKey = _publicKeys.count() == 0 ? "" : _publicKeys.last(); + QString key = keyPair.first->toBase64(); + _publicKeys.push_back(key); + qCDebug(commerce) << "public key:" << key; // 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(_publicKeys.last()); + return ledger->receiveAt(key, oldKey); } QStringList Wallet::listPublicKeys() { qCInfo(commerce) << "Enumerating public keys."; @@ -378,7 +378,7 @@ QString Wallet::signWithKey(const QByteArray& text, const QString& key) { RSA_free(rsaPrivateKey); if (encryptReturn != -1) { - return QUrl::toPercentEncoding(signature.toBase64()); + return signature.toBase64(); } } return QString(); diff --git a/interface/src/main.cpp b/interface/src/main.cpp index a19055d4da..503daa177d 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -216,7 +216,12 @@ int main(int argc, const char* argv[]) { SandboxUtils::runLocalSandbox(serverContentPath, true, noUpdater); } - Application app(argc, const_cast(argv), startupTime, runningMarkerExisted); + // Extend argv to enable WebGL rendering + std::vector argvExtended(&argv[0], &argv[argc]); + argvExtended.push_back("--ignore-gpu-blacklist"); + int argcExtended = (int)argvExtended.size(); + + Application app(argcExtended, const_cast(argvExtended.data()), startupTime, runningMarkerExisted); // If we failed the OpenGLVersion check, log it. if (override) { diff --git a/interface/src/raypick/JointRayPick.cpp b/interface/src/raypick/JointRayPick.cpp new file mode 100644 index 0000000000..48ab908201 --- /dev/null +++ b/interface/src/raypick/JointRayPick.cpp @@ -0,0 +1,48 @@ +// +// JointRayPick.cpp +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// 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 +// +#include "JointRayPick.h" + +#include "DependencyManager.h" +#include "avatar/AvatarManager.h" + +JointRayPick::JointRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const RayPickFilter& filter, const float maxDistance, const bool enabled) : + RayPick(filter, maxDistance, enabled), + _jointName(jointName), + _posOffset(posOffset), + _dirOffset(dirOffset) +{ +} + +const PickRay JointRayPick::getPickRay(bool& valid) const { + auto myAvatar = DependencyManager::get()->getMyAvatar(); + int jointIndex = myAvatar->getJointIndex(QString::fromStdString(_jointName)); + bool useAvatarHead = _jointName == "Avatar"; + const int INVALID_JOINT = -1; + if (jointIndex != INVALID_JOINT || useAvatarHead) { + glm::vec3 jointPos = useAvatarHead ? myAvatar->getHeadPosition() : myAvatar->getAbsoluteJointTranslationInObjectFrame(jointIndex); + glm::quat jointRot = useAvatarHead ? myAvatar->getHeadOrientation() : myAvatar->getAbsoluteJointRotationInObjectFrame(jointIndex); + glm::vec3 avatarPos = myAvatar->getPosition(); + glm::quat avatarRot = myAvatar->getOrientation(); + + glm::vec3 pos = useAvatarHead ? jointPos : avatarPos + (avatarRot * jointPos); + glm::quat rot = useAvatarHead ? jointRot * glm::angleAxis(-PI / 2.0f, Vectors::RIGHT) : avatarRot * jointRot; + + // Apply offset + pos = pos + (rot * _posOffset); + glm::vec3 dir = rot * glm::normalize(_dirOffset); + + valid = true; + return PickRay(pos, dir); + } + + valid = false; + return PickRay(); +} diff --git a/interface/src/raypick/JointRayPick.h b/interface/src/raypick/JointRayPick.h new file mode 100644 index 0000000000..3cd622fbbe --- /dev/null +++ b/interface/src/raypick/JointRayPick.h @@ -0,0 +1,30 @@ +// +// JointRayPick.h +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// 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 +// +#ifndef hifi_JointRayPick_h +#define hifi_JointRayPick_h + +#include "RayPick.h" + +class JointRayPick : public RayPick { + +public: + JointRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const RayPickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false); + + const PickRay getPickRay(bool& valid) const override; + +private: + std::string _jointName; + glm::vec3 _posOffset; + glm::vec3 _dirOffset; + +}; + +#endif // hifi_JointRayPick_h diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp new file mode 100644 index 0000000000..f4adcc736d --- /dev/null +++ b/interface/src/raypick/LaserPointer.cpp @@ -0,0 +1,221 @@ +// +// LaserPointer.cpp +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// 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 +// +#include "LaserPointer.h" + +#include "Application.h" +#include "ui/overlays/Overlay.h" +#include "avatar/AvatarManager.h" + +LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, + const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool enabled) : + _renderingEnabled(enabled), + _renderStates(renderStates), + _defaultRenderStates(defaultRenderStates), + _faceAvatar(faceAvatar), + _centerEndY(centerEndY), + _lockEnd(lockEnd) +{ + _rayPickUID = DependencyManager::get()->createRayPick(rayProps); + + for (auto& state : _renderStates) { + if (!enabled || state.first != _currentRenderState) { + disableRenderState(state.second); + } + } + for (auto& state : _defaultRenderStates) { + if (!enabled || state.first != _currentRenderState) { + disableRenderState(state.second.second); + } + } +} + +LaserPointer::~LaserPointer() { + DependencyManager::get()->removeRayPick(_rayPickUID); + + for (auto& renderState : _renderStates) { + renderState.second.deleteOverlays(); + } + for (auto& renderState : _defaultRenderStates) { + renderState.second.second.deleteOverlays(); + } +} + +void LaserPointer::enable() { + DependencyManager::get()->enableRayPick(_rayPickUID); + _renderingEnabled = true; +} + +void LaserPointer::disable() { + DependencyManager::get()->disableRayPick(_rayPickUID); + _renderingEnabled = false; + if (!_currentRenderState.empty()) { + if (_renderStates.find(_currentRenderState) != _renderStates.end()) { + disableRenderState(_renderStates[_currentRenderState]); + } + if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { + disableRenderState(_defaultRenderStates[_currentRenderState].second); + } + } +} + +void LaserPointer::setRenderState(const std::string& state) { + if (!_currentRenderState.empty() && state != _currentRenderState) { + if (_renderStates.find(_currentRenderState) != _renderStates.end()) { + disableRenderState(_renderStates[_currentRenderState]); + } + if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { + disableRenderState(_defaultRenderStates[_currentRenderState].second); + } + } + _currentRenderState = state; +} + +void LaserPointer::editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) { + updateRenderStateOverlay(_renderStates[state].getStartID(), startProps); + updateRenderStateOverlay(_renderStates[state].getPathID(), pathProps); + updateRenderStateOverlay(_renderStates[state].getEndID(), endProps); +} + +void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) { + if (!id.isNull() && props.isValid()) { + qApp->getOverlays().editOverlay(id, props); + } +} + +void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, const float distance, const QUuid& objectID, const bool defaultState) { + PickRay pickRay = qApp->getRayPickManager().getPickRay(_rayPickUID); + if (!renderState.getStartID().isNull()) { + QVariantMap startProps; + startProps.insert("position", vec3toVariant(pickRay.origin)); + startProps.insert("visible", true); + startProps.insert("ignoreRayIntersection", renderState.doesStartIgnoreRays()); + qApp->getOverlays().editOverlay(renderState.getStartID(), startProps); + } + glm::vec3 endVec; + if (((defaultState || !_lockEnd) && _objectLockEnd.first.isNull()) || type == IntersectionType::HUD) { + endVec = pickRay.origin + pickRay.direction * distance; + } else { + if (!_objectLockEnd.first.isNull()) { + glm::vec3 pos; + glm::quat rot; + glm::vec3 dim; + glm::vec3 registrationPoint; + if (_objectLockEnd.second) { + pos = vec3FromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "position").value); + rot = quatFromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "rotation").value); + dim = vec3FromVariant(qApp->getOverlays().getProperty(_objectLockEnd.first, "dimensions").value); + registrationPoint = glm::vec3(0.5f); + } else { + EntityItemProperties props = DependencyManager::get()->getEntityProperties(_objectLockEnd.first); + pos = props.getPosition(); + rot = props.getRotation(); + dim = props.getDimensions(); + registrationPoint = props.getRegistrationPoint(); + } + const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f); + endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint)); + } else { + if (type == IntersectionType::ENTITY) { + endVec = DependencyManager::get()->getEntityTransform(objectID)[3]; + } else if (type == IntersectionType::OVERLAY) { + endVec = vec3FromVariant(qApp->getOverlays().getProperty(objectID, "position").value); + } else if (type == IntersectionType::AVATAR) { + endVec = DependencyManager::get()->getAvatar(objectID)->getPosition(); + } + } + } + QVariant end = vec3toVariant(endVec); + if (!renderState.getPathID().isNull()) { + QVariantMap pathProps; + pathProps.insert("start", vec3toVariant(pickRay.origin)); + pathProps.insert("end", end); + pathProps.insert("visible", true); + pathProps.insert("ignoreRayIntersection", renderState.doesPathIgnoreRays()); + qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps); + } + if (!renderState.getEndID().isNull()) { + QVariantMap endProps; + if (_centerEndY) { + endProps.insert("position", end); + } else { + glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(renderState.getEndID(), "dimensions").value); + endProps.insert("position", vec3toVariant(endVec + glm::vec3(0, 0.5f * dim.y, 0))); + } + if (_faceAvatar) { + glm::quat rotation = glm::inverse(glm::quat_cast(glm::lookAt(endVec, DependencyManager::get()->getMyAvatar()->getPosition(), Vectors::UP))); + endProps.insert("rotation", quatToVariant(glm::quat(glm::radians(glm::vec3(0, glm::degrees(safeEulerAngles(rotation)).y, 0))))); + } + endProps.insert("visible", true); + endProps.insert("ignoreRayIntersection", renderState.doesEndIgnoreRays()); + qApp->getOverlays().editOverlay(renderState.getEndID(), endProps); + } +} + +void LaserPointer::disableRenderState(const RenderState& renderState) { + if (!renderState.getStartID().isNull()) { + QVariantMap startProps; + startProps.insert("visible", false); + startProps.insert("ignoreRayIntersection", true); + qApp->getOverlays().editOverlay(renderState.getStartID(), startProps); + } + if (!renderState.getPathID().isNull()) { + QVariantMap pathProps; + pathProps.insert("visible", false); + pathProps.insert("ignoreRayIntersection", true); + qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps); + } + if (!renderState.getEndID().isNull()) { + QVariantMap endProps; + endProps.insert("visible", false); + endProps.insert("ignoreRayIntersection", true); + qApp->getOverlays().editOverlay(renderState.getEndID(), endProps); + } +} + +void LaserPointer::update() { + RayPickResult prevRayPickResult = DependencyManager::get()->getPrevRayPickResult(_rayPickUID); + if (_renderingEnabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && prevRayPickResult.type != IntersectionType::NONE) { + updateRenderState(_renderStates[_currentRenderState], prevRayPickResult.type, prevRayPickResult.distance, prevRayPickResult.objectID, false); + disableRenderState(_defaultRenderStates[_currentRenderState].second); + } else if (_renderingEnabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { + disableRenderState(_renderStates[_currentRenderState]); + updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), true); + } else if (!_currentRenderState.empty()) { + disableRenderState(_renderStates[_currentRenderState]); + disableRenderState(_defaultRenderStates[_currentRenderState].second); + } +} + +RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) : + _startID(startID), _pathID(pathID), _endID(endID) +{ + if (!_startID.isNull()) { + _startIgnoreRays = qApp->getOverlays().getProperty(_startID, "ignoreRayIntersection").value.toBool(); + } + if (!_pathID.isNull()) { + _pathIgnoreRays = qApp->getOverlays().getProperty(_pathID, "ignoreRayIntersection").value.toBool(); + } + if (!_endID.isNull()) { + _endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignoreRayIntersection").value.toBool(); + } +} + +void RenderState::deleteOverlays() { + if (!_startID.isNull()) { + qApp->getOverlays().deleteOverlay(_startID); + } + if (!_pathID.isNull()) { + qApp->getOverlays().deleteOverlay(_pathID); + } + if (!_endID.isNull()) { + qApp->getOverlays().deleteOverlay(_endID); + } +} \ No newline at end of file diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h new file mode 100644 index 0000000000..d901d12cf4 --- /dev/null +++ b/interface/src/raypick/LaserPointer.h @@ -0,0 +1,96 @@ +// +// LaserPointer.h +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// 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 +// +#ifndef hifi_LaserPointer_h +#define hifi_LaserPointer_h + +#include +#include "glm/glm.hpp" + +#include +#include "raypick/RayPickScriptingInterface.h" + +class RayPickResult; + +class RenderState { + +public: + RenderState() {} + RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID); + + const OverlayID& getStartID() const { return _startID; } + const OverlayID& getPathID() const { return _pathID; } + const OverlayID& getEndID() const { return _endID; } + const bool& doesStartIgnoreRays() const { return _startIgnoreRays; } + const bool& doesPathIgnoreRays() const { return _pathIgnoreRays; } + const bool& doesEndIgnoreRays() const { return _endIgnoreRays; } + + void deleteOverlays(); + +private: + OverlayID _startID; + OverlayID _pathID; + OverlayID _endID; + bool _startIgnoreRays; + bool _pathIgnoreRays; + bool _endIgnoreRays; +}; + + +class LaserPointer { + +public: + + typedef std::unordered_map RenderStateMap; + typedef std::unordered_map> DefaultRenderStateMap; + + LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, + const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool enabled); + ~LaserPointer(); + + QUuid getRayUID() { return _rayPickUID; } + void enable(); + void disable(); + const RayPickResult getPrevRayPickResult() { return DependencyManager::get()->getPrevRayPickResult(_rayPickUID); } + + void setRenderState(const std::string& state); + // You cannot use editRenderState to change the overlay type of any part of the laser pointer. You can only edit the properties of the existing overlays. + void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps); + + void setIgnoreEntities(const QScriptValue& ignoreEntities) { DependencyManager::get()->setIgnoreEntities(_rayPickUID, ignoreEntities); } + void setIncludeEntities(const QScriptValue& includeEntities) { DependencyManager::get()->setIncludeEntities(_rayPickUID, includeEntities); } + void setIgnoreOverlays(const QScriptValue& ignoreOverlays) { DependencyManager::get()->setIgnoreOverlays(_rayPickUID, ignoreOverlays); } + void setIncludeOverlays(const QScriptValue& includeOverlays) { DependencyManager::get()->setIncludeOverlays(_rayPickUID, includeOverlays); } + void setIgnoreAvatars(const QScriptValue& ignoreAvatars) { DependencyManager::get()->setIgnoreAvatars(_rayPickUID, ignoreAvatars); } + void setIncludeAvatars(const QScriptValue& includeAvatars) { DependencyManager::get()->setIncludeAvatars(_rayPickUID, includeAvatars); } + + void setLockEndUUID(QUuid objectID, const bool isOverlay) { _objectLockEnd = std::pair(objectID, isOverlay); } + + void update(); + +private: + bool _renderingEnabled; + std::string _currentRenderState { "" }; + RenderStateMap _renderStates; + DefaultRenderStateMap _defaultRenderStates; + bool _faceAvatar; + bool _centerEndY; + bool _lockEnd; + std::pair _objectLockEnd { std::pair(QUuid(), false)}; + + QUuid _rayPickUID; + + void updateRenderStateOverlay(const OverlayID& id, const QVariant& props); + void updateRenderState(const RenderState& renderState, const IntersectionType type, const float distance, const QUuid& objectID, const bool defaultState); + void disableRenderState(const RenderState& renderState); + +}; + +#endif // hifi_LaserPointer_h diff --git a/interface/src/raypick/LaserPointerManager.cpp b/interface/src/raypick/LaserPointerManager.cpp new file mode 100644 index 0000000000..908bcc39f1 --- /dev/null +++ b/interface/src/raypick/LaserPointerManager.cpp @@ -0,0 +1,154 @@ +// +// LaserPointerManager.cpp +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// 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 +// +#include "LaserPointerManager.h" + +QUuid LaserPointerManager::createLaserPointer(const QVariant& rayProps, const LaserPointer::RenderStateMap& renderStates, const LaserPointer::DefaultRenderStateMap& defaultRenderStates, + const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool enabled) { + std::shared_ptr laserPointer = std::make_shared(rayProps, renderStates, defaultRenderStates, faceAvatar, centerEndY, lockEnd, enabled); + if (!laserPointer->getRayUID().isNull()) { + QWriteLocker lock(&_addLock); + QUuid id = QUuid::createUuid(); + _laserPointersToAdd.push(std::pair>(id, laserPointer)); + return id; + } + return QUuid(); +} + +void LaserPointerManager::removeLaserPointer(const QUuid uid) { + QWriteLocker lock(&_removeLock); + _laserPointersToRemove.push(uid); +} + +void LaserPointerManager::enableLaserPointer(const QUuid uid) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->enable(); + } +} + +void LaserPointerManager::disableLaserPointer(const QUuid uid) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->disable(); + } +} + +void LaserPointerManager::setRenderState(QUuid uid, const std::string& renderState) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->setRenderState(renderState); + } +} + +void LaserPointerManager::editRenderState(QUuid uid, const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->editRenderState(state, startProps, pathProps, endProps); + } +} + +const RayPickResult LaserPointerManager::getPrevRayPickResult(const QUuid uid) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QReadLocker laserLock(_laserPointerLocks[uid].get()); + return _laserPointers[uid]->getPrevRayPickResult(); + } + return RayPickResult(); +} + +void LaserPointerManager::update() { + for (QUuid& uid : _laserPointers.keys()) { + // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts + QReadLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->update(); + } + + QWriteLocker containsLock(&_containsLock); + { + QWriteLocker lock(&_addLock); + while (!_laserPointersToAdd.empty()) { + std::pair> laserPointerToAdd = _laserPointersToAdd.front(); + _laserPointersToAdd.pop(); + _laserPointers[laserPointerToAdd.first] = laserPointerToAdd.second; + _laserPointerLocks[laserPointerToAdd.first] = std::make_shared(); + } + } + + { + QWriteLocker lock(&_removeLock); + while (!_laserPointersToRemove.empty()) { + QUuid uid = _laserPointersToRemove.front(); + _laserPointersToRemove.pop(); + _laserPointers.remove(uid); + _laserPointerLocks.remove(uid); + } + } +} + +void LaserPointerManager::setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->setIgnoreEntities(ignoreEntities); + } +} + +void LaserPointerManager::setIncludeEntities(QUuid uid, const QScriptValue& includeEntities) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->setIncludeEntities(includeEntities); + } +} + +void LaserPointerManager::setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->setIgnoreOverlays(ignoreOverlays); + } +} + +void LaserPointerManager::setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->setIncludeOverlays(includeOverlays); + } +} + +void LaserPointerManager::setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->setIgnoreAvatars(ignoreAvatars); + } +} + +void LaserPointerManager::setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->setIncludeAvatars(includeAvatars); + } +} + +void LaserPointerManager::setLockEndUUID(QUuid uid, QUuid objectID, const bool isOverlay) { + QReadLocker lock(&_containsLock); + if (_laserPointers.contains(uid)) { + QWriteLocker laserLock(_laserPointerLocks[uid].get()); + _laserPointers[uid]->setLockEndUUID(objectID, isOverlay); + } +} diff --git a/interface/src/raypick/LaserPointerManager.h b/interface/src/raypick/LaserPointerManager.h new file mode 100644 index 0000000000..020b778983 --- /dev/null +++ b/interface/src/raypick/LaserPointerManager.h @@ -0,0 +1,56 @@ +// +// LaserPointerManager.h +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// 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 +// +#ifndef hifi_LaserPointerManager_h +#define hifi_LaserPointerManager_h + +#include +#include +#include + +#include "LaserPointer.h" + +class RayPickResult; + +class LaserPointerManager { + +public: + QUuid createLaserPointer(const QVariant& rayProps, const LaserPointer::RenderStateMap& renderStates, const LaserPointer::DefaultRenderStateMap& defaultRenderStates, + const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool enabled); + void removeLaserPointer(const QUuid uid); + void enableLaserPointer(const QUuid uid); + void disableLaserPointer(const QUuid uid); + void setRenderState(QUuid uid, const std::string& renderState); + void editRenderState(QUuid uid, const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps); + const RayPickResult getPrevRayPickResult(const QUuid uid); + + void setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities); + void setIncludeEntities(QUuid uid, const QScriptValue& includeEntities); + void setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays); + void setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays); + void setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars); + void setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars); + + void setLockEndUUID(QUuid uid, QUuid objectID, const bool isOverlay); + + void update(); + +private: + QHash> _laserPointers; + QHash> _laserPointerLocks; + QReadWriteLock _addLock; + std::queue>> _laserPointersToAdd; + QReadWriteLock _removeLock; + std::queue _laserPointersToRemove; + QReadWriteLock _containsLock; + +}; + +#endif // hifi_LaserPointerManager_h diff --git a/interface/src/raypick/LaserPointerScriptingInterface.cpp b/interface/src/raypick/LaserPointerScriptingInterface.cpp new file mode 100644 index 0000000000..a976a00893 --- /dev/null +++ b/interface/src/raypick/LaserPointerScriptingInterface.cpp @@ -0,0 +1,120 @@ +// +// LaserPointerScriptingInterface.cpp +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// 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 +// + +#include "LaserPointerScriptingInterface.h" + +#include +#include "GLMHelpers.h" + +QUuid LaserPointerScriptingInterface::createLaserPointer(const QVariant& properties) { + QVariantMap propertyMap = properties.toMap(); + + bool faceAvatar = false; + if (propertyMap["faceAvatar"].isValid()) { + faceAvatar = propertyMap["faceAvatar"].toBool(); + } + + bool centerEndY = true; + if (propertyMap["centerEndY"].isValid()) { + centerEndY = propertyMap["centerEndY"].toBool(); + } + + bool lockEnd = false; + if (propertyMap["lockEnd"].isValid()) { + lockEnd = propertyMap["lockEnd"].toBool(); + } + + bool enabled = false; + if (propertyMap["enabled"].isValid()) { + enabled = propertyMap["enabled"].toBool(); + } + + LaserPointer::RenderStateMap renderStates; + if (propertyMap["renderStates"].isValid()) { + QList renderStateVariants = propertyMap["renderStates"].toList(); + for (QVariant& renderStateVariant : renderStateVariants) { + if (renderStateVariant.isValid()) { + QVariantMap renderStateMap = renderStateVariant.toMap(); + if (renderStateMap["name"].isValid()) { + std::string name = renderStateMap["name"].toString().toStdString(); + renderStates[name] = buildRenderState(renderStateMap); + } + } + } + } + + LaserPointer::DefaultRenderStateMap defaultRenderStates; + if (propertyMap["defaultRenderStates"].isValid()) { + QList renderStateVariants = propertyMap["defaultRenderStates"].toList(); + for (QVariant& renderStateVariant : renderStateVariants) { + if (renderStateVariant.isValid()) { + QVariantMap renderStateMap = renderStateVariant.toMap(); + if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) { + std::string name = renderStateMap["name"].toString().toStdString(); + float distance = renderStateMap["distance"].toFloat(); + defaultRenderStates[name] = std::pair(distance, buildRenderState(renderStateMap)); + } + } + } + } + + return qApp->getLaserPointerManager().createLaserPointer(properties, renderStates, defaultRenderStates, faceAvatar, centerEndY, lockEnd, enabled); +} + +void LaserPointerScriptingInterface::editRenderState(QUuid uid, const QString& renderState, const QVariant& properties) { + QVariantMap propMap = properties.toMap(); + + QVariant startProps; + if (propMap["start"].isValid()) { + startProps = propMap["start"]; + } + + QVariant pathProps; + if (propMap["path"].isValid()) { + pathProps = propMap["path"]; + } + + QVariant endProps; + if (propMap["end"].isValid()) { + endProps = propMap["end"]; + } + + qApp->getLaserPointerManager().editRenderState(uid, renderState.toStdString(), startProps, pathProps, endProps); +} + +const RenderState LaserPointerScriptingInterface::buildRenderState(const QVariantMap& propMap) { + QUuid startID; + if (propMap["start"].isValid()) { + QVariantMap startMap = propMap["start"].toMap(); + if (startMap["type"].isValid()) { + startID = qApp->getOverlays().addOverlay(startMap["type"].toString(), startMap); + } + } + + QUuid pathID; + if (propMap["path"].isValid()) { + QVariantMap pathMap = propMap["path"].toMap(); + // right now paths must be line3ds + if (pathMap["type"].isValid() && pathMap["type"].toString() == "line3d") { + pathID = qApp->getOverlays().addOverlay(pathMap["type"].toString(), pathMap); + } + } + + QUuid endID; + if (propMap["end"].isValid()) { + QVariantMap endMap = propMap["end"].toMap(); + if (endMap["type"].isValid()) { + endID = qApp->getOverlays().addOverlay(endMap["type"].toString(), endMap); + } + } + + return RenderState(startID, pathID, endID); +} \ No newline at end of file diff --git a/interface/src/raypick/LaserPointerScriptingInterface.h b/interface/src/raypick/LaserPointerScriptingInterface.h new file mode 100644 index 0000000000..8ae263b0ec --- /dev/null +++ b/interface/src/raypick/LaserPointerScriptingInterface.h @@ -0,0 +1,47 @@ +// +// LaserPointerScriptingInterface.h +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// 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 +// +#ifndef hifi_LaserPointerScriptingInterface_h +#define hifi_LaserPointerScriptingInterface_h + +#include + +#include "RegisteredMetaTypes.h" +#include "DependencyManager.h" +#include "Application.h" + +class LaserPointerScriptingInterface : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public slots: + Q_INVOKABLE QUuid createLaserPointer(const QVariant& properties); + Q_INVOKABLE void enableLaserPointer(QUuid uid) { qApp->getLaserPointerManager().enableLaserPointer(uid); } + Q_INVOKABLE void disableLaserPointer(QUuid uid) { qApp->getLaserPointerManager().disableLaserPointer(uid); } + Q_INVOKABLE void removeLaserPointer(QUuid uid) { qApp->getLaserPointerManager().removeLaserPointer(uid); } + Q_INVOKABLE void editRenderState(QUuid uid, const QString& renderState, const QVariant& properties); + Q_INVOKABLE void setRenderState(QUuid uid, const QString& renderState) { qApp->getLaserPointerManager().setRenderState(uid, renderState.toStdString()); } + Q_INVOKABLE RayPickResult getPrevRayPickResult(QUuid uid) { return qApp->getLaserPointerManager().getPrevRayPickResult(uid); } + + Q_INVOKABLE void setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) { qApp->getLaserPointerManager().setIgnoreEntities(uid, ignoreEntities); } + Q_INVOKABLE void setIncludeEntities(QUuid uid, const QScriptValue& includeEntities) { qApp->getLaserPointerManager().setIncludeEntities(uid, includeEntities); } + Q_INVOKABLE void setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays) { qApp->getLaserPointerManager().setIgnoreOverlays(uid, ignoreOverlays); } + Q_INVOKABLE void setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays) { qApp->getLaserPointerManager().setIncludeOverlays(uid, includeOverlays); } + Q_INVOKABLE void setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars) { qApp->getLaserPointerManager().setIgnoreAvatars(uid, ignoreAvatars); } + Q_INVOKABLE void setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars) { qApp->getLaserPointerManager().setIncludeAvatars(uid, includeAvatars); } + + Q_INVOKABLE void setLockEndUUID(QUuid uid, QUuid objectID, const bool isOverlay) { qApp->getLaserPointerManager().setLockEndUUID(uid, objectID, isOverlay); } + +private: + const RenderState buildRenderState(const QVariantMap& propMap); + +}; + +#endif // hifi_LaserPointerScriptingInterface_h diff --git a/interface/src/raypick/MouseRayPick.cpp b/interface/src/raypick/MouseRayPick.cpp new file mode 100644 index 0000000000..de59fde88d --- /dev/null +++ b/interface/src/raypick/MouseRayPick.cpp @@ -0,0 +1,32 @@ +// +// MouseRayPick.cpp +// interface/src/raypick +// +// Created by Sam Gondelman 7/19/2017 +// 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 +// +#include "MouseRayPick.h" + +#include "DependencyManager.h" +#include "Application.h" +#include "display-plugins/CompositorHelper.h" + +MouseRayPick::MouseRayPick(const RayPickFilter& filter, const float maxDistance, const bool enabled) : + RayPick(filter, maxDistance, enabled) +{ +} + +const PickRay MouseRayPick::getPickRay(bool& valid) const { + QVariant position = qApp->getApplicationCompositor().getReticleInterface()->getPosition(); + if (position.isValid()) { + QVariantMap posMap = position.toMap(); + valid = true; + return qApp->getCamera().computePickRay(posMap["x"].toFloat(), posMap["y"].toFloat()); + } + + valid = false; + return PickRay(); +} diff --git a/interface/src/raypick/MouseRayPick.h b/interface/src/raypick/MouseRayPick.h new file mode 100644 index 0000000000..848a5de336 --- /dev/null +++ b/interface/src/raypick/MouseRayPick.h @@ -0,0 +1,24 @@ +// +// MouseRayPick.h +// interface/src/raypick +// +// Created by Sam Gondelman 7/19/2017 +// 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 +// +#ifndef hifi_MouseRayPick_h +#define hifi_MouseRayPick_h + +#include "RayPick.h" + +class MouseRayPick : public RayPick { + +public: + MouseRayPick(const RayPickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false); + + const PickRay getPickRay(bool& valid) const override; +}; + +#endif // hifi_MouseRayPick_h diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp new file mode 100644 index 0000000000..70170a8f85 --- /dev/null +++ b/interface/src/raypick/RayPick.cpp @@ -0,0 +1,18 @@ +// +// RayPick.cpp +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// 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 +// +#include "RayPick.h" + +RayPick::RayPick(const RayPickFilter& filter, const float maxDistance, const bool enabled) : + _filter(filter), + _maxDistance(maxDistance), + _enabled(enabled) +{ +} diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h new file mode 100644 index 0000000000..e108145d55 --- /dev/null +++ b/interface/src/raypick/RayPick.h @@ -0,0 +1,134 @@ +// +// RayPick.h +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// 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 +// +#ifndef hifi_RayPick_h +#define hifi_RayPick_h + +#include +#include "RegisteredMetaTypes.h" + +#include "EntityItemID.h" +#include "ui/overlays/Overlay.h" + +class RayPickFilter { +public: + enum FlagBit { + PICK_NOTHING = 0, + PICK_ENTITIES, + PICK_OVERLAYS, + PICK_AVATARS, + PICK_HUD, + + PICK_COURSE, // if not set, does precise intersection, otherwise, doesn't + + PICK_INCLUDE_INVISIBLE, // if not set, will not intersect invisible elements, otherwise, intersects both visible and invisible elements + PICK_INCLUDE_NONCOLLIDABLE, // if not set, will not intersect noncollidable elements, otherwise, intersects both collidable and noncollidable elements + + // NOT YET IMPLEMENTED + PICK_ALL_INTERSECTIONS, // if not set, returns closest intersection, otherwise, returns list of all intersections + + NUM_FLAGS, // Not a valid flag + }; + typedef std::bitset Flags; + + // The key is the Flags + Flags _flags; + + RayPickFilter() : _flags(PICK_NOTHING) {} + RayPickFilter(const Flags& flags) : _flags(flags) {} + + bool operator== (const RayPickFilter& rhs) const { return _flags == rhs._flags; } + bool operator!= (const RayPickFilter& rhs) const { return _flags != rhs._flags; } + + bool doesPickNothing() const { return _flags[PICK_NOTHING]; } + bool doesPickEntities() const { return _flags[PICK_ENTITIES]; } + bool doesPickOverlays() const { return _flags[PICK_OVERLAYS]; } + bool doesPickAvatars() const { return _flags[PICK_AVATARS]; } + bool doesPickHUD() const { return _flags[PICK_HUD]; } + + bool doesPickCourse() const { return _flags[PICK_COURSE]; } + bool doesPickInvisible() const { return _flags[PICK_INCLUDE_INVISIBLE]; } + bool doesPickNonCollidable() const { return _flags[PICK_INCLUDE_NONCOLLIDABLE]; } + + bool doesWantAllIntersections() const { return _flags[PICK_ALL_INTERSECTIONS]; } + + // Helpers for RayPickManager + Flags getEntityFlags() const { + Flags toReturn(PICK_ENTITIES); + if (doesPickInvisible()) { + toReturn |= Flags(PICK_INCLUDE_INVISIBLE); + } + if (doesPickNonCollidable()) { + toReturn |= Flags(PICK_INCLUDE_NONCOLLIDABLE); + } + return toReturn; + } + Flags getOverlayFlags() const { + Flags toReturn(PICK_OVERLAYS); + if (doesPickInvisible()) { + toReturn |= Flags(PICK_INCLUDE_INVISIBLE); + } + if (doesPickNonCollidable()) { + toReturn |= Flags(PICK_INCLUDE_NONCOLLIDABLE); + } + return toReturn; + } + Flags getAvatarFlags() const { return Flags(PICK_AVATARS); } + Flags getHUDFlags() const { return Flags(PICK_HUD); } + + static unsigned int getBitMask(FlagBit bit) { return 1 << bit; } + +}; + +class RayPick { + +public: + RayPick(const RayPickFilter& filter, const float maxDistance, const bool enabled); + + virtual const PickRay getPickRay(bool& valid) const = 0; + + void enable() { _enabled = true; } + void disable() { _enabled = false; } + + const RayPickFilter& getFilter() { return _filter; } + const float& getMaxDistance() { return _maxDistance; } + const bool& isEnabled() { return _enabled; } + const RayPickResult& getPrevRayPickResult() { return _prevResult; } + + void setRayPickResult(const RayPickResult& rayPickResult) { _prevResult = rayPickResult; } + + const QVector& getIgnoreEntites() { return _ignoreEntities; } + const QVector& getIncludeEntites() { return _includeEntities; } + const QVector& getIgnoreOverlays() { return _ignoreOverlays; } + const QVector& getIncludeOverlays() { return _includeOverlays; } + const QVector& getIgnoreAvatars() { return _ignoreAvatars; } + const QVector& getIncludeAvatars() { return _includeAvatars; } + void setIgnoreEntities(const QScriptValue& ignoreEntities) { _ignoreEntities = qVectorEntityItemIDFromScriptValue(ignoreEntities); } + void setIncludeEntities(const QScriptValue& includeEntities) { _includeEntities = qVectorEntityItemIDFromScriptValue(includeEntities); } + void setIgnoreOverlays(const QScriptValue& ignoreOverlays) { _ignoreOverlays = qVectorOverlayIDFromScriptValue(ignoreOverlays); } + void setIncludeOverlays(const QScriptValue& includeOverlays) { _includeOverlays = qVectorOverlayIDFromScriptValue(includeOverlays); } + void setIgnoreAvatars(const QScriptValue& ignoreAvatars) { _ignoreAvatars = qVectorEntityItemIDFromScriptValue(ignoreAvatars); } + void setIncludeAvatars(const QScriptValue& includeAvatars) { _includeAvatars = qVectorEntityItemIDFromScriptValue(includeAvatars); } + +private: + RayPickFilter _filter; + float _maxDistance; + bool _enabled; + RayPickResult _prevResult; + + QVector _ignoreEntities; + QVector _includeEntities; + QVector _ignoreOverlays; + QVector _includeOverlays; + QVector _ignoreAvatars; + QVector _includeAvatars; +}; + +#endif // hifi_RayPick_h diff --git a/interface/src/raypick/RayPickManager.cpp b/interface/src/raypick/RayPickManager.cpp new file mode 100644 index 0000000000..8b639d4a81 --- /dev/null +++ b/interface/src/raypick/RayPickManager.cpp @@ -0,0 +1,253 @@ +// +// RayPickManager.cpp +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// 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 +// +#include "RayPickManager.h" + +#include "Application.h" +#include "EntityScriptingInterface.h" +#include "ui/overlays/Overlays.h" +#include "avatar/AvatarManager.h" +#include "scripting/HMDScriptingInterface.h" +#include "DependencyManager.h" + +#include "JointRayPick.h" +#include "StaticRayPick.h" +#include "MouseRayPick.h" + +bool RayPickManager::checkAndCompareCachedResults(QPair& ray, RayPickCache& cache, RayPickResult& res, const RayPickFilter::Flags& mask) { + if (cache.contains(ray) && cache[ray].find(mask) != cache[ray].end()) { + if (cache[ray][mask].distance < res.distance) { + res = cache[ray][mask]; + } + return true; + } + return false; +} + +void RayPickManager::cacheResult(const bool intersects, const RayPickResult& resTemp, const RayPickFilter::Flags& mask, RayPickResult& res, QPair& ray, RayPickCache& cache) { + if (intersects) { + cache[ray][mask] = resTemp; + if (resTemp.distance < res.distance) { + res = resTemp; + } + } else { + cache[ray][mask] = RayPickResult(); + } +} + +void RayPickManager::update() { + RayPickCache results; + for (auto& uid : _rayPicks.keys()) { + std::shared_ptr rayPick = _rayPicks[uid]; + if (!rayPick->isEnabled() || rayPick->getFilter().doesPickNothing() || rayPick->getMaxDistance() < 0.0f) { + continue; + } + + bool valid; + PickRay ray = rayPick->getPickRay(valid); + + if (!valid) { + continue; + } + + QPair rayKey = QPair(ray.origin, ray.direction); + RayPickResult res; + + if (rayPick->getFilter().doesPickEntities()) { + RayToEntityIntersectionResult entityRes; + bool fromCache = true; + bool invisible = rayPick->getFilter().doesPickInvisible(); + bool noncollidable = rayPick->getFilter().doesPickNonCollidable(); + RayPickFilter::Flags entityMask = rayPick->getFilter().getEntityFlags(); + if (!checkAndCompareCachedResults(rayKey, results, res, entityMask)) { + entityRes = DependencyManager::get()->findRayIntersectionVector(ray, true, rayPick->getIncludeEntites(), rayPick->getIgnoreEntites(), !invisible, !noncollidable); + fromCache = false; + } + + if (!fromCache) { + cacheResult(entityRes.intersects, RayPickResult(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, entityRes.surfaceNormal), + entityMask, res, rayKey, results); + } + } + + if (rayPick->getFilter().doesPickOverlays()) { + RayToOverlayIntersectionResult overlayRes; + bool fromCache = true; + bool invisible = rayPick->getFilter().doesPickInvisible(); + bool noncollidable = rayPick->getFilter().doesPickNonCollidable(); + RayPickFilter::Flags overlayMask = rayPick->getFilter().getOverlayFlags(); + if (!checkAndCompareCachedResults(rayKey, results, res, overlayMask)) { + overlayRes = qApp->getOverlays().findRayIntersectionVector(ray, true, rayPick->getIncludeOverlays(), rayPick->getIgnoreOverlays(), !invisible, !noncollidable); + fromCache = false; + } + + if (!fromCache) { + cacheResult(overlayRes.intersects, RayPickResult(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, overlayRes.surfaceNormal), + overlayMask, res, rayKey, results); + } + } + + if (rayPick->getFilter().doesPickAvatars()) { + RayPickFilter::Flags avatarMask = rayPick->getFilter().getAvatarFlags(); + if (!checkAndCompareCachedResults(rayKey, results, res, avatarMask)) { + RayToAvatarIntersectionResult avatarRes = DependencyManager::get()->findRayIntersectionVector(ray, rayPick->getIncludeAvatars(), rayPick->getIgnoreAvatars()); + cacheResult(avatarRes.intersects, RayPickResult(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection), avatarMask, res, rayKey, results); + } + } + + // Can't intersect with HUD in desktop mode + if (rayPick->getFilter().doesPickHUD() && DependencyManager::get()->isHMDMode()) { + RayPickFilter::Flags hudMask = rayPick->getFilter().getHUDFlags(); + if (!checkAndCompareCachedResults(rayKey, results, res, hudMask)) { + glm::vec3 hudRes = DependencyManager::get()->calculateRayUICollisionPoint(ray.origin, ray.direction); + cacheResult(true, RayPickResult(IntersectionType::HUD, 0, glm::distance(ray.origin, hudRes), hudRes), hudMask, res, rayKey, results); + } + } + + QWriteLocker lock(_rayPickLocks[uid].get()); + if (rayPick->getMaxDistance() == 0.0f || (rayPick->getMaxDistance() > 0.0f && res.distance < rayPick->getMaxDistance())) { + rayPick->setRayPickResult(res); + } else { + rayPick->setRayPickResult(RayPickResult()); + } + } + + QWriteLocker containsLock(&_containsLock); + { + QWriteLocker lock(&_addLock); + while (!_rayPicksToAdd.empty()) { + std::pair> rayPickToAdd = _rayPicksToAdd.front(); + _rayPicksToAdd.pop(); + _rayPicks[rayPickToAdd.first] = rayPickToAdd.second; + _rayPickLocks[rayPickToAdd.first] = std::make_shared(); + } + } + + { + QWriteLocker lock(&_removeLock); + while (!_rayPicksToRemove.empty()) { + QUuid uid = _rayPicksToRemove.front(); + _rayPicksToRemove.pop(); + _rayPicks.remove(uid); + _rayPickLocks.remove(uid); + } + } +} + +QUuid RayPickManager::createRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const RayPickFilter& filter, const float maxDistance, const bool enabled) { + QWriteLocker lock(&_addLock); + QUuid id = QUuid::createUuid(); + _rayPicksToAdd.push(std::pair>(id, std::make_shared(jointName, posOffset, dirOffset, filter, maxDistance, enabled))); + return id; +} + +QUuid RayPickManager::createRayPick(const RayPickFilter& filter, const float maxDistance, const bool enabled) { + QWriteLocker lock(&_addLock); + QUuid id = QUuid::createUuid(); + _rayPicksToAdd.push(std::pair>(id, std::make_shared(filter, maxDistance, enabled))); + return id; +} + +QUuid RayPickManager::createRayPick(const glm::vec3& position, const glm::vec3& direction, const RayPickFilter& filter, const float maxDistance, const bool enabled) { + QWriteLocker lock(&_addLock); + QUuid id = QUuid::createUuid(); + _rayPicksToAdd.push(std::pair>(id, std::make_shared(position, direction, filter, maxDistance, enabled))); + return id; +} + +void RayPickManager::removeRayPick(const QUuid uid) { + QWriteLocker lock(&_removeLock); + _rayPicksToRemove.push(uid); +} + +void RayPickManager::enableRayPick(const QUuid uid) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + QWriteLocker rayPickLock(_rayPickLocks[uid].get()); + _rayPicks[uid]->enable(); + } +} + +void RayPickManager::disableRayPick(const QUuid uid) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + QWriteLocker rayPickLock(_rayPickLocks[uid].get()); + _rayPicks[uid]->disable(); + } +} + +const PickRay RayPickManager::getPickRay(const QUuid uid) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + bool valid; + PickRay pickRay = _rayPicks[uid]->getPickRay(valid); + if (valid) { + return pickRay; + } + } + return PickRay(); +} + +const RayPickResult RayPickManager::getPrevRayPickResult(const QUuid uid) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + QReadLocker lock(_rayPickLocks[uid].get()); + return _rayPicks[uid]->getPrevRayPickResult(); + } + return RayPickResult(); +} + +void RayPickManager::setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + QWriteLocker lock(_rayPickLocks[uid].get()); + _rayPicks[uid]->setIgnoreEntities(ignoreEntities); + } +} + +void RayPickManager::setIncludeEntities(QUuid uid, const QScriptValue& includeEntities) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + QWriteLocker lock(_rayPickLocks[uid].get()); + _rayPicks[uid]->setIncludeEntities(includeEntities); + } +} + +void RayPickManager::setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + QWriteLocker lock(_rayPickLocks[uid].get()); + _rayPicks[uid]->setIgnoreOverlays(ignoreOverlays); + } +} + +void RayPickManager::setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + QWriteLocker lock(_rayPickLocks[uid].get()); + _rayPicks[uid]->setIncludeOverlays(includeOverlays); + } +} + +void RayPickManager::setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + QWriteLocker lock(_rayPickLocks[uid].get()); + _rayPicks[uid]->setIgnoreAvatars(ignoreAvatars); + } +} + +void RayPickManager::setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars) { + QReadLocker containsLock(&_containsLock); + if (_rayPicks.contains(uid)) { + QWriteLocker lock(_rayPickLocks[uid].get()); + _rayPicks[uid]->setIncludeAvatars(includeAvatars); + } +} \ No newline at end of file diff --git a/interface/src/raypick/RayPickManager.h b/interface/src/raypick/RayPickManager.h new file mode 100644 index 0000000000..8ef2ceebe0 --- /dev/null +++ b/interface/src/raypick/RayPickManager.h @@ -0,0 +1,64 @@ +// +// RayPickManager.h +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// 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 +// +#ifndef hifi_RayPickManager_h +#define hifi_RayPickManager_h + +#include "RayPick.h" + +#include +#include +#include + +#include "RegisteredMetaTypes.h" + +#include +#include + +class RayPickResult; + +class RayPickManager { + +public: + void update(); + const PickRay getPickRay(const QUuid uid); + + QUuid createRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const RayPickFilter& filter, const float maxDistance, const bool enabled); + QUuid createRayPick(const RayPickFilter& filter, const float maxDistance, const bool enabled); + QUuid createRayPick(const glm::vec3& position, const glm::vec3& direction, const RayPickFilter& filter, const float maxDistance, const bool enabled); + void removeRayPick(const QUuid uid); + void enableRayPick(const QUuid uid); + void disableRayPick(const QUuid uid); + const RayPickResult getPrevRayPickResult(const QUuid uid); + + void setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities); + void setIncludeEntities(QUuid uid, const QScriptValue& includeEntities); + void setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays); + void setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays); + void setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars); + void setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars); + +private: + QHash> _rayPicks; + QHash> _rayPickLocks; + QReadWriteLock _addLock; + std::queue>> _rayPicksToAdd; + QReadWriteLock _removeLock; + std::queue _rayPicksToRemove; + QReadWriteLock _containsLock; + + typedef QHash, std::unordered_map> RayPickCache; + + // Returns true if this ray exists in the cache, and if it does, update res if the cached result is closer + bool checkAndCompareCachedResults(QPair& ray, RayPickCache& cache, RayPickResult& res, const RayPickFilter::Flags& mask); + void cacheResult(const bool intersects, const RayPickResult& resTemp, const RayPickFilter::Flags& mask, RayPickResult& res, QPair& ray, RayPickCache& cache); +}; + +#endif // hifi_RayPickManager_h \ No newline at end of file diff --git a/interface/src/raypick/RayPickScriptingInterface.cpp b/interface/src/raypick/RayPickScriptingInterface.cpp new file mode 100644 index 0000000000..aa1613d696 --- /dev/null +++ b/interface/src/raypick/RayPickScriptingInterface.cpp @@ -0,0 +1,107 @@ +// +// RayPickScriptingInterface.cpp +// interface/src/raypick +// +// Created by Sam Gondelman 8/15/2017 +// 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 +// + +#include "RayPickScriptingInterface.h" + +#include +#include "GLMHelpers.h" +#include "Application.h" + +QUuid RayPickScriptingInterface::createRayPick(const QVariant& properties) { + QVariantMap propMap = properties.toMap(); + + bool enabled = false; + if (propMap["enabled"].isValid()) { + enabled = propMap["enabled"].toBool(); + } + + RayPickFilter filter = RayPickFilter(); + if (propMap["filter"].isValid()) { + filter = RayPickFilter(propMap["filter"].toUInt()); + } + + float maxDistance = 0.0f; + if (propMap["maxDistance"].isValid()) { + maxDistance = propMap["maxDistance"].toFloat(); + } + + if (propMap["joint"].isValid()) { + std::string jointName = propMap["joint"].toString().toStdString(); + + if (jointName != "Mouse") { + // x = upward, y = forward, z = lateral + glm::vec3 posOffset = Vectors::ZERO; + if (propMap["posOffset"].isValid()) { + posOffset = vec3FromVariant(propMap["posOffset"]); + } + + glm::vec3 dirOffset = Vectors::UP; + if (propMap["dirOffset"].isValid()) { + dirOffset = vec3FromVariant(propMap["dirOffset"]); + } + + return qApp->getRayPickManager().createRayPick(jointName, posOffset, dirOffset, filter, maxDistance, enabled); + } else { + return qApp->getRayPickManager().createRayPick(filter, maxDistance, enabled); + } + } else if (propMap["position"].isValid()) { + glm::vec3 position = vec3FromVariant(propMap["position"]); + + glm::vec3 direction = -Vectors::UP; + if (propMap["direction"].isValid()) { + direction = vec3FromVariant(propMap["direction"]); + } + + return qApp->getRayPickManager().createRayPick(position, direction, filter, maxDistance, enabled); + } + + return QUuid(); +} + +void RayPickScriptingInterface::enableRayPick(QUuid uid) { + qApp->getRayPickManager().enableRayPick(uid); +} + +void RayPickScriptingInterface::disableRayPick(QUuid uid) { + qApp->getRayPickManager().disableRayPick(uid); +} + +void RayPickScriptingInterface::removeRayPick(QUuid uid) { + qApp->getRayPickManager().removeRayPick(uid); +} + +RayPickResult RayPickScriptingInterface::getPrevRayPickResult(QUuid uid) { + return qApp->getRayPickManager().getPrevRayPickResult(uid); +} + +void RayPickScriptingInterface::setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities) { + qApp->getRayPickManager().setIgnoreEntities(uid, ignoreEntities); +} + +void RayPickScriptingInterface::setIncludeEntities(QUuid uid, const QScriptValue& includeEntities) { + qApp->getRayPickManager().setIncludeEntities(uid, includeEntities); +} + +void RayPickScriptingInterface::setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays) { + qApp->getRayPickManager().setIgnoreOverlays(uid, ignoreOverlays); +} + +void RayPickScriptingInterface::setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays) { + qApp->getRayPickManager().setIncludeOverlays(uid, includeOverlays); +} + +void RayPickScriptingInterface::setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars) { + qApp->getRayPickManager().setIgnoreAvatars(uid, ignoreAvatars); +} + +void RayPickScriptingInterface::setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars) { + qApp->getRayPickManager().setIncludeAvatars(uid, includeAvatars); +} \ No newline at end of file diff --git a/interface/src/raypick/RayPickScriptingInterface.h b/interface/src/raypick/RayPickScriptingInterface.h new file mode 100644 index 0000000000..e576b5d076 --- /dev/null +++ b/interface/src/raypick/RayPickScriptingInterface.h @@ -0,0 +1,70 @@ +// +// RayPickScriptingInterface.h +// interface/src/raypick +// +// Created by Sam Gondelman 8/15/2017 +// 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 +// +#ifndef hifi_RayPickScriptingInterface_h +#define hifi_RayPickScriptingInterface_h + +#include + +#include "RegisteredMetaTypes.h" +#include "DependencyManager.h" + +#include "RayPick.h" + +class RayPickScriptingInterface : public QObject, public Dependency { + Q_OBJECT + Q_PROPERTY(unsigned int PICK_NOTHING READ PICK_NOTHING CONSTANT) + Q_PROPERTY(unsigned int PICK_ENTITIES READ PICK_ENTITIES CONSTANT) + Q_PROPERTY(unsigned int PICK_OVERLAYS READ PICK_OVERLAYS CONSTANT) + Q_PROPERTY(unsigned int PICK_AVATARS READ PICK_AVATARS CONSTANT) + Q_PROPERTY(unsigned int PICK_HUD READ PICK_HUD CONSTANT) + Q_PROPERTY(unsigned int PICK_COURSE READ PICK_COURSE CONSTANT) + Q_PROPERTY(unsigned int PICK_INCLUDE_INVISIBLE READ PICK_INCLUDE_INVISIBLE CONSTANT) + Q_PROPERTY(unsigned int PICK_INCLUDE_NONCOLLIDABLE READ PICK_INCLUDE_NONCOLLIDABLE CONSTANT) + Q_PROPERTY(unsigned int PICK_ALL_INTERSECTIONS READ PICK_ALL_INTERSECTIONS CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_NONE READ INTERSECTED_NONE CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_ENTITY READ INTERSECTED_ENTITY CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_OVERLAY READ INTERSECTED_OVERLAY CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_AVATAR READ INTERSECTED_AVATAR CONSTANT) + Q_PROPERTY(unsigned int INTERSECTED_HUD READ INTERSECTED_HUD CONSTANT) + SINGLETON_DEPENDENCY + +public slots: + Q_INVOKABLE QUuid createRayPick(const QVariant& properties); + Q_INVOKABLE void enableRayPick(QUuid uid); + Q_INVOKABLE void disableRayPick(QUuid uid); + Q_INVOKABLE void removeRayPick(QUuid uid); + Q_INVOKABLE RayPickResult getPrevRayPickResult(QUuid uid); + + Q_INVOKABLE void setIgnoreEntities(QUuid uid, const QScriptValue& ignoreEntities); + Q_INVOKABLE void setIncludeEntities(QUuid uid, const QScriptValue& includeEntities); + Q_INVOKABLE void setIgnoreOverlays(QUuid uid, const QScriptValue& ignoreOverlays); + Q_INVOKABLE void setIncludeOverlays(QUuid uid, const QScriptValue& includeOverlays); + Q_INVOKABLE void setIgnoreAvatars(QUuid uid, const QScriptValue& ignoreAvatars); + Q_INVOKABLE void setIncludeAvatars(QUuid uid, const QScriptValue& includeAvatars); + +private: + unsigned int PICK_NOTHING() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_NOTHING); } + unsigned int PICK_ENTITIES() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_ENTITIES); } + unsigned int PICK_OVERLAYS() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_OVERLAYS); } + unsigned int PICK_AVATARS() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_AVATARS); } + unsigned int PICK_HUD() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_HUD); } + unsigned int PICK_COURSE() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_COURSE); } + unsigned int PICK_INCLUDE_INVISIBLE() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_INCLUDE_INVISIBLE); } + unsigned int PICK_INCLUDE_NONCOLLIDABLE() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_INCLUDE_NONCOLLIDABLE); } + unsigned int PICK_ALL_INTERSECTIONS() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_ALL_INTERSECTIONS); } + unsigned int INTERSECTED_NONE() { return IntersectionType::NONE; } + unsigned int INTERSECTED_ENTITY() { return IntersectionType::ENTITY; } + unsigned int INTERSECTED_OVERLAY() { return IntersectionType::OVERLAY; } + unsigned int INTERSECTED_AVATAR() { return IntersectionType::AVATAR; } + unsigned int INTERSECTED_HUD() { return IntersectionType::HUD; } +}; + +#endif // hifi_RayPickScriptingInterface_h diff --git a/interface/src/raypick/StaticRayPick.cpp b/interface/src/raypick/StaticRayPick.cpp new file mode 100644 index 0000000000..89bcddb3df --- /dev/null +++ b/interface/src/raypick/StaticRayPick.cpp @@ -0,0 +1,22 @@ +// +// StaticRayPick.cpp +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// 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 +// +#include "StaticRayPick.h" + +StaticRayPick::StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const RayPickFilter& filter, const float maxDistance, const bool enabled) : + RayPick(filter, maxDistance, enabled), + _pickRay(position, direction) +{ +} + +const PickRay StaticRayPick::getPickRay(bool& valid) const { + valid = true; + return _pickRay; +} \ No newline at end of file diff --git a/interface/src/raypick/StaticRayPick.h b/interface/src/raypick/StaticRayPick.h new file mode 100644 index 0000000000..fc09ee6a27 --- /dev/null +++ b/interface/src/raypick/StaticRayPick.h @@ -0,0 +1,28 @@ +// +// StaticRayPick.h +// interface/src/raypick +// +// Created by Sam Gondelman 7/11/2017 +// 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 +// +#ifndef hifi_StaticRayPick_h +#define hifi_StaticRayPick_h + +#include "RayPick.h" + +class StaticRayPick : public RayPick { + +public: + StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const RayPickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false); + + const PickRay getPickRay(bool& valid) const override; + +private: + PickRay _pickRay; + +}; + +#endif // hifi_StaticRayPick_h diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 35f2e2aa86..93c3a7652e 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -82,6 +82,22 @@ bool HMDScriptingInterface::shouldShowHandControllers() const { return _showHandControllersCount > 0; } +void HMDScriptingInterface::activateHMDHandMouse() { + QWriteLocker lock(&_hmdHandMouseLock); + auto offscreenUi = DependencyManager::get(); + offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", true); + _hmdHandMouseCount++; +} + +void HMDScriptingInterface::deactivateHMDHandMouse() { + QWriteLocker lock(&_hmdHandMouseLock); + _hmdHandMouseCount = std::max(_hmdHandMouseCount - 1, 0); + if (_hmdHandMouseCount == 0) { + auto offscreenUi = DependencyManager::get(); + offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", false); + } +} + void HMDScriptingInterface::closeTablet() { _showTablet = false; } @@ -153,50 +169,6 @@ QString HMDScriptingInterface::preferredAudioOutput() const { return qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice(); } -bool HMDScriptingInterface::setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction) { - if (QThread::currentThread() != thread()) { - bool result; - BLOCKING_INVOKE_METHOD(this, "setHandLasers", Q_RETURN_ARG(bool, result), - Q_ARG(int, hands), Q_ARG(bool, enabled), Q_ARG(glm::vec4, color), Q_ARG(glm::vec3, direction)); - return result; - } - - auto offscreenUi = DependencyManager::get(); - offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", enabled); - return qApp->getActiveDisplayPlugin()->setHandLaser(hands, - enabled ? DisplayPlugin::HandLaserMode::Overlay : DisplayPlugin::HandLaserMode::None, - color, direction); -} - -bool HMDScriptingInterface::setExtraLaser(const glm::vec3& worldStart, bool enabled, const glm::vec4& color, const glm::vec3& direction) { - if (QThread::currentThread() != thread()) { - bool result; - BLOCKING_INVOKE_METHOD(this, "setExtraLaser", Q_RETURN_ARG(bool, result), - Q_ARG(glm::vec3, worldStart), Q_ARG(bool, enabled), Q_ARG(glm::vec4, color), Q_ARG(glm::vec3, direction)); - return result; - } - - auto offscreenUi = DependencyManager::get(); - offscreenUi->getDesktop()->setProperty("hmdHandMouseActive", enabled); - - auto myAvatar = DependencyManager::get()->getMyAvatar(); - auto sensorToWorld = myAvatar->getSensorToWorldMatrix(); - auto worldToSensor = glm::inverse(sensorToWorld); - auto sensorStart = ::transformPoint(worldToSensor, worldStart); - auto sensorDirection = ::transformVectorFast(worldToSensor, direction); - - return qApp->getActiveDisplayPlugin()->setExtraLaser(enabled ? DisplayPlugin::HandLaserMode::Overlay : DisplayPlugin::HandLaserMode::None, - color, sensorStart, sensorDirection); -} - -void HMDScriptingInterface::disableExtraLaser() { - setExtraLaser(vec3(0), false, vec4(0), vec3(0)); -} - -void HMDScriptingInterface::disableHandLasers(int hands) { - setHandLasers(hands, false, vec4(0), vec3(0)); -} - bool HMDScriptingInterface::suppressKeyboard() { return qApp->getActiveDisplayPlugin()->suppressKeyboard(); } diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index 3ed7db0232..2eefe6ea22 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -22,6 +22,7 @@ class QScriptEngine; #include #include +#include class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Dependency { Q_OBJECT @@ -51,12 +52,8 @@ public: Q_INVOKABLE void requestHideHandControllers(); Q_INVOKABLE bool shouldShowHandControllers() const; - Q_INVOKABLE bool setHandLasers(int hands, bool enabled, const glm::vec4& color, const glm::vec3& direction); - Q_INVOKABLE void disableHandLasers(int hands); - - Q_INVOKABLE bool setExtraLaser(const glm::vec3& worldStart, bool enabled, const glm::vec4& color, const glm::vec3& direction); - Q_INVOKABLE void disableExtraLaser(); - + Q_INVOKABLE void activateHMDHandMouse(); + Q_INVOKABLE void deactivateHMDHandMouse(); /// Suppress the activation of any on-screen keyboard so that a script operation will /// not be interrupted by a keyboard popup @@ -119,6 +116,9 @@ private: bool getHUDLookAtPosition3D(glm::vec3& result) const; glm::mat4 getWorldHMDMatrix() const; std::atomic _showHandControllersCount { 0 }; + + QReadWriteLock _hmdHandMouseLock; + int _hmdHandMouseCount; }; #endif // hifi_HMDScriptingInterface_h diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 277989439c..f63c128cf5 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -264,6 +264,10 @@ void WindowScriptingInterface::showAssetServer(const QString& upload) { QMetaObject::invokeMethod(qApp, "showAssetServerWidget", Qt::QueuedConnection, Q_ARG(QString, upload)); } +QString WindowScriptingInterface::checkVersion() { + return QCoreApplication::applicationVersion(); +} + int WindowScriptingInterface::getInnerWidth() { return qApp->getWindow()->geometry().width(); } diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 28f1bafa5d..4b5e2e81fc 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -58,6 +58,7 @@ public slots: QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); QScriptValue browseAssets(const QString& title = "", const QString& directory = "", const QString& nameFilter = ""); void showAssetServer(const QString& upload = ""); + QString checkVersion(); void copyToClipboard(const QString& text); void takeSnapshot(bool notify = true, bool includeAnimated = false, float aspectRatio = 0.0f); void takeSecondaryCameraSnapshot(); diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 827417a912..52a3d7a929 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -267,7 +267,7 @@ const render::ShapeKey Circle3DOverlay::getShapeKey() { if (getAlpha() != 1.0f) { builder.withTranslucent(); } - if (!getIsSolid()) { + if (!getIsSolid() || shouldDrawHUDLayer()) { builder.withUnlit().withDepthBias(); } return builder.build(); diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index a353545245..31cbe5e822 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -120,7 +120,7 @@ const render::ShapeKey Cube3DOverlay::getShapeKey() { if (getAlpha() != 1.0f) { builder.withTranslucent(); } - if (!getIsSolid()) { + if (!getIsSolid() || shouldDrawHUDLayer()) { builder.withUnlit().withDepthBias(); } return builder.build(); diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index 7dfee2c491..82417db83a 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -123,7 +123,7 @@ void Image3DOverlay::render(RenderArgs* args) { const render::ShapeKey Image3DOverlay::getShapeKey() { auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias(); - if (_emissive) { + if (_emissive || shouldDrawHUDLayer()) { builder.withUnlit(); } if (getAlpha() != 1.0f) { diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index 675dff7e93..8b88d1e963 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -13,6 +13,8 @@ #include #include +#include "Application.h" + static const xColor DEFAULT_OVERLAY_COLOR = { 255, 255, 255 }; static const float DEFAULT_ALPHA = 0.7f; @@ -30,6 +32,7 @@ Overlay::Overlay() : _colorPulse(0.0f), _color(DEFAULT_OVERLAY_COLOR), _visible(true), + _drawHUDLayer(false), _anchor(NO_ANCHOR) { } @@ -48,6 +51,7 @@ Overlay::Overlay(const Overlay* overlay) : _colorPulse(overlay->_colorPulse), _color(overlay->_color), _visible(overlay->_visible), + _drawHUDLayer(overlay->_drawHUDLayer), _anchor(overlay->_anchor) { } @@ -86,6 +90,11 @@ void Overlay::setProperties(const QVariantMap& properties) { setColorPulse(properties["colorPulse"].toFloat()); } + if (properties["drawHUDLayer"].isValid()) { + bool drawHUDLayer = properties["drawHUDLayer"].toBool(); + setDrawHUDLayer(drawHUDLayer); + } + if (properties["visible"].isValid()) { bool visible = properties["visible"].toBool(); setVisible(visible); @@ -161,6 +170,12 @@ float Overlay::getAlpha() { return (_alphaPulse >= 0.0f) ? _alpha * pulseLevel : _alpha * (1.0f - pulseLevel); } +void Overlay::setDrawHUDLayer(bool drawHUDLayer) { + if (drawHUDLayer != _drawHUDLayer) { + qApp->getOverlays().setOverlayDrawHUDLayer(getOverlayID(), drawHUDLayer); + _drawHUDLayer = drawHUDLayer; + } +} // pulse travels from min to max, then max to min in one period. float Overlay::updatePulse() { diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index 494c287676..a9774eea06 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -58,6 +58,7 @@ public: virtual bool is3D() const = 0; bool isLoaded() { return _isLoaded; } bool getVisible() const { return _visible; } + bool shouldDrawHUDLayer() const { return _drawHUDLayer; } xColor getColor(); float getAlpha(); Anchor getAnchor() const { return _anchor; } @@ -72,6 +73,7 @@ public: // setters void setVisible(bool visible) { _visible = visible; } + void setDrawHUDLayer(bool drawHUDLayer); void setColor(const xColor& color) { _color = color; } void setAlpha(float alpha) { _alpha = alpha; } void setAnchor(Anchor anchor) { _anchor = anchor; } @@ -114,6 +116,7 @@ protected: xColor _color; bool _visible; // should the overlay be drawn at all + bool _drawHUDLayer; // should the overlay be drawn on the HUD layer Anchor _anchor; unsigned int _stackOrder { 0 }; diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index fc9371935c..0f7de8bd79 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -37,20 +37,29 @@ #include "Web3DOverlay.h" #include +#include "render/ShapePipeline.h" + Q_LOGGING_CATEGORY(trace_render_overlays, "trace.render.overlays") +extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest = false); + void Overlays::cleanupAllOverlays() { QMap overlaysHUD; + QMap overlays3DHUD; QMap overlaysWorld; { QMutexLocker locker(&_mutex); overlaysHUD.swap(_overlaysHUD); + overlays3DHUD.swap(_overlays3DHUD); overlaysWorld.swap(_overlaysWorld); } foreach(Overlay::Pointer overlay, overlaysHUD) { _overlaysToDelete.push_back(overlay); } + foreach(Overlay::Pointer overlay, overlays3DHUD) { + _overlaysToDelete.push_back(overlay); + } foreach(Overlay::Pointer overlay, overlaysWorld) { _overlaysToDelete.push_back(overlay); } @@ -64,6 +73,8 @@ void Overlays::init() { #if OVERLAY_PANELS _scriptEngine = new QScriptEngine(); #endif + _shapePlumber = std::make_shared(); + initOverlay3DPipelines(*_shapePlumber, true); } void Overlays::update(float deltatime) { @@ -72,6 +83,9 @@ void Overlays::update(float deltatime) { foreach(const auto& thisOverlay, _overlaysHUD) { thisOverlay->update(deltatime); } + foreach(const auto& thisOverlay, _overlays3DHUD) { + thisOverlay->update(deltatime); + } foreach(const auto& thisOverlay, _overlaysWorld) { thisOverlay->update(deltatime); } @@ -128,6 +142,23 @@ void Overlays::renderHUD(RenderArgs* renderArgs) { } } +void Overlays::render3DHUDOverlays(RenderArgs* renderArgs) { + PROFILE_RANGE(render_overlays, __FUNCTION__); + gpu::Batch& batch = *renderArgs->_batch; + + auto textureCache = DependencyManager::get(); + + QMutexLocker lock(&_mutex); + foreach(Overlay::Pointer thisOverlay, _overlays3DHUD) { + // Reset necessary batch pipeline settings between overlays + batch.setResourceTexture(0, textureCache->getWhiteTexture()); // FIXME - do we really need to do this?? + batch.setModelTransform(Transform()); + + renderArgs->_shapePipeline = _shapePlumber->pickPipeline(renderArgs, thisOverlay->getShapeKey()); + thisOverlay->render(renderArgs); + } +} + void Overlays::disable() { _enabled = false; } @@ -142,8 +173,9 @@ Overlay::Pointer Overlays::getOverlay(OverlayID id) const { QMutexLocker locker(&_mutex); if (_overlaysHUD.contains(id)) { return _overlaysHUD[id]; - } - if (_overlaysWorld.contains(id)) { + } else if (_overlays3DHUD.contains(id)) { + return _overlays3DHUD[id]; + } else if (_overlaysWorld.contains(id)) { return _overlaysWorld[id]; } return nullptr; @@ -200,7 +232,7 @@ OverlayID Overlays::addOverlay(const Overlay::Pointer& overlay) { OverlayID thisID = OverlayID(QUuid::createUuid()); overlay->setOverlayID(thisID); overlay->setStackOrder(_stackOrder++); - if (overlay->is3D()) { + if (overlay->is3D() && !overlay->shouldDrawHUDLayer()) { { QMutexLocker locker(&_mutex); _overlaysWorld[thisID] = overlay; @@ -210,6 +242,9 @@ OverlayID Overlays::addOverlay(const Overlay::Pointer& overlay) { render::Transaction transaction; overlay->addToScene(overlay, scene, transaction); scene->enqueueTransaction(transaction); + } else if (overlay->is3D() && overlay->shouldDrawHUDLayer()) { + QMutexLocker locker(&_mutex); + _overlays3DHUD[thisID] = overlay; } else { QMutexLocker locker(&_mutex); _overlaysHUD[thisID] = overlay; @@ -218,6 +253,28 @@ OverlayID Overlays::addOverlay(const Overlay::Pointer& overlay) { return thisID; } +void Overlays::setOverlayDrawHUDLayer(const OverlayID& id, const bool drawHUDLayer) { + QMutexLocker locker(&_mutex); + if (drawHUDLayer && _overlaysWorld.contains(id)) { + std::shared_ptr overlay = _overlaysWorld.take(id); + render::ScenePointer scene = qApp->getMain3DScene(); + render::Transaction transaction; + auto itemID = overlay->getRenderItemID(); + if (render::Item::isValidID(itemID)) { + overlay->removeFromScene(overlay, scene, transaction); + scene->enqueueTransaction(transaction); + } + _overlays3DHUD[id] = overlay; + } else if (!drawHUDLayer && _overlays3DHUD.contains(id)) { + std::shared_ptr overlay = _overlays3DHUD.take(id); + render::ScenePointer scene = qApp->getMain3DScene(); + render::Transaction transaction; + overlay->addToScene(overlay, scene, transaction); + scene->enqueueTransaction(transaction); + _overlaysWorld[id] = overlay; + } +} + OverlayID Overlays::cloneOverlay(OverlayID id) { if (QThread::currentThread() != thread()) { OverlayID result; @@ -294,6 +351,8 @@ void Overlays::deleteOverlay(OverlayID id) { QMutexLocker locker(&_mutex); if (_overlaysHUD.contains(id)) { overlayToDelete = _overlaysHUD.take(id); + } else if (_overlays3DHUD.contains(id)) { + overlayToDelete = _overlays3DHUD.take(id); } else if (_overlaysWorld.contains(id)) { overlayToDelete = _overlaysWorld.take(id); } else { @@ -475,15 +534,15 @@ RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray, const QVector overlaysToInclude = qVectorOverlayIDFromScriptValue(overlayIDsToInclude); const QVector overlaysToDiscard = qVectorOverlayIDFromScriptValue(overlayIDsToDiscard); - return findRayIntersectionInternal(ray, precisionPicking, - overlaysToInclude, overlaysToDiscard, visibleOnly, collidableOnly); + return findRayIntersectionVector(ray, precisionPicking, + overlaysToInclude, overlaysToDiscard, visibleOnly, collidableOnly); } -RayToOverlayIntersectionResult Overlays::findRayIntersectionInternal(const PickRay& ray, bool precisionPicking, - const QVector& overlaysToInclude, - const QVector& overlaysToDiscard, - bool visibleOnly, bool collidableOnly) { +RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay& ray, bool precisionPicking, + const QVector& overlaysToInclude, + const QVector& overlaysToDiscard, + bool visibleOnly, bool collidableOnly) { float bestDistance = std::numeric_limits::max(); bool bestIsFront = false; @@ -702,7 +761,7 @@ bool Overlays::isAddedOverlay(OverlayID id) { } QMutexLocker locker(&_mutex); - return _overlaysHUD.contains(id) || _overlaysWorld.contains(id); + return _overlaysHUD.contains(id) || _overlays3DHUD.contains(id) || _overlaysWorld.contains(id); } void Overlays::sendMousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event) { @@ -849,21 +908,21 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionForMouseEvent(PickRa // first priority is tablet screen overlaysToInclude << qApp->getTabletScreenID(); - rayPickResult = findRayIntersectionInternal(ray, true, overlaysToInclude, overlaysToDiscard); + rayPickResult = findRayIntersectionVector(ray, true, overlaysToInclude, overlaysToDiscard); if (rayPickResult.intersects) { return rayPickResult; } // then tablet home button overlaysToInclude.clear(); overlaysToInclude << qApp->getTabletHomeButtonID(); - rayPickResult = findRayIntersectionInternal(ray, true, overlaysToInclude, overlaysToDiscard); + rayPickResult = findRayIntersectionVector(ray, true, overlaysToInclude, overlaysToDiscard); if (rayPickResult.intersects) { return rayPickResult; } // then tablet frame overlaysToInclude.clear(); overlaysToInclude << OverlayID(qApp->getTabletFrameID()); - rayPickResult = findRayIntersectionInternal(ray, true, overlaysToInclude, overlaysToDiscard); + rayPickResult = findRayIntersectionVector(ray, true, overlaysToInclude, overlaysToDiscard); if (rayPickResult.intersects) { return rayPickResult; } diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index a915acb06a..1e85562485 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -90,6 +90,7 @@ public: void init(); void update(float deltatime); void renderHUD(RenderArgs* renderArgs); + void render3DHUDOverlays(RenderArgs* renderArgs); void disable(); void enable(); @@ -102,6 +103,8 @@ public: OverlayID addOverlay(Overlay* overlay) { return addOverlay(Overlay::Pointer(overlay)); } OverlayID addOverlay(const Overlay::Pointer& overlay); + void setOverlayDrawHUDLayer(const OverlayID& id, const bool drawHUDLayer); + bool mousePressEvent(QMouseEvent* event); bool mouseDoublePressEvent(QMouseEvent* event); bool mouseReleaseEvent(QMouseEvent* event); @@ -213,6 +216,12 @@ public slots: bool visibleOnly = false, bool collidableOnly = false); + // Same as above but with QVectors + RayToOverlayIntersectionResult findRayIntersectionVector(const PickRay& ray, bool precisionPicking, + const QVector& overlaysToInclude, + const QVector& overlaysToDiscard, + bool visibleOnly = false, bool collidableOnly = false); + /**jsdoc * Return a list of 3d overlays with bounding boxes that touch the given sphere * @@ -325,7 +334,11 @@ private: mutable QMutex _mutex { QMutex::Recursive }; QMap _overlaysHUD; + QMap _overlays3DHUD; QMap _overlaysWorld; + + render::ShapePlumberPointer _shapePlumber; + #if OVERLAY_PANELS QMap _panels; #endif @@ -343,10 +356,6 @@ private: OverlayID _currentClickingOnOverlayID { UNKNOWN_OVERLAY_ID }; OverlayID _currentHoverOverOverlayID { UNKNOWN_OVERLAY_ID }; - Q_INVOKABLE RayToOverlayIntersectionResult findRayIntersectionInternal(const PickRay& ray, bool precisionPicking, - const QVector& overlaysToInclude, - const QVector& overlaysToDiscard, - bool visibleOnly = false, bool collidableOnly = false); RayToOverlayIntersectionResult findRayIntersectionForMouseEvent(PickRay ray); }; diff --git a/interface/src/ui/overlays/OverlaysPayload.cpp b/interface/src/ui/overlays/OverlaysPayload.cpp index d14805c1ba..8b7100205a 100644 --- a/interface/src/ui/overlays/OverlaysPayload.cpp +++ b/interface/src/ui/overlays/OverlaysPayload.cpp @@ -37,9 +37,6 @@ namespace render { if (std::static_pointer_cast(overlay)->getDrawInFront()) { builder.withLayered(); } - if (!std::static_pointer_cast(overlay)->isAA()) { - builder.withLayered(); - } if (overlay->getAlphaPulse() != 0.0f || overlay->getAlpha() != 1.0f) { builder.withTransparent(); } @@ -53,21 +50,17 @@ namespace render { } template <> int payloadGetLayer(const Overlay::Pointer& overlay) { // Magic number while we are defining the layering mechanism: - const int LAYER_NO_AA = 3; const int LAYER_2D = 2; const int LAYER_3D_FRONT = 1; const int LAYER_3D = 0; if (overlay->is3D()) { auto overlay3D = std::dynamic_pointer_cast(overlay); - if (overlay3D->isAA()) - if (overlay3D->getDrawInFront()) { - return LAYER_3D_FRONT; - } else { - return LAYER_3D; - } - else - return LAYER_NO_AA; + if (overlay3D->getDrawInFront()) { + return LAYER_3D_FRONT; + } else { + return LAYER_3D; + } } else { return LAYER_2D; } diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index a6fcacc769..7126f7bde4 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -65,7 +65,7 @@ const render::ShapeKey Shape3DOverlay::getShapeKey() { if (getAlpha() != 1.0f) { builder.withTranslucent(); } - if (!getIsSolid()) { + if (!getIsSolid() || shouldDrawHUDLayer()) { builder.withUnlit().withDepthBias(); } return builder.build(); diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index 5bbf41eb94..ee3f9b9784 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -62,7 +62,7 @@ const render::ShapeKey Sphere3DOverlay::getShapeKey() { if (getAlpha() != 1.0f) { builder.withTranslucent(); } - if (!getIsSolid()) { + if (!getIsSolid() || shouldDrawHUDLayer()) { builder.withUnlit().withDepthBias(); } return builder.build(); diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index 29b8aee08b..4dc8d3378c 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -349,10 +349,9 @@ bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, c auto relativePosition = vec3(relativePosition4) / relativePosition4.w; auto relativeDirection = glm::inverse(glm::quat_cast(UITransform)) * direction; - float uiRadius = _hmdUIRadius; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale - + const float UI_RADIUS = 1.0f; // * myAvatar->getUniformScale(); // FIXME - how do we want to handle avatar scale float instersectionDistance; - if (raySphereIntersect(relativeDirection, relativePosition, uiRadius, &instersectionDistance)){ + if (raySphereIntersect(relativeDirection, relativePosition, UI_RADIUS, &instersectionDistance)){ result = position + glm::normalize(direction) * instersectionDistance; return true; } diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index 5be2d68cf9..e6a32dcfb9 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -111,8 +111,6 @@ public: void setDisplayPlugin(const DisplayPluginPointer& displayPlugin) { _currentDisplayPlugin = displayPlugin; } void setFrameInfo(uint32_t frame, const glm::mat4& camera) { _currentCamera = camera; } - float getHmdUiRadius() const { return _hmdUIRadius; } - signals: void allowMouseCaptureChanged(); void alphaChanged(); @@ -142,7 +140,6 @@ private: float _textureAspectRatio { VIRTUAL_UI_ASPECT_RATIO }; float _alpha { 1.0f }; - float _hmdUIRadius { 1.0f }; int _previousBorderWidth { -1 }; int _previousBorderHeight { -1 }; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 7144e401e4..98f4e04492 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -626,6 +626,12 @@ void OpenGLDisplayPlugin::compositeLayers() { compositeScene(); } + // Clear the depth framebuffer after drawing the scene so that the HUD elements can depth test against each other + render([&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setFramebuffer(_compositeFramebuffer); + batch.clearDepthFramebuffer((float) UINT32_MAX); + }); #ifdef HIFI_ENABLE_NSIGHT_DEBUG if (false) // do not compositeoverlay if running nsight debug @@ -635,16 +641,33 @@ void OpenGLDisplayPlugin::compositeLayers() { compositeOverlay(); } - auto compositorHelper = DependencyManager::get(); - if (compositorHelper->getReticleVisible()) { - PROFILE_RANGE_EX(render_detail, "compositePointer", 0xff0077ff, (uint64_t)presentCount()) - compositePointer(); + // Only render HUD layer 3D overlays in HMD mode + if (isHmd()) { + PROFILE_RANGE_EX(render_detail, "compositeHUDOverlays", 0xff0077ff, (uint64_t)presentCount()) + render([&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setFramebuffer(_compositeFramebuffer); + }); + _gpuContext->executeBatch(_currentFrame->postCompositeBatch); } { PROFILE_RANGE_EX(render_detail, "compositeExtra", 0xff0077ff, (uint64_t)presentCount()) compositeExtra(); } + + // Clear the depth buffer again and draw the pointer last so it's on top of everything + render([&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.setFramebuffer(_compositeFramebuffer); + batch.clearDepthFramebuffer((float) UINT32_MAX); + }); + + auto compositorHelper = DependencyManager::get(); + if (compositorHelper->getReticleVisible()) { + PROFILE_RANGE_EX(render_detail, "compositePointer", 0xff0077ff, (uint64_t)presentCount()) + compositePointer(); + } } void OpenGLDisplayPlugin::internalPresent() { @@ -864,7 +887,8 @@ OpenGLDisplayPlugin::~OpenGLDisplayPlugin() { void OpenGLDisplayPlugin::updateCompositeFramebuffer() { auto renderSize = getRecommendedRenderSize(); if (!_compositeFramebuffer || _compositeFramebuffer->getSize() != renderSize) { - _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y)); + auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); + _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, depthFormat, renderSize.x, renderSize.y)); } } diff --git a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp index fd45398236..1e0e7e6c1f 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/DebugHmdDisplayPlugin.cpp @@ -35,16 +35,7 @@ bool DebugHmdDisplayPlugin::beginFrameRender(uint32_t frameIndex) { //_currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose; withNonPresentThreadLock([&] { - _uiModelTransform = DependencyManager::get()->getModelTransform(); _frameInfos[frameIndex] = _currentRenderFrameInfo; - - _handPoses[0] = glm::translate(mat4(), vec3(0.3f * cosf(secTimestampNow() * 3.0f), -0.3f * sinf(secTimestampNow() * 5.0f), 0.0f)); - _handLasers[0].color = vec4(1, 0, 0, 1); - _handLasers[0].mode = HandLaserMode::Overlay; - - _handPoses[1] = glm::translate(mat4(), vec3(0.3f * sinf(secTimestampNow() * 3.0f), -0.3f * cosf(secTimestampNow() * 5.0f), 0.0f)); - _handLasers[1].color = vec4(0, 1, 1, 1); - _handLasers[1].mode = HandLaserMode::Overlay; }); return Parent::beginFrameRender(frameIndex); } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index b183850e7f..aef5c73fa3 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -32,9 +32,6 @@ #include "../Logging.h" #include "../CompositorHelper.h" -#include <../render-utils/shaders/render-utils/glowLine_vert.h> -#include <../render-utils/shaders/render-utils/glowLine_frag.h> - static const QString MONO_PREVIEW = "Mono Preview"; static const QString DISABLE_PREVIEW = "Disable Preview"; @@ -45,17 +42,10 @@ static const bool DEFAULT_MONO_VIEW = true; static const bool DEFAULT_DISABLE_PREVIEW = false; #endif static const glm::mat4 IDENTITY_MATRIX; -static const size_t NUMBER_OF_HANDS = 2; //#define LIVE_SHADER_RELOAD 1 extern glm::vec3 getPoint(float yaw, float pitch); -struct HandLaserData { - vec4 p1; - vec4 p2; - vec4 color; -}; - static QString readFile(const QString& filename) { QFile file(filename); file.open(QFile::Text | QFile::ReadOnly); @@ -118,31 +108,9 @@ void HmdDisplayPlugin::internalDeactivate() { Parent::internalDeactivate(); } -static const int32_t LINE_DATA_SLOT = 1; - void HmdDisplayPlugin::customizeContext() { Parent::customizeContext(); _overlayRenderer.build(); - - { - auto state = std::make_shared(); - auto VS = gpu::Shader::createVertex(std::string(glowLine_vert)); - auto PS = gpu::Shader::createPixel(std::string(glowLine_frag)); - auto program = gpu::Shader::createProgram(VS, PS); - state->setCullMode(gpu::State::CULL_NONE); - state->setDepthTest(true, false, gpu::LESS_EQUAL); - state->setBlendFunction(true, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - - gpu::Shader::BindingSet bindings; - bindings.insert({ "lineData", LINE_DATA_SLOT });; - gpu::Shader::makeProgram(*program, bindings); - _glowLinePipeline = gpu::Pipeline::create(program, state); - _handLaserUniforms = std::array{ { std::make_shared(), std::make_shared() } }; - _extraLaserUniforms = std::make_shared(); - }; - } void HmdDisplayPlugin::uncustomizeContext() { @@ -157,10 +125,6 @@ void HmdDisplayPlugin::uncustomizeContext() { }); _overlayRenderer = OverlayRenderer(); _previewTexture.reset(); - _handLaserUniforms[0].reset(); - _handLaserUniforms[1].reset(); - _extraLaserUniforms.reset(); - _glowLinePipeline.reset(); Parent::uncustomizeContext(); } @@ -383,132 +347,13 @@ void HmdDisplayPlugin::updateFrameData() { getGLBackend()->setCameraCorrection(correction); } - withPresentThreadLock([&] { - _presentHandLasers = _handLasers; - _presentHandPoses = _handPoses; - _presentUiModelTransform = _uiModelTransform; - - _presentExtraLaser = _extraLaser; - _presentExtraLaserStart = _extraLaserStart; - }); - auto compositorHelper = DependencyManager::get(); glm::mat4 modelMat = compositorHelper->getModelTransform().getMatrix(); - static const float OUT_OF_BOUNDS = -1; - std::array handGlowPoints { { vec2(OUT_OF_BOUNDS), vec2(OUT_OF_BOUNDS) } }; - vec2 extraGlowPoint(OUT_OF_BOUNDS); - - float uiRadius = compositorHelper->getHmdUiRadius(); - - // compute the glow point interesections - for (size_t i = 0; i < NUMBER_OF_HANDS; ++i) { - if (_presentHandPoses[i] == IDENTITY_MATRIX) { - continue; - } - const auto& handLaser = _presentHandLasers[i]; - if (!handLaser.valid()) { - continue; - } - - const vec3& laserDirection = handLaser.direction; - mat4 model = _presentHandPoses[i]; - vec3 castStart = vec3(model[3]); - vec3 castDirection = glm::quat_cast(model) * laserDirection; - - // this offset needs to match GRAB_POINT_SPHERE_OFFSET in scripts/system/libraries/controllers.js:19 - static const vec3 GRAB_POINT_SPHERE_OFFSET(0.04f, 0.13f, 0.039f); // x = upward, y = forward, z = lateral - - // swizzle grab point so that (x = upward, y = lateral, z = forward) - vec3 grabPointOffset = glm::vec3(GRAB_POINT_SPHERE_OFFSET.x, GRAB_POINT_SPHERE_OFFSET.z, -GRAB_POINT_SPHERE_OFFSET.y); - if (i == 0) { - grabPointOffset.x *= -1.0f; // this changes between left and right hands - } - castStart += glm::quat_cast(model) * grabPointOffset; - - // Find the intersection of the laser with he UI and use it to scale the model matrix - float distance; - if (!glm::intersectRaySphere(castStart, castDirection, - _presentUiModelTransform.getTranslation(), uiRadius * uiRadius, distance)) { - continue; - } - - _presentHandLaserPoints[i].first = castStart; - _presentHandLaserPoints[i].second = _presentHandLaserPoints[i].first + (castDirection * distance); - - vec3 intersectionPosition = castStart + (castDirection * distance) - _presentUiModelTransform.getTranslation(); - intersectionPosition = glm::inverse(_presentUiModelTransform.getRotation()) * intersectionPosition; - - // Take the interesection normal and convert it to a texture coordinate - vec2 yawPitch; - { - vec2 xdir = glm::normalize(vec2(intersectionPosition.x, -intersectionPosition.z)); - yawPitch.x = glm::atan(xdir.x, xdir.y); - yawPitch.y = (acosf(intersectionPosition.y) * -1.0f) + (float)M_PI_2; - } - vec2 halfFov = CompositorHelper::VIRTUAL_UI_TARGET_FOV / 2.0f; - - // Are we out of range - if (glm::any(glm::greaterThan(glm::abs(yawPitch), halfFov))) { - continue; - } - - yawPitch /= CompositorHelper::VIRTUAL_UI_TARGET_FOV; - yawPitch += 0.5f; - handGlowPoints[i] = yawPitch; - } - - // compute the glow point interesections - if (_presentExtraLaser.valid()) { - const vec3& laserDirection = _presentExtraLaser.direction; - vec3 castStart = _presentExtraLaserStart; - vec3 castDirection = laserDirection; - - // Find the intersection of the laser with he UI and use it to scale the model matrix - float distance; - if (glm::intersectRaySphere(castStart, castDirection, - _presentUiModelTransform.getTranslation(), uiRadius * uiRadius, distance)) { - - - _presentExtraLaserPoints.first = castStart; - _presentExtraLaserPoints.second = _presentExtraLaserPoints.first + (castDirection * distance); - - vec3 intersectionPosition = castStart + (castDirection * distance) - _presentUiModelTransform.getTranslation(); - intersectionPosition = glm::inverse(_presentUiModelTransform.getRotation()) * intersectionPosition; - - // Take the interesection normal and convert it to a texture coordinate - vec2 yawPitch; - { - vec2 xdir = glm::normalize(vec2(intersectionPosition.x, -intersectionPosition.z)); - yawPitch.x = glm::atan(xdir.x, xdir.y); - yawPitch.y = (acosf(intersectionPosition.y) * -1.0f) + (float)M_PI_2; - } - vec2 halfFov = CompositorHelper::VIRTUAL_UI_TARGET_FOV / 2.0f; - - // Are we out of range - if (!glm::any(glm::greaterThan(glm::abs(yawPitch), halfFov))) { - yawPitch /= CompositorHelper::VIRTUAL_UI_TARGET_FOV; - yawPitch += 0.5f; - extraGlowPoint = yawPitch; - } - } - } - for_each_eye([&](Eye eye) { auto modelView = glm::inverse(_currentPresentFrameInfo.presentPose * getEyeToHeadTransform(eye)) * modelMat; _overlayRenderer.mvps[eye] = _eyeProjections[eye] * modelView; }); - - // Setup the uniforms - { - auto& uniforms = _overlayRenderer.uniforms; - uniforms.alpha = _compositeOverlayAlpha; - uniforms.glowPoints = vec4(handGlowPoints[0], handGlowPoints[1]); - uniforms.glowColors[0] = _presentHandLasers[0].color; - uniforms.glowColors[1] = _presentHandLasers[1].color; - uniforms.extraGlowPoint = extraGlowPoint; - uniforms.extraGlowColor = _presentExtraLaser.color; - } } void HmdDisplayPlugin::OverlayRenderer::build() { @@ -573,8 +418,8 @@ void HmdDisplayPlugin::OverlayRenderer::build() { } void HmdDisplayPlugin::OverlayRenderer::updatePipeline() { - static const QString vsFile = PathUtils::resourcesPath() + "/shaders/hmd_ui_glow.vert"; - static const QString fsFile = PathUtils::resourcesPath() + "/shaders/hmd_ui_glow.frag"; + static const QString vsFile = PathUtils::resourcesPath() + "/shaders/hmd_ui.vert"; + static const QString fsFile = PathUtils::resourcesPath() + "/shaders/hmd_ui.frag"; #if LIVE_SHADER_RELOAD static qint64 vsBuiltAge = 0; @@ -598,7 +443,7 @@ void HmdDisplayPlugin::OverlayRenderer::updatePipeline() { this->uniformsLocation = program->getUniformBuffers().findLocation("overlayBuffer"); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(gpu::State::DepthTest(false)); + state->setDepthTest(gpu::State::DepthTest(true, true, gpu::LESS_EQUAL)); state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); @@ -665,84 +510,6 @@ void HmdDisplayPlugin::compositeOverlay() { _overlayRenderer.render(*this); } -bool HmdDisplayPlugin::setHandLaser(uint32_t hands, HandLaserMode mode, const vec4& color, const vec3& direction) { - HandLaserInfo info; - info.mode = mode; - info.color = color; - info.direction = direction; - withNonPresentThreadLock([&] { - if (hands & Hand::LeftHand) { - _handLasers[0] = info; - } - if (hands & Hand::RightHand) { - _handLasers[1] = info; - } - }); - // FIXME defer to a child class plugin to determine if hand lasers are actually - // available based on the presence or absence of hand controllers - return true; -} - -bool HmdDisplayPlugin::setExtraLaser(HandLaserMode mode, const vec4& color, const glm::vec3& sensorSpaceStart, const vec3& sensorSpaceDirection) { - HandLaserInfo info; - info.mode = mode; - info.color = color; - info.direction = sensorSpaceDirection; - withNonPresentThreadLock([&] { - _extraLaser = info; - _extraLaserStart = sensorSpaceStart; - }); - - // FIXME defer to a child class plugin to determine if hand lasers are actually - // available based on the presence or absence of hand controllers - return true; -} - - -void HmdDisplayPlugin::compositeExtra() { - // If neither hand laser is activated, exit - if (!_presentHandLasers[0].valid() && !_presentHandLasers[1].valid() && !_presentExtraLaser.valid()) { - return; - } - - if (_presentHandPoses[0] == IDENTITY_MATRIX && _presentHandPoses[1] == IDENTITY_MATRIX && !_presentExtraLaser.valid()) { - return; - } - - render([&](gpu::Batch& batch) { - batch.setFramebuffer(_compositeFramebuffer); - batch.setModelTransform(Transform()); - batch.setViewportTransform(ivec4(uvec2(0), _renderTargetSize)); - batch.setViewTransform(_currentPresentFrameInfo.presentPose, false); - // Compile the shaders - batch.setPipeline(_glowLinePipeline); - - - bilateral::for_each_side([&](bilateral::Side side){ - auto index = bilateral::index(side); - if (_presentHandPoses[index] == IDENTITY_MATRIX) { - return; - } - const auto& laser = _presentHandLasers[index]; - if (laser.valid()) { - const auto& points = _presentHandLaserPoints[index]; - _handLaserUniforms[index]->resize(sizeof(HandLaserData)); - _handLaserUniforms[index]->setSubData(0, HandLaserData { vec4(points.first, 1.0f), vec4(points.second, 1.0f), _handLasers[index].color }); - batch.setUniformBuffer(LINE_DATA_SLOT, _handLaserUniforms[index]); - batch.draw(gpu::TRIANGLE_STRIP, 4, 0); - } - }); - - if (_presentExtraLaser.valid()) { - const auto& points = _presentExtraLaserPoints; - _extraLaserUniforms->resize(sizeof(HandLaserData)); - _extraLaserUniforms->setSubData(0, HandLaserData { vec4(points.first, 1.0f), vec4(points.second, 1.0f), _presentExtraLaser.color }); - batch.setUniformBuffer(LINE_DATA_SLOT, _extraLaserUniforms); - batch.draw(gpu::TRIANGLE_STRIP, 4, 0); - } - }); -} - HmdDisplayPlugin::~HmdDisplayPlugin() { } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index 055328ee21..0827d04922 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -37,9 +37,6 @@ public: virtual glm::mat4 getHeadPose() const override; - bool setHandLaser(uint32_t hands, HandLaserMode mode, const vec4& color, const vec3& direction) override; - bool setExtraLaser(HandLaserMode mode, const vec4& color, const glm::vec3& sensorSpaceStart, const vec3& sensorSpaceDirection) override; - bool wantVsync() const override { return false; } @@ -63,33 +60,6 @@ protected: void customizeContext() override; void uncustomizeContext() override; void updateFrameData() override; - void compositeExtra() override; - - struct HandLaserInfo { - HandLaserMode mode { HandLaserMode::None }; - vec4 color { 1.0f }; - vec3 direction { 0, 0, -1 }; - - // Is this hand laser info suitable for drawing? - bool valid() const { - return (mode != HandLaserMode::None && color.a > 0.0f && direction != vec3()); - } - }; - - Transform _uiModelTransform; - std::array _handLasers; - std::array _handPoses; - - Transform _presentUiModelTransform; - std::array _presentHandLasers; - std::array _presentHandPoses; - std::array, 2> _presentHandLaserPoints; - - HandLaserInfo _extraLaser; - HandLaserInfo _presentExtraLaser; - vec3 _extraLaserStart; - vec3 _presentExtraLaserStart; - std::pair _presentExtraLaserPoints; std::array _eyeOffsets; std::array _eyeProjections; @@ -120,9 +90,6 @@ private: bool _disablePreviewItemAdded { false }; bool _monoPreview { true }; bool _clearPreviewFlag { false }; - std::array _handLaserUniforms; - gpu::BufferPointer _extraLaserUniforms; - gpu::PipelinePointer _glowLinePipeline; gpu::TexturePointer _previewTexture; glm::vec2 _lastWindowSize; @@ -140,14 +107,7 @@ private: struct Uniforms { mat4 mvp; - vec4 glowPoints { -1 }; - vec4 glowColors[2]; - vec2 resolution { CompositorHelper::VIRTUAL_SCREEN_SIZE }; - float radius { 0.005f }; float alpha { 1.0f }; - - vec4 extraGlowColor; - vec2 extraGlowPoint { -1 }; } uniforms; struct Vertex { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 36ac6ba1cc..0801c32cea 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -262,7 +262,7 @@ public: glm::vec3 getRegistrationPoint() const; /// registration point as ratio of entity /// registration point as ratio of entity - void setRegistrationPoint(const glm::vec3& value); + virtual void setRegistrationPoint(const glm::vec3& value); bool hasAngularVelocity() const { return getAngularVelocity() != ENTITY_ITEM_ZERO_VEC3; } bool hasLocalAngularVelocity() const { return getLocalAngularVelocity() != ENTITY_ITEM_ZERO_VEC3; } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index e21c9581e1..f3e677fc29 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -679,11 +679,17 @@ QVector EntityScriptingInterface::findEntitiesByType(const QString entity RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking, const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) { - PROFILE_RANGE(script_entities, __FUNCTION__); - QVector entitiesToInclude = qVectorEntityItemIDFromScriptValue(entityIdsToInclude); QVector entitiesToDiscard = qVectorEntityItemIDFromScriptValue(entityIdsToDiscard); - return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking, entitiesToInclude, entitiesToDiscard, visibleOnly, collidableOnly); + + return findRayIntersectionVector(ray, precisionPicking, entitiesToInclude, entitiesToDiscard, visibleOnly, collidableOnly); +} + +RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionVector(const PickRay& ray, bool precisionPicking, + const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) { + PROFILE_RANGE(script_entities, __FUNCTION__); + + return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly); } // FIXME - we should remove this API and encourage all users to use findRayIntersection() instead. We've changed diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 575528fa78..60c12a2f7d 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -229,6 +229,11 @@ public slots: const QScriptValue& entityIdsToInclude = QScriptValue(), const QScriptValue& entityIdsToDiscard = QScriptValue(), bool visibleOnly = false, bool collidableOnly = false); + /// Same as above but with QVectors + RayToEntityIntersectionResult findRayIntersectionVector(const PickRay& ray, bool precisionPicking, + const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, + bool visibleOnly, bool collidableOnly); + /// If the scripting context has visible entities, this will determine a ray intersection, and will block in /// order to return an accurate result Q_INVOKABLE RayToEntityIntersectionResult findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking = false, const QScriptValue& entityIdsToInclude = QScriptValue(), const QScriptValue& entityIdsToDiscard = QScriptValue()); diff --git a/libraries/entities/src/PolyLineEntityItem.cpp b/libraries/entities/src/PolyLineEntityItem.cpp index c1f6508a76..aa31130c82 100644 --- a/libraries/entities/src/PolyLineEntityItem.cpp +++ b/libraries/entities/src/PolyLineEntityItem.cpp @@ -92,13 +92,12 @@ bool PolyLineEntityItem::appendPoint(const glm::vec3& point) { qCDebug(entities) << "MAX POINTS REACHED!"; return false; } - glm::vec3 halfBox = getDimensions() * 0.5f; - if ((point.x < -halfBox.x || point.x > halfBox.x) || (point.y < -halfBox.y || point.y > halfBox.y) || (point.z < -halfBox.z || point.z > halfBox.z)) { - qCDebug(entities) << "Point is outside entity's bounding box"; - return false; - } + _points << point; _pointsChanged = true; + + calculateScaleAndRegistrationPoint(); + return true; } @@ -141,23 +140,69 @@ bool PolyLineEntityItem::setLinePoints(const QVector& points) { return; } - for (int i = 0; i < points.size(); i++) { - glm::vec3 point = points.at(i); - glm::vec3 halfBox = getDimensions() * 0.5f; - if ((point.x < -halfBox.x || point.x > halfBox.x) || - (point.y < -halfBox.y || point.y > halfBox.y) || - (point.z < -halfBox.z || point.z > halfBox.z)) { - qCDebug(entities) << "Point is outside entity's bounding box"; - return; - } - } _points = points; + + calculateScaleAndRegistrationPoint(); + result = true; }); return result; } +void PolyLineEntityItem::calculateScaleAndRegistrationPoint() { + glm::vec3 high(0.0f, 0.0f, 0.0f); + glm::vec3 low(0.0f, 0.0f, 0.0f); + for (int i = 0; i < _points.size(); i++) { + glm::vec3 point = _points.at(i); + + if (point.x > high.x) { + high.x = point.x; + } else if (point.x < low.x) { + low.x = point.x; + } + + if (point.y > high.y) { + high.y = point.y; + } else if (point.y < low.y) { + low.y = point.y; + } + + if (point.z > high.z) { + high.z = point.z; + } else if (point.z < low.z) { + low.z = point.z; + } + } + const float EPSILON = 0.0001f; + if (_points.size() > 1) { + // if all the points in the Polyline are at the same place in space, use default dimension settings + if ((low - high).length() < EPSILON) { + SpatiallyNestable::setScale(glm::vec3(1.0f, 1.0f, 1.0f)); + EntityItem::setRegistrationPoint(glm::vec3(0.5f)); + return; + } + + glm::vec3 result; + const float halfLineWidth = 0.075f; // sadly _strokeWidths() don't seem to correspond to reality, so just use a flat assumption of the stroke width + result.x = fabsf(high.x) + fabsf(low.x) + halfLineWidth; + result.y = fabsf(high.y) + fabsf(low.y) + halfLineWidth; + result.z = fabsf(high.z) + fabsf(low.z) + halfLineWidth; + SpatiallyNestable::setScale(result); + + // Center the poly line in the bounding box + glm::vec3 point = _points.at(0); + glm::vec3 startPointInScaleSpace = point - low; + startPointInScaleSpace += glm::vec3(halfLineWidth * 0.5f); + glm::vec3 newRegistrationPoint = startPointInScaleSpace / result; + EntityItem::setRegistrationPoint(newRegistrationPoint); + } else { + // if Polyline has only one or fewer points, use default dimension settings + SpatiallyNestable::setScale(glm::vec3(1.0f, 1.0f, 1.0f)); + EntityItem::setRegistrationPoint(glm::vec3(0.5f)); + } +} + int PolyLineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index ed161762fc..eca9a1ec79 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -81,10 +81,17 @@ class PolyLineEntityItem : public EntityItem { BoxFace& face, glm::vec3& surfaceNormal, void** intersectedObject, bool precisionPicking) const override { return false; } + // disable these external interfaces as PolyLineEntities caculate their own dimensions based on the points they contain + virtual void setRegistrationPoint(const glm::vec3& value) override {}; + virtual void setScale(const glm::vec3& scale) override {}; + virtual void setScale(float value) override {}; + virtual void debugDump() const override; static const float DEFAULT_LINE_WIDTH; static const int MAX_POINTS_PER_LINE; - +private: + void calculateScaleAndRegistrationPoint(); + protected: rgbColor _color; float _lineWidth; diff --git a/libraries/gpu/src/gpu/Frame.h b/libraries/gpu/src/gpu/Frame.h index 3c6fed9393..bfebe85753 100644 --- a/libraries/gpu/src/gpu/Frame.h +++ b/libraries/gpu/src/gpu/Frame.h @@ -32,6 +32,8 @@ namespace gpu { Mat4 pose; /// The collection of batches which make up the frame Batches batches; + /// Single batch containing overlays to be drawn in the composite framebuffer + Batch postCompositeBatch; /// The main thread updates to buffers that are applicable for this frame. BufferUpdates bufferUpdates; /// The destination framebuffer in which the results will be placed diff --git a/libraries/networking/src/NodePermissions.cpp b/libraries/networking/src/NodePermissions.cpp index cc5df515aa..e94c43b6fb 100644 --- a/libraries/networking/src/NodePermissions.cpp +++ b/libraries/networking/src/NodePermissions.cpp @@ -53,8 +53,12 @@ QVariant NodePermissions::toVariant(QHash groupRanks) { values["permissions_id"] = _id; if (_groupIDSet) { values["group_id"] = _groupID; - if (groupRanks.contains(_rankID)) { + + if (!_rankID.isNull()) { values["rank_id"] = _rankID; + } + + if (groupRanks.contains(_rankID)) { values["rank_name"] = groupRanks[_rankID].name; values["rank_order"] = groupRanks[_rankID].order; } diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index d7531e66a7..d3054c9bd8 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -109,24 +109,6 @@ public: RightHand = 0x02, }; - enum class HandLaserMode { - None, // Render no hand lasers - Overlay, // Render hand lasers only if they intersect with the UI layer, and stop at the UI layer - }; - - virtual bool setHandLaser( - uint32_t hands, // Bits from the Hand enum - HandLaserMode mode, // Mode in which to render - const vec4& color = vec4(1), // The color of the rendered laser - const vec3& direction = vec3(0, 0, -1) // The direction in which to render the hand lasers - ) { - return false; - } - - virtual bool setExtraLaser(HandLaserMode mode, const vec4& color, const glm::vec3& sensorSpaceStart, const vec3& sensorSpaceDirection) { - return false; - } - virtual bool suppressKeyboard() { return false; } virtual void unsuppressKeyboard() {}; virtual bool isKeyboardVisible() { return false; } diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index 55a46a526f..3013ad9ebb 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -70,7 +70,7 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline(RenderArgs* ar gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - PrepareStencil::testMask(*state); + PrepareStencil::testMaskNoAA(*state); state->setDepthTest(false, false, gpu::LESS_EQUAL); @@ -95,7 +95,7 @@ const gpu::PipelinePointer& Antialiasing::getBlendPipeline() { gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(false, false, gpu::LESS_EQUAL); - PrepareStencil::testMask(*state); + PrepareStencil::testMaskNoAA(*state); // Good to go add the brand new pipeline _blendPipeline = gpu::Pipeline::create(program, state); @@ -159,7 +159,6 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const // Blend step - getBlendPipeline(); batch.setResourceTexture(0, _antialiasingTexture); batch.setFramebuffer(sourceBuffer); batch.setPipeline(getBlendPipeline()); diff --git a/libraries/render-utils/src/FadeEffectJobs.cpp b/libraries/render-utils/src/FadeEffectJobs.cpp index 90459cf0e6..d00632c017 100644 --- a/libraries/render-utils/src/FadeEffectJobs.cpp +++ b/libraries/render-utils/src/FadeEffectJobs.cpp @@ -594,7 +594,7 @@ void FadeJob::run(const render::RenderContextPointer& renderContext, FadeJob::Ou if (update(*jobConfig, scene, transaction, state, deltaTime)) { hasTransaction = true; } - if (isFirstItem) { + if (isFirstItem && jobConfig->manualFade && (state.threshold != jobConfig->threshold)) { jobConfig->setProperty("threshold", state.threshold); isFirstItem = false; } @@ -645,9 +645,6 @@ bool FadeJob::update(const Config& config, const render::ScenePointer& scene, re { transition.threshold = computeElementEnterRatio(transition.time, eventDuration, timing); transition.baseOffset = transition.noiseOffset; - transition.baseInvSize.x = 1.f / dimensions.x; - transition.baseInvSize.y = 1.f / dimensions.y; - transition.baseInvSize.z = 1.f / dimensions.z; transition.isFinished += (transition.threshold >= 1.f) & 1; if (transition.eventType == render::Transition::ELEMENT_ENTER_DOMAIN) { transition.threshold = 1.f - transition.threshold; diff --git a/libraries/render-utils/src/FadeEffectJobs.h b/libraries/render-utils/src/FadeEffectJobs.h index f827bb6e99..a585d5b98a 100644 --- a/libraries/render-utils/src/FadeEffectJobs.h +++ b/libraries/render-utils/src/FadeEffectJobs.h @@ -50,7 +50,7 @@ class FadeConfig : public render::Job::Config { Q_PROPERTY(float baseSizeY READ getBaseSizeY WRITE setBaseSizeY NOTIFY dirty) Q_PROPERTY(float baseSizeZ READ getBaseSizeZ WRITE setBaseSizeZ NOTIFY dirty) Q_PROPERTY(float baseLevel READ getBaseLevel WRITE setBaseLevel NOTIFY dirty) - Q_PROPERTY(bool _isInverted READ isInverted WRITE setInverted NOTIFY dirty) + Q_PROPERTY(bool isInverted READ isInverted WRITE setInverted NOTIFY dirty) Q_PROPERTY(float noiseSizeX READ getNoiseSizeX WRITE setNoiseSizeX NOTIFY dirty) Q_PROPERTY(float noiseSizeY READ getNoiseSizeY WRITE setNoiseSizeY NOTIFY dirty) Q_PROPERTY(float noiseSizeZ READ getNoiseSizeZ WRITE setNoiseSizeZ NOTIFY dirty) diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 856b6dceab..286674488b 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -42,9 +42,7 @@ #include "simple_textured_fade_frag.h" #include "simple_textured_unlit_fade_frag.h" #include "simple_opaque_web_browser_frag.h" -#include "simple_opaque_web_browser_overlay_frag.h" #include "simple_transparent_web_browser_frag.h" -#include "simple_transparent_web_browser_overlay_frag.h" #include "glowLine_vert.h" #include "glowLine_frag.h" @@ -1828,7 +1826,7 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { auto stateNoBlend = std::make_shared(); - PrepareStencil::testMaskDrawShape(*state); + PrepareStencil::testMaskDrawShape(*stateNoBlend); auto noBlendPS = gpu::StandardShaderLib::getDrawTextureOpaquePS(); auto programNoBlend = gpu::Shader::createProgram(vs, noBlendPS); @@ -1925,7 +1923,7 @@ inline bool operator==(const SimpleProgramKey& a, const SimpleProgramKey& b) { return a.getRaw() == b.getRaw(); } -static void buildWebShader(const std::string& vertShaderText, const std::string& fragShaderText, bool blendEnable, +static void buildWebShader(const std::string& vertShaderText, const std::string& fragShaderText, bool blendEnable, bool isAA, gpu::ShaderPointer& shaderPointerOut, gpu::PipelinePointer& pipelinePointerOut) { auto VS = gpu::Shader::createVertex(vertShaderText); auto PS = gpu::Shader::createPixel(fragShaderText); @@ -1940,10 +1938,11 @@ static void buildWebShader(const std::string& vertShaderText, const std::string& state->setBlendFunction(blendEnable, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - if (blendEnable) { - PrepareStencil::testMask(*state); + + if (isAA) { + blendEnable ? PrepareStencil::testMask(*state) : PrepareStencil::testMaskDrawShape(*state); } else { - PrepareStencil::testMaskDrawShape(*state); + PrepareStencil::testMaskDrawShapeNoAA(*state); } pipelinePointerOut = gpu::Pipeline::create(shaderPointerOut, state); @@ -1957,11 +1956,11 @@ gpu::PipelinePointer GeometryCache::getOpaqueWebBrowserProgram(bool isAA) { static std::once_flag once; std::call_once(once, [&]() { const bool BLEND_ENABLE = false; - buildWebShader(simple_vert, simple_opaque_web_browser_frag, BLEND_ENABLE, _simpleOpaqueWebBrowserShader, _simpleOpaqueWebBrowserPipeline); - buildWebShader(simple_vert, simple_opaque_web_browser_overlay_frag, BLEND_ENABLE, _simpleOpaqueWebBrowserOverlayShader, _simpleOpaqueWebBrowserOverlayPipeline); + buildWebShader(simple_vert, simple_opaque_web_browser_frag, BLEND_ENABLE, true, _simpleOpaqueWebBrowserShader, _simpleOpaqueWebBrowserPipeline); + buildWebShader(simple_vert, simple_opaque_web_browser_frag, BLEND_ENABLE, false, _simpleOpaqueWebBrowserShader, _simpleOpaqueWebBrowserPipelineNoAA); }); - return isAA ? _simpleOpaqueWebBrowserPipeline : _simpleOpaqueWebBrowserOverlayPipeline; + return isAA ? _simpleOpaqueWebBrowserPipeline : _simpleOpaqueWebBrowserPipelineNoAA; } void GeometryCache::bindTransparentWebBrowserProgram(gpu::Batch& batch, bool isAA) { @@ -1971,13 +1970,12 @@ void GeometryCache::bindTransparentWebBrowserProgram(gpu::Batch& batch, bool isA gpu::PipelinePointer GeometryCache::getTransparentWebBrowserProgram(bool isAA) { static std::once_flag once; std::call_once(once, [&]() { - const bool BLEND_ENABLE = true; - buildWebShader(simple_vert, simple_transparent_web_browser_frag, BLEND_ENABLE, _simpleTransparentWebBrowserShader, _simpleTransparentWebBrowserPipeline); - buildWebShader(simple_vert, simple_transparent_web_browser_overlay_frag, BLEND_ENABLE, _simpleTransparentWebBrowserOverlayShader, _simpleTransparentWebBrowserOverlayPipeline); + buildWebShader(simple_vert, simple_transparent_web_browser_frag, BLEND_ENABLE, true, _simpleTransparentWebBrowserShader, _simpleTransparentWebBrowserPipeline); + buildWebShader(simple_vert, simple_transparent_web_browser_frag, BLEND_ENABLE, false, _simpleTransparentWebBrowserShader, _simpleTransparentWebBrowserPipelineNoAA); }); - return isAA ? _simpleTransparentWebBrowserPipeline : _simpleTransparentWebBrowserOverlayPipeline; + return isAA ? _simpleTransparentWebBrowserPipeline : _simpleTransparentWebBrowserPipelineNoAA; } void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool transparent, bool culled, bool unlit, bool depthBiased) { diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 40aa829444..37ba54ea80 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -460,13 +460,10 @@ private: gpu::ShaderPointer _simpleOpaqueWebBrowserShader; gpu::PipelinePointer _simpleOpaqueWebBrowserPipeline; + gpu::PipelinePointer _simpleOpaqueWebBrowserPipelineNoAA; gpu::ShaderPointer _simpleTransparentWebBrowserShader; gpu::PipelinePointer _simpleTransparentWebBrowserPipeline; - - gpu::ShaderPointer _simpleOpaqueWebBrowserOverlayShader; - gpu::PipelinePointer _simpleOpaqueWebBrowserOverlayPipeline; - gpu::ShaderPointer _simpleTransparentWebBrowserOverlayShader; - gpu::PipelinePointer _simpleTransparentWebBrowserOverlayPipeline; + gpu::PipelinePointer _simpleTransparentWebBrowserPipelineNoAA; static render::ShapePipelinePointer getShapePipeline(bool textured = false, bool transparent = false, bool culled = true, bool unlit = false, bool depthBias = false); diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 5b51b2d1cc..68c0250bec 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -45,7 +45,7 @@ using namespace render; -extern void initOverlay3DPipelines(render::ShapePlumber& plumber); +extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest = false); extern void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); RenderDeferredTask::RenderDeferredTask() { @@ -76,10 +76,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren fadeEffect->build(task, opaques); - // Filter the non antialiaased overlays - const int LAYER_NO_AA = 3; - const auto nonAAOverlays = task.addJob("Filter2DWebOverlays", overlayOpaques, LAYER_NO_AA); - // Prepare deferred, generate the shared Deferred Frame Transform const auto deferredFrameTransform = task.addJob("DeferredFrameTransform"); const auto lightingModel = task.addJob("LightingModel"); @@ -166,7 +162,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto toneMappingInputs = render::Varying(ToneMappingDeferred::Inputs(lightingFramebuffer, primaryFramebuffer)); task.addJob("ToneMapping", toneMappingInputs); - { // DEbug the bounds of the rendered items, still look at the zbuffer + { // Debug the bounds of the rendered items, still look at the zbuffer task.addJob("DrawMetaBounds", metas); task.addJob("DrawOpaqueBounds", opaques); task.addJob("DrawTransparentBounds", transparents); @@ -181,11 +177,11 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DrawOverlay3DOpaque", overlayOpaquesInputs, true); task.addJob("DrawOverlay3DTransparent", overlayTransparentsInputs, false); - { // DEbug the bounds of the rendered OVERLAY items, still look at the zbuffer + { // Debug the bounds of the rendered Overlay items, still look at the zbuffer task.addJob("DrawOverlayOpaqueBounds", overlayOpaques); task.addJob("DrawOverlayTransparentBounds", overlayTransparents); } - + // Debugging stages { // Debugging Deferred buffer job @@ -216,14 +212,9 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DrawZoneStack", deferredFrameTransform); } - // AA job to be revisited task.addJob("Antialiasing", primaryFramebuffer); - // Draw 2DWeb non AA - const auto nonAAOverlaysInputs = DrawOverlay3D::Inputs(nonAAOverlays, lightingModel).asVarying(); - task.addJob("Draw2DWebSurfaces", nonAAOverlaysInputs, false); - task.addJob("ToneAndPostRangeTimer", toneAndPostRangeTimer); // Blit! diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index e7575a1c95..865849b579 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -152,7 +152,7 @@ public: protected: render::ShapePlumberPointer _shapePlumber; int _maxDrawn; // initialized by Config - bool _opaquePass{ true }; + bool _opaquePass { true }; }; class Blit { diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index c5949cb336..0353e10407 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -99,7 +99,7 @@ using namespace render; using namespace std::placeholders; -void initOverlay3DPipelines(ShapePlumber& plumber); +void initOverlay3DPipelines(ShapePlumber& plumber, bool depthTest = false); void initDeferredPipelines(ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); void initForwardPipelines(ShapePlumber& plumber); @@ -110,7 +110,7 @@ void addPlumberPipeline(ShapePlumber& plumber, void batchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderArgs* args); void lightBatchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderArgs* args); -void initOverlay3DPipelines(ShapePlumber& plumber) { +void initOverlay3DPipelines(ShapePlumber& plumber, bool depthTest) { auto vertex = gpu::Shader::createVertex(std::string(overlay3D_vert)); auto vertexModel = gpu::Shader::createVertex(std::string(model_vert)); auto pixel = gpu::Shader::createPixel(std::string(overlay3D_frag)); @@ -137,7 +137,11 @@ void initOverlay3DPipelines(ShapePlumber& plumber) { bool isOpaque = (i & 4); auto state = std::make_shared(); - state->setDepthTest(false); + if (depthTest) { + state->setDepthTest(true, true, gpu::LESS_EQUAL); + } else { + state->setDepthTest(false); + } state->setCullMode(isCulled ? gpu::State::CULL_BACK : gpu::State::CULL_NONE); if (isBiased) { state->setDepthBias(1.0f); diff --git a/libraries/render-utils/src/StencilMaskPass.cpp b/libraries/render-utils/src/StencilMaskPass.cpp index 2d4efc0573..295e124ed1 100644 --- a/libraries/render-utils/src/StencilMaskPass.cpp +++ b/libraries/render-utils/src/StencilMaskPass.cpp @@ -112,6 +112,10 @@ void PrepareStencil::testMask(gpu::State& state) { state.setStencilTest(true, 0x00, gpu::State::StencilTest(PrepareStencil::STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); } +void PrepareStencil::testMaskNoAA(gpu::State& state) { + state.setStencilTest(true, 0x00, gpu::State::StencilTest(PrepareStencil::STENCIL_MASK | PrepareStencil::STENCIL_NO_AA, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); +} + void PrepareStencil::testBackground(gpu::State& state) { state.setStencilTest(true, 0x00, gpu::State::StencilTest(PrepareStencil::STENCIL_BACKGROUND, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); } @@ -120,6 +124,10 @@ void PrepareStencil::testMaskDrawShape(gpu::State& state) { state.setStencilTest(true, 0xFF, gpu::State::StencilTest(PrepareStencil::STENCIL_MASK, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_ZERO)); } +void PrepareStencil::testMaskDrawShapeNoAA(gpu::State& state) { + state.setStencilTest(true, 0xFF, gpu::State::StencilTest(PrepareStencil::STENCIL_MASK | PrepareStencil::STENCIL_NO_AA, 0xFF, gpu::ALWAYS, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_REPLACE)); +} + void PrepareStencil::testShape(gpu::State& state) { state.setStencilTest(true, 0x00, gpu::State::StencilTest(PrepareStencil::STENCIL_SHAPE, 0xFF, gpu::EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP)); } \ No newline at end of file diff --git a/libraries/render-utils/src/StencilMaskPass.h b/libraries/render-utils/src/StencilMaskPass.h index 01601d1ae6..2c0294c471 100644 --- a/libraries/render-utils/src/StencilMaskPass.h +++ b/libraries/render-utils/src/StencilMaskPass.h @@ -41,15 +41,18 @@ public: void run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& dstFramebuffer); - static const gpu::int8 STENCIL_MASK = 2; - static const gpu::int8 STENCIL_BACKGROUND = 1; static const gpu::int8 STENCIL_SHAPE = 0; + static const gpu::int8 STENCIL_BACKGROUND = 1 << 0; + static const gpu::int8 STENCIL_MASK = 1 << 1; + static const gpu::int8 STENCIL_NO_AA = 1 << 2; static void drawMask(gpu::State& state); static void testMask(gpu::State& state); + static void testMaskNoAA(gpu::State& state); static void testBackground(gpu::State& state); static void testMaskDrawShape(gpu::State& state); + static void testMaskDrawShapeNoAA(gpu::State& state); static void testShape(gpu::State& state); diff --git a/libraries/render-utils/src/simple_opaque_web_browser_overlay.slf b/libraries/render-utils/src/simple_opaque_web_browser_overlay.slf deleted file mode 100644 index 6d4d35591f..0000000000 --- a/libraries/render-utils/src/simple_opaque_web_browser_overlay.slf +++ /dev/null @@ -1,30 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// simple_opaque_web_browser_overlay.slf -// fragment shader -// -// Created by Anthony Thibault on 1/30/17. -// 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 -// -// Same as simple_opaque_web_browser.slf except frame buffer is sRGB, so colorToLinearRGBA is not necessary. - -<@include gpu/Color.slh@> -<@include DeferredBufferWrite.slh@> - -// the albedo texture -uniform sampler2D originalTexture; - -// the interpolated normal -in vec3 _normal; -in vec4 _color; -in vec2 _texCoord0; - -void main(void) { - vec4 texel = texture(originalTexture, _texCoord0.st); - _fragColor0 = vec4(_color.rgb * texel.rgb, 1.0); -} diff --git a/libraries/render-utils/src/simple_transparent_web_browser_overlay.slf b/libraries/render-utils/src/simple_transparent_web_browser_overlay.slf deleted file mode 100644 index af52389d5b..0000000000 --- a/libraries/render-utils/src/simple_transparent_web_browser_overlay.slf +++ /dev/null @@ -1,31 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// simple_transparent_web_browser_overlay.slf -// fragment shader -// -// Created by Anthony Thibault on 1/30/17. -// 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 -// -// Same as simple_transparent_web_browser.slf except frame buffer is sRGB, So colorToLinearRGBA is not necessary. -// - -<@include gpu/Color.slh@> -<@include DeferredBufferWrite.slh@> - -// the albedo texture -uniform sampler2D originalTexture; - -// the interpolated normal -in vec3 _normal; -in vec4 _color; -in vec2 _texCoord0; - -void main(void) { - vec4 texel = texture(originalTexture, _texCoord0.st); - _fragColor0 = vec4(_color.rgb * texel.rgb, _color.a); -} diff --git a/libraries/script-engine/src/FileScriptingInterface.cpp b/libraries/script-engine/src/FileScriptingInterface.cpp index 5f2460be78..68a141ad97 100644 --- a/libraries/script-engine/src/FileScriptingInterface.cpp +++ b/libraries/script-engine/src/FileScriptingInterface.cpp @@ -32,10 +32,11 @@ FileScriptingInterface::FileScriptingInterface(QObject* parent) : QObject(parent // nothing for now } -void FileScriptingInterface::runUnzip(QString path, QUrl url, bool autoAdd, bool isZip) { +void FileScriptingInterface::runUnzip(QString path, QUrl url, bool autoAdd, bool isZip, bool isBlocks) { qCDebug(scriptengine) << "Url that was downloaded: " + url.toString(); qCDebug(scriptengine) << "Path where download is saved: " + path; QString fileName = "/" + path.section("/", -1); + qCDebug(scriptengine) << "Filename: " << fileName; QString tempDir = path; if (!isZip) { tempDir.remove(fileName); @@ -52,14 +53,17 @@ void FileScriptingInterface::runUnzip(QString path, QUrl url, bool autoAdd, bool } QStringList fileList = unzipFile(path, tempDir); - QString filename = QUrl::fromLocalFile(fileList.first()).toString(); - if (filename != "") { - qCDebug(scriptengine) << "File to upload: " + filename; + if (!fileList.isEmpty()) { + qCDebug(scriptengine) << "File to upload: " + fileList.first(); } else { qCDebug(scriptengine) << "Unzip failed"; } - emit unzipResult(path, fileList, autoAdd, isZip); + + if (path.contains("vr.google.com/downloads")) { + isZip = true; + } + emit unzipResult(path, fileList, autoAdd, isZip, isBlocks); } diff --git a/libraries/script-engine/src/FileScriptingInterface.h b/libraries/script-engine/src/FileScriptingInterface.h index 4069e7cc78..e4c27dbf7f 100644 --- a/libraries/script-engine/src/FileScriptingInterface.h +++ b/libraries/script-engine/src/FileScriptingInterface.h @@ -24,11 +24,11 @@ public: public slots: QString convertUrlToPath(QUrl url); - void runUnzip(QString path, QUrl url, bool autoAdd, bool isZip); + void runUnzip(QString path, QUrl url, bool autoAdd, bool isZip, bool isBlocks); QString getTempDir(); signals: - void unzipResult(QString zipFile, QStringList unzipFile, bool autoAdd, bool isZip); + void unzipResult(QString zipFile, QStringList unzipFile, bool autoAdd, bool isZip, bool isBlocks); private: bool isTempDir(QString tempDir); diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index b30637c83f..78b54d26f1 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -34,6 +34,7 @@ int vec2MetaTypeId = qRegisterMetaType(); int quatMetaTypeId = qRegisterMetaType(); int xColorMetaTypeId = qRegisterMetaType(); int pickRayMetaTypeId = qRegisterMetaType(); +int rayPickResultMetaTypeId = qRegisterMetaType(); int collisionMetaTypeId = qRegisterMetaType(); int qMapURLStringMetaTypeId = qRegisterMetaType>(); int socketErrorMetaTypeId = qRegisterMetaType(); @@ -56,6 +57,7 @@ void registerMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, qColorToScriptValue, qColorFromScriptValue); qScriptRegisterMetaType(engine, qURLToScriptValue, qURLFromScriptValue); qScriptRegisterMetaType(engine, pickRayToScriptValue, pickRayFromScriptValue); + qScriptRegisterMetaType(engine, rayPickResultToScriptValue, rayPickResultFromScriptValue); qScriptRegisterMetaType(engine, collisionToScriptValue, collisionFromScriptValue); qScriptRegisterMetaType(engine, quuidToScriptValue, quuidFromScriptValue); qScriptRegisterMetaType(engine, qSizeFToScriptValue, qSizeFFromScriptValue); @@ -751,6 +753,23 @@ void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay) { } } +QScriptValue rayPickResultToScriptValue(QScriptEngine* engine, const RayPickResult& rayPickResult) { + QScriptValue obj = engine->newObject(); + obj.setProperty("type", rayPickResult.type); + QScriptValue objectID = quuidToScriptValue(engine, rayPickResult.objectID); + obj.setProperty("objectID", objectID); + obj.setProperty("distance", rayPickResult.distance); + QScriptValue intersection = vec3toScriptValue(engine, rayPickResult.intersection); + obj.setProperty("intersection", intersection); + QScriptValue surfaceNormal = vec3toScriptValue(engine, rayPickResult.surfaceNormal); + obj.setProperty("surfaceNormal", surfaceNormal); + return obj; +} + +void rayPickResultFromScriptValue(const QScriptValue& object, RayPickResult& rayPickResult) { + // TODO: cannot currently accept RayPickResults from JS +} + QScriptValue collisionToScriptValue(QScriptEngine* engine, const Collision& collision) { QScriptValue obj = engine->newObject(); obj.setProperty("type", collision.type); diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 123c769a96..f4fc9109ac 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -136,6 +136,29 @@ Q_DECLARE_METATYPE(PickRay) QScriptValue pickRayToScriptValue(QScriptEngine* engine, const PickRay& pickRay); void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay); +enum IntersectionType { + NONE, + ENTITY, + OVERLAY, + AVATAR, + HUD +}; + +class RayPickResult { +public: + RayPickResult() {} + RayPickResult(const IntersectionType type, const QUuid& objectID, const float distance, const glm::vec3& intersection, const glm::vec3& surfaceNormal = glm::vec3(NAN)) : + type(type), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal) {} + IntersectionType type { NONE }; + QUuid objectID { 0 }; + float distance { FLT_MAX }; + glm::vec3 intersection { NAN }; + glm::vec3 surfaceNormal { NAN }; +}; +Q_DECLARE_METATYPE(RayPickResult) +QScriptValue rayPickResultToScriptValue(QScriptEngine* engine, const RayPickResult& rayPickResult); +void rayPickResultFromScriptValue(const QScriptValue& object, RayPickResult& rayPickResult); + enum ContactEventType { CONTACT_EVENT_TYPE_START, CONTACT_EVENT_TYPE_CONTINUE, diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 2012ebbe30..47b526c925 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -43,7 +43,6 @@ #include "types/FileTypeProfile.h" #include "types/HFWebEngineProfile.h" -#include "types/HFTabletWebEngineProfile.h" #include "types/SoundEffect.h" #include "Logging.h" @@ -328,7 +327,6 @@ void initializeQmlEngine(QQmlEngine* engine, QQuickWindow* window) { } rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext)); rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext)); - rootContext->setContextProperty("HFTabletWebEngineProfile", new HFTabletWebEngineProfile(rootContext)); rootContext->setContextProperty("Paths", DependencyManager::get().data()); } diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 984d743ebf..adff219e0f 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -604,9 +604,9 @@ void TabletProxy::loadWebScreenOnTop(const QVariant& url, const QString& injectJ _state = State::Web; } -void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaScriptUrl) { +void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaScriptUrl, bool loadOtherBase) { if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "gotoWebScreen", Q_ARG(QString, url), Q_ARG(QString, injectedJavaScriptUrl)); + QMetaObject::invokeMethod(this, "gotoWebScreen", Q_ARG(QString, url), Q_ARG(QString, injectedJavaScriptUrl), Q_ARG(bool, loadOtherBase)); return; } @@ -619,7 +619,11 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS if (root) { removeButtonsFromHomeScreen(); - QMetaObject::invokeMethod(root, "loadWebBase"); + if (loadOtherBase) { + QMetaObject::invokeMethod(root, "loadTabletWebBase"); + } else { + QMetaObject::invokeMethod(root, "loadWebBase"); + } QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true))); QMetaObject::invokeMethod(root, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); } diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index af38cb9351..822bae839e 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -122,7 +122,7 @@ public: * @param [injectedJavaScriptUrl] {string} optional url to an additional JS script to inject into the web page. */ Q_INVOKABLE void gotoWebScreen(const QString& url); - Q_INVOKABLE void gotoWebScreen(const QString& url, const QString& injectedJavaScriptUrl); + Q_INVOKABLE void gotoWebScreen(const QString& url, const QString& injectedJavaScriptUrl, bool loadOtherBase = false); Q_INVOKABLE void loadQMLSource(const QVariant& path); // FIXME: This currently relies on a script initializing the tablet (hence the bool denoting success); diff --git a/libraries/ui/src/ui/types/HFTabletWebEngineProfile.cpp b/libraries/ui/src/ui/types/HFTabletWebEngineProfile.cpp deleted file mode 100644 index a3e3906497..0000000000 --- a/libraries/ui/src/ui/types/HFTabletWebEngineProfile.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// -// HFTabletWebEngineProfile.h -// interface/src/networking -// -// Created by Dante Ruiz on 2017-03-31. -// 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 -// - -#include "HFTabletWebEngineProfile.h" -#include "HFTabletWebEngineRequestInterceptor.h" - -static const QString QML_WEB_ENGINE_NAME = "qmlTabletWebEngine"; - -HFTabletWebEngineProfile::HFTabletWebEngineProfile(QObject* parent) : QQuickWebEngineProfile(parent) { - - static const QString WEB_ENGINE_USER_AGENT = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36"; - - setHttpUserAgent(WEB_ENGINE_USER_AGENT); - setStorageName(QML_WEB_ENGINE_NAME); - - auto requestInterceptor = new HFTabletWebEngineRequestInterceptor(this); - setRequestInterceptor(requestInterceptor); -} - diff --git a/libraries/ui/src/ui/types/HFTabletWebEngineRequestInterceptor.cpp b/libraries/ui/src/ui/types/HFTabletWebEngineRequestInterceptor.cpp deleted file mode 100644 index 6ee8589615..0000000000 --- a/libraries/ui/src/ui/types/HFTabletWebEngineRequestInterceptor.cpp +++ /dev/null @@ -1,46 +0,0 @@ -// -// HFTabletWebEngineRequestInterceptor.cpp -// interface/src/networking -// -// Created by Dante Ruiz on 2017-3-31. -// 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 -// - -#include "HFTabletWebEngineRequestInterceptor.h" -#include -#include "AccountManager.h" - -bool isTabletAuthableHighFidelityURL(const QUrl& url) { - static const QStringList HF_HOSTS = { - "highfidelity.com", "highfidelity.io", - "metaverse.highfidelity.com", "metaverse.highfidelity.io" - }; - - return url.scheme() == "https" && HF_HOSTS.contains(url.host()); -} - -void HFTabletWebEngineRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { - // check if this is a request to a highfidelity URL - if (isTabletAuthableHighFidelityURL(info.requestUrl())) { - // if we have an access token, add it to the right HTTP header for authorization - auto accountManager = DependencyManager::get(); - - if (accountManager->hasValidAccessToken()) { - static const QString OAUTH_AUTHORIZATION_HEADER = "Authorization"; - - QString bearerTokenString = "Bearer " + accountManager->getAccountInfo().getAccessToken().token; - info.setHttpHeader(OAUTH_AUTHORIZATION_HEADER.toLocal8Bit(), bearerTokenString.toLocal8Bit()); - } - - static const QString USER_AGENT = "User-Agent"; - QString tokenString = "Chrome/48.0 (HighFidelityInterface)"; - info.setHttpHeader(USER_AGENT.toLocal8Bit(), tokenString.toLocal8Bit()); - } else { - static const QString USER_AGENT = "User-Agent"; - QString tokenString = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36"; - info.setHttpHeader(USER_AGENT.toLocal8Bit(), tokenString.toLocal8Bit()); - } -} diff --git a/libraries/ui/src/ui/types/HFWebEngineProfile.cpp b/libraries/ui/src/ui/types/HFWebEngineProfile.cpp index a69d4d653b..685af45dad 100644 --- a/libraries/ui/src/ui/types/HFWebEngineProfile.cpp +++ b/libraries/ui/src/ui/types/HFWebEngineProfile.cpp @@ -18,8 +18,6 @@ static const QString QML_WEB_ENGINE_STORAGE_NAME = "qmlWebEngine"; HFWebEngineProfile::HFWebEngineProfile(QObject* parent) : QQuickWebEngineProfile(parent) { - static const QString WEB_ENGINE_USER_AGENT = "Chrome/48.0 (HighFidelityInterface)"; - setHttpUserAgent(WEB_ENGINE_USER_AGENT); setStorageName(QML_WEB_ENGINE_STORAGE_NAME); // we use the HFWebEngineRequestInterceptor to make sure that web requests are authenticated for the interface user diff --git a/libraries/ui/src/ui/types/RequestFilters.cpp b/libraries/ui/src/ui/types/RequestFilters.cpp index 3e72b8a8bd..6ef3effa4c 100644 --- a/libraries/ui/src/ui/types/RequestFilters.cpp +++ b/libraries/ui/src/ui/types/RequestFilters.cpp @@ -13,6 +13,7 @@ #include "NetworkingConstants.h" #include +#include #include "AccountManager.h" @@ -42,7 +43,8 @@ namespace { void RequestFilters::interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info) { // check if this is a request to a highfidelity URL - if (isAuthableHighFidelityURL(info.requestUrl())) { + bool isAuthable = isAuthableHighFidelityURL(info.requestUrl()); + if (isAuthable) { // if we have an access token, add it to the right HTTP header for authorization auto accountManager = DependencyManager::get(); @@ -53,6 +55,17 @@ void RequestFilters::interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info) info.setHttpHeader(OAUTH_AUTHORIZATION_HEADER.toLocal8Bit(), bearerTokenString.toLocal8Bit()); } } + static const QString USER_AGENT = "User-Agent"; + const QString tokenStringMobile{ "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36" }; + const QString tokenStringMetaverse{ "Chrome/48.0 (HighFidelityInterface)" }; + + // During the period in which we have HFC commerce in the system, but not applied everywhere: + const QString tokenStringCommerce{ "Chrome/48.0 (HighFidelityInterface WithHFC)" }; + static Setting::Handle _settingSwitch{ "inspectionMode", false }; + bool isMoney = _settingSwitch.get(); + + const QString tokenString = !isAuthable ? tokenStringMobile : (isMoney ? tokenStringCommerce : tokenStringMetaverse); + info.setHttpHeader(USER_AGENT.toLocal8Bit(), tokenString.toLocal8Bit()); } void RequestFilters::interceptFileType(QWebEngineUrlRequestInfo& info) { diff --git a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp index 0df504dfa2..69ad8a5710 100644 --- a/plugins/oculus/src/OculusBaseDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusBaseDisplayPlugin.cpp @@ -51,8 +51,6 @@ bool OculusBaseDisplayPlugin::beginFrameRender(uint32_t frameIndex) { }); withNonPresentThreadLock([&] { - _uiModelTransform = DependencyManager::get()->getModelTransform(); - _handPoses = handPoses; _frameInfos[frameIndex] = _currentRenderFrameInfo; }); return Parent::beginFrameRender(frameIndex); diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index b31f55edeb..771ba8a3d8 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -594,9 +594,6 @@ bool OpenVrDisplayPlugin::beginFrameRender(uint32_t frameIndex) { } withNonPresentThreadLock([&] { - _uiModelTransform = DependencyManager::get()->getModelTransform(); - // Make controller poses available to the presentation thread - _handPoses = handPoses; _frameInfos[frameIndex] = _currentRenderFrameInfo; }); return Parent::beginFrameRender(frameIndex); diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index da8add5117..2844940d2b 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -197,53 +197,24 @@ Mouse.prototype.restoreRotateCursor = function() { var mouse = new Mouse(); - -// Beacon class stores info for drawing a line at object's target position -function Beacon() { - this.height = 0.10; - this.overlayID = Overlays.addOverlay("line3d", { - color: { - red: 200, - green: 200, - blue: 200 - }, - alpha: 1, - visible: false, - lineWidth: 2 - }); -} - -Beacon.prototype.enable = function() { - Overlays.editOverlay(this.overlayID, { - visible: true - }); +var beacon = { + type: "cube", + dimensions: { + x: 0.01, + y: 0, + z: 0.01 + }, + color: { + red: 200, + green: 200, + blue: 200 + }, + alpha: 1, + solid: true, + ignoreRayIntersection: true, + visible: true }; -Beacon.prototype.disable = function() { - Overlays.editOverlay(this.overlayID, { - visible: false - }); -}; - -Beacon.prototype.updatePosition = function(position) { - Overlays.editOverlay(this.overlayID, { - visible: true, - start: { - x: position.x, - y: position.y + this.height, - z: position.z - }, - end: { - x: position.x, - y: position.y - this.height, - z: position.z - } - }); -}; - -var beacon = new Beacon(); - - // TODO: play sounds again when we aren't leaking AudioInjector threads // var grabSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/CloseClamp.wav"); // var releaseSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/ReleaseClamp.wav"); @@ -285,6 +256,21 @@ function Grabber() { this.liftKey = false; // SHIFT this.rotateKey = false; // CONTROL + + this.mouseRayOverlays = RayPick.createRayPick({ + joint: "Mouse", + filter: RayPick.PICK_OVERLAYS, + enabled: true + }); + RayPick.setIncludeOverlays(this.mouseRayOverlays, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]); + var renderStates = [{name: "grabbed", end: beacon}]; + this.mouseRayEntities = LaserPointers.createLaserPointer({ + joint: "Mouse", + filter: RayPick.PICK_ENTITIES, + faceAvatar: true, + enabled: true, + renderStates: renderStates + }); } Grabber.prototype.computeNewGrabPlane = function() { @@ -333,40 +319,42 @@ Grabber.prototype.pressEvent = function(event) { return; } - var pickRay = Camera.computePickRay(event.x, event.y); - - var overlayResult = Overlays.findRayIntersection(pickRay, true, [HMD.tabletID, HMD.tabletScreenID, HMD.homeButtonID]); - if (overlayResult.intersects) { + var overlayResult = RayPick.getPrevRayPickResult(this.mouseRayOverlays); + if (overlayResult.type != RayPick.INTERSECTED_NONE) { return; } - var pickResults = Entities.findRayIntersection(pickRay, true); // accurate picking - if (!pickResults.intersects) { - // didn't click on anything + var pickResults = LaserPointers.getPrevRayPickResult(this.mouseRayEntities); + if (pickResults.type == RayPick.INTERSECTED_NONE) { + LaserPointers.setRenderState(this.mouseRayEntities, ""); return; } - var isDynamic = Entities.getEntityProperties(pickResults.entityID, "dynamic").dynamic; + var isDynamic = Entities.getEntityProperties(pickResults.objectID, "dynamic").dynamic; if (!isDynamic) { // only grab dynamic objects return; } - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, pickResults.entityID, DEFAULT_GRABBABLE_DATA); + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, pickResults.objectID, DEFAULT_GRABBABLE_DATA); if (grabbableData.grabbable === false) { return; } + LaserPointers.setRenderState(this.mouseRayEntities, "grabbed"); + LaserPointers.setLockEndUUID(this.mouseRayEntities, pickResults.objectID, false); + mouse.startDrag(event); - var clickedEntity = pickResults.entityID; + var clickedEntity = pickResults.objectID; var entityProperties = Entities.getEntityProperties(clickedEntity); this.startPosition = entityProperties.position; this.lastRotation = entityProperties.rotation; var cameraPosition = Camera.getPosition(); var objectBoundingDiameter = Vec3.length(entityProperties.dimensions); - beacon.height = objectBoundingDiameter; + beacon.dimensions.y = objectBoundingDiameter; + LaserPointers.editRenderState(this.mouseRayEntities, "grabbed", {end: beacon}); this.maxDistance = objectBoundingDiameter / MAX_SOLID_ANGLE; if (Vec3.distance(this.startPosition, cameraPosition) > this.maxDistance) { // don't allow grabs of things far away @@ -385,6 +373,7 @@ Grabber.prototype.pressEvent = function(event) { }; // compute the grab point + var pickRay = Camera.computePickRay(event.x, event.y); var nearestPoint = Vec3.subtract(this.startPosition, cameraPosition); var distanceToGrab = Vec3.dot(nearestPoint, pickRay.direction); nearestPoint = Vec3.multiply(distanceToGrab, pickRay.direction); @@ -395,8 +384,6 @@ Grabber.prototype.pressEvent = function(event) { this.computeNewGrabPlane(); - beacon.updatePosition(this.startPosition); - if (!entityIsGrabbedByOther(this.entityID)) { this.moveEvent(event); } @@ -431,7 +418,7 @@ Grabber.prototype.releaseEvent = function(event) { } this.actionID = null; - beacon.disable(); + LaserPointers.setRenderState(this.mouseRayEntities, ""); var args = "mouse"; Entities.callEntityMethod(this.entityID, "releaseGrab", args); @@ -552,7 +539,6 @@ Grabber.prototype.moveEventProcess = function() { ttl: ACTION_TTL }; - beacon.updatePosition(this.targetPosition); } if (!this.actionID) { @@ -586,6 +572,11 @@ Grabber.prototype.keyPressEvent = function(event) { this.computeNewGrabPlane(); }; +Grabber.prototype.cleanup = function() { + LaserPointers.removeLaserPointer(this.mouseRayEntities); + RayPick.removeRayPick(this.mouseRayOverlays); +}; + var grabber = new Grabber(); function pressEvent(event) { @@ -608,10 +599,15 @@ function keyReleaseEvent(event) { grabber.keyReleaseEvent(event); } +function cleanup() { + grabber.cleanup(); +} + Controller.mousePressEvent.connect(pressEvent); Controller.mouseMoveEvent.connect(moveEvent); Controller.mouseReleaseEvent.connect(releaseEvent); Controller.keyPressEvent.connect(keyPressEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent); +Script.scriptEnding.connect(cleanup); }()); // END LOCAL_SCOPE diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index bd8ab26506..9318873f64 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -64,8 +64,6 @@ var HAPTIC_STYLUS_DURATION = 20.0; var HAPTIC_LASER_UI_STRENGTH = 1.0; var HAPTIC_LASER_UI_DURATION = 20.0; -var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only use head for search/move. - var PICK_WITH_HAND_RAY = true; var EQUIP_SPHERE_SCALE_FACTOR = 0.65; @@ -157,7 +155,6 @@ var INCHES_TO_METERS = 1.0 / 39.3701; // these control how long an abandoned pointer line or action will hang around var ACTION_TTL = 15; // seconds var ACTION_TTL_REFRESH = 5; -var PICKS_PER_SECOND_PER_HAND = 60; var MSECS_PER_SEC = 1000.0; var GRABBABLE_PROPERTIES = [ "position", @@ -355,7 +352,7 @@ function projectOntoXYPlane(worldPos, position, rotation, dimensions, registrati function projectOntoEntityXYPlane(entityID, worldPos) { var props = entityPropertiesCache.getProps(entityID); - if (props) { + if (props && props.position && props.rotation && props.dimensions && props.registrationPoint) { return projectOntoXYPlane(worldPos, props.position, props.rotation, props.dimensions, props.registrationPoint); } } @@ -371,16 +368,20 @@ function projectOntoOverlayXYPlane(overlayID, worldPos) { var resolution = Overlays.getProperty(overlayID, "resolution"); resolution.z = 1; // Circumvent divide-by-zero. var scale = Overlays.getProperty(overlayID, "dimensions"); - scale.z = 0.01; // overlay dimensions are 2D, not 3D. - dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); + if (scale) { + scale.z = 0.01; // overlay dimensions are 2D, not 3D. + dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); + } } else { dimensions = Overlays.getProperty(overlayID, "dimensions"); - if (dimensions.z) { + if (dimensions && dimensions.z) { dimensions.z = 0.01; // overlay dimensions are 2D, not 3D. } } - return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT); + if (position && rotation && dimensions) { + return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT); + } } function handLaserIntersectItem(position, rotation, start) { @@ -458,15 +459,6 @@ function entityHasActions(entityID) { return Entities.getActionIDs(entityID).length > 0; } -function findRayIntersection(pickRay, precise, include, exclude) { - var entities = Entities.findRayIntersection(pickRay, precise, include, exclude, true); - var overlays = Overlays.findRayIntersection(pickRay, precise, [], [HMD.tabletID]); - if (!overlays.intersects || (entities.intersects && (entities.distance <= overlays.distance))) { - return entities; - } - return overlays; -} - function entityIsGrabbedByOther(entityID) { // by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*. var actionIDs = Entities.getActionIDs(entityID); @@ -1062,6 +1054,74 @@ function getControllerJointIndex(hand) { // global EquipHotspotBuddy instance var equipHotspotBuddy = new EquipHotspotBuddy(); +var halfPath = { + type: "line3d", + color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID +} +var halfEnd = { + type: "sphere", + solid: true, + color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE, + alpha: 0.9, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. + visible: true +} +var fullPath = { + type: "line3d", + color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID +} +var fullEnd = { + type: "sphere", + solid: true, + color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE, + alpha: 0.9, + ignoreRayIntersection: true, + drawInFront: true, // Even when burried inside of something, show it. + visible: true +} +var holdPath = { + type: "line3d", + color: COLORS_GRAB_DISTANCE_HOLD, + visible: true, + alpha: 1, + solid: true, + glow: 1.0, + lineWidth: 5, + ignoreRayIntersection: true, // always ignore this + drawInFront: true, // Even when burried inside of something, show it. + parentID: AVATAR_SELF_ID +} + +var renderStates = [{name: "half", path: halfPath, end: halfEnd}, + {name: "full", path: fullPath, end: fullEnd}, + {name: "hold", path: holdPath}]; +var headRenderStates = [{name: "half", end: halfEnd}, + {name: "full", end: fullEnd}, + {name: "hold", path: holdPath}]; + +// how far from camera to search intersection? +var DEFAULT_SEARCH_SPHERE_DISTANCE = 1000; +var defaultRenderStates = [{name: "half", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: halfPath}, + {name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: fullPath}, + {name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: holdPath}]; + function MyController(hand) { this.hand = hand; this.autoUnequipCounter = 0; @@ -1110,7 +1170,6 @@ function MyController(hand) { this.grabbedThingID = null; // on this entity. this.grabbedOverlay = null; this.state = STATE_OFF; - this.pointer = null; // entity-id of line object this.triggerValue = 0; // rolling average of trigger value this.triggerClicked = false; @@ -1119,18 +1178,33 @@ function MyController(hand) { this.rawThumbValue = 0; // for visualizations - this.overlayLine = null; - this.searchSphere = null; - this.otherGrabbingLine = null; + this.halfEnd = halfEnd; + this.fullEnd = fullEnd; + this.laserPointer = LaserPointers.createLaserPointer({ + joint: (hand == RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, + maxDistance: PICK_MAX_DISTANCE, + posOffset: getGrabPointSphereOffset(this.handToController()), + renderStates: renderStates, + faceAvatar: true, + defaultRenderStates: defaultRenderStates + }); + this.headLaserPointer = LaserPointers.createLaserPointer({ + joint: "Avatar", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, + maxDistance: PICK_MAX_DISTANCE, + renderStates: headRenderStates, + faceAvatar: true, + defaultRenderStates: defaultRenderStates + }); + LaserPointers.setIgnoreOverlays(this.laserPointer, [HMD.tabletID]); + LaserPointers.setIgnoreOverlays(this.headLaserPointer, [HMD.tabletID]); this.otherGrabbingUUID = null; this.waitForTriggerRelease = false; - // how far from camera to search intersection? - var DEFAULT_SEARCH_SPHERE_DISTANCE = 1000; this.intersectionDistance = 0.0; - this.searchSphereDistance = DEFAULT_SEARCH_SPHERE_DISTANCE; this.ignoreIK = false; this.offsetPosition = Vec3.ZERO; @@ -1311,41 +1385,6 @@ function MyController(hand) { } }; - this.searchSphereOn = function(location, size, color) { - - var rotation = Quat.lookAt(location, Camera.getPosition(), Vec3.UP); - var brightColor = colorPow(color, 0.06); - if (this.searchSphere === null) { - var sphereProperties = { - name: "searchSphere", - position: location, - rotation: rotation, - outerRadius: size * 1.2, - innerColor: brightColor, - outerColor: color, - innerAlpha: 0.9, - outerAlpha: 0.0, - solid: true, - ignoreRayIntersection: true, - drawInFront: true, // Even when burried inside of something, show it. - visible: true - }; - this.searchSphere = Overlays.addOverlay("circle3d", sphereProperties); - } else { - Overlays.editOverlay(this.searchSphere, { - position: location, - rotation: rotation, - innerColor: brightColor, - outerColor: color, - innerAlpha: 1.0, - outerAlpha: 0.0, - outerRadius: size * 1.2, - visible: true, - ignoreRayIntersection: true - }); - } - }; - this.showStylus = function() { if (this.stylus) { return; @@ -1381,99 +1420,40 @@ function MyController(hand) { this.stylus = null; }; - this.overlayLineOn = function(closePoint, farPoint, color, farParentID) { - if (this.overlayLine === null) { - var lineProperties = { - name: "line", - glow: 1.0, - lineWidth: 5, - start: closePoint, - end: farPoint, - color: color, - ignoreRayIntersection: true, // always ignore this - drawInFront: true, // Even when burried inside of something, show it. - visible: true, - alpha: 1, - parentID: AVATAR_SELF_ID, - parentJointIndex: MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? - "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : - "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"), - endParentID: farParentID - }; - this.overlayLine = Overlays.addOverlay("line3d", lineProperties); - - } else { - if (farParentID && farParentID != NULL_UUID) { - Overlays.editOverlay(this.overlayLine, { - color: color, - endParentID: farParentID - }); - } else { - Overlays.editOverlay(this.overlayLine, { - length: Vec3.distance(farPoint, closePoint), - color: color, - endParentID: farParentID - }); - } - } - }; - - this.searchIndicatorOn = function(distantPickRay) { - var handPosition = distantPickRay.origin; + this.updateLaserPointer = function() { var SEARCH_SPHERE_SIZE = 0.011; - var SEARCH_SPHERE_FOLLOW_RATE = 0.50; - - if (this.intersectionDistance > 0) { - // If we hit something with our pick ray, move the search sphere toward that distance - this.searchSphereDistance = this.searchSphereDistance * SEARCH_SPHERE_FOLLOW_RATE + - this.intersectionDistance * (1.0 - SEARCH_SPHERE_FOLLOW_RATE); + var MIN_SPHERE_SIZE = 0.0005; + var radius = Math.max(1.2 * SEARCH_SPHERE_SIZE * this.intersectionDistance, MIN_SPHERE_SIZE); + var dim = {x: radius, y: radius, z: radius}; + var mode = "hold"; + if (this.state !== STATE_DISTANCE_HOLDING && this.state !== STATE_DISTANCE_ROTATING) { + mode = (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? "full" : "half"; } - var searchSphereLocation = Vec3.sum(distantPickRay.origin, - Vec3.multiply(distantPickRay.direction, this.searchSphereDistance)); - this.searchSphereOn(searchSphereLocation, SEARCH_SPHERE_SIZE * this.searchSphereDistance, - (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? - COLORS_GRAB_SEARCHING_FULL_SQUEEZE : - COLORS_GRAB_SEARCHING_HALF_SQUEEZE); - if (PICK_WITH_HAND_RAY) { - this.overlayLineOn(handPosition, searchSphereLocation, - (this.triggerSmoothedGrab() || this.secondarySqueezed()) ? - COLORS_GRAB_SEARCHING_FULL_SQUEEZE : - COLORS_GRAB_SEARCHING_HALF_SQUEEZE); + var laserPointerID = PICK_WITH_HAND_RAY ? this.laserPointer : this.headLaserPointer; + if (mode === "full") { + var fullEndToEdit = PICK_WITH_HAND_RAY ? this.fullEnd : fullEnd; + fullEndToEdit.dimensions = dim; + LaserPointers.editRenderState(laserPointerID, mode, {path: fullPath, end: fullEndToEdit}); + } else if (mode === "half") { + var halfEndToEdit = PICK_WITH_HAND_RAY ? this.halfEnd : halfEnd; + halfEndToEdit.dimensions = dim; + LaserPointers.editRenderState(laserPointerID, mode, {path: halfPath, end: halfEndToEdit}); } - }; - - // Turns off indicators used for searching. Overlay line and sphere. - this.searchIndicatorOff = function() { - this.searchSphereOff(); - if (PICK_WITH_HAND_RAY) { - this.overlayLineOff(); - } - }; - - this.otherGrabbingLineOn = function(avatarPosition, entityPosition, color) { - if (this.otherGrabbingLine === null) { - var lineProperties = { - lineWidth: 5, - start: avatarPosition, - end: entityPosition, - color: color, - glow: 1.0, - ignoreRayIntersection: true, - drawInFront: true, - visible: true, - alpha: 1 - }; - this.otherGrabbingLine = Overlays.addOverlay("line3d", lineProperties); + LaserPointers.enableLaserPointer(laserPointerID); + LaserPointers.setRenderState(laserPointerID, mode); + if (this.state === STATE_DISTANCE_HOLDING || this.state === STATE_DISTANCE_ROTATING) { + LaserPointers.setLockEndUUID(laserPointerID, this.grabbedThingID, this.grabbedIsOverlay); } else { - Overlays.editOverlay(this.otherGrabbingLine, { - start: avatarPosition, - end: entityPosition, - color: color - }); + LaserPointers.setLockEndUUID(laserPointerID, null, false); } }; + this.laserPointerOff = function() { + var laserPointerID = PICK_WITH_HAND_RAY ? this.laserPointer : this.headLaserPointer; + LaserPointers.disableLaserPointer(laserPointerID); + }; + this.evalLightWorldTransform = function(modelPos, modelRot) { var MODEL_LIGHT_POSITION = { @@ -1494,42 +1474,9 @@ function MyController(hand) { }; }; - this.lineOff = function() { - if (this.pointer !== null) { - Entities.deleteEntity(this.pointer); - } - this.pointer = null; - }; - - this.overlayLineOff = function() { - if (this.overlayLine !== null) { - Overlays.deleteOverlay(this.overlayLine); - } - this.overlayLine = null; - }; - - this.searchSphereOff = function() { - if (this.searchSphere !== null) { - Overlays.deleteOverlay(this.searchSphere); - this.searchSphere = null; - this.searchSphereDistance = DEFAULT_SEARCH_SPHERE_DISTANCE; - this.intersectionDistance = 0.0; - } - }; - - this.otherGrabbingLineOff = function() { - if (this.otherGrabbingLine !== null) { - Overlays.deleteOverlay(this.otherGrabbingLine); - } - this.otherGrabbingLine = null; - }; - this.turnOffVisualizations = function() { - this.overlayLineOff(); this.grabPointSphereOff(); - this.lineOff(); - this.searchSphereOff(); - this.otherGrabbingLineOff(); + this.laserPointerOff(); restore2DMode(); }; @@ -1824,12 +1771,12 @@ function MyController(hand) { var rayPickInfo = this.calcRayPickInfo(this.hand); if (rayPickInfo.isValid) { this.intersectionDistance = (rayPickInfo.entityID || rayPickInfo.overlayID) ? rayPickInfo.distance : 0; - this.searchIndicatorOn(rayPickInfo.searchRay); + this.updateLaserPointer(); } else { - this.searchIndicatorOff(); + this.laserPointerOff(); } } else { - this.searchIndicatorOff(); + this.laserPointerOff(); } }; @@ -1870,29 +1817,26 @@ function MyController(hand) { // Performs ray pick test from the hand controller into the world // @param {number} which hand to use, RIGHT_HAND or LEFT_HAND - // @param {object} if set, use this as as the pick ray, expects origin, direction, and length fields. // @returns {object} returns object with two keys entityID and distance // - this.calcRayPickInfo = function(hand, pickRayOverride) { + this.calcRayPickInfo = function(hand) { + var controllerLocation = getControllerWorldLocation(this.handToController(), true); + var worldHandPosition = controllerLocation.position; + var worldHandRotation = controllerLocation.orientation; var pickRay; var valid = true - if (pickRayOverride) { - pickRay = pickRayOverride; - } else { - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - var worldHandPosition = controllerLocation.position; - var worldHandRotation = controllerLocation.orientation; - valid = !(worldHandPosition === undefined); - pickRay = { - origin: PICK_WITH_HAND_RAY ? worldHandPosition : Camera.position, - direction: PICK_WITH_HAND_RAY ? Quat.getUp(worldHandRotation) : Vec3.mix(Quat.getUp(worldHandRotation), - Quat.getFront(Camera.orientation), - HAND_HEAD_MIX_RATIO), - length: PICK_MAX_DISTANCE - }; - } + var controllerLocation = getControllerWorldLocation(this.handToController(), true); + var worldHandPosition = controllerLocation.position; + var worldHandRotation = controllerLocation.orientation; + valid = !(worldHandPosition === undefined); + + pickRay = { + origin: PICK_WITH_HAND_RAY ? worldHandPosition : MyAvatar.getHeadPosition(), + direction: PICK_WITH_HAND_RAY ? Quat.getUp(worldHandRotation) : Quat.getFront(Camera.orientation), + length: PICK_MAX_DISTANCE + }; var result = { entityID: null, @@ -1902,28 +1846,17 @@ function MyController(hand) { isValid: valid }; - var now = Date.now(); - if (now - this.lastPickTime < MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { - return result; - } - this.lastPickTime = now; + var laserPointerID = PICK_WITH_HAND_RAY ? this.laserPointer : this.headLaserPointer; + var intersection = LaserPointers.getPrevRayPickResult(laserPointerID); - var intersection; - if (USE_BLACKLIST === true && blacklist.length !== 0) { - intersection = findRayIntersection(pickRay, true, [], blacklist, true); - } else { - intersection = findRayIntersection(pickRay, true, [], [], true); - } - - if (intersection.intersects) { + if (intersection.type != RayPick.INTERSECTED_NONE) { return { - entityID: intersection.entityID, - overlayID: intersection.overlayID, + entityID: intersection.type == RayPick.INTERSECTED_ENTITY ? intersection.objectID : null, + overlayID: intersection.type == RayPick.INTERSECTED_OVERLAY ? intersection.objectID : null, searchRay: pickRay, - distance: Vec3.distance(pickRay.origin, intersection.intersection), + distance: intersection.distance, intersection: intersection.intersection, - normal: intersection.surfaceNormal, - properties: intersection.properties + normal: intersection.surfaceNormal }; } else { return result; @@ -2195,7 +2128,6 @@ function MyController(hand) { var rayPickInfo = this.calcRayPickInfo(this.hand); if (rayPickInfo.entityID || rayPickInfo.overlayID) { this.intersectionDistance = rayPickInfo.distance; - this.searchSphereDistance = this.intersectionDistance; } }; @@ -2387,7 +2319,7 @@ function MyController(hand) { } if (isInEditMode()) { - this.searchIndicatorOn(rayPickInfo.searchRay); + this.updateLaserPointer(); if (this.triggerSmoothedGrab()) { if (!this.editTriggered){ if (rayPickInfo.entityID) { @@ -2420,7 +2352,7 @@ function MyController(hand) { } else { // potentialFarTriggerEntity = entity; } - this.otherGrabbingLineOff(); + this.laserPointerOff(); } else if (this.entityIsDistanceGrabbable(rayPickInfo.entityID, handPosition)) { if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled && farSearching) { this.grabbedThingID = entity; @@ -2435,26 +2367,18 @@ function MyController(hand) { } else { // potentialFarGrabEntity = entity; } - this.otherGrabbingLineOff(); + this.laserPointerOff(); } else if (this.otherGrabbingUUID !== null) { if (this.triggerSmoothedGrab() && !isEditing() && farGrabEnabled && farSearching) { - var avatar = AvatarList.getAvatar(this.otherGrabbingUUID); - var IN_FRONT_OF_AVATAR = { x: 0, y: 0.2, z: 0.4 }; // Up from hips and in front of avatar. - var startPosition = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.rotation, IN_FRONT_OF_AVATAR)); - var rayHitProps = entityPropertiesCache.getProps(rayPickInfo.entityID); - var finishPisition = Vec3.sum(rayHitProps.position, // Entity's centroid. - Vec3.multiplyQbyV(rayHitProps.rotation, - Vec3.multiplyVbyV(rayHitProps.dimensions, - Vec3.subtract(DEFAULT_REGISTRATION_POINT, rayHitProps.registrationPoint)))); - this.otherGrabbingLineOn(startPosition, finishPisition, COLORS_GRAB_DISTANCE_HOLD); + this.updateLaserPointer(); } else { - this.otherGrabbingLineOff(); + this.laserPointerOff(); } } else { - this.otherGrabbingLineOff(); + this.laserPointerOff(); } } else { - this.otherGrabbingLineOff(); + this.laserPointerOff(); } this.updateEquipHaptics(potentialEquipHotspot, handPosition); @@ -2466,7 +2390,7 @@ function MyController(hand) { } if (farGrabEnabled && farSearching) { - this.searchIndicatorOn(rayPickInfo.searchRay); + this.updateLaserPointer(); } Reticle.setVisible(false); }; @@ -2799,7 +2723,7 @@ function MyController(hand) { y: 0.0, z: objDistance }); - var change = Vec3.multiply(Vec3.subtract(before, after), HAND_HEAD_MIX_RATIO); + var change = Vec3.subtract(before, after) * (PICK_WITH_HAND_RAY ? 0.0 : 1.0); this.currentCameraOrientation = Camera.orientation; this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); } @@ -2808,11 +2732,7 @@ function MyController(hand) { this.maybeScale(grabbedProperties); // visualizations - var rayPickInfo = this.calcRayPickInfo(this.hand); - this.overlayLineOn(rayPickInfo.searchRay.origin, - Vec3.subtract(grabbedProperties.position, this.offsetPosition), - COLORS_GRAB_DISTANCE_HOLD, - this.grabbedThingID); + this.updateLaserPointer(); var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition)); @@ -2919,9 +2839,7 @@ function MyController(hand) { this.getOtherHandController().offsetPosition = Vec3.multiplyQbyV(controllerRotationDelta, this.getOtherHandController().offsetPosition); - var rayPickInfo = this.calcRayPickInfo(this.hand); - this.overlayLineOn(rayPickInfo.searchRay.origin, Vec3.subtract(grabbedProperties.position, this.offsetPosition), - COLORS_GRAB_DISTANCE_HOLD, this.grabbedThingID); + this.updateLaserPointer(); this.previousWorldControllerRotation = worldControllerRotation; }; @@ -3005,10 +2923,7 @@ function MyController(hand) { this.nearGrabbingEnter = function() { this.grabPointSphereOff(); - this.lineOff(); - this.overlayLineOff(); - this.searchSphereOff(); - this.otherGrabbingLineOff(); + this.laserPointerOff(); this.dropGestureReset(); this.clearEquipHaptics(); @@ -3512,28 +3427,18 @@ function MyController(hand) { return; } - var pickRay = { - origin: getControllerWorldLocation(this.handToController(), false).position, - direction: Quat.getUp(getControllerWorldLocation(this.handToController(), false).orientation) - }; - - var now = Date.now(); - if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { - var intersection = findRayIntersection(pickRay, true, [], [], true); - if (intersection.accurate || intersection.overlayID) { - this.lastPickTime = now; - if (intersection.entityID != this.grabbedThingID) { - this.callEntityMethodOnGrabbed("stopFarTrigger"); - this.grabbedThingID = null; - this.setState(STATE_OFF, "laser moved off of entity"); - return; - } - if (intersection.intersects) { - this.intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); - } - if (farGrabEnabled) { - this.searchIndicatorOn(pickRay); - } + var laserPointerID = PICK_WITH_HAND_RAY ? this.laserPointer : this.headLaserPointer; + var intersection = LaserPointers.getPrevRayPickResult(laserPointerID); + if (intersection.type != RayPick.INTERSECTED_NONE) { + if (intersection.objectID != this.grabbedThingID) { + this.callEntityMethodOnGrabbed("stopFarTrigger"); + this.grabbedThingID = null; + this.setState(STATE_OFF, "laser moved off of entity"); + return; + } + this.intersectionDistance = intersection.distance; + if (farGrabEnabled) { + this.updateLaserPointer(); } } @@ -3541,8 +3446,6 @@ function MyController(hand) { }; this.offEnter = function() { - // Reuse the existing search distance if lasers were active since - // they will be shown in OFF state while in edit mode. var existingSearchDistance = this.searchSphereDistance; this.release(); @@ -3664,7 +3567,7 @@ function MyController(hand) { this.intersectionDistance = intersectInfo.distance; if (this.state == STATE_ENTITY_LASER_TOUCHING) { - this.searchIndicatorOn(intersectInfo.searchRay); + this.updateLaserPointer(); } Reticle.setVisible(false); } else { @@ -3792,7 +3695,7 @@ function MyController(hand) { this.intersectionDistance = intersectInfo.distance; if (this.state == STATE_OVERLAY_LASER_TOUCHING) { - this.searchIndicatorOn(intersectInfo.searchRay); + this.updateLaserPointer(); } Reticle.setVisible(false); } else { @@ -3935,7 +3838,8 @@ function MyController(hand) { this.release(); this.grabPointSphereOff(); this.hideStylus(); - this.overlayLineOff(); + LaserPointers.removeLaserPointer(this.laserPointer); + LaserPointers.removeLaserPointer(this.headLaserPointer); }; this.thisHandIsParent = function(props) { @@ -3986,8 +3890,7 @@ function MyController(hand) { children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, controllerCRJointIndex)); children.forEach(function(childID) { - if (childID !== _this.stylus && - childID !== _this.overlayLine) { + if (childID !== _this.stylus) { // we appear to be holding something and this script isn't in a state that would be holding something. // unhook it. if we previously took note of this entity's parent, put it back where it was. This // works around some problems that happen when more than one hand or avatar is passing something around. @@ -4101,6 +4004,15 @@ Messages.subscribe('Hifi-Hand-RayPick-Blacklist'); Messages.subscribe('Hifi-Object-Manipulation'); Messages.subscribe('Hifi-Hand-Drop'); +var setBlacklist = function() { + if (USE_BLACKLIST) { + LaserPointers.setIgnoreEntities(leftController.laserPointer, blacklist); + LaserPointers.setIgnoreEntities(leftController.headLaserPointer, blacklist); + LaserPointers.setIgnoreEntities(rightController.laserPointer, blacklist); + LaserPointers.setIgnoreEntities(rightController.headLaserPointer, blacklist); + } +} + var handleHandMessages = function(channel, message, sender) { var data; if (sender === MyAvatar.sessionUUID) { @@ -4175,10 +4087,12 @@ var handleHandMessages = function(channel, message, sender) { if (action === 'add' && index === -1) { blacklist.push(id); + setBlacklist(); } if (action === 'remove') { if (index > -1) { blacklist.splice(index, 1); + setBlacklist(); } } diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index eb94428100..538fe0b1e4 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -150,15 +150,92 @@ var setReticlePosition = function (point2d) { Reticle.setPosition(point2d); }; -// Generalizations of utilities that work with system and overlay elements. -function findRayIntersection(pickRay) { - // Check 3D overlays and entities. Argument is an object with origin and direction. - var result = Overlays.findRayIntersection(pickRay); - if (!result.intersects) { - result = Entities.findRayIntersection(pickRay, true); - } - return result; +// VISUAL AID ----------- +// Same properties as handControllerGrab search sphere +var LASER_ALPHA = 0.5; +var LASER_SEARCH_COLOR = {red: 10, green: 10, blue: 255}; +var LASER_TRIGGER_COLOR = {red: 250, green: 10, blue: 10}; +var END_DIAMETER = 0.05; +var systemLaserOn = false; + +var triggerPath = { + type: "line3d", + color: LASER_TRIGGER_COLOR, + ignoreRayIntersection: true, + visible: true, + alpha: LASER_ALPHA, + solid: true, + glow: 1.0, + drawHUDLayer: true } +var triggerEnd = { + type: "sphere", + dimensions: {x: END_DIAMETER, y: END_DIAMETER, z: END_DIAMETER}, + color: LASER_TRIGGER_COLOR, + ignoreRayIntersection: true, + visible: true, + alpha: LASER_ALPHA, + solid: true, + drawHUDLayer: true +} + +var searchPath = { + type: "line3d", + color: LASER_SEARCH_COLOR, + ignoreRayIntersection: true, + visible: true, + alpha: LASER_ALPHA, + solid: true, + glow: 1.0, + drawHUDLayer: true +} +var searchEnd = { + type: "sphere", + dimensions: {x: END_DIAMETER, y: END_DIAMETER, z: END_DIAMETER}, + color: LASER_SEARCH_COLOR, + ignoreRayIntersection: true, + visible: true, + alpha: LASER_ALPHA, + solid: true, + drawHUDLayer: true +} + +var hudRayStates = [{name: "trigger", path: triggerPath, end: triggerEnd}, + {name: "search", path: searchPath, end: searchEnd}]; +// this offset needs to match the one in libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp:378 +var GRAB_POINT_SPHERE_OFFSET_RIGHT = { x: 0.04, y: 0.13, z: 0.039 }; +var GRAB_POINT_SPHERE_OFFSET_LEFT = { x: -0.04, y: 0.13, z: 0.039 }; +var hudRayRight = LaserPointers.createLaserPointer({ + joint: "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", + filter: RayPick.PICK_HUD, + posOffset: GRAB_POINT_SPHERE_OFFSET_RIGHT, + renderStates: hudRayStates, + enabled: true +}); +var hudRayLeft = LaserPointers.createLaserPointer({ + joint: "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + filter: RayPick.PICK_HUD, + posOffset: GRAB_POINT_SPHERE_OFFSET_LEFT, + renderStates: hudRayStates, + enabled: true +}); + +// NOTE: keep this offset in sync with scripts/system/librarires/controllers.js:57 +var VERTICAL_HEAD_LASER_OFFSET = 0.1; +var hudRayHead = LaserPointers.createLaserPointer({ + joint: "Avatar", + filter: RayPick.PICK_HUD, + posOffset: {x: 0, y: VERTICAL_HEAD_LASER_OFFSET, z: 0}, + renderStates: hudRayStates, + enabled: true +}); + +var mouseRayPick = RayPick.createRayPick({ + joint: "Mouse", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS, + enabled: true +}); + function isPointingAtOverlay(optionalHudPosition2d) { return Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(optionalHudPosition2d || Reticle.position); } @@ -166,10 +243,21 @@ function isPointingAtOverlay(optionalHudPosition2d) { // Generalized HUD utilities, with or without HMD: // This "var" is for documentation. Do not change the value! var PLANAR_PERPENDICULAR_HUD_DISTANCE = 1; -function calculateRayUICollisionPoint(position, direction) { +function calculateRayUICollisionPoint(position, direction, isHands) { // Answer the 3D intersection of the HUD by the given ray, or falsey if no intersection. if (HMD.active) { - return HMD.calculateRayUICollisionPoint(position, direction); + var laserPointer; + if (isHands) { + laserPointer = activeHand == Controller.Standard.RightHand ? hudRayRight : hudRayLeft; + } else { + laserPointer = hudRayHead; + } + var result = LaserPointers.getPrevRayPickResult(laserPointer); + if (result.type != RayPick.INTERSECTED_NONE) { + return result.intersection; + } else { + return null; + } } // interect HUD plane, 1m in front of camera, using formula: // scale = hudNormal dot (hudPoint - position) / hudNormal dot direction @@ -215,7 +303,7 @@ function activeHudPoint2dGamePad() { var headPosition = MyAvatar.getHeadPosition(); var headDirection = Quat.getUp(Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, { x: 1, y: 0, z: 0 }))); - var hudPoint3d = calculateRayUICollisionPoint(headPosition, headDirection); + var hudPoint3d = calculateRayUICollisionPoint(headPosition, headDirection, false); if (!hudPoint3d) { if (Menu.isOptionChecked("Overlays")) { // With our hud resetting strategy, hudPoint3d should be valid here @@ -241,7 +329,7 @@ function activeHudPoint2d(activeHand) { // if controller is valid, update reticl var controllerPosition = controllerPose.position; var controllerDirection = Quat.getUp(controllerPose.rotation); - var hudPoint3d = calculateRayUICollisionPoint(controllerPosition, controllerDirection); + var hudPoint3d = calculateRayUICollisionPoint(controllerPosition, controllerDirection, true); if (!hudPoint3d) { if (Menu.isOptionChecked("Overlays")) { // With our hud resetting strategy, hudPoint3d should be valid here print('Controller is parallel to HUD'); // so let us know that our assumptions are wrong. @@ -355,7 +443,7 @@ function onMouseMove() { if (isPointingAtOverlay()) { Reticle.depth = hudReticleDistance(); } else { - var result = findRayIntersection(Camera.computePickRay(Reticle.position.x, Reticle.position.y)); + var result = RayPick.getPrevRayPickResult(mouseRayPick); Reticle.depth = result.intersects ? result.distance : APPARENT_MAXIMUM_DEPTH; } } @@ -473,14 +561,6 @@ clickMapping.from(rightTrigger.partial).to(makeToggleAction(Controller.Standard. clickMapping.from(leftTrigger.partial).to(makeToggleAction(Controller.Standard.LeftHand)); clickMapping.enable(); -// VISUAL AID ----------- -// Same properties as handControllerGrab search sphere -var LASER_ALPHA = 0.5; -var LASER_SEARCH_COLOR_XYZW = {x: 10 / 255, y: 10 / 255, z: 255 / 255, w: LASER_ALPHA}; -var LASER_TRIGGER_COLOR_XYZW = {x: 250 / 255, y: 10 / 255, z: 10 / 255, w: LASER_ALPHA}; -var SYSTEM_LASER_DIRECTION = {x: 0, y: 0, z: -1}; -var systemLaserOn = false; - var HIFI_POINTER_DISABLE_MESSAGE_CHANNEL = "Hifi-Pointer-Disable"; var isPointerEnabled = true; @@ -488,23 +568,34 @@ function clearSystemLaser() { if (!systemLaserOn) { return; } - HMD.disableHandLasers(BOTH_HUD_LASERS); - HMD.disableExtraLaser(); + HMD.deactivateHMDHandMouse(); + LaserPointers.setRenderState(hudRayRight, ""); + LaserPointers.setRenderState(hudRayLeft, ""); + LaserPointers.setRenderState(hudRayHead, ""); systemLaserOn = false; weMovedReticle = true; } function setColoredLaser() { // answer trigger state if lasers supported, else falsey. - var color = (activeTrigger.state === 'full') ? LASER_TRIGGER_COLOR_XYZW : LASER_SEARCH_COLOR_XYZW; + var mode = (activeTrigger.state === 'full') ? 'trigger' : 'search'; - if (!HMD.isHandControllerAvailable()) { - // NOTE: keep this offset in sync with scripts/system/librarires/controllers.js:57 - var VERTICAL_HEAD_LASER_OFFSET = 0.1; - var position = Vec3.sum(HMD.position, Vec3.multiplyQbyV(HMD.orientation, {x: 0, y: VERTICAL_HEAD_LASER_OFFSET, z: 0})); - var orientation = Quat.multiply(HMD.orientation, Quat.angleAxis(-90, { x: 1, y: 0, z: 0 })); - return HMD.setExtraLaser(position, true, color, Quat.getUp(orientation)); + if (!systemLaserOn) { + HMD.activateHMDHandMouse(); } - return HMD.setHandLasers(activeHudLaser, true, color, SYSTEM_LASER_DIRECTION) && activeTrigger.state; + var pose = Controller.getPoseValue(activeHand); + if (!pose.valid) { + LaserPointers.setRenderState(hudRayRight, ""); + LaserPointers.setRenderState(hudRayLeft, ""); + LaserPointers.setRenderState(hudRayHead, mode); + return true; + } + + var right = activeHand == Controller.Standard.RightHand; + LaserPointers.setRenderState(hudRayRight, right ? mode : ""); + LaserPointers.setRenderState(hudRayLeft, right ? "" : mode); + LaserPointers.setRenderState(hudRayHead, ""); + + return activeTrigger.state; } // MAIN OPERATIONS ----------- @@ -551,11 +642,13 @@ function update() { if (HMD.active) { Reticle.depth = hudReticleDistance(); - if (!HMD.isHandControllerAvailable()) { - var color = (activeTrigger.state === 'full') ? LASER_TRIGGER_COLOR_XYZW : LASER_SEARCH_COLOR_XYZW; - var position = MyAvatar.getHeadPosition(); - var direction = Quat.getUp(Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, { x: 1, y: 0, z: 0 }))); - HMD.setExtraLaser(position, true, color, direction); + var pose = Controller.getPoseValue(activeHand); + if (!pose.valid) { + var mode = (activeTrigger.state === 'full') ? 'trigger' : 'search'; + if (!systemLaserOn) { + HMD.activateHMDHandMouse(); + } + LaserPointers.setRenderState(hudRayHead, mode); } } @@ -604,6 +697,10 @@ Script.scriptEnding.connect(function () { Script.clearInterval(settingsChecker); Script.update.disconnect(update); OffscreenFlags.navigationFocusDisabled = false; + LaserPointers.removeLaserPointer(hudRayRight); + LaserPointers.removeLaserPointer(hudRayLeft); + LaserPointers.removeLaserPointer(hudRayHead); + HMD.deactivateHMDHandMouse(); }); }()); // END LOCAL_SCOPE diff --git a/scripts/system/controllers/teleport.js b/scripts/system/controllers/teleport.js index 15ba314a1d..17ca2f91c5 100644 --- a/scripts/system/controllers/teleport.js +++ b/scripts/system/controllers/teleport.js @@ -37,12 +37,6 @@ var COLORS_TELEPORT_CAN_TELEPORT = { blue: 255 }; -var COLORS_TELEPORT_CANNOT_TELEPORT = { - red: 0, - green: 121, - blue: 141 -}; - var COLORS_TELEPORT_CANCEL = { red: 255, green: 184, @@ -61,6 +55,59 @@ var handInfo = { } }; +var cancelPath = { + type: "line3d", + color: COLORS_TELEPORT_CANCEL, + ignoreRayIntersection: true, + alpha: 1, + solid: true, + drawInFront: true, + glow: 1.0 +}; +var teleportPath = { + type: "line3d", + color: COLORS_TELEPORT_CAN_TELEPORT, + ignoreRayIntersection: true, + alpha: 1, + solid: true, + drawInFront: true, + glow: 1.0 +}; +var seatPath = { + type: "line3d", + color: COLORS_TELEPORT_SEAT, + ignoreRayIntersection: true, + alpha: 1, + solid: true, + drawInFront: true, + glow: 1.0 +}; +var cancelEnd = { + type: "model", + url: TOO_CLOSE_MODEL_URL, + dimensions: TARGET_MODEL_DIMENSIONS, + ignoreRayIntersection: true +}; +var teleportEnd = { + type: "model", + url: TARGET_MODEL_URL, + dimensions: TARGET_MODEL_DIMENSIONS, + ignoreRayIntersection: true +}; +var seatEnd = { + type: "model", + url: SEAT_MODEL_URL, + dimensions: TARGET_MODEL_DIMENSIONS, + ignoreRayIntersection: true +} + +var teleportRenderStates = [{name: "cancel", path: cancelPath, end: cancelEnd}, + {name: "teleport", path: teleportPath, end: teleportEnd}, + {name: "seat", path: seatPath, end: seatEnd}]; + +var DEFAULT_DISTANCE = 50; +var teleportDefaultRenderStates = [{name: "cancel", distance: DEFAULT_DISTANCE, path: cancelPath}]; + function ThumbPad(hand) { this.hand = hand; var _thisPad = this; @@ -108,33 +155,59 @@ function Teleporter() { this.state = TELEPORTER_STATES.IDLE; this.currentTarget = TARGET.INVALID; - this.overlayLines = { - left: null, - right: null, - }; + this.teleportRayLeftVisible = LaserPointers.createLaserPointer({ + joint: "LeftHand", + filter: RayPick.PICK_ENTITIES, + faceAvatar: true, + centerEndY: false, + renderStates: teleportRenderStates, + defaultRenderStates: teleportDefaultRenderStates + }); + this.teleportRayLeftInvisible = LaserPointers.createLaserPointer({ + joint: "LeftHand", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_INCLUDE_INVISIBLE, + faceAvatar: true, + centerEndY: false, + renderStates: teleportRenderStates + }); + this.teleportRayRightVisible = LaserPointers.createLaserPointer({ + joint: "RightHand", + filter: RayPick.PICK_ENTITIES, + faceAvatar: true, + centerEndY: false, + renderStates: teleportRenderStates, + defaultRenderStates: teleportDefaultRenderStates + }); + this.teleportRayRightInvisible = LaserPointers.createLaserPointer({ + joint: "RightHand", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_INCLUDE_INVISIBLE, + faceAvatar: true, + centerEndY: false, + renderStates: teleportRenderStates + }); + + this.teleportRayHeadVisible = LaserPointers.createLaserPointer({ + joint: "Avatar", + filter: RayPick.PICK_ENTITIES, + faceAvatar: true, + centerEndY: false, + renderStates: teleportRenderStates, + defaultRenderStates: teleportDefaultRenderStates + }); + this.teleportRayHeadInvisible = LaserPointers.createLaserPointer({ + joint: "Avatar", + filter: RayPick.PICK_ENTITIES | RayPick.PICK_INCLUDE_INVISIBLE, + faceAvatar: true, + centerEndY: false, + renderStates: teleportRenderStates + }); + this.updateConnected = null; this.activeHand = null; this.teleporterMappingInternalName = 'Hifi-Teleporter-Internal-Dev-' + Math.random(); this.teleportMappingInternal = Controller.newMapping(this.teleporterMappingInternalName); - // Setup overlays - this.cancelOverlay = Overlays.addOverlay("model", { - url: TOO_CLOSE_MODEL_URL, - dimensions: TARGET_MODEL_DIMENSIONS, - visible: false - }); - this.targetOverlay = Overlays.addOverlay("model", { - url: TARGET_MODEL_URL, - dimensions: TARGET_MODEL_DIMENSIONS, - visible: false - }); - this.seatOverlay = Overlays.addOverlay("model", { - url: SEAT_MODEL_URL, - dimensions: TARGET_MODEL_DIMENSIONS, - visible: false - }); - this.enableMappings = function() { Controller.enableMapping(this.teleporterMappingInternalName); }; @@ -146,16 +219,13 @@ function Teleporter() { this.cleanup = function() { this.disableMappings(); - Overlays.deleteOverlay(this.targetOverlay); - this.targetOverlay = null; + LaserPointers.removeLaserPointer(this.teleportRayLeftVisible); + LaserPointers.removeLaserPointer(this.teleportRayLeftInvisible); + LaserPointers.removeLaserPointer(this.teleportRayRightVisible); + LaserPointers.removeLaserPointer(this.teleportRayRightInvisible); + LaserPointers.removeLaserPointer(this.teleportRayHeadVisible); + LaserPointers.removeLaserPointer(this.teleportRayHeadInvisible); - Overlays.deleteOverlay(this.cancelOverlay); - this.cancelOverlay = null; - - Overlays.deleteOverlay(this.seatOverlay); - this.seatOverlay = null; - - this.deleteOverlayBeams(); if (this.updateConnected === true) { Script.update.disconnect(this, this.update); } @@ -194,43 +264,47 @@ function Teleporter() { } this.disableMappings(); - this.deleteOverlayBeams(); - this.hideTargetOverlay(); - this.hideCancelOverlay(); + LaserPointers.disableLaserPointer(this.teleportRayLeftVisible); + LaserPointers.disableLaserPointer(this.teleportRayLeftInvisible); + LaserPointers.disableLaserPointer(this.teleportRayRightVisible); + LaserPointers.disableLaserPointer(this.teleportRayRightInvisible); + LaserPointers.disableLaserPointer(this.teleportRayHeadVisible); + LaserPointers.disableLaserPointer(this.teleportRayHeadInvisible); this.updateConnected = null; this.state = TELEPORTER_STATES.IDLE; inTeleportMode = false; }; - this.deleteOverlayBeams = function() { - for (var key in this.overlayLines) { - if (this.overlayLines[key] !== null) { - Overlays.deleteOverlay(this.overlayLines[key]); - this.overlayLines[key] = null; - } - } - }; - this.update = function() { if (_this.state === TELEPORTER_STATES.IDLE) { return; } - // Get current hand pose information so that we can get the direction of the teleport beam + // Get current hand pose information to see if the pose is valid var pose = Controller.getPoseValue(handInfo[_this.activeHand].controllerInput); - var handPosition = pose.valid ? Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation), MyAvatar.position) : MyAvatar.getHeadPosition(); - var handRotation = pose.valid ? Quat.multiply(MyAvatar.orientation, pose.rotation) : - Quat.multiply(MyAvatar.headOrientation, Quat.angleAxis(-90, { - x: 1, - y: 0, - z: 0 - })); - - var pickRay = { - origin: handPosition, - direction: Quat.getUp(handRotation), - }; + var mode = pose.valid ? _this.activeHand : 'head'; + if (!pose.valid) { + if (mode === 'right') { + LaserPointers.disableLaserPointer(_this.teleportRayRightVisible); + LaserPointers.disableLaserPointer(_this.teleportRayRightInvisible); + } else { + LaserPointers.disableLaserPointer(_this.teleportRayLeftVisible); + LaserPointers.disableLaserPointer(_this.teleportRayLeftInvisible); + } + LaserPointers.enableLaserPointer(_this.teleportRayHeadVisible); + LaserPointers.enableLaserPointer(_this.teleportRayHeadInvisible); + } else { + if (mode === 'right') { + LaserPointers.enableLaserPointer(_this.teleportRayRightVisible); + LaserPointers.enableLaserPointer(_this.teleportRayRightInvisible); + } else { + LaserPointers.enableLaserPointer(_this.teleportRayLeftVisible); + LaserPointers.enableLaserPointer(_this.teleportRayLeftInvisible); + } + LaserPointers.disableLaserPointer(_this.teleportRayHeadVisible); + LaserPointers.disableLaserPointer(_this.teleportRayHeadInvisible); + } // We do up to 2 ray picks to find a teleport location. // There are 2 types of teleport locations we are interested in: @@ -241,48 +315,40 @@ function Teleporter() { // We might hit an invisible entity that is not a seat, so we need to do a second pass. // * In the second pass we pick against visible entities only. // - var intersection = Entities.findRayIntersection(pickRay, true, [], [this.targetEntity].concat(ignoredEntities), false, true); + var result; + if (mode === 'right') { + result = LaserPointers.getPrevRayPickResult(_this.teleportRayRightInvisible); + } else if (mode === 'left') { + result = LaserPointers.getPrevRayPickResult(_this.teleportRayLeftInvisible); + } else { + result = LaserPointers.getPrevRayPickResult(_this.teleportRayHeadInvisible); + } - var teleportLocationType = getTeleportTargetType(intersection); + var teleportLocationType = getTeleportTargetType(result); if (teleportLocationType === TARGET.INVISIBLE) { - intersection = Entities.findRayIntersection(pickRay, true, [], [this.targetEntity].concat(ignoredEntities), true, true); - teleportLocationType = getTeleportTargetType(intersection); + if (mode === 'right') { + result = LaserPointers.getPrevRayPickResult(_this.teleportRayRightVisible); + } else if (mode === 'left') { + result = LaserPointers.getPrevRayPickResult(_this.teleportRayLeftVisible); + } else { + result = LaserPointers.getPrevRayPickResult(_this.teleportRayHeadVisible); + } + teleportLocationType = getTeleportTargetType(result); } if (teleportLocationType === TARGET.NONE) { - this.hideTargetOverlay(); - this.hideCancelOverlay(); - this.hideSeatOverlay(); - - var farPosition = Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, 50)); - this.updateLineOverlay(_this.activeHand, pickRay.origin, farPosition, COLORS_TELEPORT_CANNOT_TELEPORT); + // Use the cancel default state + this.setTeleportState(mode, "cancel", ""); } else if (teleportLocationType === TARGET.INVALID || teleportLocationType === TARGET.INVISIBLE) { - this.hideTargetOverlay(); - this.hideSeatOverlay(); - - this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_CANCEL); - this.updateDestinationOverlay(this.cancelOverlay, intersection); + this.setTeleportState(mode, "", "cancel"); } else if (teleportLocationType === TARGET.SURFACE) { if (this.state === TELEPORTER_STATES.COOL_IN) { - this.hideTargetOverlay(); - this.hideSeatOverlay(); - - this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_CANCEL); - this.updateDestinationOverlay(this.cancelOverlay, intersection); + this.setTeleportState(mode, "cancel", ""); } else { - this.hideCancelOverlay(); - this.hideSeatOverlay(); - - this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, - COLORS_TELEPORT_CAN_TELEPORT); - this.updateDestinationOverlay(this.targetOverlay, intersection); + this.setTeleportState(mode, "teleport", ""); } } else if (teleportLocationType === TARGET.SEAT) { - this.hideCancelOverlay(); - this.hideTargetOverlay(); - - this.updateLineOverlay(_this.activeHand, pickRay.origin, intersection.intersection, COLORS_TELEPORT_SEAT); - this.updateDestinationOverlay(this.seatOverlay, intersection); + this.setTeleportState(mode, "", "seat"); } @@ -290,79 +356,33 @@ function Teleporter() { // remember the state before we exit teleport mode and set it back to IDLE var previousState = this.state; this.exitTeleportMode(); - this.hideCancelOverlay(); - this.hideTargetOverlay(); - this.hideSeatOverlay(); if (teleportLocationType === TARGET.NONE || teleportLocationType === TARGET.INVALID || previousState === TELEPORTER_STATES.COOL_IN) { // Do nothing } else if (teleportLocationType === TARGET.SEAT) { - Entities.callEntityMethod(intersection.entityID, 'sit'); + Entities.callEntityMethod(result.objectID, 'sit'); } else if (teleportLocationType === TARGET.SURFACE) { var offset = getAvatarFootOffset(); - intersection.intersection.y += offset; - MyAvatar.goToLocation(intersection.intersection, false, {x: 0, y: 0, z: 0, w: 1}, false); + result.intersection.y += offset; + MyAvatar.goToLocation(result.intersection, false, {x: 0, y: 0, z: 0, w: 1}, false); HMD.centerUI(); MyAvatar.centerBody(); } } }; - this.updateLineOverlay = function(hand, closePoint, farPoint, color) { - if (this.overlayLines[hand] === null) { - var lineProperties = { - start: closePoint, - end: farPoint, - color: color, - ignoreRayIntersection: true, - visible: true, - alpha: 1, - solid: true, - drawInFront: true, - glow: 1.0 - }; - - this.overlayLines[hand] = Overlays.addOverlay("line3d", lineProperties); - + this.setTeleportState = function(mode, visibleState, invisibleState) { + if (mode === 'right') { + LaserPointers.setRenderState(_this.teleportRayRightVisible, visibleState); + LaserPointers.setRenderState(_this.teleportRayRightInvisible, invisibleState); + } else if (mode === 'left') { + LaserPointers.setRenderState(_this.teleportRayLeftVisible, visibleState); + LaserPointers.setRenderState(_this.teleportRayLeftInvisible, invisibleState); } else { - Overlays.editOverlay(this.overlayLines[hand], { - start: closePoint, - end: farPoint, - color: color - }); + LaserPointers.setRenderState(_this.teleportRayHeadVisible, visibleState); + LaserPointers.setRenderState(_this.teleportRayHeadInvisible, invisibleState); } }; - - this.hideCancelOverlay = function() { - Overlays.editOverlay(this.cancelOverlay, { visible: false }); - }; - - this.hideTargetOverlay = function() { - Overlays.editOverlay(this.targetOverlay, { visible: false }); - }; - - this.hideSeatOverlay = function() { - Overlays.editOverlay(this.seatOverlay, { visible: false }); - }; - - this.updateDestinationOverlay = function(overlayID, intersection) { - var rotation = Quat.lookAt(intersection.intersection, MyAvatar.position, Vec3.UP); - var euler = Quat.safeEulerAngles(rotation); - var position = { - x: intersection.intersection.x, - y: intersection.intersection.y + TARGET_MODEL_DIMENSIONS.y / 2, - z: intersection.intersection.z - }; - - var towardUs = Quat.fromPitchYawRollDegrees(0, euler.y, 0); - - Overlays.editOverlay(overlayID, { - visible: true, - position: position, - rotation: towardUs - }); - - }; } // related to repositioning the avatar after you teleport @@ -424,12 +444,12 @@ function parseJSON(json) { // than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from <0, 1, 0> (straight up), then // you can't teleport there. var MAX_ANGLE_FROM_UP_TO_TELEPORT = 70; -function getTeleportTargetType(intersection) { - if (!intersection.intersects) { +function getTeleportTargetType(result) { + if (result.type == RayPick.INTERSECTED_NONE) { return TARGET.NONE; } - var props = Entities.getEntityProperties(intersection.entityID, ['userData', 'visible']); + var props = Entities.getEntityProperties(result.objectID, ['userData', 'visible']); var data = parseJSON(props.userData); if (data !== undefined && data.seat !== undefined) { var avatarUuid = Uuid.fromString(data.seat.user); @@ -444,13 +464,13 @@ function getTeleportTargetType(intersection) { return TARGET.INVISIBLE; } - var surfaceNormal = intersection.surfaceNormal; + var surfaceNormal = result.surfaceNormal; var adj = Math.sqrt(surfaceNormal.x * surfaceNormal.x + surfaceNormal.z * surfaceNormal.z); var angleUp = Math.atan2(surfaceNormal.y, adj) * (180 / Math.PI); if (angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT) || angleUp > (90 + MAX_ANGLE_FROM_UP_TO_TELEPORT) || - Vec3.distance(MyAvatar.position, intersection.intersection) <= TELEPORT_CANCEL_RANGE) { + Vec3.distance(MyAvatar.position, result.intersection) <= TELEPORT_CANCEL_RANGE) { return TARGET.INVALID; } else { return TARGET.SURFACE; @@ -509,6 +529,15 @@ function cleanup() { } Script.scriptEnding.connect(cleanup); +var setIgnoreEntities = function() { + LaserPointers.setIgnoreEntities(teleporter.teleportRayRightVisible, ignoredEntities); + LaserPointers.setIgnoreEntities(teleporter.teleportRayRightInvisible, ignoredEntities); + LaserPointers.setIgnoreEntities(teleporter.teleportRayLeftVisible, ignoredEntities); + LaserPointers.setIgnoreEntities(teleporter.teleportRayLeftInvisible, ignoredEntities); + LaserPointers.setIgnoreEntities(teleporter.teleportRayHeadVisible, ignoredEntities); + LaserPointers.setIgnoreEntities(teleporter.teleportRayHeadInvisible, ignoredEntities); +} + var isDisabled = false; var handleTeleportMessages = function(channel, message, sender) { if (sender === MyAvatar.sessionUUID) { @@ -527,10 +556,12 @@ var handleTeleportMessages = function(channel, message, sender) { } } else if (channel === 'Hifi-Teleport-Ignore-Add' && !Uuid.isNull(message) && ignoredEntities.indexOf(message) === -1) { ignoredEntities.push(message); + setIgnoreEntities(); } else if (channel === 'Hifi-Teleport-Ignore-Remove' && !Uuid.isNull(message)) { var removeIndex = ignoredEntities.indexOf(message); if (removeIndex > -1) { ignoredEntities.splice(removeIndex, 1); + setIgnoreEntities(); } } } diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 24a8b2fee0..f94d339f84 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -133,20 +133,20 @@ -
- - @@ -681,4 +681,4 @@ - \ No newline at end of file + diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 80c8f8a0e4..77be746bf4 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -81,6 +81,7 @@ letUsKnow.replaceWith(letUsKnow.html()); // Add button links. + $('#exploreClaraMarketplace').on('click', function () { window.location = "https://clara.io/library?gameCheck=true&public=true"; }); @@ -114,7 +115,7 @@ itemId: id, itemName: name, itemAuthor: author, - itemPrice: Math.round(Math.random() * 50), + itemPrice: price ? parseInt(price, 10) : Math.round(Math.random() * 50), itemHref: href })); } @@ -129,7 +130,7 @@ buyButtonClicked($(this).closest('.grid-item').attr('data-item-id'), $(this).closest('.grid-item').find('.item-title').text(), $(this).closest('.grid-item').find('.creator').find('.value').text(), - 10, + $(this).closest('.grid-item').find('.item-cost').text(), $(this).attr('data-href')); }); } @@ -165,7 +166,7 @@ buyButtonClicked(window.location.pathname.split("/")[3], $('#top-center').find('h1').text(), $('#creator').find('.value').text(), - 10, + $('.item-cost').text(), href); }); addInventoryButton(); @@ -221,7 +222,7 @@ // One file request at a time. if (isPreparing) { - console.log("WARNIKNG: Clara.io FBX: Prepare only one download at a time"); + console.log("WARNING: Clara.io FBX: Prepare only one download at a time"); return; } @@ -397,8 +398,8 @@ var pageType = DIRECTORY; if (location.href.indexOf("highfidelity.com/") !== -1) { pageType = HIFI; } - if (location.href.indexOf("highfidelity.com/marketplace/items/") !== -1) { pageType = HIFI_ITEM_PAGE; } if (location.href.indexOf("clara.io/") !== -1) { pageType = CLARA; } + if (location.href.indexOf("highfidelity.com/marketplace/items/") !== -1) { pageType = HIFI_ITEM_PAGE; } injectCommonCode(pageType === DIRECTORY); switch (pageType) { @@ -408,12 +409,13 @@ case HIFI: injectHiFiCode(); break; - case HIFI_ITEM_PAGE: - injectHiFiItemPageCode(); - break; case CLARA: injectClaraCode(); break; + case HIFI_ITEM_PAGE: + injectHiFiItemPageCode(); + break; + } } diff --git a/scripts/system/particle_explorer/particleExplorer.js b/scripts/system/particle_explorer/particleExplorer.js index ca6a873b73..057bd1dd85 100644 --- a/scripts/system/particle_explorer/particleExplorer.js +++ b/scripts/system/particle_explorer/particleExplorer.js @@ -178,8 +178,8 @@ type: "Row" }, { - id: "emitShouldTrail", - name: "Emit Should Trail", + id: "emitterShouldTrail", + name: "Emitter Should Trail", type: "Boolean" }, { diff --git a/unpublishedScripts/marketplace/blocks/blocksApp.js b/unpublishedScripts/marketplace/blocks/blocksApp.js new file mode 100644 index 0000000000..2c20e13005 --- /dev/null +++ b/unpublishedScripts/marketplace/blocks/blocksApp.js @@ -0,0 +1,43 @@ +/// +/// blocksApp.js +/// A tablet app for downloading 3D assets from Google Blocks +/// +/// Author: Elisa Lupin-Jimenez +/// Copyright High Fidelity 2017 +/// +/// Licensed under the Apache 2.0 License +/// See accompanying license file or http://apache.org/ +/// +/// All assets are under CC Attribution Non-Commerical +/// http://creativecommons.org/licenses/ +/// + +(function () { + var APP_NAME = "BLOCKS"; + var APP_URL = "https://vr.google.com/objects/"; + var APP_ICON = "https://hifi-content.s3.amazonaws.com/elisalj/blocks/blocks-i.svg"; + + try { + print("Current Interface version: " + Window.checkVersion()); + } catch(err) { + print("Outdated Interface version does not support Blocks"); + APP_URL = "https://hifi-content.s3.amazonaws.com/elisalj/blocks/updateToBlocks.html"; + } + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + icon: APP_ICON, + text: APP_NAME + }); + + function onClicked() { + tablet.gotoWebScreen(APP_URL, "", true); + } + button.clicked.connect(onClicked); + + function cleanup() { + tablet.removeButton(button); + } + + Script.scriptEnding.connect(cleanup); +}()); diff --git a/unpublishedScripts/marketplace/emoji-tablet/emojiTablet.js b/unpublishedScripts/marketplace/emoji-tablet/emojiTablet.js new file mode 100644 index 0000000000..b4d01e77cf --- /dev/null +++ b/unpublishedScripts/marketplace/emoji-tablet/emojiTablet.js @@ -0,0 +1,72 @@ +/// +/// emojiTablet.js +/// A tablet app for sending emojis to other users +/// +/// Author: Elisa Lupin-Jimenez +/// Copyright High Fidelity 2017 +/// +/// Licensed under the Apache 2.0 License +/// See accompanying license file or http://apache.org/ +/// +/// All assets are under CC Attribution Non-Commerical +/// http://creativecommons.org/licenses/ +/// + +var lib = Script.require("https://hifi-content.s3.amazonaws.com/elisalj/emoji_scripts/emojiLib.js?" + Date.now()); + +(function() { + + var APP_NAME = "EMOJIS"; + var APP_URL = "https://hifi-content.s3.amazonaws.com/elisalj/emoji_scripts/emojiTabletUI.html?" + Date.now(); + var APP_ICON = "https://hifi-content.s3.amazonaws.com/elisalj/emoji_scripts/icons/emoji-i.svg"; + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + + var button = tablet.addButton({ + icon: APP_ICON, + text: APP_NAME + }); + + // Activates tablet UI when selected from menu + function onClicked() { + tablet.gotoWebScreen(APP_URL); + }; + button.clicked.connect(onClicked); + + // Gives position right in front of user's avatar + function getPositionToCreateEntity() { + var direction = Quat.getFront(MyAvatar.orientation); + var distance = 0.3; + var position = Vec3.sum(MyAvatar.position, Vec3.multiply(direction, distance)); + position.y += 0.5; + return position; + }; + + var emojiJSON = null; + + // Handles emoji button clicks to retrieve the link to the emoji JSON from emojiLib + function onWebEventReceived(event) { + var emojiName = (JSON.parse(event)).data; + var url = lib.getEmoji(emojiName, lib.emojiLib); + if (url != null) { + emojiJSON = Script.require(url); + create3DEmoji(emojiJSON, null); + } else { + print("Unable to create emoji"); + } + }; + tablet.webEventReceived.connect(onWebEventReceived); + + function create3DEmoji(emojiJSON, userName) { + print("Creating " + emojiJSON.name + " emoji"); + emojiJSON.position = getPositionToCreateEntity(emojiJSON.personified); + var newEmoji = Entities.addEntity(emojiJSON); + }; + + // When tablet UI is closed and app is removed from menu + function cleanup() { + tablet.removeButton(button); + }; + Script.scriptEnding.connect(cleanup); + +}()); +