diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 5836d5bfb5..90d2995e7d 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -39,7 +39,7 @@ Go to `Control Panel > System > Advanced System Settings > Environment Variables ### Step 6. Installing OpenSSL via vcpkg - * In the vcpkg directory, install the 64 bit OpenSSL package with the command `vcpkg install openssl:x64-windows` + * In the vcpkg directory, install the 64 bit OpenSSL package with the command `.\vcpkg install openssl:x64-windows` * Once the build completes you should have a file `ssl.h` in `${VCPKG_ROOT}/installed/x64-windows/include/openssl` ### Step 7. Running CMake to Generate Build Files diff --git a/INSTALL.md b/INSTALL.md index 90e8712b19..00be5f2f8f 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -21,6 +21,7 @@ To produce an executable installer on Windows, the following are required: - [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0 - [nsisSlideshow Plug-in for Nullsoft](http://nsis.sourceforge.net/NsisSlideshow_plug-in) - 1.7 - [Nsisunz plug-in for Nullsoft](http://nsis.sourceforge.net/Nsisunz_plug-in) +- [ApplicationID plug-in for Nullsoft](http://nsis.sourceforge.net/ApplicationID_plug-in) - 1.0 Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System. diff --git a/README.md b/README.md index e0bbed3105..363064964a 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ High Fidelity (hifi) is an early-stage technology lab experimenting with Virtual Worlds and VR. -In this repository you'll find the source to many of the components in our -alpha-stage virtual world. The project embraces distributed development -and if you'd like to help, we'll pay you -- find out more at [Worklist.net](https://worklist.net). +This repository contains the source to many of the components in our +alpha-stage virtual world. The project embraces distributed development. +If you'd like to help, we'll pay you -- find out more at [Worklist.net](https://worklist.net). If you find a small bug and have a fix, pull requests are welcome. If you'd like to get paid for your work, make sure you report the bug via a job on [Worklist.net](https://worklist.net). @@ -32,9 +32,10 @@ Running Interface When you launch interface, you will automatically connect to our default domain: "root.highfidelity.io". If you don't see anything, make sure your preferences are pointing to -root.highfidelity.io (set your domain via Cmnd+D/Cntrl+D), if you still have no luck it's possible our servers are -simply down; if you're experiencing a major bug, let us know by adding an issue to this repository. -Make sure to include details about your computer and how to reproduce the bug. +root.highfidelity.io (set your domain via Cmnd+D/Cntrl+D). If you still have no luck, +it's possible our servers are down. If you're experiencing a major bug, let us know by +adding an issue to this repository. Include details about your computer and how to +reproduce the bug in your issue. To move around in-world, use the arrow keys (and Shift + up/down to fly up or down) or W A S D, and E or C to fly up/down. All of the other possible options @@ -48,7 +49,8 @@ you to run the full stack of the virtual world. In order to set up your own virtual world, you need to set up and run your own local "domain". -The domain-server gives a number different types of assignments to the assignment-client for different features: audio, avatars, voxels, particles, meta-voxels and models. +The domain-server gives a number different types of assignments to the assignment-client +for different features: audio, avatars, voxels, particles, meta-voxels and models. Follow the instructions in the [build guide](BUILD.md) to build the various components. @@ -56,7 +58,8 @@ From the domain-server build directory, launch a domain-server. ./domain-server -Then, run an assignment-client. The assignment-client uses localhost as its assignment-server and talks to it on port 40102 (the default domain-server port). +Then, run an assignment-client. The assignment-client uses localhost as its assignment-server +and talks to it on port 40102 (the default domain-server port). In a new Terminal window, run: @@ -64,13 +67,20 @@ In a new Terminal window, run: Any target can be terminated with Ctrl-C (SIGINT) in the associated Terminal window. -This assignment-client will grab one assignment from the domain-server. You can tell the assignment-client what type you want it to be with the `-t` option. You can also run an assignment-client that forks off *n* assignment-clients with the `-n` option. The `-min` and `-max` options allow you to set a range of required assignment-clients, this allows you to have flexibility in the number of assignment-clients that are running. See `--help` for more options. +This assignment-client will grab one assignment from the domain-server. You can tell the +assignment-client what type you want it to be with the `-t` option. You can also run an +assignment-client that forks off *n* assignment-clients with the `-n` option. The `-min` +and `-max` options allow you to set a range of required assignment-clients. This allows +you to have flexibility in the number of assignment-clients that are running. +See `--help` for more options. ./assignment-client --min 6 --max 20 -To test things out you'll want to run the Interface client. +To test things out, you'll need to run the Interface client. -To access your local domain in Interface, open your Preferences -- on OS X this is available in the Interface menu, on Linux you'll find it in the File menu. Enter "localhost" in the "Domain server" field. +To access your local domain in Interface, open your Preferences. On OS X, this is available +in the Interface menu. On Linux, you'll find it in the File menu. Enter "localhost" in the +"Domain server" field. -If everything worked you should see that you are connected to at least one server. +If everything worked, you should see that you are connected to at least one server. Nice work! diff --git a/android/app/build.gradle b/android/app/build.gradle index 699008092c..d5058a7f40 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,15 +1,20 @@ +import org.apache.tools.ant.taskdefs.condition.Os + apply plugin: 'com.android.application' android { compileSdkVersion 26 //buildToolsVersion '27.0.3' + def appVersionCode = Integer.valueOf(VERSION_CODE ?: 1) + def appVersionName = RELEASE_NUMBER ?: "1.0" + defaultConfig { applicationId "io.highfidelity.hifiinterface" minSdkVersion 24 targetSdkVersion 26 - versionCode 1 - versionName "1.0" + versionCode appVersionCode + versionName appVersionName ndk { abiFilters 'arm64-v8a' } externalNativeBuild { cmake { @@ -24,7 +29,8 @@ android { '-DRELEASE_TYPE=' + RELEASE_TYPE, '-DSTABLE_BUILD=' + STABLE_BUILD, '-DDISABLE_QML=OFF', - '-DDISABLE_KTX_CACHE=OFF' + '-DDISABLE_KTX_CACHE=OFF', + '-DUSE_BREAKPAD=' + (System.getenv("CMAKE_BACKTRACE_URL") && System.getenv("CMAKE_BACKTRACE_TOKEN") ? 'ON' : 'OFF'); } } signingConfigs { @@ -43,6 +49,10 @@ android { } buildTypes { + debug { + buildConfigField "String", "BACKTRACE_URL", "\"" + (System.getenv("CMAKE_BACKTRACE_URL") ? System.getenv("CMAKE_BACKTRACE_URL") : '') + "\"" + buildConfigField "String", "BACKTRACE_TOKEN", "\"" + (System.getenv("CMAKE_BACKTRACE_TOKEN") ? System.getenv("CMAKE_BACKTRACE_TOKEN") : '') + "\"" + } release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' @@ -50,6 +60,8 @@ android { project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") && project.hasProperty("HIFI_ANDROID_KEY_ALIAS") && project.hasProperty("HIFI_ANDROID_KEY_PASSWORD")? signingConfigs.release : null + buildConfigField "String", "BACKTRACE_URL", "\"" + (System.getenv("CMAKE_BACKTRACE_URL") ? System.getenv("CMAKE_BACKTRACE_URL") : '') + "\"" + buildConfigField "String", "BACKTRACE_TOKEN", "\"" + (System.getenv("CMAKE_BACKTRACE_TOKEN") ? System.getenv("CMAKE_BACKTRACE_TOKEN") : '') + "\"" } } @@ -64,6 +76,12 @@ android { // so our merge has to depend on the external native build variant.externalNativeBuildTasks.each { task -> variant.mergeResources.dependsOn(task) + if (Os.isFamily(Os.FAMILY_UNIX)) { + def uploadDumpSymsTask = rootProject.getTasksByName("uploadBreakpadDumpSyms${variant.name.capitalize()}", false).first() + def runDumpSymsTask = rootProject.getTasksByName("runBreakpadDumpSyms${variant.name.capitalize()}", false).first() + runDumpSymsTask.dependsOn(task) + variant.assemble.dependsOn(uploadDumpSymsTask) + } } variant.mergeAssets.doLast { diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0b52046057..7255e1f295 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -22,13 +22,15 @@ android:icon="@drawable/ic_launcher" android:launchMode="singleTop" android:roundIcon="@drawable/ic_launcher"> - + + + + + + + diff --git a/interface/resources/icons/tablet-icons/avatar-i.svg b/interface/resources/icons/tablet-icons/avatar-i.svg new file mode 100644 index 0000000000..2d3f80cbf8 --- /dev/null +++ b/interface/resources/icons/tablet-icons/avatar-i.svg @@ -0,0 +1,17 @@ + + + + + + + + + diff --git a/interface/resources/images/FavoriteIconActive.svg b/interface/resources/images/FavoriteIconActive.svg new file mode 100644 index 0000000000..5f03217d27 --- /dev/null +++ b/interface/resources/images/FavoriteIconActive.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/interface/resources/images/FavoriteIconInActive.svg b/interface/resources/images/FavoriteIconInActive.svg new file mode 100644 index 0000000000..7cca31ac66 --- /dev/null +++ b/interface/resources/images/FavoriteIconInActive.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/interface/resources/images/avatarapp/AvatarIsland.jpg b/interface/resources/images/avatarapp/AvatarIsland.jpg new file mode 100644 index 0000000000..f5f649abea Binary files /dev/null and b/interface/resources/images/avatarapp/AvatarIsland.jpg differ diff --git a/interface/resources/images/avatarapp/BodyMart.PNG b/interface/resources/images/avatarapp/BodyMart.PNG new file mode 100644 index 0000000000..c51ca880cb Binary files /dev/null and b/interface/resources/images/avatarapp/BodyMart.PNG differ diff --git a/interface/resources/images/avatarapp/guy-in-circle.svg b/interface/resources/images/avatarapp/guy-in-circle.svg new file mode 100644 index 0000000000..78cbf5186b --- /dev/null +++ b/interface/resources/images/avatarapp/guy-in-circle.svg @@ -0,0 +1,20 @@ + + + + + + + diff --git a/interface/resources/images/buttonBezel.png b/interface/resources/images/buttonBezel.png new file mode 100644 index 0000000000..fe55855462 Binary files /dev/null and b/interface/resources/images/buttonBezel.png differ diff --git a/interface/resources/images/buttonBezel_highlight.png b/interface/resources/images/buttonBezel_highlight.png new file mode 100644 index 0000000000..ab0a99e4c5 Binary files /dev/null and b/interface/resources/images/buttonBezel_highlight.png differ diff --git a/interface/resources/meshes/tablet-with-home-button-small-bezel.fbx b/interface/resources/meshes/tablet-with-home-button-small-bezel.fbx index d94ce30429..9d50c36c15 100644 Binary files a/interface/resources/meshes/tablet-with-home-button-small-bezel.fbx and b/interface/resources/meshes/tablet-with-home-button-small-bezel.fbx differ diff --git a/interface/resources/qml/+android/Web3DOverlay.qml b/interface/resources/qml/+android/Web3DOverlay.qml new file mode 100644 index 0000000000..d7b8306d6c --- /dev/null +++ b/interface/resources/qml/+android/Web3DOverlay.qml @@ -0,0 +1,76 @@ +// +// Web3DOverlay.qml +// +// Created by Gabriel Calero & Cristian Duarte on Jun 22, 2018 +// Copyright 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 +// + +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +Item { + + property string url + RadialGradient { + anchors.fill: parent + gradient: Gradient { + GradientStop { position: 0.0; color: "#262626" } + GradientStop { position: 1.0; color: "#000000" } + } + } + + function shortUrl(url) { + var hostBegin = url.indexOf("://"); + if (hostBegin > -1) { + url = url.substring(hostBegin + 3); + } + + var portBegin = url.indexOf(":"); + if (portBegin > -1) { + url = url.substring(0, portBegin); + } + + var pathBegin = url.indexOf("/"); + if (pathBegin > -1) { + url = url.substring(0, pathBegin); + } + + if (url.length > 45) { + url = url.substring(0, 45); + } + + return url; + } + + Text { + id: urlText + text: shortUrl(url) + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + anchors.fill: parent + anchors.rightMargin: 10 + anchors.leftMargin: 10 + font.family: "Cairo" + font.weight: Font.DemiBold + font.pointSize: 48 + fontSizeMode: Text.Fit + color: "#FFFFFF" + minimumPixelSize: 5 + } + + Image { + id: hand + source: "../../icons/hand.svg" + width: 300 + height: 300 + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.bottomMargin: 100 + anchors.rightMargin: 100 + } + + +} diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index f0475dfebd..4474cfb2cd 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -26,6 +26,7 @@ ScrollingWindow { y: 100 Component.onCompleted: { + focus = true shown = true addressBar.text = webview.url } diff --git a/interface/resources/qml/InteractiveWindow.qml b/interface/resources/qml/InteractiveWindow.qml new file mode 100644 index 0000000000..800026710d --- /dev/null +++ b/interface/resources/qml/InteractiveWindow.qml @@ -0,0 +1,288 @@ +// +// InteractiveWindow.qml +// +// Created by Thijs Wenker on 2018-06-25 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.3 + +import "windows" as Windows +import "controls" +import "controls-uit" as Controls +import "styles" +import "styles-uit" + +Windows.Window { + id: root; + HifiConstants { id: hifi } + title: "InteractiveWindow"; + resizable: true; + // Virtual window visibility + shown: false; + focus: true; + property var channel; + // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer + destroyOnCloseButton: false; + + signal selfDestruct(); + + property var flags: 0; + + property var source; + property var dynamicContent; + property var nativeWindow; + + // custom visibility flag for interactiveWindow to proxy virtualWindow.shown / nativeWindow.visible + property var interactiveWindowVisible: true; + + property point interactiveWindowPosition; + + property size interactiveWindowSize; + + // Keyboard control properties in case needed by QML content. + property bool keyboardEnabled: false; + property bool keyboardRaised: false; + property bool punctuationMode: false; + + property int presentationMode: 0; + + property var initialized: false; + onSourceChanged: { + if (dynamicContent) { + dynamicContent.destroy(); + dynamicContent = null; + } + QmlSurface.load(source, contentHolder, function(newObject) { + dynamicContent = newObject; + if (dynamicContent && dynamicContent.anchors) { + dynamicContent.anchors.fill = contentHolder; + } + }); + } + + function updateInteractiveWindowPositionForMode() { + if (presentationMode === Desktop.PresentationMode.VIRTUAL) { + x = interactiveWindowPosition.x; + y = interactiveWindowPosition.y; + } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) { + if (interactiveWindowPosition.x === 0 && interactiveWindowPosition.y === 0) { + // default position for native window in center of main application window + nativeWindow.x = Math.floor(Window.x + (Window.innerWidth / 2) - (interactiveWindowSize.width / 2)); + nativeWindow.y = Math.floor(Window.y + (Window.innerHeight / 2) - (interactiveWindowSize.height / 2)); + } else { + nativeWindow.x = interactiveWindowPosition.x; + nativeWindow.y = interactiveWindowPosition.y; + } + } + } + + function updateInteractiveWindowSizeForMode() { + if (presentationMode === Desktop.PresentationMode.VIRTUAL) { + width = interactiveWindowSize.width; + height = interactiveWindowSize.height; + } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) { + nativeWindow.width = interactiveWindowSize.width; + nativeWindow.height = interactiveWindowSize.height; + } + } + + function updateContentParent() { + if (presentationMode === Desktop.PresentationMode.VIRTUAL) { + contentHolder.parent = root; + } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) { + contentHolder.parent = nativeWindow.contentItem; + } + } + + function setupPresentationMode() { + if (presentationMode === Desktop.PresentationMode.VIRTUAL) { + if (nativeWindow) { + nativeWindow.setVisible(false); + } + updateContentParent(); + updateInteractiveWindowPositionForMode(); + shown = interactiveWindowVisible; + } else if (presentationMode === Desktop.PresentationMode.NATIVE) { + shown = false; + if (nativeWindow) { + updateContentParent(); + updateInteractiveWindowPositionForMode(); + nativeWindow.setVisible(interactiveWindowVisible); + } + } else if (presentationMode === modeNotSet) { + console.error("presentationMode should be set."); + } + } + + Component.onCompleted: { + // Fix for parent loss on OSX: + parent.heightChanged.connect(updateContentParent); + parent.widthChanged.connect(updateContentParent); + + x = interactiveWindowPosition.x; + y = interactiveWindowPosition.y; + width = interactiveWindowSize.width; + height = interactiveWindowSize.height; + + nativeWindow = Qt.createQmlObject(' + import QtQuick 2.3; + import QtQuick.Window 2.3; + + Window { + id: root; + Rectangle { + color: hifi.colors.baseGray + anchors.fill: parent + } + }', root, 'InteractiveWindow.qml->nativeWindow'); + nativeWindow.title = root.title; + var nativeWindowFlags = Qt.Window | + Qt.WindowTitleHint | + Qt.WindowSystemMenuHint | + Qt.WindowCloseButtonHint | + Qt.WindowMaximizeButtonHint | + Qt.WindowMinimizeButtonHint; + if ((flags & Desktop.ALWAYS_ON_TOP) === Desktop.ALWAYS_ON_TOP) { + nativeWindowFlags |= Qt.WindowStaysOnTopHint; + } + nativeWindow.flags = nativeWindowFlags; + + nativeWindow.x = interactiveWindowPosition.x; + nativeWindow.y = interactiveWindowPosition.y; + + nativeWindow.width = interactiveWindowSize.width; + nativeWindow.height = interactiveWindowSize.height; + + nativeWindow.xChanged.connect(function() { + if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) { + interactiveWindowPosition = Qt.point(nativeWindow.x, interactiveWindowPosition.y); + } + }); + nativeWindow.yChanged.connect(function() { + if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) { + interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, nativeWindow.y); + } + }); + + nativeWindow.widthChanged.connect(function() { + if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) { + interactiveWindowSize = Qt.size(nativeWindow.width, interactiveWindowSize.height); + } + }); + nativeWindow.heightChanged.connect(function() { + if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) { + interactiveWindowSize = Qt.size(interactiveWindowSize.width, nativeWindow.height); + } + }); + + nativeWindow.closing.connect(function(closeEvent) { + closeEvent.accepted = false; + windowClosed(); + }); + + // finally set the initial window mode: + setupPresentationMode(); + + initialized = true; + } + + Component.onDestruction: { + parent.heightChanged.disconnect(updateContentParent); + parent.widthChanged.disconnect(updateContentParent); + } + + // Handle message traffic from the script that launched us to the loaded QML + function fromScript(message) { + if (root.dynamicContent && root.dynamicContent.fromScript) { + root.dynamicContent.fromScript(message); + } + } + + function show() { + interactiveWindowVisible = true; + raiseWindow(); + } + + function raiseWindow() { + if (presentationMode === Desktop.PresentationMode.VIRTUAL) { + raise(); + } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) { + nativeWindow.raise(); + } + } + + // Handle message traffic from our loaded QML to the script that launched us + signal sendToScript(var message); + + onDynamicContentChanged: { + if (dynamicContent && dynamicContent.sendToScript) { + dynamicContent.sendToScript.connect(sendToScript); + } + } + + onInteractiveWindowVisibleChanged: { + if (presentationMode === Desktop.PresentationMode.VIRTUAL) { + shown = interactiveWindowVisible; + } else if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow) { + if (!nativeWindow.visible && interactiveWindowVisible) { + nativeWindow.showNormal(); + } else { + nativeWindow.setVisible(interactiveWindowVisible); + } + } + } + + onTitleChanged: { + if (nativeWindow) { + nativeWindow.title = title; + } + } + + onXChanged: { + if (presentationMode === Desktop.PresentationMode.VIRTUAL) { + interactiveWindowPosition = Qt.point(x, interactiveWindowPosition.y); + } + } + + onYChanged: { + if (presentationMode === Desktop.PresentationMode.VIRTUAL) { + interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, y); + } + } + + onWidthChanged: { + if (presentationMode === Desktop.PresentationMode.VIRTUAL) { + interactiveWindowSize = Qt.size(width, interactiveWindowSize.height); + } + } + + onHeightChanged: { + if (presentationMode === Desktop.PresentationMode.VIRTUAL) { + interactiveWindowSize = Qt.size(interactiveWindowSize.width, height); + } + } + + onPresentationModeChanged: { + if (initialized) { + setupPresentationMode(); + } + } + + onWindowClosed: { + // set invisible on close, to make it not re-appear unintended after switching PresentationMode + interactiveWindowVisible = false; + + if ((flags & Desktop.CLOSE_BUTTON_HIDES) !== Desktop.CLOSE_BUTTON_HIDES) { + selfDestruct(); + } + } + + Item { + id: contentHolder + anchors.fill: parent + } +} diff --git a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml index 38e65af4ca..bf7807c85d 100644 --- a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml @@ -135,6 +135,8 @@ Item { placeholderText: qsTr("Password") echoMode: TextInput.Password + + Keys.onReturnPressed: linkAccountBody.login() } } diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 4708bfdebe..814778a4b1 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -126,10 +126,12 @@ Item { activeFocusOnPress: true ShortcutText { + z: 10 anchors { - verticalCenter: usernameField.textFieldLabel.verticalCenter - left: usernameField.textFieldLabel.right - leftMargin: 10 + left: usernameField.left + top: usernameField.top + leftMargin: usernameField.textFieldLabel.contentWidth + 10 + topMargin: -19 } text: "Forgot Username?" @@ -154,10 +156,12 @@ Item { activeFocusOnPress: true ShortcutText { + z: 10 anchors { - verticalCenter: passwordField.textFieldLabel.verticalCenter - left: passwordField.textFieldLabel.right - leftMargin: 10 + left: passwordField.left + top: passwordField.top + leftMargin: passwordField.textFieldLabel.contentWidth + 10 + topMargin: -19 } text: "Forgot Password?" @@ -168,10 +172,13 @@ Item { onLinkActivated: loginDialog.openUrl(link) } + onFocusChanged: { root.text = ""; root.isPassword = true; } + + Keys.onReturnPressed: linkAccountBody.login() } CheckBox { diff --git a/interface/resources/qml/LoginDialog/SignUpBody.qml b/interface/resources/qml/LoginDialog/SignUpBody.qml index 5eb99e0ece..bb30696e4c 100644 --- a/interface/resources/qml/LoginDialog/SignUpBody.qml +++ b/interface/resources/qml/LoginDialog/SignUpBody.qml @@ -164,6 +164,8 @@ Item { root.text = ""; root.isPassword = focus } + + Keys.onReturnPressed: signupBody.signup() } Row { diff --git a/interface/resources/qml/OverlayWindowTest.qml b/interface/resources/qml/OverlayWindowTest.qml index 7b82b2f705..9c1b993ba8 100644 --- a/interface/resources/qml/OverlayWindowTest.qml +++ b/interface/resources/qml/OverlayWindowTest.qml @@ -12,7 +12,7 @@ Rectangle { } Label { - text: OverlayWindowTestString + text: "OverlayWindowTestString" anchors.centerIn: parent } } diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 2406fa048d..bff13cea54 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -263,6 +263,12 @@ Item { } StatText { text: "GPU: " + root.gpuFrameTime.toFixed(1) + " ms" + } + StatText { + text: "GPU (Per pixel): " + root.gpuFrameTimePerPixel.toFixed(5) + " ns/pp" + } + StatText { + text: "GPU frame size: " + root.gpuFrameSize.x + " x " + root.gpuFrameSize.y } StatText { text: "Triangles: " + root.triangles + diff --git a/interface/resources/qml/controls-uit/+android/Button.qml b/interface/resources/qml/controls-uit/+android/Button.qml deleted file mode 100644 index 2f05b35685..0000000000 --- a/interface/resources/qml/controls-uit/+android/Button.qml +++ /dev/null @@ -1,125 +0,0 @@ -// -// Button.qml -// -// Created by David Rowe on 16 Feb 2016 -// Copyright 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 -// - -import QtQuick 2.5 -import QtQuick.Controls 1.4 as Original -import QtQuick.Controls.Styles 1.4 -import TabletScriptingInterface 1.0 - -import "../styles-uit" - -Original.Button { - id: root; - - property int color: 0 - property int colorScheme: hifi.colorSchemes.light - property string buttonGlyph: ""; - - width: hifi.dimensions.buttonWidth - height: hifi.dimensions.controlLineHeight - - HifiConstants { id: hifi } - - onHoveredChanged: { - if (hovered) { - Tablet.playSound(TabletEnums.ButtonHover); - } - } - - onFocusChanged: { - if (focus) { - Tablet.playSound(TabletEnums.ButtonHover); - } - } - - onClicked: { - Tablet.playSound(TabletEnums.ButtonClick); - } - - style: ButtonStyle { - - background: Rectangle { - radius: hifi.buttons.radius - - border.width: (control.color === hifi.buttons.none || - (control.color === hifi.buttons.noneBorderless && control.hovered) || - (control.color === hifi.buttons.noneBorderlessWhite && control.hovered) || - (control.color === hifi.buttons.noneBorderlessGray && control.hovered)) ? 1 : 0; - border.color: control.color === hifi.buttons.noneBorderless ? hifi.colors.blueHighlight : - (control.color === hifi.buttons.noneBorderlessGray ? hifi.colors.baseGray : hifi.colors.white); - - gradient: Gradient { - GradientStop { - position: 0.2 - color: { - if (!control.enabled) { - hifi.buttons.disabledColorStart[control.colorScheme] - } else if (control.pressed) { - hifi.buttons.pressedColor[control.color] - } else if (control.hovered) { - hifi.buttons.hoveredColor[control.color] - } else if (!control.hovered && control.focus) { - hifi.buttons.focusedColor[control.color] - } else { - hifi.buttons.colorStart[control.color] - } - } - } - GradientStop { - position: 1.0 - color: { - if (!control.enabled) { - hifi.buttons.disabledColorFinish[control.colorScheme] - } else if (control.pressed) { - hifi.buttons.pressedColor[control.color] - } else if (control.hovered) { - hifi.buttons.hoveredColor[control.color] - } else if (!control.hovered && control.focus) { - hifi.buttons.focusedColor[control.color] - } else { - hifi.buttons.colorFinish[control.color] - } - } - } - } - } - - label: Item { - HiFiGlyphs { - id: buttonGlyph; - visible: root.buttonGlyph !== ""; - text: root.buttonGlyph === "" ? hifi.glyphs.question : root.buttonGlyph; - // Size - size: 34; - // Anchors - anchors.right: buttonText.left; - anchors.top: parent.top; - anchors.bottom: parent.bottom; - // Style - color: enabled ? hifi.buttons.textColor[control.color] - : hifi.buttons.disabledTextColor[control.colorScheme]; - // Alignment - horizontalAlignment: Text.AlignHCenter; - verticalAlignment: Text.AlignVCenter; - } - RalewayBold { - id: buttonText; - anchors.centerIn: parent; - font.capitalization: Font.AllUppercase - color: enabled ? hifi.buttons.textColor[control.color] - : hifi.buttons.disabledTextColor[control.colorScheme] - size: hifi.fontSizes.buttonLabel - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - text: control.text - } - } - } -} diff --git a/interface/resources/qml/controls-uit/+android/Table.qml b/interface/resources/qml/controls-uit/+android/Table.qml deleted file mode 100644 index 3c1d0fcd3c..0000000000 --- a/interface/resources/qml/controls-uit/+android/Table.qml +++ /dev/null @@ -1,165 +0,0 @@ -// -// Table.qml -// -// Created by David Rowe on 18 Feb 2016 -// Copyright 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 -// - -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtQuick.Controls 2.2 as QQC2 - -import "../styles-uit" - -TableView { - id: tableView - - property int colorScheme: hifi.colorSchemes.light - readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light - property bool expandSelectedRow: false - property bool centerHeaderText: false - readonly property real headerSpacing: 3 //spacing between sort indicator and table header title - property var titlePaintedPos: [] // storing extra data position behind painted - // title text and sort indicatorin table's header - signal titlePaintedPosSignal(int column) //signal that extradata position gets changed - - model: ListModel { } - - Component.onCompleted: { - if (flickableItem !== null && flickableItem !== undefined) { - tableView.flickableItem.QQC2.ScrollBar.vertical = scrollbar - } - } - - QQC2.ScrollBar { - id: scrollbar - parent: tableView.flickableItem - policy: QQC2.ScrollBar.AsNeeded - orientation: Qt.Vertical - visible: size < 1.0 - topPadding: tableView.headerVisible ? hifi.dimensions.tableHeaderHeight + 1 : 1 - anchors.top: tableView.top - anchors.left: tableView.right - anchors.bottom: tableView.bottom - - background: Item { - implicitWidth: hifi.dimensions.scrollbarBackgroundWidth - Rectangle { - anchors { - fill: parent; - topMargin: tableView.headerVisible ? hifi.dimensions.tableHeaderHeight : 0 - } - color: isLightColorScheme ? hifi.colors.tableScrollBackgroundLight - : hifi.colors.tableScrollBackgroundDark - } - } - - contentItem: Item { - implicitWidth: hifi.dimensions.scrollbarHandleWidth - Rectangle { - anchors.fill: parent - radius: (width - 4)/2 - color: isLightColorScheme ? hifi.colors.tableScrollHandleLight : hifi.colors.tableScrollHandleDark - } - } - } - - headerVisible: false - headerDelegate: Rectangle { - height: hifi.dimensions.tableHeaderHeight - color: isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark - - - RalewayRegular { - id: titleText - x: centerHeaderText ? (parent.width - paintedWidth - - ((sortIndicatorVisible && - sortIndicatorColumn === styleData.column) ? - (titleSort.paintedWidth / 5 + tableView.headerSpacing) : 0)) / 2 : - hifi.dimensions.tablePadding - text: styleData.value - size: hifi.fontSizes.tableHeading - font.capitalization: Font.AllUppercase - color: hifi.colors.baseGrayHighlight - horizontalAlignment: (centerHeaderText ? Text.AlignHCenter : Text.AlignLeft) - anchors.verticalCenter: parent.verticalCenter - } - - //actual image of sort indicator in glyph font only 20% of real font size - //i.e. if the charachter size set to 60 pixels, actual image is 12 pixels - HiFiGlyphs { - id: titleSort - text: sortIndicatorOrder == Qt.AscendingOrder ? hifi.glyphs.caratUp : hifi.glyphs.caratDn - color: hifi.colors.darkGray - opacity: 0.6; - size: hifi.fontSizes.tableHeadingIcon - anchors.verticalCenter: titleText.verticalCenter - anchors.left: titleText.right - anchors.leftMargin: -(hifi.fontSizes.tableHeadingIcon / 2.5) + tableView.headerSpacing - visible: sortIndicatorVisible && sortIndicatorColumn === styleData.column - onXChanged: { - titlePaintedPos[styleData.column] = titleText.x + titleText.paintedWidth + - paintedWidth / 5 + tableView.headerSpacing*2 - titlePaintedPosSignal(styleData.column) - } - } - - Rectangle { - width: 1 - anchors { - left: parent.left - top: parent.top - topMargin: 1 - bottom: parent.bottom - bottomMargin: 2 - } - color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight - visible: styleData.column > 0 - } - - Rectangle { - height: 1 - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - } - color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight - } - } - - // Use rectangle to draw border with rounded corners. - frameVisible: false - Rectangle { - color: "#00000000" - anchors { fill: parent; margins: -2 } - border.color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight - border.width: 2 - } - anchors.margins: 2 // Shrink TableView to lie within border. - - backgroundVisible: true - - horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff - - style: TableViewStyle { - // Needed in order for rows to keep displaying rows after end of table entries. - backgroundColor: tableView.isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark - alternateBackgroundColor: tableView.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd - padding.top: headerVisible ? hifi.dimensions.tableHeaderHeight: 0 - } - - rowDelegate: Rectangle { - height: (styleData.selected && expandSelectedRow ? 1.8 : 1) * hifi.dimensions.tableRowHeight - color: styleData.selected - ? hifi.colors.primaryHighlight - : tableView.isLightColorScheme - ? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd) - : (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd) - } -} diff --git a/interface/resources/qml/controls-uit/Button.qml b/interface/resources/qml/controls-uit/Button.qml index 0119d76ac2..1509abdae3 100644 --- a/interface/resources/qml/controls-uit/Button.qml +++ b/interface/resources/qml/controls-uit/Button.qml @@ -19,6 +19,8 @@ Original.Button { property int color: 0 property int colorScheme: hifi.colorSchemes.light + property int fontSize: hifi.fontSizes.buttonLabel + property alias implicitTextWidth: buttonText.implicitWidth property string buttonGlyph: ""; width: hifi.dimensions.buttonWidth @@ -62,8 +64,6 @@ Original.Button { hifi.buttons.pressedColor[control.color] } else if (control.hovered) { hifi.buttons.hoveredColor[control.color] - } else if (!control.hovered && control.focus) { - hifi.buttons.focusedColor[control.color] } else { hifi.buttons.colorStart[control.color] } @@ -78,8 +78,6 @@ Original.Button { hifi.buttons.pressedColor[control.color] } else if (control.hovered) { hifi.buttons.hoveredColor[control.color] - } else if (!control.hovered && control.focus) { - hifi.buttons.focusedColor[control.color] } else { hifi.buttons.colorFinish[control.color] } @@ -112,7 +110,7 @@ Original.Button { font.capitalization: Font.AllUppercase color: enabled ? hifi.buttons.textColor[control.color] : hifi.buttons.disabledTextColor[control.colorScheme] - size: hifi.fontSizes.buttonLabel + size: control.fontSize verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter text: control.text diff --git a/interface/resources/qml/controls-uit/ComboBox.qml b/interface/resources/qml/controls-uit/ComboBox.qml index ab8a6c2344..be8c9f6740 100644 --- a/interface/resources/qml/controls-uit/ComboBox.qml +++ b/interface/resources/qml/controls-uit/ComboBox.qml @@ -171,6 +171,10 @@ FocusScope { } } + function textAt(index) { + return comboBox.textAt(index); + } + HifiControls.Label { id: comboBoxLabel text: root.label diff --git a/interface/resources/qml/controls-uit/RadioButton.qml b/interface/resources/qml/controls-uit/RadioButton.qml index ebfe1ff9a9..56324c55d7 100644 --- a/interface/resources/qml/controls-uit/RadioButton.qml +++ b/interface/resources/qml/controls-uit/RadioButton.qml @@ -25,10 +25,16 @@ Original.RadioButton { property int colorScheme: hifi.colorSchemes.light readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light - readonly property int boxSize: 14 - readonly property int boxRadius: 3 - readonly property int checkSize: 10 - readonly property int checkRadius: 2 + property real letterSpacing: 1 + property int fontSize: hifi.fontSizes.inputLabel + property int boxSize: defaultBoxSize + property real scaleFactor: boxSize / defaultBoxSize + + readonly property int defaultBoxSize: 14 + readonly property int boxRadius: 3 * scaleFactor + readonly property int checkSize: 10 * scaleFactor + readonly property int checkRadius: 2 * scaleFactor + readonly property int indicatorRadius: 7 * scaleFactor onClicked: { Tablet.playSound(TabletEnums.ButtonClick); @@ -44,7 +50,7 @@ Original.RadioButton { id: box width: boxSize height: boxSize - radius: 7 + radius: indicatorRadius x: radioButton.leftPadding y: parent.height / 2 - height / 2 gradient: Gradient { @@ -66,7 +72,7 @@ Original.RadioButton { id: check width: checkSize height: checkSize - radius: 7 + radius: indicatorRadius anchors.centerIn: parent color: "#00B4EF" border.width: 1 @@ -77,7 +83,8 @@ Original.RadioButton { contentItem: RalewaySemiBold { text: radioButton.text - size: hifi.fontSizes.inputLabel + size: radioButton.fontSize + font.letterSpacing: letterSpacing color: isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter diff --git a/interface/resources/qml/controls-uit/SpinBox.qml b/interface/resources/qml/controls-uit/SpinBox.qml index 9d63122dbc..5a4ba70080 100644 --- a/interface/resources/qml/controls-uit/SpinBox.qml +++ b/interface/resources/qml/controls-uit/SpinBox.qml @@ -17,12 +17,19 @@ import "../controls-uit" as HifiControls SpinBox { id: spinBox + HifiConstants { + id: hifi + } + property int colorScheme: hifi.colorSchemes.light readonly property bool isLightColorScheme: colorScheme === hifi.colorSchemes.light property string label: "" property string suffix: "" property string labelInside: "" property color colorLabelInside: hifi.colors.white + property color backgroundColor: isLightColorScheme + ? (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGray) + : (spinBox.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow) property real controlHeight: height + (spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0) property int decimals: 2; property real factor: Math.pow(10, decimals) @@ -31,8 +38,8 @@ SpinBox { property real maximumValue: 0.0 property real realValue: 0.0 - property real realFrom: 0.0 - property real realTo: 100.0 + property real realFrom: minimumValue + property real realTo: maximumValue property real realStepSize: 1.0 signal editingFinished() @@ -51,9 +58,14 @@ SpinBox { onValueModified: realValue = value/factor onValueChanged: realValue = value/factor + onRealValueChanged: { + var newValue = Math.round(realValue*factor); + if(value != newValue) { + value = newValue; + } + } stepSize: realStepSize*factor - value: realValue*factor to : realTo*factor from : realFrom*factor @@ -64,9 +76,7 @@ SpinBox { y: spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0 background: Rectangle { - color: isLightColorScheme - ? (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGray) - : (spinBox.activeFocus ? hifi.colors.black : hifi.colors.baseGrayShadow) + color: backgroundColor border.color: spinBoxLabelInside.visible ? spinBoxLabelInside.color : hifi.colors.primaryHighlight border.width: spinBox.activeFocus ? spinBoxLabelInside.visible ? 2 : 1 : 0 } @@ -81,6 +91,7 @@ SpinBox { } valueFromText: function(text, locale) { + spinBox.value = 0; // Force valueChanged signal to be emitted so that validator fires. return Number.fromLocaleString(locale, text)*factor; } @@ -110,7 +121,7 @@ SpinBox { anchors.centerIn: parent text: hifi.glyphs.caratUp size: hifi.dimensions.spinnerSize - color: spinBox.down.pressed || spinBox.up.hovered ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray + color: spinBox.up.pressed || spinBox.up.hovered ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray } } @@ -149,26 +160,14 @@ SpinBox { visible: spinBox.labelInside != "" } -// MouseArea { -// anchors.fill: parent -// propagateComposedEvents: true -// onWheel: { -// if(spinBox.activeFocus) -// wheel.accepted = false -// else -// wheel.accepted = true -// } -// onPressed: { -// mouse.accepted = false -// } -// onReleased: { -// mouse.accepted = false -// } -// onClicked: { -// mouse.accepted = false -// } -// onDoubleClicked: { -// mouse.accepted = false -// } -// } + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + onWheel: { + if (wheel.angleDelta.y > 0) + value += stepSize + else + value -= stepSize + } + } } diff --git a/interface/resources/qml/controls/+android/WebEntityView.qml b/interface/resources/qml/controls/+android/WebEntityView.qml new file mode 100644 index 0000000000..848077cea0 --- /dev/null +++ b/interface/resources/qml/controls/+android/WebEntityView.qml @@ -0,0 +1,76 @@ +// +// Web3DOverlay.qml +// +// Created by Gabriel Calero & Cristian Duarte on Jun 22, 2018 +// Copyright 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 +// + +import QtQuick 2.5 +import QtGraphicalEffects 1.0 + +Item { + + property string url + RadialGradient { + anchors.fill: parent + gradient: Gradient { + GradientStop { position: 0.0; color: "#262626" } + GradientStop { position: 1.0; color: "#000000" } + } + } + + function shortUrl(url) { + var hostBegin = url.indexOf("://"); + if (hostBegin > -1) { + url = url.substring(hostBegin + 3); + } + + var portBegin = url.indexOf(":"); + if (portBegin > -1) { + url = url.substring(0, portBegin); + } + + var pathBegin = url.indexOf("/"); + if (pathBegin > -1) { + url = url.substring(0, pathBegin); + } + + if (url.length > 45) { + url = url.substring(0, 45); + } + + return url; + } + + Text { + id: urlText + text: shortUrl(url) + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + anchors.fill: parent + anchors.rightMargin: 10 + anchors.leftMargin: 10 + font.family: "Cairo" + font.weight: Font.DemiBold + font.pointSize: 48 + fontSizeMode: Text.Fit + color: "#FFFFFF" + minimumPixelSize: 5 + } + + Image { + id: hand + source: "../../../icons/hand.svg" + width: 300 + height: 300 + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.bottomMargin: 100 + anchors.rightMargin: 100 + } + + +} diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index 477422cfa1..bc958aa876 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -11,7 +11,6 @@ Item { height: parent !== null ? parent.height : undefined property var parentStackItem: null property int headerHeight: 70 - property string url property string scriptURL property bool keyboardEnabled: false property bool keyboardRaised: false @@ -23,6 +22,7 @@ Item { property bool punctuationMode: false property bool passwordField: false property bool isDesktop: false + property alias url: web.url property alias webView: web.webViewCore property alias profile: web.webViewCoreProfile property bool remove: false @@ -81,7 +81,7 @@ Item { color: hifi.colors.baseGray font.pixelSize: 12 verticalAlignment: Text.AlignLeft - text: root.url + text: web.url anchors { top: nav.bottom horizontalCenter: parent.horizontalCenter; @@ -131,11 +131,11 @@ Item { function loadUrl(url) { web.webViewCore.url = url - root.url = web.webViewCore.url; } - onUrlChanged: { - loadUrl(url); + Rectangle { + anchors.fill: web; + color: hifi.colors.white; } FlickableWebViewCore { diff --git a/interface/resources/qml/desktop/+android/Desktop.qml b/interface/resources/qml/desktop/+android/Desktop.qml deleted file mode 100644 index 6a68f63d0a..0000000000 --- a/interface/resources/qml/desktop/+android/Desktop.qml +++ /dev/null @@ -1,575 +0,0 @@ -// -// Desktop.qml -// -// Created by Bradley Austin Davis on 15 Apr 2015 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.7 -import QtQuick.Controls 1.4 - -import "../dialogs" -import "../js/Utils.js" as Utils - -// This is our primary 'desktop' object to which all VR dialogs and windows are childed. -FocusScope { - id: desktop - objectName: "desktop" - anchors.fill: parent - - readonly property int invalid_position: -9999; - property rect recommendedRect: Qt.rect(0,0,0,0); - property var expectedChildren; - property bool repositionLocked: true - property bool hmdHandMouseActive: false - - onRepositionLockedChanged: { - if (!repositionLocked) { - d.handleSizeChanged(); - } - - } - - onHeightChanged: d.handleSizeChanged(); - - onWidthChanged: d.handleSizeChanged(); - - // Controls and windows can trigger this signal to ensure the desktop becomes visible - // when they're opened. - signal showDesktop(); - - // This is for JS/QML communication, which is unused in the Desktop, - // but not having this here results in spurious warnings about a - // missing signal - signal sendToScript(var message); - - // Allows QML/JS to find the desktop through the parent chain - property bool desktopRoot: true - - // The VR version of the primary menu - property var rootMenu: Menu { - id: rootMenuId - objectName: "rootMenu" - - property var exclusionGroups: ({}); - property Component exclusiveGroupMaker: Component { - ExclusiveGroup { - } - } - - function addExclusionGroup(qmlAction, exclusionGroup) { - - var exclusionGroupId = exclusionGroup.toString(); - if(!exclusionGroups[exclusionGroupId]) { - exclusionGroups[exclusionGroupId] = exclusiveGroupMaker.createObject(rootMenuId); - } - - qmlAction.exclusiveGroup = exclusionGroups[exclusionGroupId] - } - } - - // FIXME: Alpha gradients display as fuschia under QtQuick 2.5 on OSX/AMD - // because shaders are 4.2, and do not include #version declarations. - property bool gradientsSupported: Qt.platform.os != "osx" && !~GL.vendor.indexOf("ATI") - - readonly property alias zLevels: zLevels - QtObject { - id: zLevels; - readonly property real normal: 1 // make windows always appear higher than QML overlays and other non-window controls. - readonly property real top: 2000 - readonly property real modal: 4000 - readonly property real menu: 8000 - } - - QtObject { - id: d - - function handleSizeChanged() { - if (desktop.repositionLocked) { - return; - } - var oldRecommendedRect = recommendedRect; - var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedHUDRect(); - var newRecommendedRect = Qt.rect(newRecommendedRectJS.x, newRecommendedRectJS.y, - newRecommendedRectJS.width, - newRecommendedRectJS.height); - - var oldChildren = expectedChildren; - var newChildren = d.getRepositionChildren(); - if (oldRecommendedRect != Qt.rect(0,0,0,0) && oldRecommendedRect != Qt.rect(0,0,1,1) - && (oldRecommendedRect != newRecommendedRect - || oldChildren != newChildren) - ) { - expectedChildren = newChildren; - d.repositionAll(); - } - recommendedRect = newRecommendedRect; - } - - function findChild(item, name) { - for (var i = 0; i < item.children.length; ++i) { - if (item.children[i].objectName === name) { - return item.children[i]; - } - } - return null; - } - - function findParentMatching(item, predicate) { - while (item) { - if (predicate(item)) { - break; - } - item = item.parent; - } - return item; - } - - function findMatchingChildren(item, predicate) { - var results = []; - for (var i in item.children) { - var child = item.children[i]; - if (predicate(child)) { - results.push(child); - } - } - return results; - } - - function isTopLevelWindow(item) { - return item.topLevelWindow; - } - - function isAlwaysOnTopWindow(window) { - return window.alwaysOnTop; - } - - function isModalWindow(window) { - return window.modality !== Qt.NonModal; - } - - function getTopLevelWindows(predicate) { - return findMatchingChildren(desktop, function(child) { - return (isTopLevelWindow(child) && (!predicate || predicate(child))); - }); - } - - function getDesktopWindow(item) { - return findParentMatching(item, isTopLevelWindow) - } - - function fixupZOrder(windows, basis, topWindow) { - windows.sort(function(a, b){ return a.z - b.z; }); - - if ((topWindow.z >= basis) && (windows[windows.length - 1] === topWindow)) { - return; - } - - var lastZ = -1; - var lastTargetZ = basis - 1; - for (var i = 0; i < windows.length; ++i) { - var window = windows[i]; - if (!window.visible) { - continue - } - - if (topWindow && (topWindow === window)) { - continue - } - - if (window.z > lastZ) { - lastZ = window.z; - ++lastTargetZ; - } - if (DebugQML) { - console.log("Assigning z order " + lastTargetZ + " to " + window) - } - - window.z = lastTargetZ; - } - if (topWindow) { - ++lastTargetZ; - if (DebugQML) { - console.log("Assigning z order " + lastTargetZ + " to " + topWindow) - } - topWindow.z = lastTargetZ; - } - - return lastTargetZ; - } - - function raiseWindow(targetWindow) { - var predicate; - var zBasis; - if (isModalWindow(targetWindow)) { - predicate = isModalWindow; - zBasis = zLevels.modal - } else if (isAlwaysOnTopWindow(targetWindow)) { - predicate = function(window) { - return (isAlwaysOnTopWindow(window) && !isModalWindow(window)); - } - zBasis = zLevels.top - } else { - predicate = function(window) { - return (!isAlwaysOnTopWindow(window) && !isModalWindow(window)); - } - zBasis = zLevels.normal - } - - var windows = getTopLevelWindows(predicate); - fixupZOrder(windows, zBasis, targetWindow); - } - - Component.onCompleted: { - //offscreenWindow.activeFocusItemChanged.connect(onWindowFocusChanged); - focusHack.start(); - } - - function onWindowFocusChanged() { - //console.log("Focus item is " + offscreenWindow.activeFocusItem); - - // FIXME this needs more testing before it can go into production - // and I already cant produce any way to have a modal dialog lose focus - // to a non-modal one. - /* - var focusedWindow = getDesktopWindow(offscreenWindow.activeFocusItem); - - if (isModalWindow(focusedWindow)) { - return; - } - - // new focused window is not modal... check if there are any modal windows - var windows = getTopLevelWindows(isModalWindow); - if (0 === windows.length) { - return; - } - - // There are modal windows present, force focus back to the top-most modal window - windows.sort(function(a, b){ return a.z - b.z; }); - windows[windows.length - 1].focus = true; - */ - -// var focusedItem = offscreenWindow.activeFocusItem ; -// if (DebugQML && focusedItem) { -// var rect = desktop.mapFromItem(focusedItem, 0, 0, focusedItem.width, focusedItem.height); -// focusDebugger.x = rect.x; -// focusDebugger.y = rect.y; -// focusDebugger.width = rect.width -// focusDebugger.height = rect.height -// } - } - - function getRepositionChildren(predicate) { - return findMatchingChildren(desktop, function(child) { - return (child.shouldReposition === true && (!predicate || predicate(child))); - }); - } - - function repositionAll() { - if (desktop.repositionLocked) { - return; - } - - var oldRecommendedRect = recommendedRect; - var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height }; - var newRecommendedRect = Controller.getRecommendedHUDRect(); - var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height }; - var windows = d.getTopLevelWindows(); - for (var i = 0; i < windows.length; ++i) { - var targetWindow = windows[i]; - if (targetWindow.visible) { - repositionWindow(targetWindow, true, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions); - } - } - - // also reposition the other children that aren't top level windows but want to be repositioned - var otherChildren = d.getRepositionChildren(); - for (var i = 0; i < otherChildren.length; ++i) { - var child = otherChildren[i]; - repositionWindow(child, true, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions); - } - - } - } - - property bool pinned: false - property var hiddenChildren: [] - - function togglePinned() { - pinned = !pinned - } - - function isPointOnWindow(point) { - for (var i = 0; i < desktop.visibleChildren.length; i++) { - var child = desktop.visibleChildren[i]; - if (child.hasOwnProperty("modality")) { - var mappedPoint = mapToItem(child, point.x, point.y); - if (child.hasOwnProperty("frame")) { - var outLine = child.frame.children[2]; - var framePoint = outLine.mapFromGlobal(point.x, point.y); - if (outLine.contains(framePoint)) { - return true; - } - } - - if (child.contains(mappedPoint)) { - return true; - } - } - } - return false; - } - - function setPinned(newPinned) { - pinned = newPinned - } - - property real unpinnedAlpha: 1.0; - - Behavior on unpinnedAlpha { - NumberAnimation { - easing.type: Easing.Linear; - duration: 300 - } - } - - state: "NORMAL" - states: [ - State { - name: "NORMAL" - PropertyChanges { target: desktop; unpinnedAlpha: 1.0 } - }, - State { - name: "PINNED" - PropertyChanges { target: desktop; unpinnedAlpha: 0.0 } - } - ] - - transitions: [ - Transition { - NumberAnimation { properties: "unpinnedAlpha"; duration: 300 } - } - ] - - onPinnedChanged: { - if (pinned) { - d.raiseWindow(desktop); - desktop.focus = true; - desktop.forceActiveFocus(); - - // recalculate our non-pinned children - hiddenChildren = d.findMatchingChildren(desktop, function(child){ - return !d.isTopLevelWindow(child) && child.visible && !child.pinned; - }); - - hiddenChildren.forEach(function(child){ - child.opacity = Qt.binding(function(){ return desktop.unpinnedAlpha }); - }); - } - state = pinned ? "PINNED" : "NORMAL" - } - - onShowDesktop: pinned = false - - function raise(item) { - var targetWindow = d.getDesktopWindow(item); - if (!targetWindow) { - console.warn("Could not find top level window for " + item); - return; - } - - // Fix up the Z-order (takes into account if this is a modal window) - d.raiseWindow(targetWindow); - var setFocus = true; - if (!d.isModalWindow(targetWindow)) { - var modalWindows = d.getTopLevelWindows(d.isModalWindow); - if (modalWindows.length) { - setFocus = false; - } - } - - if (setFocus) { - targetWindow.focus = true; - } - - showDesktop(); - } - - function ensureTitleBarVisible(targetWindow) { - // Reposition window to ensure that title bar is vertically inside window. - if (targetWindow.frame && targetWindow.frame.decoration) { - var topMargin = -targetWindow.frame.decoration.anchors.topMargin; // Frame's topMargin is a negative value. - targetWindow.y = Math.max(targetWindow.y, topMargin); - } - } - - function centerOnVisible(item) { - var targetWindow = d.getDesktopWindow(item); - if (!targetWindow) { - console.warn("Could not find top level window for " + item); - return; - } - - if (typeof Controller === "undefined") { - console.warn("Controller not yet available... can't center"); - return; - } - - var newRecommendedRectJS = (typeof Controller === "undefined") ? Qt.rect(0,0,0,0) : Controller.getRecommendedHUDRect(); - var newRecommendedRect = Qt.rect(newRecommendedRectJS.x, newRecommendedRectJS.y, - newRecommendedRectJS.width, - newRecommendedRectJS.height); - var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height }; - var newX = newRecommendedRect.x + ((newRecommendedRect.width - targetWindow.width) / 2); - var newY = newRecommendedRect.y + ((newRecommendedRect.height - targetWindow.height) / 2); - targetWindow.x = newX; - targetWindow.y = newY; - - ensureTitleBarVisible(targetWindow); - - // If we've noticed that our recommended desktop rect has changed, record that change here. - if (recommendedRect != newRecommendedRect) { - recommendedRect = newRecommendedRect; - } - } - - function repositionOnVisible(item) { - var targetWindow = d.getDesktopWindow(item); - if (!targetWindow) { - console.warn("Could not find top level window for " + item); - return; - } - - if (typeof Controller === "undefined") { - console.warn("Controller not yet available... can't reposition targetWindow:" + targetWindow); - return; - } - - var oldRecommendedRect = recommendedRect; - var oldRecommendedDimmensions = { x: oldRecommendedRect.width, y: oldRecommendedRect.height }; - var newRecommendedRect = Controller.getRecommendedHUDRect(); - var newRecommendedDimmensions = { x: newRecommendedRect.width, y: newRecommendedRect.height }; - repositionWindow(targetWindow, false, oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions); - } - - function repositionWindow(targetWindow, forceReposition, - oldRecommendedRect, oldRecommendedDimmensions, newRecommendedRect, newRecommendedDimmensions) { - - if (desktop.width === 0 || desktop.height === 0) { - return; - } - - if (!targetWindow) { - console.warn("Could not find top level window for " + item); - return; - } - - var recommended = Controller.getRecommendedHUDRect(); - var maxX = recommended.x + recommended.width; - var maxY = recommended.y + recommended.height; - var newPosition = Qt.vector2d(targetWindow.x, targetWindow.y); - - // if we asked to force reposition, or if the window is completely outside of the recommended rectangle, reposition it - if (forceReposition || (targetWindow.x > maxX || (targetWindow.x + targetWindow.width) < recommended.x) || - (targetWindow.y > maxY || (targetWindow.y + targetWindow.height) < recommended.y)) { - newPosition.x = -1 - newPosition.y = -1 - } - - if (newPosition.x === -1 && newPosition.y === -1) { - var originRelativeX = (targetWindow.x - oldRecommendedRect.x); - var originRelativeY = (targetWindow.y - oldRecommendedRect.y); - if (isNaN(originRelativeX)) { - originRelativeX = 0; - } - if (isNaN(originRelativeY)) { - originRelativeY = 0; - } - var fractionX = Utils.clamp(originRelativeX / oldRecommendedDimmensions.x, 0, 1); - var fractionY = Utils.clamp(originRelativeY / oldRecommendedDimmensions.y, 0, 1); - var newX = (fractionX * newRecommendedDimmensions.x) + newRecommendedRect.x; - var newY = (fractionY * newRecommendedDimmensions.y) + newRecommendedRect.y; - newPosition = Qt.vector2d(newX, newY); - } - targetWindow.x = newPosition.x; - targetWindow.y = newPosition.y; - - ensureTitleBarVisible(targetWindow); - } - - Component { id: messageDialogBuilder; MessageDialog { } } - function messageBox(properties) { - return messageDialogBuilder.createObject(desktop, properties); - } - - Component { id: inputDialogBuilder; QueryDialog { } } - function inputDialog(properties) { - return inputDialogBuilder.createObject(desktop, properties); - } - - Component { id: customInputDialogBuilder; CustomQueryDialog { } } - function customInputDialog(properties) { - return customInputDialogBuilder.createObject(desktop, properties); - } - - Component { id: fileDialogBuilder; FileDialog { } } - function fileDialog(properties) { - return fileDialogBuilder.createObject(desktop, properties); - } - - Component { id: assetDialogBuilder; AssetDialog { } } - function assetDialog(properties) { - return assetDialogBuilder.createObject(desktop, properties); - } - - function unfocusWindows() { - // First find the active focus item, and unfocus it, all the way - // up the parent chain to the window - var currentFocus = offscreenWindow.activeFocusItem; - var targetWindow = d.getDesktopWindow(currentFocus); - while (currentFocus) { - if (currentFocus === targetWindow) { - break; - } - currentFocus.focus = false; - currentFocus = currentFocus.parent; - } - - // Unfocus all windows - var windows = d.getTopLevelWindows(); - for (var i = 0; i < windows.length; ++i) { - windows[i].focus = false; - } - - // For the desktop to have active focus - desktop.focus = true; - desktop.forceActiveFocus(); - } - - function openBrowserWindow(request, profile) { - var component = Qt.createComponent("../Browser.qml"); - var newWindow = component.createObject(desktop); - newWindow.webView.profile = profile; - request.openIn(newWindow.webView); - } - - FocusHack { id: focusHack; } - - Rectangle { - id: focusDebugger; - objectName: "focusDebugger" - z: 9999; visible: false; color: "red" - ColorAnimation on color { from: "#7fffff00"; to: "#7f0000ff"; duration: 1000; loops: 9999 } - } - - Action { - text: "Toggle Focus Debugger" - shortcut: "Ctrl+Shift+F" - enabled: DebugQML - onTriggered: focusDebugger.visible = !focusDebugger.visible - } - -} diff --git a/interface/resources/qml/dialogs/+android/CustomQueryDialog.qml b/interface/resources/qml/dialogs/+android/CustomQueryDialog.qml deleted file mode 100644 index aadd7c88ae..0000000000 --- a/interface/resources/qml/dialogs/+android/CustomQueryDialog.qml +++ /dev/null @@ -1,338 +0,0 @@ -// -// CustomQueryDialog.qml -// -// Created by Zander Otavka on 7/14/16 -// Copyright 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 -// - -import QtQuick 2.7; -import QtQuick.Dialogs 1.2 as OriginalDialogs; -import QtQuick.Controls 1.4; - -import "../controls-uit"; -import "../styles-uit"; -import "../windows"; - -ModalWindow { - id: root; - HifiConstants { id: hifi; } - implicitWidth: 640; - implicitHeight: 320; - visible: true; - keyboardOverride: true // Disable ModalWindow's keyboard. - - signal selected(var result); - signal canceled(); - - property int icon: hifi.icons.none; - property string iconText: ""; - property int iconSize: 35; - onIconChanged: updateIcon(); - - property var textInput; - property var comboBox; - property var checkBox; - onTextInputChanged: { - if (textInput && textInput.text !== undefined) { - textField.text = textInput.text; - } - } - onComboBoxChanged: { - if (comboBox && comboBox.index !== undefined) { - comboBoxField.currentIndex = comboBox.index; - } - } - onCheckBoxChanged: { - if (checkBox && checkBox.checked !== undefined) { - checkBoxField.checked = checkBox.checked; - } - } - - property bool keyboardEnabled: false - property bool keyboardRaised: false - property bool punctuationMode: false - onKeyboardRaisedChanged: d.resize(); - - property var warning: ""; - property var result; - - property var implicitCheckState: null; - - property int titleWidth: 0; - onTitleWidthChanged: d.resize(); - - function updateIcon() { - if (!root) { - return; - } - iconText = hifi.glyphForIcon(root.icon); - } - - function updateCheckbox() { - if (checkBox.disableForItems) { - var currentItemInDisableList = false; - for (var i in checkBox.disableForItems) { - if (comboBoxField.currentIndex === checkBox.disableForItems[i]) { - currentItemInDisableList = true; - break; - } - } - - if (currentItemInDisableList) { - checkBoxField.enabled = false; - if (checkBox.checkStateOnDisable !== null && checkBox.checkStateOnDisable !== undefined) { - root.implicitCheckState = checkBoxField.checked; - checkBoxField.checked = checkBox.checkStateOnDisable; - } - root.warning = checkBox.warningOnDisable; - } else { - checkBoxField.enabled = true; - if (root.implicitCheckState !== null) { - checkBoxField.checked = root.implicitCheckState; - root.implicitCheckState = null; - } - root.warning = ""; - } - } - } - - Item { - clip: true; - width: pane.width; - height: pane.height; - anchors.margins: 0; - - QtObject { - id: d; - readonly property int minWidth: 480 - readonly property int maxWdith: 1280 - readonly property int minHeight: 120 - readonly property int maxHeight: 720 - - function resize() { - var targetWidth = Math.max(titleWidth, pane.width); - var targetHeight = (textField.visible ? textField.controlHeight + hifi.dimensions.contentSpacing.y : 0) + - (extraInputs.visible ? extraInputs.height + hifi.dimensions.contentSpacing.y : 0) + - (buttons.height + 3 * hifi.dimensions.contentSpacing.y) + - ((keyboardEnabled && keyboardRaised) ? (keyboard.raisedHeight + hifi.dimensions.contentSpacing.y) : 0); - - root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth); - root.height = (targetHeight < d.minHeight) ? d.minHeight : ((targetHeight > d.maxHeight) ? - d.maxHeight : targetHeight); - } - } - - Item { - anchors { - top: parent.top; - bottom: extraInputs.visible ? extraInputs.top : buttons.top; - left: parent.left; - right: parent.right; - margins: 0; - } - - // FIXME make a text field type that can be bound to a history for autocompletion - TextField { - id: textField; - label: root.textInput.label; - focus: root.textInput ? true : false; - visible: root.textInput ? true : false; - anchors { - left: parent.left; - right: parent.right; - bottom: keyboard.top; - bottomMargin: hifi.dimensions.contentSpacing.y; - } - } - - Keyboard { - id: keyboard - raised: keyboardEnabled && keyboardRaised - numeric: punctuationMode - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - bottomMargin: raised ? hifi.dimensions.contentSpacing.y : 0 - } - } - } - - Item { - id: extraInputs; - visible: Boolean(root.checkBox || root.comboBox); - anchors { - left: parent.left; - right: parent.right; - bottom: buttons.top; - bottomMargin: hifi.dimensions.contentSpacing.y; - } - height: comboBoxField.controlHeight; - onHeightChanged: d.resize(); - onWidthChanged: d.resize(); - - CheckBox { - id: checkBoxField; - text: root.checkBox.label; - focus: Boolean(root.checkBox); - visible: Boolean(root.checkBox); - anchors { - left: parent.left; - bottom: parent.bottom; - leftMargin: 6; // Magic number to align with warning icon - bottomMargin: 6; - } - } - - ComboBox { - id: comboBoxField; - label: root.comboBox.label; - focus: Boolean(root.comboBox); - visible: Boolean(root.comboBox); - Binding on x { - when: comboBoxField.visible - value: !checkBoxField.visible ? buttons.x : acceptButton.x - } - - Binding on width { - when: comboBoxField.visible - value: !checkBoxField.visible ? buttons.width : buttons.width - acceptButton.x - } - anchors { - right: parent.right; - bottom: parent.bottom; - } - model: root.comboBox ? root.comboBox.items : []; - onAccepted: { - updateCheckbox(); - focus = true; - } - } - } - - Row { - id: buttons; - focus: true; - spacing: hifi.dimensions.contentSpacing.x; - layoutDirection: Qt.RightToLeft; - onHeightChanged: d.resize(); - onWidthChanged: { - d.resize(); - resizeWarningText(); - } - - anchors { - bottom: parent.bottom; - left: parent.left; - right: parent.right; - bottomMargin: hifi.dimensions.contentSpacing.y; - } - - function resizeWarningText() { - var rowWidth = buttons.width; - var buttonsWidth = acceptButton.width + cancelButton.width + hifi.dimensions.contentSpacing.x * 2; - var warningIconWidth = warningIcon.width + hifi.dimensions.contentSpacing.x; - warningText.width = rowWidth - buttonsWidth - warningIconWidth; - } - - Button { - id: cancelButton; - action: cancelAction; - } - - Button { - id: acceptButton; - action: acceptAction; - } - - Text { - id: warningText; - visible: Boolean(root.warning); - text: root.warning; - wrapMode: Text.WordWrap; - font.italic: true; - maximumLineCount: 3; - } - - HiFiGlyphs { - id: warningIcon; - visible: Boolean(root.warning); - text: hifi.glyphs.alert; - size: hifi.dimensions.controlLineHeight; - width: 20 // Line up with checkbox. - } - } - - Action { - id: cancelAction; - text: qsTr("Cancel"); - shortcut: "Esc"; - onTriggered: { - root.result = null; - root.canceled(); - // FIXME we are leaking memory to avoid a crash - // root.destroy(); - - root.disableFade = true - visible = false; - } - } - - Action { - id: acceptAction; - text: qsTr("Add"); - shortcut: "Return" - onTriggered: { - var result = {}; - if (textInput) { - result.textInput = textField.text; - } - if (comboBox) { - result.comboBox = comboBoxField.currentIndex; - result.comboBoxText = comboBoxField.currentText; - } - if (checkBox) { - result.checkBox = checkBoxField.enabled ? checkBoxField.checked : null; - } - root.result = JSON.stringify(result); - root.selected(root.result); - // FIXME we are leaking memory to avoid a crash - // root.destroy(); - - root.disableFade = true - visible = false; - } - } - } - - Keys.onPressed: { - if (!visible) { - return; - } - - switch (event.key) { - case Qt.Key_Escape: - case Qt.Key_Back: - cancelAction.trigger(); - event.accepted = true; - break; - - case Qt.Key_Return: - case Qt.Key_Enter: - acceptAction.trigger(); - event.accepted = true; - break; - } - } - - Component.onCompleted: { - keyboardEnabled = HMD.active; - updateIcon(); - updateCheckbox(); - d.resize(); - textField.forceActiveFocus(); - } -} diff --git a/interface/resources/qml/dialogs/+android/FileDialog.qml b/interface/resources/qml/dialogs/+android/FileDialog.qml deleted file mode 100644 index be6524d2b8..0000000000 --- a/interface/resources/qml/dialogs/+android/FileDialog.qml +++ /dev/null @@ -1,840 +0,0 @@ -// -// FileDialog.qml -// -// Created by Bradley Austin Davis on 14 Jan 2016 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.7 -import Qt.labs.folderlistmodel 2.1 -import Qt.labs.settings 1.0 -import QtQuick.Dialogs 1.2 as OriginalDialogs -import QtQuick.Controls 1.4 - -import ".." -import "../controls-uit" -import "../styles-uit" -import "../windows" - -import "fileDialog" - -//FIXME implement shortcuts for favorite location -ModalWindow { - id: root - resizable: true - implicitWidth: 480 - implicitHeight: 360 + (fileDialogItem.keyboardEnabled && fileDialogItem.keyboardRaised ? keyboard.raisedHeight + hifi.dimensions.contentSpacing.y : 0) - - minSize: Qt.vector2d(360, 240) - draggable: true - - HifiConstants { id: hifi } - - property var filesModel: ListModel { } - - Settings { - category: "FileDialog" - property alias width: root.width - property alias height: root.height - property alias x: root.x - property alias y: root.y - } - - - // Set from OffscreenUi::getOpenFile() - property alias caption: root.title; - // Set from OffscreenUi::getOpenFile() - property alias dir: fileTableModel.folder; - // Set from OffscreenUi::getOpenFile() - property alias filter: selectionType.filtersString; - // Set from OffscreenUi::getOpenFile() - property int options; // <-- FIXME unused - - property string iconText: root.title !== "" ? hifi.glyphs.scriptUpload : "" - property int iconSize: 40 - - property bool selectDirectory: false; - property bool showHidden: true; - // FIXME implement - property bool multiSelect: false; - property bool saveDialog: false; - property var helper: fileDialogHelper - property alias model: fileTableView.model - property var drives: helper.drives() - - property int titleWidth: 0 - - signal selectedFile(var file); - signal canceled(); - signal selected(int button); - function click(button) { - clickedButton = button; - selected(button); - destroy(); - } - - property int clickedButton: OriginalDialogs.StandardButton.NoButton; - - Component.onCompleted: { - console.log("Helper " + helper + " drives " + drives); - - fileDialogItem.keyboardEnabled = HMD.active; - - // HACK: The following lines force the model to initialize properly such that the go-up button - // works properly from the initial screen. - var initialFolder = folderListModel.folder; - fileTableModel.folder = helper.pathToUrl(drives[0]); - fileTableModel.folder = initialFolder; - - iconText = root.title !== "" ? hifi.glyphs.scriptUpload : ""; - - // Clear selection when click on external frame. - frameClicked.connect(function() { d.clearSelection(); }); - - if (selectDirectory) { - currentSelection.text = d.capitalizeDrive(helper.urlToPath(initialFolder)); - d.currentSelectionIsFolder = true; - d.currentSelectionUrl = initialFolder; - } - - helper.contentsChanged.connect(function() { - if (folderListModel) { - // Make folderListModel refresh. - var save = folderListModel.folder; - folderListModel.folder = ""; - folderListModel.folder = save; - } - }); - - focusTimer.start(); - } - - Timer { - id: focusTimer - interval: 10 - running: false - repeat: false - onTriggered: { - fileTableView.contentItem.forceActiveFocus(); - } - } - - Item { - id: fileDialogItem - clip: true - width: pane.width - height: pane.height - anchors.margins: 0 - - property bool keyboardEnabled: false - property bool keyboardRaised: false - property bool punctuationMode: false - - MouseArea { - // Clear selection when click on internal unused area. - anchors.fill: parent - drag.target: root - onClicked: { - d.clearSelection(); - // Defocus text field so that the keyboard gets hidden. - // Clicking also breaks keyboard navigation apart from backtabbing to cancel - frame.forceActiveFocus(); - } - } - - Row { - id: navControls - anchors { - top: parent.top - topMargin: hifi.dimensions.contentMargin.y - left: parent.left - } - spacing: hifi.dimensions.contentSpacing.x - - GlyphButton { - id: upButton - glyph: hifi.glyphs.levelUp - width: height - size: 30 - enabled: fileTableModel.parentFolder && fileTableModel.parentFolder !== "" - onClicked: d.navigateUp(); - Keys.onReturnPressed: { d.navigateUp(); } - KeyNavigation.tab: homeButton - KeyNavigation.backtab: upButton - KeyNavigation.left: upButton - KeyNavigation.right: homeButton - } - - GlyphButton { - id: homeButton - property var destination: helper.home(); - glyph: hifi.glyphs.home - size: 28 - width: height - enabled: d.homeDestination ? true : false - onClicked: d.navigateHome(); - Keys.onReturnPressed: { d.navigateHome(); } - KeyNavigation.tab: fileTableView.contentItem - KeyNavigation.backtab: upButton - KeyNavigation.left: upButton - } - } - - ComboBox { - id: pathSelector - anchors { - top: parent.top - topMargin: hifi.dimensions.contentMargin.y - left: navControls.right - leftMargin: hifi.dimensions.contentSpacing.x - right: parent.right - } - - property var lastValidFolder: helper.urlToPath(fileTableModel.folder) - - function calculatePathChoices(folder) { - var folders = folder.split("/"), - choices = [], - i, length; - - if (folders[folders.length - 1] === "") { - folders.pop(); - } - - choices.push(folders[0]); - - for (i = 1, length = folders.length; i < length; i++) { - choices.push(choices[i - 1] + "/" + folders[i]); - } - - if (folders[0] === "") { - // Special handling for OSX root dir. - choices[0] = "/"; - } - - choices.reverse(); - - if (drives && drives.length > 1) { - choices.push("This PC"); - } - - if (choices.length > 0) { - pathSelector.model = choices; - } - } - - onLastValidFolderChanged: { - var folder = d.capitalizeDrive(lastValidFolder); - calculatePathChoices(folder); - } - - onCurrentTextChanged: { - var folder = currentText; - - if (/^[a-zA-z]:$/.test(folder)) { - folder = "file:///" + folder + "/"; - } else if (folder === "This PC") { - folder = "file:///"; - } else { - folder = helper.pathToUrl(folder); - } - - if (helper.urlToPath(folder).toLowerCase() !== helper.urlToPath(fileTableModel.folder).toLowerCase()) { - if (root.selectDirectory) { - currentSelection.text = currentText !== "This PC" ? currentText : ""; - d.currentSelectionUrl = helper.pathToUrl(currentText); - } - fileTableModel.folder = folder; - } - } - - KeyNavigation.up: fileTableView.contentItem - KeyNavigation.down: fileTableView.contentItem - KeyNavigation.tab: fileTableView.contentItem - KeyNavigation.backtab: fileTableView.contentItem - KeyNavigation.left: fileTableView.contentItem - KeyNavigation.right: fileTableView.contentItem - } - - QtObject { - id: d - property var currentSelectionUrl; - readonly property string currentSelectionPath: helper.urlToPath(currentSelectionUrl); - property bool currentSelectionIsFolder; - property var backStack: [] - property var tableViewConnection: Connections { target: fileTableView; onCurrentRowChanged: d.update(); } - property var modelConnection: Connections { target: fileTableModel; onFolderChanged: d.update(); } - property var homeDestination: helper.home(); - - function capitalizeDrive(path) { - // Consistently capitalize drive letter for Windows. - if (/[a-zA-Z]:/.test(path)) { - return path.charAt(0).toUpperCase() + path.slice(1); - } - return path; - } - - function update() { - var row = fileTableView.currentRow; - - if (row === -1) { - if (!root.selectDirectory) { - currentSelection.text = ""; - currentSelectionIsFolder = false; - } - return; - } - - currentSelectionUrl = helper.pathToUrl(fileTableView.model.get(row).filePath); - currentSelectionIsFolder = fileTableView.model !== filesModel ? - fileTableView.model.isFolder(row) : - fileTableModel.isFolder(row); - if (root.selectDirectory || !currentSelectionIsFolder) { - currentSelection.text = capitalizeDrive(helper.urlToPath(currentSelectionUrl)); - } else { - currentSelection.text = ""; - } - } - - function navigateUp() { - if (fileTableModel.parentFolder && fileTableModel.parentFolder !== "") { - fileTableModel.folder = fileTableModel.parentFolder; - return true; - } - } - - function navigateHome() { - fileTableModel.folder = homeDestination; - return true; - } - - function clearSelection() { - fileTableView.selection.clear(); - fileTableView.currentRow = -1; - update(); - } - } - - FolderListModel { - id: folderListModel - nameFilters: selectionType.currentFilter - showDirsFirst: true - showDotAndDotDot: false - showFiles: !root.selectDirectory - showHidden: root.showHidden - Component.onCompleted: { - showFiles = !root.selectDirectory - showHidden = root.showHidden - } - - onFolderChanged: { - d.clearSelection(); - fileTableModel.update(); // Update once the data from the folder change is available. - } - - function getItem(index, field) { - return get(index, field); - } - } - - ListModel { - // Emulates FolderListModel but contains drive data. - id: driveListModel - - property int count: 1 - - Component.onCompleted: initialize(); - - function initialize() { - var drive, - i; - - count = drives.length; - - for (i = 0; i < count; i++) { - drive = drives[i].slice(0, -1); // Remove trailing "/". - append({ - fileName: drive, - fileModified: new Date(0), - fileSize: 0, - filePath: drive + "/", - fileIsDir: true, - fileNameSort: drive.toLowerCase() - }); - } - } - - function getItem(index, field) { - return get(index)[field]; - } - } - - Component { - id: filesModelBuilder - ListModel { } - } - - QtObject { - id: fileTableModel - - // FolderListModel has a couple of problems: - // 1) Files and directories sort case-sensitively: https://bugreports.qt.io/browse/QTBUG-48757 - // 2) Cannot browse up to the "computer" level to view Windows drives: https://bugreports.qt.io/browse/QTBUG-42901 - // - // To solve these problems an intermediary ListModel is used that implements proper sorting and can be populated with - // drive information when viewing at the computer level. - - property var folder - property int sortOrder: Qt.AscendingOrder - property int sortColumn: 0 - property var model: folderListModel - property string parentFolder: calculateParentFolder(); - - readonly property string rootFolder: "file:///" - - function calculateParentFolder() { - if (model === folderListModel) { - if (folderListModel.parentFolder.toString() === "" && driveListModel.count > 1) { - return rootFolder; - } else { - return folderListModel.parentFolder; - } - } else { - return ""; - } - } - - onFolderChanged: { - if (folder === rootFolder) { - model = driveListModel; - helper.monitorDirectory(""); - update(); - } else { - var needsUpdate = model === driveListModel && folder === folderListModel.folder; - - model = folderListModel; - folderListModel.folder = folder; - helper.monitorDirectory(helper.urlToPath(folder)); - - if (needsUpdate) { - update(); - } - } - } - - function isFolder(row) { - if (row === -1) { - return false; - } - return filesModel.get(row).fileIsDir; - } - - function get(row) { - return filesModel.get(row) - } - - function update() { - var dataFields = ["fileName", "fileModified", "fileSize"], - sortFields = ["fileNameSort", "fileModified", "fileSize"], - dataField = dataFields[sortColumn], - sortField = sortFields[sortColumn], - sortValue, - fileName, - fileIsDir, - comparisonFunction, - lower, - middle, - upper, - rows = 0, - i; - - filesModel = filesModelBuilder.createObject(root); - - comparisonFunction = sortOrder === Qt.AscendingOrder - ? function(a, b) { return a < b; } - : function(a, b) { return a > b; } - - for (i = 0; i < model.count; i++) { - fileName = model.getItem(i, "fileName"); - fileIsDir = model.getItem(i, "fileIsDir"); - - sortValue = model.getItem(i, dataField); - if (dataField === "fileName") { - // Directories first by prefixing a "*". - // Case-insensitive. - sortValue = (fileIsDir ? "*" : "") + sortValue.toLowerCase(); - } - - lower = 0; - upper = rows; - while (lower < upper) { - middle = Math.floor((lower + upper) / 2); - var lessThan; - if (comparisonFunction(sortValue, filesModel.get(middle)[sortField])) { - lessThan = true; - upper = middle; - } else { - lessThan = false; - lower = middle + 1; - } - } - - filesModel.insert(lower, { - fileName: fileName, - fileModified: (fileIsDir ? new Date(0) : model.getItem(i, "fileModified")), - fileSize: model.getItem(i, "fileSize"), - filePath: model.getItem(i, "filePath"), - fileIsDir: fileIsDir, - fileNameSort: (fileIsDir ? "*" : "") + fileName.toLowerCase() - }); - - rows++; - } - } - } - - Table { - id: fileTableView - colorScheme: hifi.colorSchemes.light - anchors { - top: navControls.bottom - topMargin: hifi.dimensions.contentSpacing.y - left: parent.left - right: parent.right - bottom: currentSelection.top - bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height - } - headerVisible: !selectDirectory - onDoubleClicked: navigateToRow(row); - Keys.onReturnPressed: navigateToCurrentRow(); - Keys.onEnterPressed: navigateToCurrentRow(); - - sortIndicatorColumn: 0 - sortIndicatorOrder: Qt.AscendingOrder - sortIndicatorVisible: true - - model: filesModel - - function updateSort() { - fileTableModel.sortOrder = sortIndicatorOrder; - fileTableModel.sortColumn = sortIndicatorColumn; - fileTableModel.update(); - } - - onSortIndicatorColumnChanged: { updateSort(); } - - onSortIndicatorOrderChanged: { updateSort(); } - - itemDelegate: Item { - clip: true - - FiraSansSemiBold { - text: getText(); - elide: styleData.elideMode - anchors { - left: parent.left - leftMargin: hifi.dimensions.tablePadding - right: parent.right - rightMargin: hifi.dimensions.tablePadding - verticalCenter: parent.verticalCenter - } - size: hifi.fontSizes.tableText - color: hifi.colors.baseGrayHighlight - font.family: (styleData.row !== -1 && fileTableView.model.get(styleData.row).fileIsDir) - ? "Fira Sans SemiBold" : "Fira Sans" - - function getText() { - if (styleData.row === -1) { - return styleData.value; - } - - switch (styleData.column) { - case 1: return fileTableView.model.get(styleData.row).fileIsDir ? "" : styleData.value; - case 2: return fileTableView.model.get(styleData.row).fileIsDir ? "" : formatSize(styleData.value); - default: return styleData.value; - } - } - function formatSize(size) { - var suffixes = [ "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" ]; - var suffixIndex = 0 - while ((size / 1024.0) > 1.1) { - size /= 1024.0; - ++suffixIndex; - } - - size = Math.round(size*1000)/1000; - size = size.toLocaleString() - - return size + " " + suffixes[suffixIndex]; - } - } - } - - TableViewColumn { - id: fileNameColumn - role: "fileName" - title: "Name" - width: (selectDirectory ? 1.0 : 0.5) * fileTableView.width - movable: false - resizable: true - } - TableViewColumn { - id: fileModifiedColumn - role: "fileModified" - title: "Date" - width: 0.3 * fileTableView.width - movable: false - resizable: true - visible: !selectDirectory - } - TableViewColumn { - role: "fileSize" - title: "Size" - width: fileTableView.width - fileNameColumn.width - fileModifiedColumn.width - movable: false - resizable: true - visible: !selectDirectory - } - - function navigateToRow(row) { - currentRow = row; - navigateToCurrentRow(); - } - - function navigateToCurrentRow() { - var currentModel = fileTableView.model !== filesModel ? fileTableView.model : fileTableModel - var row = fileTableView.currentRow - var isFolder = currentModel.isFolder(row); - var file = currentModel.get(row).filePath; - if (isFolder) { - currentModel.folder = helper.pathToUrl(file); - } else { - okAction.trigger(); - } - } - - property string prefix: "" - - function addToPrefix(event) { - if (!event.text || event.text === "") { - return false; - } - var newPrefix = prefix + event.text.toLowerCase(); - var matchedIndex = -1; - for (var i = 0; i < model.count; ++i) { - var name = model !== filesModel ? model.get(i).fileName.toLowerCase() : - filesModel.get(i).fileName.toLowerCase(); - if (0 === name.indexOf(newPrefix)) { - matchedIndex = i; - break; - } - } - - if (matchedIndex !== -1) { - fileTableView.selection.clear(); - fileTableView.selection.select(matchedIndex); - fileTableView.currentRow = matchedIndex; - fileTableView.prefix = newPrefix; - } - prefixClearTimer.restart(); - return true; - } - - Timer { - id: prefixClearTimer - interval: 1000 - repeat: false - running: false - onTriggered: fileTableView.prefix = ""; - } - - Keys.onPressed: { - switch (event.key) { - case Qt.Key_Backspace: - case Qt.Key_Tab: - case Qt.Key_Backtab: - event.accepted = false; - break; - case Qt.Key_Escape: - event.accepted = true; - root.click(OriginalDialogs.StandardButton.Cancel); - break; - default: - if (addToPrefix(event)) { - event.accepted = true - } else { - event.accepted = false; - } - break; - } - } - - KeyNavigation.tab: root.saveDialog ? currentSelection : openButton - } - - TextField { - id: currentSelection - label: selectDirectory ? "Directory:" : "File name:" - anchors { - left: parent.left - right: selectionType.visible ? selectionType.left: parent.right - rightMargin: selectionType.visible ? hifi.dimensions.contentSpacing.x : 0 - bottom: keyboard.top - bottomMargin: hifi.dimensions.contentSpacing.y - } - readOnly: !root.saveDialog - activeFocusOnTab: !readOnly - onActiveFocusChanged: if (activeFocus) { selectAll(); } - onAccepted: okAction.trigger(); - KeyNavigation.up: fileTableView.contentItem - KeyNavigation.down: openButton - KeyNavigation.tab: openButton - KeyNavigation.backtab: fileTableView.contentItem - } - - FileTypeSelection { - id: selectionType - anchors { - top: currentSelection.top - left: buttonRow.left - right: parent.right - } - visible: !selectDirectory && filtersCount > 1 - } - - Keyboard { - id: keyboard - raised: parent.keyboardEnabled && parent.keyboardRaised - numeric: parent.punctuationMode - anchors { - left: parent.left - right: parent.right - bottom: buttonRow.top - bottomMargin: visible ? hifi.dimensions.contentSpacing.y : 0 - } - } - - Row { - id: buttonRow - anchors { - right: parent.right - bottom: parent.bottom - } - spacing: hifi.dimensions.contentSpacing.y - - Button { - id: openButton - color: hifi.buttons.blue - action: okAction - Keys.onReturnPressed: okAction.trigger() - KeyNavigation.right: cancelButton - KeyNavigation.up: root.saveDialog ? currentSelection : fileTableView.contentItem - KeyNavigation.tab: cancelButton - } - - Button { - id: cancelButton - action: cancelAction - Keys.onReturnPressed: { cancelAction.trigger() } - KeyNavigation.left: openButton - KeyNavigation.up: root.saveDialog ? currentSelection : fileTableView.contentItem - KeyNavigation.backtab: openButton - } - } - - Action { - id: okAction - text: currentSelection.text ? (root.selectDirectory && fileTableView.currentRow === -1 ? "Choose" : (root.saveDialog ? "Save" : "Open")) : "Open" - enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false - onTriggered: { - if (!root.selectDirectory && !d.currentSelectionIsFolder - || root.selectDirectory && fileTableView.currentRow === -1) { - okActionTimer.start(); - } else { - fileTableView.navigateToCurrentRow(); - } - } - } - - Timer { - id: okActionTimer - interval: 50 - running: false - repeat: false - onTriggered: { - if (!root.saveDialog) { - selectedFile(d.currentSelectionUrl); - root.destroy() - return; - } - - // Handle the ambiguity between different cases - // * typed name (with or without extension) - // * full path vs relative vs filename only - var selection = helper.saveHelper(currentSelection.text, root.dir, selectionType.currentFilter); - - if (!selection) { - desktop.messageBox({ icon: OriginalDialogs.StandardIcon.Warning, text: "Unable to parse selection" }) - return; - } - - if (helper.urlIsDir(selection)) { - root.dir = selection; - currentSelection.text = ""; - return; - } - - // Check if the file is a valid target - if (!helper.urlIsWritable(selection)) { - desktop.messageBox({ - icon: OriginalDialogs.StandardIcon.Warning, - text: "Unable to write to location " + selection - }) - return; - } - - if (helper.urlExists(selection)) { - var messageBox = desktop.messageBox({ - icon: OriginalDialogs.StandardIcon.Question, - buttons: OriginalDialogs.StandardButton.Yes | OriginalDialogs.StandardButton.No, - text: "Do you wish to overwrite " + selection + "?", - }); - var result = messageBox.exec(); - if (OriginalDialogs.StandardButton.Yes !== result) { - return; - } - } - - console.log("Selecting " + selection) - selectedFile(selection); - root.destroy(); - } - } - - Action { - id: cancelAction - text: "Cancel" - onTriggered: { canceled(); root.shown = false; } - } - } - - Keys.onPressed: { - switch (event.key) { - case Qt.Key_Backspace: - event.accepted = d.navigateUp(); - break; - - case Qt.Key_Home: - event.accepted = d.navigateHome(); - break; - - case Qt.Key_Escape: - event.accepted = true; - root.click(OriginalDialogs.StandardButton.Cancel); - break; - } - } -} diff --git a/interface/resources/qml/dialogs/+android/QueryDialog.qml b/interface/resources/qml/dialogs/+android/QueryDialog.qml deleted file mode 100644 index aec6d8a286..0000000000 --- a/interface/resources/qml/dialogs/+android/QueryDialog.qml +++ /dev/null @@ -1,231 +0,0 @@ -// -// QueryDialog.qml -// -// Created by Bradley Austin Davis on 22 Jan 2016 -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -import QtQuick 2.7 -import QtQuick.Controls 1.4 - -import "../controls-uit" -import "../styles-uit" -import "../windows" - -ModalWindow { - id: root - HifiConstants { id: hifi } - implicitWidth: 640 - implicitHeight: 320 - visible: true - keyboardOverride: true // Disable ModalWindow's keyboard. - - signal selected(var result); - signal canceled(); - - property int icon: hifi.icons.none - property string iconText: "" - property int iconSize: 35 - onIconChanged: updateIcon(); - - property var items; - property string label - property var result; - property alias current: textResult.text - - // For text boxes - property alias placeholderText: textResult.placeholderText - - // For combo boxes - property bool editable: true; - - property int titleWidth: 0 - onTitleWidthChanged: d.resize(); - - property bool keyboardEnabled: false - property bool keyboardRaised: false - property bool punctuationMode: false - - onKeyboardRaisedChanged: d.resize(); - - function updateIcon() { - if (!root) { - return; - } - iconText = hifi.glyphForIcon(root.icon); - } - - Item { - id: modalWindowItem - clip: true - width: pane.width - height: pane.height - anchors.margins: 0 - - QtObject { - id: d - readonly property int minWidth: 480 - readonly property int maxWdith: 1280 - readonly property int minHeight: 120 - readonly property int maxHeight: 720 - - function resize() { - var targetWidth = Math.max(titleWidth, pane.width) - var targetHeight = (items ? comboBox.controlHeight : textResult.controlHeight) + 5 * hifi.dimensions.contentSpacing.y + buttons.height - root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth); - root.height = ((targetHeight < d.minHeight) ? d.minHeight : ((targetHeight > d.maxHeight) ? d.maxHeight : targetHeight)) + ((keyboardEnabled && keyboardRaised) ? (keyboard.raisedHeight + 2 * hifi.dimensions.contentSpacing.y) : 0) - } - } - - Item { - anchors { - top: parent.top - bottom: keyboard.top; - left: parent.left; - right: parent.right; - margins: 0 - bottomMargin: 2 * hifi.dimensions.contentSpacing.y - } - - // FIXME make a text field type that can be bound to a history for autocompletion - TextField { - id: textResult - label: root.label - visible: items ? false : true - anchors { - left: parent.left; - right: parent.right; - bottom: parent.bottom - } - KeyNavigation.down: acceptButton - KeyNavigation.tab: acceptButton - } - - ComboBox { - id: comboBox - label: root.label - visible: items ? true : false - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - } - model: items ? items : [] - KeyNavigation.down: acceptButton - KeyNavigation.tab: acceptButton - } - } - - property alias keyboardOverride: root.keyboardOverride - property alias keyboardRaised: root.keyboardRaised - property alias punctuationMode: root.punctuationMode - Keyboard { - id: keyboard - raised: keyboardEnabled && keyboardRaised - numeric: punctuationMode - anchors { - left: parent.left - right: parent.right - bottom: buttons.top - bottomMargin: raised ? 2 * hifi.dimensions.contentSpacing.y : 0 - } - } - - Flow { - id: buttons - spacing: hifi.dimensions.contentSpacing.x - onHeightChanged: d.resize(); onWidthChanged: d.resize(); - layoutDirection: Qt.RightToLeft - anchors { - bottom: parent.bottom - right: parent.right - margins: 0 - bottomMargin: hifi.dimensions.contentSpacing.y - } - Button { - id: cancelButton - action: cancelAction - KeyNavigation.left: acceptButton - KeyNavigation.up: items ? comboBox : textResult - KeyNavigation.backtab: acceptButton - } - Button { - id: acceptButton - action: acceptAction - KeyNavigation.right: cancelButton - KeyNavigation.up: items ? comboBox : textResult - KeyNavigation.tab: cancelButton - KeyNavigation.backtab: items ? comboBox : textResult - } - } - - Action { - id: cancelAction - text: qsTr("Cancel"); - shortcut: "Esc" - onTriggered: { - root.canceled(); - // FIXME we are leaking memory to avoid a crash - // root.destroy(); - - root.disableFade = true - visible = false; - } - } - - Action { - id: acceptAction - text: qsTr("OK"); - shortcut: "Return" - onTriggered: { - root.result = items ? comboBox.currentText : textResult.text - root.selected(root.result); - // FIXME we are leaking memory to avoid a crash - // root.destroy(); - - root.disableFade = true - visible = false; - } - } - } - - Keys.onPressed: { - if (!visible) { - return - } - - switch (event.key) { - case Qt.Key_Escape: - case Qt.Key_Back: - cancelAction.trigger() - event.accepted = true; - break; - - case Qt.Key_Return: - case Qt.Key_Enter: - if (acceptButton.focus) { - acceptAction.trigger() - } else if (cancelButton.focus) { - cancelAction.trigger() - } else if (comboBox.focus || comboBox.popup.focus) { - comboBox.showList() - } - event.accepted = true; - break; - } - } - - Component.onCompleted: { - keyboardEnabled = HMD.active; - updateIcon(); - d.resize(); - if (items) { - comboBox.forceActiveFocus() - } else { - textResult.forceActiveFocus() - } - } -} diff --git a/interface/resources/qml/dialogs/assetDialog/+android/AssetDialogContent.qml b/interface/resources/qml/dialogs/assetDialog/+android/AssetDialogContent.qml deleted file mode 100644 index 54bdb0a888..0000000000 --- a/interface/resources/qml/dialogs/assetDialog/+android/AssetDialogContent.qml +++ /dev/null @@ -1,533 +0,0 @@ -// -// AssetDialogContent.qml -// -// Created by David Rowe on 19 Apr 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 -// - -import QtQuick 2.7 -import QtQuick.Controls 1.5 - -import "../../controls-uit" -import "../../styles-uit" - -import "../fileDialog" - -Item { - // Set from OffscreenUi::assetDialog() - property alias dir: assetTableModel.folder - property alias filter: selectionType.filtersString // FIXME: Currently only supports simple filters, "*.xxx". - property int options // Not used. - - property bool selectDirectory: false - - // Not implemented. - //property bool saveDialog: false; - //property bool multiSelect: false; - - property bool singleClickNavigate: false - - HifiConstants { id: hifi } - - Component.onCompleted: { - homeButton.destination = dir; - - if (selectDirectory) { - d.currentSelectionIsFolder = true; - d.currentSelectionPath = assetTableModel.folder; - } - - assetTableView.forceActiveFocus(); - } - - Item { - id: assetDialogItem - anchors.fill: parent - clip: true - - MouseArea { - // Clear selection when click on internal unused area. - anchors.fill: parent - drag.target: root - onClicked: { - d.clearSelection(); - frame.forceActiveFocus(); - assetTableView.forceActiveFocus(); - } - } - - Row { - id: navControls - anchors { - top: parent.top - topMargin: hifi.dimensions.contentMargin.y - left: parent.left - } - spacing: hifi.dimensions.contentSpacing.x - - GlyphButton { - id: upButton - glyph: hifi.glyphs.levelUp - width: height - size: 30 - enabled: assetTableModel.parentFolder !== "" - onClicked: d.navigateUp(); - } - - GlyphButton { - id: homeButton - property string destination: "" - glyph: hifi.glyphs.home - size: 28 - width: height - enabled: destination !== "" - //onClicked: d.navigateHome(); - onClicked: assetTableModel.folder = destination; - } - } - - ComboBox { - id: pathSelector - anchors { - top: parent.top - topMargin: hifi.dimensions.contentMargin.y - left: navControls.right - leftMargin: hifi.dimensions.contentSpacing.x - right: parent.right - } - z: 10 - - property string lastValidFolder: assetTableModel.folder - - function calculatePathChoices(folder) { - var folders = folder.split("/"), - choices = [], - i, length; - - if (folders[folders.length - 1] === "") { - folders.pop(); - } - - choices.push(folders[0]); - - for (i = 1, length = folders.length; i < length; i++) { - choices.push(choices[i - 1] + "/" + folders[i]); - } - - if (folders[0] === "") { - choices[0] = "/"; - } - - choices.reverse(); - - if (choices.length > 0) { - pathSelector.model = choices; - } - } - - onLastValidFolderChanged: { - var folder = lastValidFolder; - calculatePathChoices(folder); - } - - onCurrentTextChanged: { - var folder = currentText; - - if (folder !== "/") { - folder += "/"; - } - - if (folder !== assetTableModel.folder) { - if (root.selectDirectory) { - currentSelection.text = currentText; - d.currentSelectionPath = currentText; - } - assetTableModel.folder = folder; - assetTableView.forceActiveFocus(); - } - } - } - - QtObject { - id: d - - property string currentSelectionPath - property bool currentSelectionIsFolder - property var tableViewConnection: Connections { target: assetTableView; onCurrentRowChanged: d.update(); } - - function update() { - var row = assetTableView.currentRow; - - if (row === -1) { - if (!root.selectDirectory) { - currentSelection.text = ""; - currentSelectionIsFolder = false; - } - return; - } - - var rowInfo = assetTableModel.get(row); - currentSelectionPath = rowInfo.filePath; - currentSelectionIsFolder = rowInfo.fileIsDir; - if (root.selectDirectory || !currentSelectionIsFolder) { - currentSelection.text = currentSelectionPath; - } else { - currentSelection.text = ""; - } - } - - function navigateUp() { - if (assetTableModel.parentFolder !== "") { - assetTableModel.folder = assetTableModel.parentFolder; - return true; - } - return false; - } - - function navigateHome() { - assetTableModel.folder = homeButton.destination; - return true; - } - - function clearSelection() { - assetTableView.selection.clear(); - assetTableView.currentRow = -1; - update(); - } - } - - ListModel { - id: assetTableModel - - property string folder - property string parentFolder: "" - readonly property string rootFolder: "/" - - onFolderChanged: { - parentFolder = calculateParentFolder(); - update(); - } - - function calculateParentFolder() { - if (folder !== "/") { - return folder.slice(0, folder.slice(0, -1).lastIndexOf("/") + 1); - } - return ""; - } - - function isFolder(row) { - if (row === -1) { - return false; - } - return get(row).fileIsDir; - } - - function onGetAllMappings(error, map) { - var mappings, - fileTypeFilter, - index, - path, - fileName, - fileType, - fileIsDir, - isValid, - subDirectory, - subDirectories = [], - fileNameSort, - rows = 0, - lower, - middle, - upper, - i, - length; - - clear(); - - if (error === "") { - mappings = Object.keys(map); - fileTypeFilter = filter.replace("*", "").toLowerCase(); - - for (i = 0, length = mappings.length; i < length; i++) { - index = mappings[i].lastIndexOf("/"); - - path = mappings[i].slice(0, mappings[i].lastIndexOf("/") + 1); - fileName = mappings[i].slice(path.length); - fileType = fileName.slice(fileName.lastIndexOf(".")); - fileIsDir = false; - isValid = false; - - if (fileType.toLowerCase() === fileTypeFilter) { - if (path === folder) { - isValid = !selectDirectory; - } else if (path.length > folder.length) { - subDirectory = path.slice(folder.length); - index = subDirectory.indexOf("/"); - if (index === subDirectory.lastIndexOf("/")) { - fileName = subDirectory.slice(0, index); - if (subDirectories.indexOf(fileName) === -1) { - fileIsDir = true; - isValid = true; - subDirectories.push(fileName); - } - } - } - } - - if (isValid) { - fileNameSort = (fileIsDir ? "*" : "") + fileName.toLowerCase(); - - lower = 0; - upper = rows; - while (lower < upper) { - middle = Math.floor((lower + upper) / 2); - var lessThan; - if (fileNameSort < get(middle)["fileNameSort"]) { - lessThan = true; - upper = middle; - } else { - lessThan = false; - lower = middle + 1; - } - } - - insert(lower, { - fileName: fileName, - filePath: path + (fileIsDir ? "" : fileName), - fileIsDir: fileIsDir, - fileNameSort: fileNameSort - }); - - rows++; - } - } - - } else { - console.log("Error getting mappings from Asset Server"); - } - } - - function update() { - d.clearSelection(); - clear(); - Assets.getAllMappings(onGetAllMappings); - } - } - - Table { - id: assetTableView - colorScheme: hifi.colorSchemes.light - anchors { - top: navControls.bottom - topMargin: hifi.dimensions.contentSpacing.y - left: parent.left - right: parent.right - bottom: currentSelection.top - bottomMargin: hifi.dimensions.contentSpacing.y + currentSelection.controlHeight - currentSelection.height - } - - model: assetTableModel - - focus: true - - onClicked: { - if (singleClickNavigate) { - navigateToRow(row); - } - } - - onDoubleClicked: navigateToRow(row); - Keys.onReturnPressed: navigateToCurrentRow(); - Keys.onEnterPressed: navigateToCurrentRow(); - - itemDelegate: Item { - clip: true - - FiraSansSemiBold { - text: styleData.value - elide: styleData.elideMode - anchors { - left: parent.left - leftMargin: hifi.dimensions.tablePadding - right: parent.right - rightMargin: hifi.dimensions.tablePadding - verticalCenter: parent.verticalCenter - } - size: hifi.fontSizes.tableText - color: hifi.colors.baseGrayHighlight - font.family: (styleData.row !== -1 && assetTableView.model.get(styleData.row).fileIsDir) - ? "Fira Sans SemiBold" : "Fira Sans" - } - } - - TableViewColumn { - id: fileNameColumn - role: "fileName" - title: "Name" - width: assetTableView.width - movable: false - resizable: false - } - - function navigateToRow(row) { - currentRow = row; - navigateToCurrentRow(); - } - - function navigateToCurrentRow() { - if (model.isFolder(currentRow)) { - model.folder = model.get(currentRow).filePath; - } else { - okAction.trigger(); - } - } - - Timer { - id: prefixClearTimer - interval: 1000 - repeat: false - running: false - onTriggered: assetTableView.prefix = ""; - } - - property string prefix: "" - - function addToPrefix(event) { - if (!event.text || event.text === "") { - return false; - } - var newPrefix = prefix + event.text.toLowerCase(); - var matchedIndex = -1; - for (var i = 0; i < model.count; ++i) { - var name = model.get(i).fileName.toLowerCase(); - if (0 === name.indexOf(newPrefix)) { - matchedIndex = i; - break; - } - } - - if (matchedIndex !== -1) { - assetTableView.selection.clear(); - assetTableView.selection.select(matchedIndex); - assetTableView.currentRow = matchedIndex; - assetTableView.prefix = newPrefix; - } - prefixClearTimer.restart(); - return true; - } - - Keys.onPressed: { - switch (event.key) { - case Qt.Key_Backspace: - case Qt.Key_Tab: - case Qt.Key_Backtab: - event.accepted = false; - break; - - default: - if (addToPrefix(event)) { - event.accepted = true - } else { - event.accepted = false; - } - break; - } - } - } - - TextField { - id: currentSelection - label: selectDirectory ? "Directory:" : "File name:" - anchors { - left: parent.left - right: selectionType.visible ? selectionType.left: parent.right - rightMargin: selectionType.visible ? hifi.dimensions.contentSpacing.x : 0 - bottom: buttonRow.top - bottomMargin: hifi.dimensions.contentSpacing.y - } - readOnly: true - activeFocusOnTab: !readOnly - onActiveFocusChanged: if (activeFocus) { selectAll(); } - onAccepted: okAction.trigger(); - } - - FileTypeSelection { - id: selectionType - anchors { - top: currentSelection.top - left: buttonRow.left - right: parent.right - } - visible: !selectDirectory && filtersCount > 1 - KeyNavigation.left: assetTableView - KeyNavigation.right: openButton - } - - Action { - id: okAction - text: currentSelection.text && root.selectDirectory && assetTableView.currentRow === -1 ? "Choose" : "Open" - enabled: currentSelection.text || !root.selectDirectory && d.currentSelectionIsFolder ? true : false - onTriggered: { - if (!root.selectDirectory && !d.currentSelectionIsFolder - || root.selectDirectory && assetTableView.currentRow === -1) { - selectedAsset(d.currentSelectionPath); - root.destroy(); - } else { - assetTableView.navigateToCurrentRow(); - } - } - } - - Action { - id: cancelAction - text: "Cancel" - onTriggered: { - canceled(); - root.destroy(); - } - } - - Row { - id: buttonRow - anchors { - right: parent.right - bottom: parent.bottom - } - spacing: hifi.dimensions.contentSpacing.y - - Button { - id: openButton - color: hifi.buttons.blue - action: okAction - Keys.onReturnPressed: okAction.trigger() - KeyNavigation.up: selectionType - KeyNavigation.left: selectionType - KeyNavigation.right: cancelButton - } - - Button { - id: cancelButton - action: cancelAction - KeyNavigation.up: selectionType - KeyNavigation.left: openButton - KeyNavigation.right: assetTableView.contentItem - Keys.onReturnPressed: { canceled(); root.enabled = false } - } - } - } - - Keys.onPressed: { - switch (event.key) { - case Qt.Key_Backspace: - event.accepted = d.navigateUp(); - break; - - case Qt.Key_Home: - event.accepted = d.navigateHome(); - break; - - } - } -} diff --git a/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml b/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml index 89e1096a04..b2c334b674 100644 --- a/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml @@ -26,11 +26,9 @@ Preference { preference.save(); } - Item { + Row { id: control anchors { - left: parent.left - right: parent.right bottom: parent.bottom } height: Math.max(spinnerLabel.height, spinner.controlHeight) @@ -40,15 +38,14 @@ Preference { text: root.label + ":" colorScheme: hifi.colorSchemes.dark anchors { - left: parent.left - right: spinner.left - rightMargin: hifi.dimensions.labelPadding verticalCenter: parent.verticalCenter } horizontalAlignment: Text.AlignRight wrapMode: Text.Wrap } + spacing: hifi.dimensions.labelPadding + SpinBox { id: spinner decimals: preference.decimals @@ -56,7 +53,6 @@ Preference { maximumValue: preference.max width: 100 anchors { - right: parent.right verticalCenter: parent.verticalCenter } colorScheme: hifi.colorSchemes.dark diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml index 731acc7e5b..126e62fc30 100644 --- a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -56,8 +56,8 @@ Preference { id: slider value: preference.value width: 100 - minimumValue: MyAvatar.getDomainMinScale() - maximumValue: MyAvatar.getDomainMaxScale() + minimumValue: preference.min + maximumValue: preference.max stepSize: preference.step onValueChanged: { spinner.realValue = value @@ -74,8 +74,8 @@ Preference { id: spinner decimals: preference.decimals realValue: preference.value - minimumValue: MyAvatar.getDomainMinScale() - maximumValue: MyAvatar.getDomainMaxScale() + minimumValue: preference.min + maximumValue: preference.max width: 100 onValueChanged: { slider.value = realValue; diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml new file mode 100644 index 0000000000..10e71062ac --- /dev/null +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -0,0 +1,840 @@ +import QtQuick 2.6 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQml.Models 2.1 +import QtGraphicalEffects 1.0 +import "../controls-uit" as HifiControls +import "../styles-uit" +import "avatarapp" + +Rectangle { + id: root + width: 480 + height: 706 + + property bool keyboardEnabled: true + property bool keyboardRaised: false + property bool punctuationMode: false + + HifiControls.Keyboard { + id: keyboard + z: 1000 + raised: parent.keyboardEnabled && parent.keyboardRaised + numeric: parent.punctuationMode + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + } + + color: style.colors.white + property string getAvatarsMethod: 'getAvatars' + + signal sendToScript(var message); + function emitSendToScript(message) { + sendToScript(message); + } + + ListModel { // the only purpose of this model is to convert JS object to ListElement + id: currentAvatarModel + dynamicRoles: true; + function makeAvatarEntry(avatarObject) { + clear(); + append(avatarObject); + return get(count - 1); + } + } + + property var jointNames; + property var currentAvatarSettings; + + function fetchAvatarModelName(marketId, avatar) { + var xmlhttp = new XMLHttpRequest(); + var url = "https://highfidelity.com/api/v1/marketplace/items/" + marketId; + xmlhttp.onreadystatechange = function() { + if (xmlhttp.readyState === XMLHttpRequest.DONE && xmlhttp.status === 200) { + try { + var marketResponse = JSON.parse(xmlhttp.responseText.trim()) + + if(marketResponse.status === 'success') { + avatar.modelName = marketResponse.data.title; + } + } + catch(err) { + console.error(err); + } + } + } + xmlhttp.open("GET", url, true); + xmlhttp.send(); + } + + function getAvatarModelName() { + + if(currentAvatar === null) { + return ''; + } + if(currentAvatar.modelName !== undefined) { + return currentAvatar.modelName; + } else { + var marketId = allAvatars.extractMarketId(currentAvatar.avatarUrl); + if(marketId !== '') { + fetchAvatarModelName(marketId, currentAvatar); + } + } + + var avatarUrl = currentAvatar.avatarUrl; + var splitted = avatarUrl.split('/'); + + return splitted[splitted.length - 1]; + } + + property string avatarName: currentAvatar ? currentAvatar.name : '' + property string avatarUrl: currentAvatar ? currentAvatar.thumbnailUrl : null + property bool isAvatarInFavorites: currentAvatar ? allAvatars.findAvatar(currentAvatar.name) !== undefined : false + property int avatarWearablesCount: currentAvatar ? currentAvatar.wearables.count : 0 + property var currentAvatar: null; + function setCurrentAvatar(avatar, bookmarkName) { + var currentAvatarObject = allAvatars.makeAvatarObject(avatar, bookmarkName); + currentAvatar = currentAvatarModel.makeAvatarEntry(currentAvatarObject); + } + + property url externalAvatarThumbnailUrl: '../../images/avatarapp/guy-in-circle.svg' + + function fromScript(message) { + if(message.method === 'initialize') { + jointNames = message.data.jointNames; + emitSendToScript({'method' : getAvatarsMethod}); + } else if(message.method === 'wearableUpdated') { + adjustWearables.refreshWearable(message.entityID, message.wearableIndex, message.properties, message.updateUI); + } else if(message.method === 'wearablesUpdated') { + var wearablesModel = currentAvatar.wearables; + wearablesModel.clear(); + message.wearables.forEach(function(wearable) { + wearablesModel.append(wearable); + }); + adjustWearables.refresh(currentAvatar); + } else if(message.method === 'scaleChanged') { + currentAvatar.avatarScale = message.value; + updateCurrentAvatarInBookmarks(currentAvatar); + } else if(message.method === 'externalAvatarApplied') { + currentAvatar.avatarUrl = message.avatarURL; + currentAvatar.thumbnailUrl = allAvatars.makeThumbnailUrl(message.avatarURL); + currentAvatar.entry.avatarUrl = currentAvatar.avatarUrl; + currentAvatar.modelName = undefined; + updateCurrentAvatarInBookmarks(currentAvatar); + } else if(message.method === 'settingChanged') { + currentAvatarSettings[message.name] = message.value; + } else if(message.method === 'changeSettings') { + currentAvatarSettings = message.settings; + } else if(message.method === 'bookmarkLoaded') { + setCurrentAvatar(message.data.currentAvatar, message.data.name); + var avatarIndex = allAvatars.findAvatarIndex(currentAvatar.name); + allAvatars.move(avatarIndex, 0, 1); + view.setPage(0); + } else if(message.method === 'bookmarkAdded') { + var avatar = allAvatars.findAvatar(message.bookmarkName); + if(avatar !== undefined) { + var avatarObject = allAvatars.makeAvatarObject(message.bookmark, message.bookmarkName); + for(var prop in avatarObject) { + avatar[prop] = avatarObject[prop]; + } + if(currentAvatar.name === message.bookmarkName) { + currentAvatar = currentAvatarModel.makeAvatarEntry(avatarObject); + } + } else { + allAvatars.addAvatarEntry(message.bookmark, message.bookmarkName); + } + updateCurrentAvatarInBookmarks(currentAvatar); + } else if(message.method === 'bookmarkDeleted') { + pageOfAvatars.isUpdating = true; + + var index = pageOfAvatars.findAvatarIndex(message.name); + var absoluteIndex = view.currentPage * view.itemsPerPage + index + + allAvatars.remove(absoluteIndex) + pageOfAvatars.remove(index); + + var itemsOnPage = pageOfAvatars.count; + var newItemIndex = view.currentPage * view.itemsPerPage + itemsOnPage; + + if(newItemIndex <= (allAvatars.count - 1)) { + pageOfAvatars.append(allAvatars.get(newItemIndex)); + } else { + if(!pageOfAvatars.hasGetAvatars()) + pageOfAvatars.appendGetAvatars(); + } + + pageOfAvatars.isUpdating = false; + } else if(message.method === getAvatarsMethod) { + var getAvatarsData = message.data; + allAvatars.populate(getAvatarsData.bookmarks); + setCurrentAvatar(getAvatarsData.currentAvatar, ''); + displayNameInput.text = getAvatarsData.displayName; + currentAvatarSettings = getAvatarsData.currentAvatarSettings; + + updateCurrentAvatarInBookmarks(currentAvatar); + } else if(message.method === 'updateAvatarInBookmarks') { + updateCurrentAvatarInBookmarks(currentAvatar); + } else if(message.method === 'selectAvatarEntity') { + adjustWearables.selectWearableByID(message.entityID); + } + } + + function updateCurrentAvatarInBookmarks(avatar) { + var bookmarkAvatarIndex = allAvatars.findAvatarIndexByValue(avatar); + if(bookmarkAvatarIndex === -1) { + avatar.name = ''; + view.setPage(0); + } else { + var bookmarkAvatar = allAvatars.get(bookmarkAvatarIndex); + avatar.name = bookmarkAvatar.name; + view.selectAvatar(bookmarkAvatar); + } + } + + property bool isInManageState: false + + Component.onCompleted: { + } + + AvatarAppStyle { + id: style + } + + AvatarAppHeader { + id: header + z: 100 + + property string currentPage: "Avatar" + property bool mainPageVisible: !settings.visible && !adjustWearables.visible + + Binding on currentPage { + when: settings.visible + value: "Avatar Settings" + } + Binding on currentPage { + when: adjustWearables.visible + value: "Adjust Wearables" + } + Binding on currentPage { + when: header.mainPageVisible + value: "Avatar" + } + + pageTitle: currentPage + avatarIconVisible: mainPageVisible + settingsButtonVisible: mainPageVisible + onSettingsClicked: { + settings.open(currentAvatarSettings, currentAvatar.avatarScale); + } + } + + Settings { + id: settings + anchors.left: parent.left + anchors.right: parent.right + anchors.top: header.bottom + anchors.bottom: parent.bottom + + z: 3 + + onSaveClicked: function() { + var avatarSettings = { + dominantHand : settings.dominantHandIsLeft ? 'left' : 'right', + collisionsEnabled : settings.avatarCollisionsOn, + animGraphOverrideUrl : settings.avatarAnimationOverrideJSON, + collisionSoundUrl : settings.avatarCollisionSoundUrl + }; + + emitSendToScript({'method' : 'saveSettings', 'settings' : avatarSettings, 'avatarScale': settings.scaleValue}) + + close(); + } + onCancelClicked: function() { + emitSendToScript({'method' : 'revertScale', 'avatarScale' : avatarScaleBackup}); + + close(); + } + + onScaleChanged: { + emitSendToScript({'method' : 'setScale', 'avatarScale' : scale}) + } + } + + AdjustWearables { + id: adjustWearables + anchors.left: parent.left + anchors.right: parent.right + anchors.top: header.bottom + anchors.bottom: parent.bottom + jointNames: root.jointNames + onWearableUpdated: { + emitSendToScript({'method' : 'adjustWearable', 'entityID' : id, 'wearableIndex' : index, 'properties' : properties}) + } + onWearableDeleted: { + emitSendToScript({'method' : 'deleteWearable', 'entityID' : id, 'avatarName' : avatarName}); + } + onAdjustWearablesOpened: { + emitSendToScript({'method' : 'adjustWearablesOpened', 'avatarName' : avatarName}); + } + onAdjustWearablesClosed: { + emitSendToScript({'method' : 'adjustWearablesClosed', 'save' : status, 'avatarName' : avatarName}); + } + onWearableSelected: { + emitSendToScript({'method' : 'selectWearable', 'entityID' : id}); + } + + z: 3 + } + + Rectangle { + id: mainBlock + anchors.left: parent.left + anchors.leftMargin: 30 + anchors.right: parent.right + anchors.rightMargin: 30 + anchors.top: header.bottom + anchors.bottom: favoritesBlock.top + + // TextStyle1 + RalewaySemiBold { + size: 24; + anchors.left: parent.left + anchors.top: parent.top + anchors.topMargin: 34 + } + + // TextStyle1 + RalewaySemiBold { + id: displayNameLabel + size: 24; + anchors.left: parent.left + anchors.top: parent.top + anchors.topMargin: 25 + text: 'Display Name' + } + + InputField { + id: displayNameInput + + font.family: "Fira Sans" + font.pixelSize: 15 + anchors.left: displayNameLabel.right + anchors.leftMargin: 30 + anchors.verticalCenter: displayNameLabel.verticalCenter + anchors.right: parent.right + width: 232 + + text: 'ThisIsDisplayName' + + onEditingFinished: { + emitSendToScript({'method' : 'changeDisplayName', 'displayName' : text}) + focus = false; + } + } + + ShadowImage { + id: avatarImage + width: 134 + height: 134 + anchors.top: displayNameLabel.bottom + anchors.topMargin: 31 + Binding on source { + when: avatarUrl !== '' + value: avatarUrl + } + + visible: avatarImage.status !== Image.Loading && avatarImage.status !== Image.Error + fillMode: Image.PreserveAspectCrop + } + + ShadowImage { + id: customAvatarImage + anchors.fill: avatarImage; + visible: avatarUrl === '' || avatarImage.status === Image.Error + source: externalAvatarThumbnailUrl + } + + ShadowRectangle { + anchors.fill: avatarImage; + color: 'white' + visible: avatarImage.status === Image.Loading + radius: avatarImage.radius + + dropShadowRadius: avatarImage.dropShadowRadius; + dropShadowHorizontalOffset: avatarImage.dropShadowHorizontalOffset + dropShadowVerticalOffset: avatarImage.dropShadowVerticalOffset + + Spinner { + id: spinner + visible: parent.visible + anchors.fill: parent; + } + } + + AvatarWearablesIndicator { + anchors.right: avatarImage.right + anchors.bottom: avatarImage.bottom + anchors.rightMargin: -radius + anchors.bottomMargin: 6.08 + wearablesCount: avatarWearablesCount + visible: avatarWearablesCount !== 0 + } + + RowLayout { + id: star + anchors.top: avatarImage.top + anchors.topMargin: 11 + anchors.left: avatarImage.right + anchors.leftMargin: 30.5 + anchors.right: parent.right + + spacing: 12.3 + + Image { + width: 21.2 + height: 19.3 + source: isAvatarInFavorites ? '../../images/FavoriteIconActive.svg' : '../../images/FavoriteIconInActive.svg' + anchors.verticalCenter: parent.verticalCenter + } + + // TextStyle5 + FiraSansSemiBold { + size: 22; + Layout.fillWidth: true + text: isAvatarInFavorites ? avatarName : "Add to Favorites" + elide: Qt.ElideRight + anchors.verticalCenter: parent.verticalCenter + } + } + + MouseArea { + enabled: !isAvatarInFavorites + anchors.fill: star + onClicked: { + createFavorite.onSaveClicked = function() { + var entry = currentAvatar.entry; + + var wearables = []; + for(var i = 0; i < currentAvatar.wearables.count; ++i) { + wearables.push(currentAvatar.wearables.get(i)); + } + + entry.avatarEntites = wearables; + currentAvatar.name = createFavorite.favoriteNameText; + + emitSendToScript({'method': 'addAvatar', 'name' : currentAvatar.name}); + createFavorite.close(); + } + + var avatarThumbnail = (avatarUrl === '' || avatarImage.status === Image.Error) ? + externalAvatarThumbnailUrl : avatarUrl; + + createFavorite.open(root.currentAvatar.wearables.count, avatarThumbnail); + } + } + + // TextStyle3 + RalewayRegular { + id: avatarNameLabel + size: 22; + text: getAvatarModelName(); + elide: Qt.ElideRight + + anchors.right: linkLabel.left + anchors.left: avatarImage.right + anchors.leftMargin: 30 + anchors.top: star.bottom + anchors.topMargin: 11 + property bool hasMarketId: currentAvatar && allAvatars.extractMarketId(currentAvatar.avatarUrl) !== ''; + + MouseArea { + enabled: avatarNameLabel.hasMarketId + anchors.fill: parent; + onClicked: emitSendToScript({'method' : 'navigate', 'url' : allAvatars.makeMarketItemUrl(currentAvatar.avatarUrl)}) + } + color: hasMarketId ? style.colors.blueHighlight : 'black' + } + + // TextStyle3 + RalewayRegular { + id: wearablesLabel + size: 22; + anchors.left: avatarImage.right + anchors.leftMargin: 30 + anchors.top: avatarNameLabel.bottom + anchors.topMargin: 16 + color: 'black' + text: 'Wearables' + } + + SquareLabel { + id: linkLabel + anchors.right: parent.right + anchors.verticalCenter: avatarNameLabel.verticalCenter + glyphText: "." + glyphSize: 22 + + MouseArea { + anchors.fill: parent + onClicked: { + popup.showSpecifyAvatarUrl(currentAvatar.avatarUrl, function() { + var url = popup.inputText.text; + emitSendToScript({'method' : 'applyExternalAvatar', 'avatarURL' : url}) + }, function(link) { + Qt.openUrlExternally(link); + }); + } + } + } + + SquareLabel { + anchors.right: parent.right + anchors.verticalCenter: wearablesLabel.verticalCenter + glyphText: "\ue02e" + + visible: avatarWearablesCount !== 0 + + MouseArea { + anchors.fill: parent + onClicked: { + adjustWearables.open(currentAvatar); + } + } + } + + // TextStyle3 + RalewayRegular { + size: 22; + anchors.right: parent.right + anchors.verticalCenter: wearablesLabel.verticalCenter + font.underline: true + text: "Add" + color: 'black' + visible: avatarWearablesCount === 0 + + MouseArea { + anchors.fill: parent + onClicked: { + popup.showGetWearables(function() { + emitSendToScript({'method' : 'navigate', 'url' : 'hifi://AvatarIsland/11.5848,-8.10862,-2.80195'}) + }, function(link) { + emitSendToScript({'method' : 'navigate', 'url' : link}) + }); + } + } + } + } + + Rectangle { + id: favoritesBlock + height: 407 + + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + + color: style.colors.lightGrayBackground + + // TextStyle1 + RalewaySemiBold { + id: favoritesLabel + size: 24; + anchors.top: parent.top + anchors.topMargin: 15 + anchors.left: parent.left + anchors.leftMargin: 30 + text: "Favorites" + } + + // TextStyle8 + RalewaySemiBold { + id: manageLabel + color: style.colors.blueHighlight + size: 20; + anchors.top: parent.top + anchors.topMargin: 20 + anchors.right: parent.right + anchors.rightMargin: 30 + text: isInManageState ? "Back" : "Manage" + MouseArea { + anchors.fill: parent + onClicked: { + isInManageState = isInManageState ? false : true; + } + } + } + + Item { + anchors.left: parent.left + anchors.leftMargin: 30 + anchors.right: parent.right + anchors.rightMargin: 0 + + anchors.top: favoritesLabel.bottom + anchors.topMargin: 20 + anchors.bottom: parent.bottom + + GridView { + id: view + anchors.fill: parent + interactive: false; + currentIndex: currentAvatarIndexInBookmarksPage(); + + function currentAvatarIndexInBookmarksPage() { + return (currentAvatar && currentAvatar.name !== '' && !pageOfAvatars.isUpdating) ? pageOfAvatars.findAvatarIndex(currentAvatar.name) : -1; + } + + property int horizontalSpacing: 18 + property int verticalSpacing: 44 + property int thumbnailWidth: 92 + property int thumbnailHeight: 92 + + function selectAvatar(avatar) { + emitSendToScript({'method' : 'selectAvatar', 'name' : avatar.name}) + } + + function deleteAvatar(avatar) { + emitSendToScript({'method' : 'deleteAvatar', 'name' : avatar.name}) + } + + AvatarsModel { + id: allAvatars + } + + property int itemsPerPage: 8 + property int totalPages: Math.ceil((allAvatars.count + 1) / itemsPerPage) + property int currentPage: 0; + onCurrentPageChanged: { + currentIndex = Qt.binding(currentAvatarIndexInBookmarksPage); + } + + property bool hasNext: currentPage < (totalPages - 1) + property bool hasPrev: currentPage > 0 + + function setPage(pageIndex) { + pageOfAvatars.isUpdating = true; + pageOfAvatars.clear(); + var start = pageIndex * itemsPerPage; + var end = Math.min(start + itemsPerPage, allAvatars.count); + + for(var itemIndex = 0; start < end; ++start, ++itemIndex) { + var avatarItem = allAvatars.get(start) + pageOfAvatars.append(avatarItem); + } + + if(pageOfAvatars.count !== itemsPerPage) + pageOfAvatars.appendGetAvatars(); + + currentPage = pageIndex; + pageOfAvatars.isUpdating = false; + } + + model: AvatarsModel { + id: pageOfAvatars + + property bool isUpdating: false; + property var getMoreAvatarsEntry: {'thumbnailUrl' : '', 'name' : '', 'getMoreAvatars' : true} + + function appendGetAvatars() { + append(getMoreAvatarsEntry); + } + + function hasGetAvatars() { + return count != 0 && get(count - 1).getMoreAvatars + } + + function removeGetAvatars() { + if(hasGetAvatars()) { + remove(count - 1) + } + } + } + + flow: GridView.FlowLeftToRight + + cellHeight: thumbnailHeight + verticalSpacing + cellWidth: thumbnailWidth + horizontalSpacing + + delegate: Item { + id: delegateRoot + height: GridView.view.cellHeight + width: GridView.view.cellWidth + + Item { + id: container + width: 92 + height: 92 + + Behavior on y { + NumberAnimation { + duration: 100 + } + } + + states: [ + State { + name: "hovered" + when: favoriteAvatarMouseArea.containsMouse; + PropertyChanges { target: favoriteAvatarMouseArea; anchors.bottomMargin: -5 } + PropertyChanges { target: container; y: -5 } + PropertyChanges { target: favoriteAvatarImage; dropShadowRadius: 10 } + PropertyChanges { target: favoriteAvatarImage; dropShadowVerticalOffset: 6 } + } + ] + + property bool highlighted: delegateRoot.GridView.isCurrentItem + + AvatarThumbnail { + id: favoriteAvatarImage + externalAvatarThumbnailUrl: root.externalAvatarThumbnailUrl + avatarUrl: thumbnailUrl + border.color: container.highlighted ? style.colors.blueHighlight : 'transparent' + border.width: container.highlighted ? 4 : 0 + wearablesCount: { + return !getMoreAvatars ? wearables.count : 0 + } + + visible: !getMoreAvatars + + MouseArea { + id: favoriteAvatarMouseArea + anchors.fill: parent + anchors.margins: 0 + enabled: !container.highlighted + hoverEnabled: enabled + + onClicked: { + if(isInManageState) { + var currentItem = delegateRoot.GridView.view.model.get(index); + popup.showDeleteFavorite(currentItem.name, function() { + view.deleteAvatar(currentItem); + }); + } else { + if(delegateRoot.GridView.view.currentIndex !== index) { + var currentItem = delegateRoot.GridView.view.model.get(index); + popup.showLoadFavorite(currentItem.name, function() { + view.selectAvatar(currentItem); + }); + } + } + } + } + } + + Rectangle { + anchors.fill: favoriteAvatarImage + color: '#AFAFAF' + opacity: 0.4 + radius: 5 + visible: isInManageState && !container.highlighted && !getMoreAvatars + } + + HiFiGlyphs { + anchors.fill: parent + text: "{" + visible: isInManageState && !container.highlighted && !getMoreAvatars + horizontalAlignment: Text.AlignHCenter + size: 56 + } + + ShadowRectangle { + width: 92 + height: 92 + radius: 5 + color: style.colors.blueHighlight + visible: getMoreAvatars && !isInManageState + + HiFiGlyphs { + anchors.centerIn: parent + + color: 'white' + size: 60 + text: "K" + } + + MouseArea { + anchors.fill: parent + + onClicked: { + popup.showBuyAvatars(function() { + emitSendToScript({'method' : 'navigate', 'url' : 'hifi://BodyMart'}) + }, function(link) { + emitSendToScript({'method' : 'navigate', 'url' : link}) + }); + } + } + } + } + + // TextStyle7 + FiraSansRegular { + id: text + size: 18; + lineHeightMode: Text.FixedHeight + lineHeight: 16.9; + width: view.thumbnailWidth + height: view.verticalSpacing + elide: Qt.ElideRight + anchors.top: container.bottom + anchors.topMargin: 8 + anchors.horizontalCenter: container.horizontalCenter + verticalAlignment: Text.AlignTop + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.WrapAtWordBoundaryOrAnywhere + text: getMoreAvatars ? 'Get More Avatars' : name + visible: !getMoreAvatars || !isInManageState + } + } + } + + } + + Row { + anchors.horizontalCenter: parent.horizontalCenter + + Rectangle { + width: 40 + height: 40 + color: 'transparent' + + PageIndicator { + x: 1 + hasNext: view.hasNext + hasPrev: view.hasPrev + onClicked: view.setPage(view.currentPage - 1) + } + } + + spacing: 0 + + Rectangle { + width: 40 + height: 40 + color: 'transparent' + + PageIndicator { + x: -1 + isPrevious: false + hasNext: view.hasNext + hasPrev: view.hasPrev + onClicked: view.setPage(view.currentPage + 1) + } + } + + anchors.bottom: parent.bottom + anchors.bottomMargin: 20 + } + } + + MessageBoxes { + id: popup + } + + CreateFavoriteDialog { + avatars: allAvatars + id: createFavorite + } +} diff --git a/interface/resources/qml/hifi/Feed.qml b/interface/resources/qml/hifi/Feed.qml index d9e93c2fa7..785b586dd2 100644 --- a/interface/resources/qml/hifi/Feed.qml +++ b/interface/resources/qml/hifi/Feed.qml @@ -53,7 +53,7 @@ Column { 'protocol=' + encodeURIComponent(Window.protocolSignature()) ]; endpoint: '/api/v1/user_stories?' + options.join('&'); - itemsPerPage: 3; + itemsPerPage: 4; processPage: function (data) { return data.user_stories.map(makeModelData); }; @@ -86,7 +86,9 @@ Column { tags: tags, description: description, online_users: data.details.connections || data.details.concurrency || 0, - drillDownToPlace: false + // Server currently doesn't give isStacked (undefined). Could give bool. + drillDownToPlace: data.is_stacked || (data.action === 'concurrency'), + isStacked: !!data.is_stacked }; } @@ -104,7 +106,6 @@ Column { highlightMoveDuration: -1; highlightMoveVelocity: -1; currentIndex: -1; - onAtXEndChanged: { if (scroll.atXEnd && !scroll.atXBeginning) { suggestions.getNextPage(); } } spacing: 12; width: parent.width; @@ -124,6 +125,7 @@ Column { onlineUsers: model.online_users; storyId: model.metaverseId; drillDownToPlace: model.drillDownToPlace; + isStacked: model.isStacked; textPadding: root.textPadding; smallMargin: root.smallMargin; diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 8dcb76442b..915213508c 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -61,7 +61,7 @@ Rectangle { 'username'; } sortAscending: connectionsTable.sortIndicatorOrder === Qt.AscendingOrder; - itemsPerPage: 9; + itemsPerPage: 10; listView: connectionsTable; processPage: function (data) { return data.users.map(function (user) { @@ -145,6 +145,22 @@ Rectangle { } pal.sendToScript({method: 'refreshNearby', params: params}); } + function refreshConnections() { + var flickable = connectionsUserModel.flickable; + connectionsRefreshScrollTimer.oldY = flickable.contentY; + flickable.contentY = 0; + connectionsUserModel.getFirstPage('delayRefresh', function () { + connectionsRefreshScrollTimer.start(); + }); + } + Timer { + id: connectionsRefreshScrollTimer; + interval: 500; + property real oldY: 0; + onTriggered: { + connectionsUserModel.flickable.contentY = oldY; + } + } Rectangle { id: palTabContainer; @@ -276,7 +292,10 @@ Rectangle { id: reloadConnections; width: reloadConnections.height; glyph: hifi.glyphs.reload; - onClicked: connectionsUserModel.getFirstPage('delayRefresh'); + onClicked: { + pal.sendToScript({method: 'refreshConnections'}); + refreshConnections(); + } } } // "CONNECTIONS" text @@ -765,7 +784,7 @@ Rectangle { TableViewColumn { id: connectionsUserNameHeader; role: "userName"; - title: connectionsTable.rowCount + (connectionsTable.rowCount === 1 ? " NAME" : " NAMES"); + title: connectionsUserModel.totalEntries + (connectionsUserModel.totalEntries === 1 ? " NAME" : " NAMES"); width: connectionsNameCardWidth; movable: false; resizable: false; @@ -786,14 +805,6 @@ Rectangle { } model: connectionsUserModel; - Connections { - target: connectionsTable.flickableItem; - onAtYEndChanged: { - if (connectionsTable.flickableItem.atYEnd && !connectionsTable.flickableItem.atYBeginning) { - connectionsUserModel.getNextPage(); - } - } - } // This Rectangle refers to each Row in the connectionsTable. rowDelegate: Rectangle { @@ -1108,7 +1119,7 @@ Rectangle { function findNearbySessionIndex(sessionId, optionalData) { // no findIndex in .qml var data = optionalData || nearbyUserModelData, length = data.length; for (var i = 0; i < length; i++) { - if (data[i].sessionId === sessionId) { + if (data[i].sessionId === sessionId.toString()) { return i; } } @@ -1120,7 +1131,7 @@ Rectangle { var data = message.params; var index = -1; iAmAdmin = Users.canKick; - index = findNearbySessionIndex('', data); + index = findNearbySessionIndex("", data); if (index !== -1) { myData = data[index]; data.splice(index, 1); @@ -1197,8 +1208,8 @@ Rectangle { for (var userId in message.params) { var audioLevel = message.params[userId][0]; var avgAudioLevel = message.params[userId][1]; - // If the userId is 0, we're updating "myData". - if (userId == 0) { + // If the userId is "", we're updating "myData". + if (userId === "") { myData.audioLevel = audioLevel; myCard.audioLevel = audioLevel; // Defensive programming myData.avgAudioLevel = avgAudioLevel; @@ -1217,6 +1228,9 @@ Rectangle { case 'clearLocalQMLData': ignored = {}; break; + case 'refreshConnections': + refreshConnections(); + break; case 'avatarDisconnected': var sessionID = message.params[0]; delete ignored[sessionID]; diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index ba50b7f238..cc1ba49984 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -129,12 +129,10 @@ Rectangle { id: stereoMic spacing: muteMic.spacing; text: qsTr("Enable stereo input"); - checked: AudioScriptingInterface.isStereoInput(); + checked: AudioScriptingInterface.isStereoInput; onClicked: { - var success = AudioScriptingInterface.setStereoInput(checked); - if (!success) { - checked = !checked; - } + AudioScriptingInterface.isStereoInput = checked; + checked = Qt.binding(function() { return AudioScriptingInterface.isStereoInput; }); // restore binding } } } diff --git a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml new file mode 100644 index 0000000000..a501185853 --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml @@ -0,0 +1,359 @@ +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit +import "../../controls" as HifiControls + +Rectangle { + id: root; + visible: false; + width: 480 + height: 706 + color: 'white' + + signal wearableUpdated(var id, int index, var properties); + signal wearableSelected(var id); + signal wearableDeleted(string avatarName, var id); + + signal adjustWearablesOpened(var avatarName); + signal adjustWearablesClosed(bool status, var avatarName); + + property bool modified: false; + Component.onCompleted: { + modified = false; + } + + property var jointNames; + property string avatarName: '' + property var wearablesModel; + + function open(avatar) { + adjustWearablesOpened(avatar.name); + + visible = true; + avatarName = avatar.name; + wearablesModel = avatar.wearables; + refresh(avatar); + } + + function refresh(avatar) { + wearablesCombobox.model.clear(); + for(var i = 0; i < avatar.wearables.count; ++i) { + var wearable = avatar.wearables.get(i).properties; + for(var j = (wearable.modelURL.length - 1); j >= 0; --j) { + if(wearable.modelURL[j] === '/') { + wearable.text = wearable.modelURL.substring(j + 1) + ' [%jointIndex%]'.replace('%jointIndex%', jointNames[wearable.parentJointIndex]); + break; + } + } + wearablesCombobox.model.append(wearable); + } + + wearablesCombobox.currentIndex = 0; + } + + function refreshWearable(wearableID, wearableIndex, properties, updateUI) { + if(wearableIndex === -1) { + wearableIndex = wearablesCombobox.model.findIndexById(wearableID); + } + + var wearable = wearablesCombobox.model.get(wearableIndex); + + if(!wearable) { + return; + } + + var wearableModelItemProperties = wearablesModel.get(wearableIndex).properties; + + for(var prop in properties) { + wearable[prop] = properties[prop]; + wearableModelItemProperties[prop] = wearable[prop]; + + if(updateUI) { + if(prop === 'localPosition') { + position.set(wearable[prop]); + } else if(prop === 'localRotationAngles') { + rotation.set(wearable[prop]); + } else if(prop === 'dimensions') { + scalespinner.set(wearable[prop].x / wearable.naturalDimensions.x); + } + } + } + + wearablesModel.setProperty(wearableIndex, 'properties', wearableModelItemProperties); + } + + function getCurrentWearable() { + return wearablesCombobox.model.get(wearablesCombobox.currentIndex) + } + + function selectWearableByID(entityID) { + for(var i = 0; i < wearablesCombobox.model.count; ++i) { + var wearable = wearablesCombobox.model.get(i); + if(wearable.id === entityID) { + wearablesCombobox.currentIndex = i; + break; + } + } + } + + function close(status) { + visible = false; + adjustWearablesClosed(status, avatarName); + } + + HifiConstants { id: hifi } + + // This object is always used in a popup. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + hoverEnabled: true; + } + + Column { + anchors.top: parent.top + anchors.topMargin: 15 + anchors.horizontalCenter: parent.horizontalCenter + + spacing: 20 + width: parent.width - 30 * 2 + + HifiControlsUit.ComboBox { + id: wearablesCombobox + anchors.left: parent.left + anchors.right: parent.right + comboBox.textRole: "text" + + model: ListModel { + function findIndexById(id) { + + for(var i = 0; i < count; ++i) { + var wearable = get(i); + if(wearable.id === id) { + return i; + } + } + + return -1; + } + } + + comboBox.onCurrentIndexChanged: { + var currentWearable = getCurrentWearable(); + + if(currentWearable) { + position.set(currentWearable.localPosition); + rotation.set(currentWearable.localRotationAngles); + scalespinner.set(currentWearable.dimensions.x / currentWearable.naturalDimensions.x) + + wearableSelected(currentWearable.id); + } + } + } + + Column { + width: parent.width + spacing: 5 + + Row { + spacing: 20 + + // TextStyle5 + FiraSansSemiBold { + id: positionLabel + size: 22; + text: "Position" + } + + // TextStyle7 + FiraSansRegular { + size: 18; + lineHeightMode: Text.FixedHeight + lineHeight: 16.9; + text: "m" + anchors.verticalCenter: positionLabel.verticalCenter + } + } + + Vector3 { + id: position + backgroundColor: "lightgray" + + function set(localPosition) { + notify = false; + xvalue = localPosition.x + yvalue = localPosition.y + zvalue = localPosition.z + notify = true; + } + + function notifyPositionChanged() { + modified = true; + var properties = { + localPosition: { 'x' : xvalue, 'y' : yvalue, 'z' : zvalue } + }; + + wearableUpdated(getCurrentWearable().id, wearablesCombobox.currentIndex, properties); + } + + property bool notify: false; + + onXvalueChanged: if(notify) notifyPositionChanged(); + onYvalueChanged: if(notify) notifyPositionChanged(); + onZvalueChanged: if(notify) notifyPositionChanged(); + + decimals: 2 + realFrom: -10 + realTo: 10 + realStepSize: 0.01 + } + } + + Column { + width: parent.width + spacing: 5 + + Row { + spacing: 20 + + // TextStyle5 + FiraSansSemiBold { + id: rotationLabel + size: 22; + text: "Rotation" + } + + // TextStyle7 + FiraSansRegular { + size: 18; + lineHeightMode: Text.FixedHeight + lineHeight: 16.9; + text: "deg" + anchors.verticalCenter: rotationLabel.verticalCenter + } + } + + Vector3 { + id: rotation + backgroundColor: "lightgray" + + function set(localRotationAngles) { + notify = false; + xvalue = localRotationAngles.x + yvalue = localRotationAngles.y + zvalue = localRotationAngles.z + notify = true; + } + + function notifyRotationChanged() { + modified = true; + var properties = { + localRotationAngles: { 'x' : xvalue, 'y' : yvalue, 'z' : zvalue } + }; + + wearableUpdated(getCurrentWearable().id, wearablesCombobox.currentIndex, properties); + } + + property bool notify: false; + + onXvalueChanged: if(notify) notifyRotationChanged(); + onYvalueChanged: if(notify) notifyRotationChanged(); + onZvalueChanged: if(notify) notifyRotationChanged(); + + decimals: 0 + realFrom: -180 + realTo: 180 + realStepSize: 1 + } + } + + Column { + width: parent.width + spacing: 5 + + // TextStyle5 + FiraSansSemiBold { + size: 22; + text: "Scale" + } + + Item { + width: parent.width + height: childrenRect.height + + HifiControlsUit.SpinBox { + id: scalespinner + decimals: 2 + realStepSize: 0.1 + realFrom: 0.1 + realTo: 3.0 + realValue: 1.0 + backgroundColor: "lightgray" + width: position.spinboxWidth + colorScheme: hifi.colorSchemes.light + + property bool notify: false; + onValueChanged: if(notify) notifyScaleChanged(); + + function set(value) { + notify = false; + realValue = value + notify = true; + } + + function notifyScaleChanged() { + modified = true; + var currentWearable = getCurrentWearable(); + var naturalDimensions = currentWearable.naturalDimensions; + + var properties = { + dimensions: { + 'x' : realValue * naturalDimensions.x, + 'y' : realValue * naturalDimensions.y, + 'z' : realValue * naturalDimensions.z + } + }; + + wearableUpdated(currentWearable.id, wearablesCombobox.currentIndex, properties); + } + } + + HifiControlsUit.Button { + fontSize: 18 + height: 40 + anchors.right: parent.right + color: hifi.buttons.red; + colorScheme: hifi.colorSchemes.dark; + text: "TAKE IT OFF" + onClicked: wearableDeleted(root.avatarName, getCurrentWearable().id); + enabled: wearablesCombobox.model.count !== 0 + anchors.verticalCenter: scalespinner.verticalCenter + } + } + + } + } + + DialogButtons { + anchors.bottom: parent.bottom + anchors.bottomMargin: 30 + anchors.left: parent.left + anchors.leftMargin: 30 + anchors.right: parent.right + anchors.rightMargin: 30 + + yesText: "SAVE" + noText: "CANCEL" + + onYesClicked: function() { + root.close(true); + } + + onNoClicked: function() { + root.close(false); + } + } +} diff --git a/interface/resources/qml/hifi/avatarapp/AvatarAppHeader.qml b/interface/resources/qml/hifi/avatarapp/AvatarAppHeader.qml new file mode 100644 index 0000000000..9d9db010fb --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/AvatarAppHeader.qml @@ -0,0 +1,58 @@ +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import "../../styles-uit" + +ShadowRectangle { + id: header + anchors.left: parent.left + anchors.right: parent.right + height: 60 + + property alias pageTitle: title.text + property alias avatarIconVisible: avatarIcon.visible + property alias settingsButtonVisible: settingsButton.visible + + signal settingsClicked; + + AvatarAppStyle { + id: style + } + + color: style.colors.lightGrayBackground + + HiFiGlyphs { + id: avatarIcon + anchors.left: parent.left + anchors.leftMargin: 23 + anchors.verticalCenter: header.verticalCenter + + size: 38 + text: "<" + } + + // TextStyle6 + RalewaySemiBold { + id: title + size: 22; + anchors.left: avatarIcon.visible ? avatarIcon.right : avatarIcon.left + anchors.leftMargin: 4 + anchors.verticalCenter: avatarIcon.verticalCenter + text: 'Avatar' + } + + HiFiGlyphs { + id: settingsButton + anchors.right: parent.right + anchors.rightMargin: 30 + anchors.verticalCenter: avatarIcon.verticalCenter + text: "&" + + MouseArea { + id: settingsMouseArea + anchors.fill: parent + onClicked: { + settingsClicked(); + } + } + } +} diff --git a/interface/resources/qml/hifi/avatarapp/AvatarAppStyle.qml b/interface/resources/qml/hifi/avatarapp/AvatarAppStyle.qml new file mode 100644 index 0000000000..f66c7121cb --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/AvatarAppStyle.qml @@ -0,0 +1,29 @@ +// +// HiFiConstants.qml +// +// Created by Alexander Ivash on 17 Apr 2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtQuick.Window 2.2 +import "../../styles-uit" + +QtObject { + readonly property QtObject colors: QtObject { + readonly property color lightGrayBackground: "#f2f2f2" + readonly property color black: "#000000" + readonly property color white: "#ffffff" + readonly property color blueHighlight: "#00b4ef" + readonly property color inputFieldBackground: "#d4d4d4" + readonly property color yellowishOrange: "#ffb017" + readonly property color blueAccent: "#0093c5" + readonly property color greenHighlight: "#1fc6a6" + readonly property color lightGray: "#afafaf" + readonly property color redHighlight: "#ea4c5f" + readonly property color orangeAccent: "#ff6309" + } +} diff --git a/interface/resources/qml/hifi/avatarapp/AvatarThumbnail.qml b/interface/resources/qml/hifi/avatarapp/AvatarThumbnail.qml new file mode 100644 index 0000000000..970d132ba6 --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/AvatarThumbnail.qml @@ -0,0 +1,73 @@ +import QtQuick 2.9 +import QtGraphicalEffects 1.0 + +Item { + width: 92 + height: 92 + property alias wearableIndicator: indicator + + property int wearablesCount: 0 + onWearablesCountChanged: { + console.debug('AvatarThumbnail: wearablesCount = ', wearablesCount) + } + + property alias dropShadowRadius: avatarImage.dropShadowRadius + property alias dropShadowHorizontalOffset: avatarImage.dropShadowHorizontalOffset + property alias dropShadowVerticalOffset: avatarImage.dropShadowVerticalOffset + + property url externalAvatarThumbnailUrl; + property var avatarUrl; + property alias border: avatarImage.border + + ShadowImage { + id: avatarImage + anchors.fill: parent + radius: 5 + fillMode: Image.PreserveAspectCrop + + Binding on source { + when: avatarUrl !== '' + value: avatarUrl + } + onSourceChanged: { + console.debug('avatarImage: source = ', source); + } + + visible: avatarImage.status !== Image.Loading && avatarImage.status !== Image.Error + } + + ShadowImage { + id: customAvatarImage + anchors.fill: avatarImage; + visible: avatarUrl === '' || avatarImage.status === Image.Error + source: externalAvatarThumbnailUrl + } + + ShadowRectangle { + anchors.fill: parent; + color: 'white' + visible: avatarImage.status === Image.Loading + radius: avatarImage.radius + border.width: avatarImage.border.width + border.color: avatarImage.border.color + + dropShadowRadius: avatarImage.dropShadowRadius; + dropShadowHorizontalOffset: avatarImage.dropShadowHorizontalOffset + dropShadowVerticalOffset: avatarImage.dropShadowVerticalOffset + + Spinner { + id: spinner + visible: parent.visible + anchors.fill: parent; + } + } + + AvatarWearablesIndicator { + id: indicator + anchors.left: avatarImage.left + anchors.bottom: avatarImage.bottom + anchors.leftMargin: 57 + wearablesCount: parent.wearablesCount + visible: parent.wearablesCount !== 0 + } +} diff --git a/interface/resources/qml/hifi/avatarapp/AvatarWearablesIndicator.qml b/interface/resources/qml/hifi/avatarapp/AvatarWearablesIndicator.qml new file mode 100644 index 0000000000..cb73e9fe71 --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/AvatarWearablesIndicator.qml @@ -0,0 +1,46 @@ +import QtQuick 2.9 +import "../../controls-uit" +import "../../styles-uit" + +ShadowRectangle { + property int wearablesCount: 0 + + dropShadowRadius: 4 + dropShadowHorizontalOffset: 0 + dropShadowVerticalOffset: 0 + + width: 46.5 + height: 46.5 + radius: width / 2 + + AvatarAppStyle { + id: style + } + + color: style.colors.greenHighlight + + HiFiGlyphs { + width: 26.5 + height: 13.8 + anchors.top: parent.top + anchors.topMargin: 10 + anchors.horizontalCenter: parent.horizontalCenter + horizontalAlignment: Text.AlignHCenter + text: "\ue02e" + } + + Item { + width: 46.57 + height: 23 + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 2.76 + + // TextStyle2 + RalewayBold { + size: 15; + anchors.horizontalCenter: parent.horizontalCenter + text: wearablesCount + } + } +} diff --git a/interface/resources/qml/hifi/avatarapp/AvatarsModel.qml b/interface/resources/qml/hifi/avatarapp/AvatarsModel.qml new file mode 100644 index 0000000000..931a041747 --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/AvatarsModel.qml @@ -0,0 +1,228 @@ +import QtQuick 2.9 + +ListModel { + id: model + function extractMarketId(avatarUrl) { + + var guidRegexp = '([A-Z0-9]{8}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{12})'; + + var regexp = new RegExp(guidRegexp,["i"]); + var match = regexp.exec(avatarUrl); + if (match !== null) { + return match[1]; + } + + return ''; + } + + function makeMarketItemUrl(avatarUrl) { + var marketItemUrl = "https://highfidelity.com/marketplace/items/%marketId%" + .split('%marketId%').join(extractMarketId(avatarUrl)); + + return marketItemUrl; + } + + function makeThumbnailUrl(avatarUrl) { + var marketId = extractMarketId(avatarUrl); + if(marketId === '') + return ''; + + var avatarThumbnailUrl = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/%marketId%/large/hifi-mp-%marketId%.jpg" + .split('%marketId%').join(marketId); + + return avatarThumbnailUrl; + } + + function makeAvatarObject(avatar, avatarName) { + var avatarThumbnailUrl = makeThumbnailUrl(avatar.avatarUrl); + + return { + 'name' : avatarName, + 'avatarScale' : avatar.avatarScale, + 'thumbnailUrl' : avatarThumbnailUrl, + 'avatarUrl' : avatar.avatarUrl, + 'wearables' : avatar.avatarEntites ? avatar.avatarEntites : [], + 'attachments' : avatar.attachments ? avatar.attachments : [], + 'entry' : avatar, + 'getMoreAvatars' : false + }; + + } + + function addAvatarEntry(avatar, avatarName) { + var avatarEntry = makeAvatarObject(avatar, avatarName); + append(avatarEntry); + + return allAvatars.count - 1; + } + + function populate(bookmarks) { + clear(); + for(var avatarName in bookmarks) { + var avatar = bookmarks[avatarName]; + var avatarEntry = makeAvatarObject(avatar, avatarName); + + append(avatarEntry); + } + } + + function arraysAreEqual(a1, a2, comparer) { + if(Array.isArray(a1) && Array.isArray(a2)) { + if(a1.length !== a2.length) { + return false; + } + + for(var i = 0; i < a1.length; ++i) { + if(!comparer(a1[i], a2[i])) { + return false; + } + } + } else if(Array.isArray(a1)) { + return a1.length === 0; + } else if(Array.isArray(a2)) { + return a2.length === 0; + } + + return true; + } + + function modelsAreEqual(m1, m2, comparer) { + if(m1.count !== m2.count) { + return false; + } + + for(var i = 0; i < m1.count; ++i) { + var e1 = m1.get(i); + + var allDifferent = true; + + // it turns out order of wearables can randomly change so make position-independent comparison here + for(var j = 0; j < m2.count; ++j) { + var e2 = m2.get(j); + + if(comparer(e1, e2)) { + allDifferent = false; + break; + } + } + + if(allDifferent) { + return false; + } + } + + return true; + } + + function compareNumericObjects(o1, o2) { + if(o1 === undefined && o2 !== undefined) + return false; + if(o1 !== undefined && o2 === undefined) + return false; + + for(var prop in o1) { + if(o1.hasOwnProperty(prop) && o2.hasOwnProperty(prop)) { + var v1 = o1[prop]; + var v2 = o2[prop]; + + + if(v1 !== v2 && Math.round(v1 * 500) != Math.round(v2 * 500)) { + return false; + } + } + } + + return true; + } + + function compareObjects(o1, o2, props, arrayProp) { + for(var i = 0; i < props.length; ++i) { + var prop = props[i]; + var propertyName = prop.propertyName; + var comparer = prop.comparer; + + var o1Value = arrayProp ? o1[arrayProp][propertyName] : o1[propertyName]; + var o2Value = arrayProp ? o2[arrayProp][propertyName] : o2[propertyName]; + + if(comparer) { + if(comparer(o1Value, o2Value) === false) { + return false; + } + } else { + if(JSON.stringify(o1Value) !== JSON.stringify(o2Value)) { + return false; + } + } + } + + return true; + } + + function compareWearables(w1, w2) { + return compareObjects(w1, w2, [{'propertyName' : 'modelURL'}, + {'propertyName' : 'parentJointIndex'}, + {'propertyName' : 'marketplaceID'}, + {'propertyName' : 'itemName'}, + {'propertyName' : 'script'}, + {'propertyName' : 'localPosition', 'comparer' : compareNumericObjects}, + {'propertyName' : 'localRotationAngles', 'comparer' : compareNumericObjects}, + {'propertyName' : 'dimensions', 'comparer' : compareNumericObjects}], 'properties') + } + + function compareAttachments(a1, a2) { + return compareObjects(a1, a2, [{'propertyName' : 'position', 'comparer' : compareNumericObjects}, + {'propertyName' : 'orientation'}, + {'propertyName' : 'parentJointIndex'}, + {'propertyName' : 'modelurl'}]) + } + + function findAvatarIndexByValue(avatar) { + + var index = -1; + + // 2DO: find better way of determining selected avatar in bookmarks + for(var i = 0; i < allAvatars.count; ++i) { + var thesame = true; + var bookmarkedAvatar = allAvatars.get(i); + + if(bookmarkedAvatar.avatarUrl !== avatar.avatarUrl) + continue; + + if(bookmarkedAvatar.avatarScale !== avatar.avatarScale) + continue; + + if(!modelsAreEqual(bookmarkedAvatar.attachments, avatar.attachments, compareAttachments)) { + continue; + } + + if(!modelsAreEqual(bookmarkedAvatar.wearables, avatar.wearables, compareWearables)) { + continue; + } + + if(thesame) { + index = i; + break; + } + } + + return index; + } + + function findAvatarIndex(avatarName) { + for(var i = 0; i < count; ++i) { + if(get(i).name === avatarName) { + return i; + } + } + return -1; + } + + function findAvatar(avatarName) { + var avatarIndex = findAvatarIndex(avatarName); + if(avatarIndex === -1) + return undefined; + + return get(avatarIndex); + } + +} diff --git a/interface/resources/qml/hifi/avatarapp/BlueButton.qml b/interface/resources/qml/hifi/avatarapp/BlueButton.qml new file mode 100644 index 0000000000..e668951517 --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/BlueButton.qml @@ -0,0 +1,15 @@ +import QtQuick 2.5 +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit + +HifiControlsUit.Button { + HifiConstants { + id: hifi + } + + width: Math.max(hifi.dimensions.buttonWidth, implicitTextWidth + 20) + fontSize: 18 + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.light; + height: 40 +} diff --git a/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml b/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml new file mode 100644 index 0000000000..ab995e79e8 --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/CreateFavoriteDialog.qml @@ -0,0 +1,184 @@ +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit +import "../../controls" as HifiControls + +Rectangle { + id: root; + visible: false; + anchors.fill: parent; + color: Qt.rgba(0, 0, 0, 0.5); + z: 999; + + property string titleText: 'Create Favorite' + property string favoriteNameText: favoriteName.text + property string avatarImageUrl: null + property int wearablesCount: 0 + + property string button1color: hifi.buttons.noneBorderlessGray; + property string button1text: 'CANCEL' + property string button2color: hifi.buttons.blue; + property string button2text: 'CONFIRM' + + property var avatars; + property var onSaveClicked; + property var onCancelClicked; + + function open(wearables, thumbnail) { + favoriteName.text = ''; + favoriteName.forceActiveFocus(); + + avatarImageUrl = thumbnail; + wearablesCount = wearables; + + visible = true; + } + + function close() { + console.debug('closing'); + visible = false; + } + + HifiConstants { + id: hifi + } + + // This object is always used in a popup. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + hoverEnabled: true; + } + + Rectangle { + id: mainContainer; + width: Math.max(parent.width * 0.8, 400) + property int margin: 30; + + height: childrenRect.height + margin * 2 + onHeightChanged: { + console.debug('mainContainer: height = ', height) + } + + anchors.centerIn: parent + + color: "white" + + // TextStyle1 + RalewaySemiBold { + id: title + size: 24; + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 30 + anchors.leftMargin: 30 + anchors.rightMargin: 30 + + text: root.titleText + } + + Item { + id: contentContainer + width: parent.width - 50 + height: childrenRect.height + + anchors.top: title.bottom + anchors.topMargin: 20 + anchors.left: parent.left; + anchors.leftMargin: 30; + anchors.right: parent.right; + anchors.rightMargin: 30; + + AvatarThumbnail { + id: avatarThumbnail + avatarUrl: avatarImageUrl + onAvatarUrlChanged: { + console.debug('CreateFavoritesDialog: onAvatarUrlChanged: ', avatarUrl); + } + + wearablesCount: avatarWearablesCount + } + + InputTextStyle4 { + id: favoriteName + anchors.right: parent.right + height: 40 + anchors.left: avatarThumbnail.right + anchors.leftMargin: 44 + anchors.verticalCenter: avatarThumbnail.verticalCenter + placeholderText: "Enter Favorite Name" + + RalewayRegular { + id: wrongName + anchors.top: parent.bottom; + anchors.topMargin: 2 + + anchors.left: parent.left + anchors.right: parent.right; + anchors.rightMargin: -contentContainer.anchors.rightMargin // allow text to render beyond favorite input text + wrapMode: Text.WordWrap + text: 'Favorite name exists. Overwrite existing favorite?' + size: 15 + color: 'red' + visible: { + for(var i = 0; i < avatars.count; ++i) { + var avatarName = avatars.get(i).name; + if(avatarName === favoriteName.text) { + return true; + } + } + + return false; + } + } + } + } + + DialogButtons { + anchors.top: contentContainer.bottom + anchors.topMargin: 20 + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 30 + + yesButton.enabled: favoriteNameText !== '' + yesText: root.button2text + noText: root.button1text + + Binding on yesButton.text { + when: wrongName.visible + value: "OVERWRITE"; + } + + Binding on yesButton.color { + when: wrongName.visible + value: hifi.buttons.red; + } + + Binding on yesButton.colorScheme { + when: wrongName.visible + value: hifi.colorSchemes.dark; + } + + onYesClicked: function() { + if(onSaveClicked) { + onSaveClicked(); + } else { + root.close(); + } + } + + onNoClicked: function() { + if(onCancelClicked) { + onCancelClicked(); + } else { + root.close(); + } + } + } + } +} diff --git a/interface/resources/qml/hifi/avatarapp/DialogButtons.qml b/interface/resources/qml/hifi/avatarapp/DialogButtons.qml new file mode 100644 index 0000000000..46c17bb4dc --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/DialogButtons.qml @@ -0,0 +1,41 @@ +import QtQuick 2.9 + +Row { + id: root + property string yesText; + property string noText; + property var onYesClicked; + property var onNoClicked; + + property alias yesButton: yesButton + property alias noButton: noButton + + height: childrenRect.height + layoutDirection: Qt.RightToLeft + + spacing: 30 + + BlueButton { + id: yesButton; + text: yesText; + onClicked: { + console.debug('bluebutton.clicked', onYesClicked); + + if(onYesClicked) { + onYesClicked(); + } + } + } + + WhiteButton { + id: noButton + text: noText; + onClicked: { + console.debug('whitebutton.clicked', onNoClicked); + + if(onNoClicked) { + onNoClicked(); + } + } + } +} diff --git a/interface/resources/qml/hifi/avatarapp/InputField.qml b/interface/resources/qml/hifi/avatarapp/InputField.qml new file mode 100644 index 0000000000..905518ef0f --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/InputField.qml @@ -0,0 +1,48 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.2 +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit + +TextField { + id: textField + + property bool error: false; + text: 'ThisIsDisplayName' + + states: [ + State { + name: "hovered" + when: textField.hovered && !textField.focus && !textField.error; + PropertyChanges { target: background; color: '#afafaf' } + }, + State { + name: "focused" + when: textField.focus && !textField.error + PropertyChanges { target: background; color: '#f2f2f2' } + PropertyChanges { target: background; border.color: '#00b4ef' } + }, + State { + name: "error" + when: textField.error + PropertyChanges { target: background; color: '#f2f2f2' } + PropertyChanges { target: background; border.color: '#e84e62' } + } + ] + + background: Rectangle { + id: background + implicitWidth: 200 + implicitHeight: 40 + color: '#d4d4d4' + border.color: '#afafaf' + border.width: 1 + radius: 2 + } + + HiFiGlyphs { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + size: 36 + text: "\ue00d" + } +} diff --git a/interface/resources/qml/hifi/avatarapp/InputTextStyle4.qml b/interface/resources/qml/hifi/avatarapp/InputTextStyle4.qml new file mode 100644 index 0000000000..4b868b47ce --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/InputTextStyle4.qml @@ -0,0 +1,16 @@ +import "../../controls-uit" as HifiControlsUit +import "../../styles-uit" + +import QtQuick 2.0 +import QtQuick.Controls 2.2 + +HifiControlsUit.TextField { + id: control + font.family: "Fira Sans" + font.pixelSize: 15; + implicitHeight: 40 + + AvatarAppStyle { + id: style + } +} diff --git a/interface/resources/qml/hifi/avatarapp/MessageBox.qml b/interface/resources/qml/hifi/avatarapp/MessageBox.qml new file mode 100644 index 0000000000..f2df0b5199 --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/MessageBox.qml @@ -0,0 +1,184 @@ +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit +import "../../controls" as HifiControls + +Rectangle { + id: root; + visible: false; + anchors.fill: parent; + color: Qt.rgba(0, 0, 0, 0.5); + z: 999; + + property string titleText: '' + property string bodyText: '' + property alias inputText: input; + + property string imageSource: null + onImageSourceChanged: { + console.debug('imageSource = ', imageSource) + } + + property string button1color: hifi.buttons.noneBorderlessGray; + property string button1text: '' + property string button2color: hifi.buttons.blue; + property string button2text: '' + + property var onButton2Clicked; + property var onButton1Clicked; + property var onLinkClicked; + + function open() { + visible = true; + } + + function close() { + visible = false; + + onButton1Clicked = null; + onButton2Clicked = null; + button1text = ''; + button2text = ''; + imageSource = null; + inputText.visible = false; + inputText.placeholderText = ''; + inputText.text = ''; + } + + HifiConstants { + id: hifi + } + + // This object is always used in a popup. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + hoverEnabled: true; + } + + Rectangle { + id: mainContainer; + width: Math.max(parent.width * 0.8, 400) + property int margin: 30; + + height: childrenRect.height + margin * 2 + onHeightChanged: { + console.debug('mainContainer: height = ', height) + } + + anchors.centerIn: parent + + color: "white" + + // TextStyle1 + RalewaySemiBold { + id: title + size: 24; + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 30 + anchors.leftMargin: 30 + anchors.rightMargin: 30 + + text: root.titleText + elide: Qt.ElideRight + } + + Column { + id: contentContainer + spacing: 15 + + anchors.top: title.bottom + anchors.topMargin: 10 + anchors.left: parent.left; + anchors.leftMargin: 30; + anchors.right: parent.right; + anchors.rightMargin: 30; + + InputTextStyle4 { + id: input + visible: false + height: visible ? implicitHeight : 0 + + anchors.left: parent.left; + anchors.right: parent.right; + } + + // TextStyle3 + RalewayRegular { + id: body + + AvatarAppStyle { + id: style + } + + size: 18 + text: root.bodyText; + linkColor: style.colors.blueHighlight + anchors.left: parent.left; + anchors.right: parent.right; + height: paintedHeight; + verticalAlignment: Text.AlignTop; + wrapMode: Text.WordWrap; + + onLinkActivated: { + if(onLinkClicked) + onLinkClicked(link); + } + } + + Image { + id: image + Binding on height { + when: imageSource === null + value: 0 + } + + anchors.left: parent.left; + anchors.right: parent.right; + + Binding on source { + when: imageSource !== null + value: imageSource + } + + visible: imageSource !== null ? true : false + } + } + + DialogButtons { + id: buttons + + anchors.top: contentContainer.bottom + anchors.topMargin: 30 + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 30 + + yesButton.enabled: !input.visible || input.text.length !== 0 + yesText: root.button2text + noText: root.button1text + + onYesClicked: function() { + if(onButton2Clicked) { + onButton2Clicked(); + } else { + root.close(); + } + } + + onNoClicked: function() { + if(onButton1Clicked) { + onButton1Clicked(); + } else { + root.close(); + } + } + } + + } +} diff --git a/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml new file mode 100644 index 0000000000..fd65d014a0 --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/MessageBoxes.qml @@ -0,0 +1,124 @@ +import QtQuick 2.5 + +MessageBox { + id: popup + + function showSpecifyAvatarUrl(url, callback, linkCallback) { + popup.onButton2Clicked = callback; + popup.titleText = 'Specify Avatar URL' + popup.bodyText = 'This will not overwrite your existing favorite if you are wearing one.
' + + '' + + 'Learn to make a custom avatar by opening this link on your desktop.' + + '' + popup.inputText.visible = true; + popup.inputText.placeholderText = 'Enter Avatar Url'; + popup.inputText.text = url; + popup.inputText.selectAll(); + popup.button1text = 'CANCEL'; + popup.button2text = 'CONFIRM'; + + popup.onButton2Clicked = function() { + if(callback) + callback(); + + popup.close(); + } + + popup.onLinkClicked = function(link) { + if(linkCallback) + linkCallback(link); + } + + popup.open(); + popup.inputText.forceActiveFocus(); + } + + property url getWearablesUrl: '../../../images/avatarapp/AvatarIsland.jpg' + + function showGetWearables(callback, linkCallback) { + popup.button2text = 'AvatarIsland' + popup.button1text = 'CANCEL' + popup.titleText = 'Get Wearables' + popup.bodyText = 'Buy wearables from Marketplace' + '
' + + 'Wear wearables from My Purchases' + '
' + + 'You can visit the domain “AvatarIsland†to get wearables' + + popup.imageSource = getWearablesUrl; + popup.onButton2Clicked = function() { + popup.close(); + + if(callback) + callback(); + } + + popup.onLinkClicked = function(link) { + popup.close(); + + if(linkCallback) + linkCallback(link); + } + + popup.open(); + } + + function showDeleteFavorite(favoriteName, callback) { + popup.titleText = 'Delete Favorite: {AvatarName}'.replace('{AvatarName}', favoriteName) + popup.bodyText = 'This will delete your favorite. You will retain access to the wearables and avatar that made up the favorite from My Purchases.' + popup.imageSource = null; + popup.button1text = 'CANCEL' + popup.button2text = 'DELETE' + + popup.onButton2Clicked = function() { + popup.close(); + + if(callback) + callback(); + } + popup.open(); + } + + function showLoadFavorite(favoriteName, callback) { + popup.button2text = 'CONFIRM' + popup.button1text = 'CANCEL' + popup.titleText = 'Load Favorite: {AvatarName}'.replace('{AvatarName}', favoriteName) + popup.bodyText = 'This will switch your current avatar and wearables that you are wearing with a new avatar and wearables.' + popup.imageSource = null; + popup.onButton2Clicked = function() { + popup.close(); + + if(callback) + callback(); + } + popup.open(); + } + + property url getAvatarsUrl: '../../../images/avatarapp/BodyMart.PNG' + + function showBuyAvatars(callback, linkCallback) { + popup.button2text = 'BodyMart' + popup.button1text = 'CANCEL' + popup.titleText = 'Get Avatars' + + popup.bodyText = 'Buy avatars from Marketplace' + '
' + + 'Wear avatars from My Purchases' + '
' + + 'You can visit the domain “BodyMart†to get avatars' + + popup.imageSource = getAvatarsUrl; + popup.onButton2Clicked = function() { + popup.close(); + + if(callback) + callback(); + } + + popup.onLinkClicked = function(link) { + popup.close(); + + if(linkCallback) + linkCallback(link); + } + + popup.open(); + } +} + diff --git a/interface/resources/qml/hifi/avatarapp/PageIndicator.qml b/interface/resources/qml/hifi/avatarapp/PageIndicator.qml new file mode 100644 index 0000000000..28575e4496 --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/PageIndicator.qml @@ -0,0 +1,44 @@ +import QtQuick 2.9 + +ShadowGlyph { + id: indicator + property bool isPrevious: true; + property bool hasNext: false + property bool hasPrev: false + property bool isEnabled: isPrevious ? hasPrev : hasNext + signal clicked; + + states: [ + State { + name: "hovered" + when: pageIndicatorMouseArea.containsMouse; + PropertyChanges { target: pageIndicatorMouseArea; anchors.bottomMargin: -5 } + PropertyChanges { target: indicator; y: -5 } + PropertyChanges { target: indicator; dropShadowVerticalOffset: 9 } + } + ] + + Behavior on y { + NumberAnimation { + duration: 100 + } + } + + text: isPrevious ? "E" : "D"; + width: 40 + height: 40 + font.pixelSize: 100 + color: isEnabled ? 'black' : 'gray' + visible: hasNext || hasPrev + horizontalAlignment: Text.AlignHCenter + + MouseArea { + id: pageIndicatorMouseArea + anchors.fill: parent + enabled: isEnabled + hoverEnabled: enabled + onClicked: { + parent.clicked(); + } + } +} diff --git a/interface/resources/qml/hifi/avatarapp/RoundImage.qml b/interface/resources/qml/hifi/avatarapp/RoundImage.qml new file mode 100644 index 0000000000..f941cd0f1d --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/RoundImage.qml @@ -0,0 +1,36 @@ +import QtQuick 2.0 + +Item { + + property alias border: borderRectangle.border + property alias source: image.source + property alias fillMode: image.fillMode + property alias radius: mask.radius + property alias status: image.status + property alias progress: image.progress + + Image { + id: image + anchors.fill: parent + anchors.margins: borderRectangle.border.width + } + + Rectangle { + id: mask + anchors.fill: image + } + + TransparencyMask { + anchors.fill: image + source: image + maskSource: mask + } + + Rectangle { + id: borderRectangle + anchors.fill: parent + + radius: mask.radius + color: "transparent" + } +} diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml new file mode 100644 index 0000000000..e1b55866c2 --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -0,0 +1,355 @@ +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit +import "../../controls" as HifiControls + +Rectangle { + id: root + + color: 'white' + visible: false; + + signal scaleChanged(real scale); + + property alias onSaveClicked: dialogButtons.onYesClicked + property alias onCancelClicked: dialogButtons.onNoClicked + + property real scaleValue: scaleSlider.value / 10 + property alias dominantHandIsLeft: leftHandRadioButton.checked + property alias avatarCollisionsOn: collisionsEnabledRadiobutton.checked + property alias avatarAnimationOverrideJSON: avatarAnimationUrlInputText.text + property alias avatarAnimationJSON: avatarAnimationUrlInputText.placeholderText + property alias avatarCollisionSoundUrl: avatarCollisionSoundUrlInputText.text + + property real avatarScaleBackup; + function open(settings, avatarScale) { + console.debug('Settings.qml: open: ', JSON.stringify(settings, 0, 4)); + avatarScaleBackup = avatarScale; + + scaleSlider.notify = false; + scaleSlider.value = Math.round(avatarScale * 10); + scaleSlider.notify = true;; + + if(settings.dominantHand === 'left') { + leftHandRadioButton.checked = true; + } else { + rightHandRadioButton.checked = true; + } + + if(settings.collisionsEnabled) { + collisionsEnabledRadiobutton.checked = true; + } else { + collisionsDisabledRadioButton.checked = true; + } + + avatarAnimationJSON = settings.animGraphUrl; + avatarAnimationOverrideJSON = settings.animGraphOverrideUrl; + avatarCollisionSoundUrl = settings.collisionSoundUrl; + + visible = true; + } + + function close() { + visible = false + } + + // This object is always used in a popup. + // This MouseArea is used to prevent a user from being + // able to click on a button/mouseArea underneath the popup. + MouseArea { + anchors.fill: parent; + propagateComposedEvents: false; + hoverEnabled: true; + } + + Item { + anchors.left: parent.left + anchors.leftMargin: 27 + anchors.top: parent.top + anchors.topMargin: 25 + anchors.right: parent.right + anchors.rightMargin: 32 + anchors.bottom: parent.bottom + anchors.bottomMargin: 57 + + RowLayout { + id: avatarScaleRow + anchors.left: parent.left + anchors.right: parent.right + + spacing: 17 + + // TextStyle9 + RalewaySemiBold { + size: 17; + text: "Avatar Scale" + verticalAlignment: Text.AlignVCenter + anchors.verticalCenter: parent.verticalCenter + } + + RowLayout { + anchors.verticalCenter: parent.verticalCenter + Layout.fillWidth: true + + spacing: 0 + + HiFiGlyphs { + size: 30 + text: 'T' + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + anchors.verticalCenter: parent.verticalCenter + } + + HifiControlsUit.Slider { + id: scaleSlider + property bool notify: false; + + from: 1 + to: 40 + + onValueChanged: { + console.debug('value changed: ', value); + if(notify) { + console.debug('notifying.. '); + root.scaleChanged(value / 10); + } + } + + anchors.verticalCenter: parent.verticalCenter + Layout.fillWidth: true + + // TextStyle9 + RalewaySemiBold { + size: 17; + anchors.left: scaleSlider.left + anchors.leftMargin: 5 + anchors.top: scaleSlider.bottom + anchors.topMargin: 2 + text: String(scaleSlider.from / 10) + 'x' + } + + // TextStyle9 + RalewaySemiBold { + size: 17; + anchors.right: scaleSlider.right + anchors.rightMargin: 5 + anchors.top: scaleSlider.bottom + anchors.topMargin: 2 + text: String(scaleSlider.to / 10) + 'x' + } + } + + HiFiGlyphs { + size: 40 + text: 'T' + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + anchors.verticalCenter: parent.verticalCenter + } + } + + ShadowRectangle { + width: 37 + height: 28 + AvatarAppStyle { + id: style + } + + gradient: Gradient { + GradientStop { position: 0.0; color: style.colors.blueHighlight } + GradientStop { position: 1.0; color: style.colors.blueAccent } + } + + radius: 3 + + RalewaySemiBold { + color: 'white' + anchors.centerIn: parent + text: "1x" + size: 18 + } + + MouseArea { + anchors.fill: parent + onClicked: { + scaleSlider.value = 10 + } + } + } + } + + GridLayout { + id: handAndCollisions + anchors.top: avatarScaleRow.bottom + anchors.topMargin: 39 + anchors.left: parent.left + anchors.right: parent.right + + rows: 2 + rowSpacing: 25 + + columns: 3 + + // TextStyle9 + RalewaySemiBold { + size: 17; + Layout.row: 0 + Layout.column: 0 + + text: "Dominant Hand" + } + + ButtonGroup { + id: leftRight + } + + HifiControlsUit.RadioButton { + id: leftHandRadioButton + + Layout.row: 0 + Layout.column: 1 + Layout.leftMargin: -40 + + ButtonGroup.group: leftRight + checked: true + + colorScheme: hifi.colorSchemes.light + fontSize: 17 + letterSpacing: 1.4 + text: "Left" + boxSize: 20 + } + + HifiControlsUit.RadioButton { + id: rightHandRadioButton + + Layout.row: 0 + Layout.column: 2 + Layout.rightMargin: 20 + + ButtonGroup.group: leftRight + + colorScheme: hifi.colorSchemes.light + fontSize: 17 + letterSpacing: 1.4 + text: "Right" + boxSize: 20 + } + + // TextStyle9 + RalewaySemiBold { + size: 17; + Layout.row: 1 + Layout.column: 0 + + text: "Avatar Collisions" + } + + ButtonGroup { + id: onOff + } + + HifiControlsUit.RadioButton { + id: collisionsEnabledRadiobutton + + Layout.row: 1 + Layout.column: 1 + Layout.leftMargin: -40 + ButtonGroup.group: onOff + + colorScheme: hifi.colorSchemes.light + fontSize: 17 + letterSpacing: 1.4 + checked: true + + text: "ON" + boxSize: 20 + } + + HifiConstants { + id: hifi + } + + HifiControlsUit.RadioButton { + id: collisionsDisabledRadioButton + + Layout.row: 1 + Layout.column: 2 + Layout.rightMargin: 20 + + ButtonGroup.group: onOff + colorScheme: hifi.colorSchemes.light + fontSize: 17 + letterSpacing: 1.4 + + text: "OFF" + boxSize: 20 + } + } + + ColumnLayout { + id: avatarAnimationLayout + anchors.top: handAndCollisions.bottom + anchors.topMargin: 25 + anchors.left: parent.left + anchors.right: parent.right + + spacing: 4 + + // TextStyle9 + RalewaySemiBold { + size: 17; + text: "Avatar Animation JSON" + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + } + + InputTextStyle4 { + id: avatarAnimationUrlInputText + font.pixelSize: 17 + anchors.left: parent.left + anchors.right: parent.right + placeholderText: 'user\\ï¬le\\dir' + } + } + + ColumnLayout { + id: avatarCollisionLayout + anchors.top: avatarAnimationLayout.bottom + anchors.topMargin: 25 + anchors.left: parent.left + anchors.right: parent.right + + spacing: 4 + + // TextStyle9 + RalewaySemiBold { + size: 17; + text: "Avatar collision sound URL (optional)" + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignLeft + } + + InputTextStyle4 { + id: avatarCollisionSoundUrlInputText + font.pixelSize: 17 + anchors.left: parent.left + anchors.right: parent.right + placeholderText: 'https://hifi-public.s3.amazonaws.com/sounds/Collisions-' + } + } + + DialogButtons { + id: dialogButtons + anchors.right: parent.right + anchors.bottom: parent.bottom + + yesText: "SAVE" + noText: "CANCEL" + } + } +} diff --git a/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml b/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml new file mode 100644 index 0000000000..c2d84bb371 --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/ShadowGlyph.qml @@ -0,0 +1,29 @@ +import "../../styles-uit" +import QtQuick 2.9 +import QtGraphicalEffects 1.0 + +Item { + property alias text: glyph.text + property alias font: glyph.font + property alias color: glyph.color + property alias horizontalAlignment: glyph.horizontalAlignment + property alias dropShadowRadius: shadow.radius + property alias dropShadowHorizontalOffset: shadow.horizontalOffset + property alias dropShadowVerticalOffset: shadow.verticalOffset + + HiFiGlyphs { + id: glyph + width: parent.width + height: parent.height + } + + DropShadow { + id: shadow + anchors.fill: glyph + radius: 4 + horizontalOffset: 0 + verticalOffset: 4 + color: Qt.rgba(0, 0, 0, 0.25) + source: glyph + } +} diff --git a/interface/resources/qml/hifi/avatarapp/ShadowImage.qml b/interface/resources/qml/hifi/avatarapp/ShadowImage.qml new file mode 100644 index 0000000000..3995446e49 --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/ShadowImage.qml @@ -0,0 +1,32 @@ +import "../../styles-uit" +import QtQuick 2.9 +import QtGraphicalEffects 1.0 + +Item { + property alias source: image.source + property alias dropShadowRadius: shadow.radius + property alias dropShadowHorizontalOffset: shadow.horizontalOffset + property alias dropShadowVerticalOffset: shadow.verticalOffset + property alias radius: image.radius + property alias border: image.border + property alias status: image.status + property alias progress: image.progress + property alias fillMode: image.fillMode + + RoundImage { + id: image + width: parent.width + height: parent.height + radius: 6 + } + + DropShadow { + id: shadow + anchors.fill: image + radius: 6 + horizontalOffset: 0 + verticalOffset: 3 + color: Qt.rgba(0, 0, 0, 0.25) + source: image + } +} diff --git a/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml b/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml new file mode 100644 index 0000000000..741fce3d8d --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/ShadowRectangle.qml @@ -0,0 +1,30 @@ +import "../../styles-uit" +import QtQuick 2.9 +import QtGraphicalEffects 1.0 + +Item { + property alias color: rectangle.color + property alias gradient: rectangle.gradient + property alias border: rectangle.border + property alias radius: rectangle.radius + property alias dropShadowRadius: shadow.radius + property alias dropShadowHorizontalOffset: shadow.horizontalOffset + property alias dropShadowVerticalOffset: shadow.verticalOffset + property alias dropShadowOpacity: shadow.opacity + + Rectangle { + id: rectangle + width: parent.width + height: parent.height + } + + DropShadow { + id: shadow + anchors.fill: rectangle + radius: 6 + horizontalOffset: 0 + verticalOffset: 3 + color: Qt.rgba(0, 0, 0, 0.25) + source: rectangle + } +} diff --git a/interface/resources/qml/hifi/avatarapp/Spinner.qml b/interface/resources/qml/hifi/avatarapp/Spinner.qml new file mode 100644 index 0000000000..3fc331346d --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/Spinner.qml @@ -0,0 +1,7 @@ +import QtQuick 2.5 +import QtWebEngine 1.5 + +AnimatedImage { + source: "../../../icons/loader-snake-64-w.gif" + playing: visible +} diff --git a/interface/resources/qml/hifi/avatarapp/SquareLabel.qml b/interface/resources/qml/hifi/avatarapp/SquareLabel.qml new file mode 100644 index 0000000000..3c5463e1dd --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/SquareLabel.qml @@ -0,0 +1,29 @@ +import "../../styles-uit" +import QtQuick 2.9 +import QtGraphicalEffects 1.0 + +ShadowRectangle { + width: 44 + height: 28 + AvatarAppStyle { + id: style + } + + gradient: Gradient { + GradientStop { position: 0.0; color: style.colors.blueHighlight } + GradientStop { position: 1.0; color: style.colors.blueAccent } + } + + property alias glyphText: glyph.text + property alias glyphRotation: glyph.rotation + property alias glyphSize: glyph.size + + radius: 3 + + HiFiGlyphs { + id: glyph + color: 'white' + anchors.centerIn: parent + size: 30 + } +} diff --git a/interface/resources/qml/hifi/avatarapp/TransparencyMask.qml b/interface/resources/qml/hifi/avatarapp/TransparencyMask.qml new file mode 100644 index 0000000000..4884d1e1ad --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/TransparencyMask.qml @@ -0,0 +1,43 @@ +import QtQuick 2.0 + +Item { + property alias source: sourceImage.sourceItem + property alias maskSource: sourceMask.sourceItem + + anchors.fill: parent + ShaderEffectSource { + id: sourceMask + smooth: true + hideSource: true + } + ShaderEffectSource { + id: sourceImage + hideSource: true + } + + ShaderEffect { + id: maskEffect + anchors.fill: parent + + property variant source: sourceImage + property variant mask: sourceMask + + fragmentShader: { +" + varying highp vec2 qt_TexCoord0; + uniform lowp sampler2D source; + uniform lowp sampler2D mask; + void main() { + + highp vec4 maskColor = texture2D(mask, vec2(qt_TexCoord0.x, qt_TexCoord0.y)); + highp vec4 sourceColor = texture2D(source, vec2(qt_TexCoord0.x, qt_TexCoord0.y)); + + if(maskColor.a > 0.0) + gl_FragColor = sourceColor; + else + gl_FragColor = maskColor; + } +" + } + } +} \ No newline at end of file diff --git a/interface/resources/qml/hifi/avatarapp/Vector3.qml b/interface/resources/qml/hifi/avatarapp/Vector3.qml new file mode 100644 index 0000000000..33e82fe0ee --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/Vector3.qml @@ -0,0 +1,65 @@ +import Hifi 1.0 as Hifi +import QtQuick 2.5 +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit +import "../../controls" as HifiControls + +Row { + id: root + width: parent.width + height: xspinner.controlHeight + + property int spinboxSpace: 10 + property int spinboxWidth: (parent.width - 2 * spinboxSpace) / 3 + property color backgroundColor: "darkgray" + + property int decimals: 4 + property real realFrom: 0 + property real realTo: 100 + property real realStepSize: 0.0001 + + spacing: spinboxSpace + + property alias xvalue: xspinner.realValue + property alias yvalue: yspinner.realValue + property alias zvalue: zspinner.realValue + + HifiControlsUit.SpinBox { + id: xspinner + width: parent.spinboxWidth + labelInside: "X:" + backgroundColor: parent.backgroundColor + colorLabelInside: hifi.colors.redHighlight + colorScheme: hifi.colorSchemes.light + decimals: root.decimals; + realFrom: root.realFrom + realTo: root.realTo + realStepSize: root.realStepSize + } + + HifiControlsUit.SpinBox { + id: yspinner + width: parent.spinboxWidth + labelInside: "Y:" + backgroundColor: parent.backgroundColor + colorLabelInside: hifi.colors.greenHighlight + colorScheme: hifi.colorSchemes.light + decimals: root.decimals; + realFrom: root.realFrom + realTo: root.realTo + realStepSize: root.realStepSize + } + + HifiControlsUit.SpinBox { + id: zspinner + width: parent.spinboxWidth + labelInside: "Z:" + backgroundColor: parent.backgroundColor + colorLabelInside: hifi.colors.primaryHighlight + colorScheme: hifi.colorSchemes.light + decimals: root.decimals; + realFrom: root.realFrom + realTo: root.realTo + realStepSize: root.realStepSize + } +} diff --git a/interface/resources/qml/hifi/avatarapp/WhiteButton.qml b/interface/resources/qml/hifi/avatarapp/WhiteButton.qml new file mode 100644 index 0000000000..dc729ae097 --- /dev/null +++ b/interface/resources/qml/hifi/avatarapp/WhiteButton.qml @@ -0,0 +1,15 @@ +import QtQuick 2.5 +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit + +HifiControlsUit.Button { + HifiConstants { + id: hifi + } + + width: Math.max(hifi.dimensions.buttonWidth, implicitTextWidth + 20) + fontSize: 18 + color: hifi.buttons.noneBorderlessGray; + colorScheme: hifi.colorSchemes.light; + height: 40 +} diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 16c1b55930..cac62d3976 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -92,9 +92,9 @@ Rectangle { onBuyResult: { if (result.status !== 'success') { - failureErrorText.text = result.message; + failureErrorText.text = result.data.message; root.activeView = "checkoutFailure"; - UserActivityLogger.commercePurchaseFailure(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned, result.message); + UserActivityLogger.commercePurchaseFailure(root.itemId, root.itemAuthor, root.itemPrice, !root.alreadyOwned, result.data.message); } else { root.certificateId = result.data.certificate_id; root.itemHref = result.data.download_url; @@ -129,7 +129,7 @@ Rectangle { } onAppInstalled: { - if (appHref === root.itemHref) { + if (appID === root.itemId) { root.isInstalled = true; } } diff --git a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml index 3e4bae4780..a515c8031f 100644 --- a/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml +++ b/interface/resources/qml/hifi/commerce/common/sendAsset/SendAsset.qml @@ -398,7 +398,7 @@ Item { http: root.http; listModelName: root.listModelName; endpoint: "/api/v1/users?filter=connections"; - itemsPerPage: 8; + itemsPerPage: 9; listView: connectionsList; processPage: function (data) { return data.users; @@ -520,7 +520,6 @@ Item { visible: !connectionsLoading.visible; clip: true; model: connectionsModel; - onAtYEndChanged: if (connectionsList.atYEnd && !connectionsList.atYBeginning) { connectionsModel.getNextPage(); } snapMode: ListView.SnapToItem; // Anchors anchors.fill: parent; diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index b43372da5c..032d9b0199 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -67,13 +67,13 @@ Item { } onAppInstalled: { - if (appHref === root.itemHref) { + if (appID === root.itemId) { root.isInstalled = true; } } onAppUninstalled: { - if (appHref === root.itemHref) { + if (appID === root.itemId) { root.isInstalled = false; } } @@ -328,7 +328,16 @@ Item { item.buttonColor = "#E2334D"; item.buttonClicked = function() { sendToPurchases({ method: 'flipCard', closeAll: true }); - sendToPurchases({method: 'updateItemClicked', itemId: root.itemId, itemEdition: root.itemEdition, upgradeUrl: root.upgradeUrl}); + sendToPurchases({ + method: 'updateItemClicked', + itemId: root.itemId, + itemEdition: root.itemEdition, + upgradeUrl: root.upgradeUrl, + itemHref: root.itemHref, + itemType: root.itemType, + isInstalled: root.isInstalled, + wornEntityID: root.wornEntityID + }); } } } @@ -646,6 +655,7 @@ Item { height: 40; enabled: root.hasPermissionToRezThis && MyAvatar.skeletonModelURL !== root.itemHref && + !root.wornEntityID && root.valid; onHoveredChanged: { @@ -722,7 +732,7 @@ Item { } HiFiGlyphs { id: rezIcon; - text: (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)]; + text: root.isInstalled ? "" : (root.buttonGlyph)[itemTypesArray.indexOf(root.itemType)]; anchors.right: rezIconLabel.left; anchors.rightMargin: 2; anchors.verticalCenter: parent.verticalCenter; diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 0d2acf4ec3..3b8e2c0f4d 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -98,7 +98,7 @@ Rectangle { } onAppInstalled: { - root.installedApps = Commerce.getInstalledApps(); + root.installedApps = Commerce.getInstalledApps(appID); } onAppUninstalled: { @@ -551,8 +551,9 @@ Rectangle { HifiModels.PSFListModel { id: purchasesModel; - itemsPerPage: 6; + itemsPerPage: 7; listModelName: 'purchases'; + listView: purchasesContentsList; getPage: function () { console.debug('getPage', purchasesModel.listModelName, root.isShowingMyItems, filterBar.primaryFilter_filterName, purchasesModel.currentPageToRetrieve, purchasesModel.itemsPerPage); Commerce.inventory( @@ -706,7 +707,58 @@ Rectangle { } } } else if (msg.method === "updateItemClicked") { - sendToScript(msg); + // These three cases are very similar to the conditionals below, under + // "if msg.method === "giftAsset". They differ in their popup's wording + // and the actions to take when continuing. + // I could see an argument for DRYing up this code, but I think the + // actions are different enough now and potentially moving forward such that I'm + // OK with "somewhat repeating myself". + if (msg.itemType === "app" && msg.isInstalled) { + lightboxPopup.titleText = "Uninstall App"; + lightboxPopup.bodyText = "The app that you are trying to update is installed.

" + + "If you proceed, the current version of the app will be uninstalled."; + lightboxPopup.button1text = "CANCEL"; + lightboxPopup.button1method = function() { + lightboxPopup.visible = false; + } + lightboxPopup.button2text = "CONFIRM"; + lightboxPopup.button2method = function() { + Commerce.uninstallApp(msg.itemHref); + sendToScript(msg); + }; + lightboxPopup.visible = true; + } else if (msg.itemType === "wearable" && msg.wornEntityID !== '') { + lightboxPopup.titleText = "Remove Wearable"; + lightboxPopup.bodyText = "You are currently wearing the wearable that you are trying to update.

" + + "If you proceed, this wearable will be removed."; + lightboxPopup.button1text = "CANCEL"; + lightboxPopup.button1method = function() { + lightboxPopup.visible = false; + } + lightboxPopup.button2text = "CONFIRM"; + lightboxPopup.button2method = function() { + Entities.deleteEntity(msg.wornEntityID); + purchasesModel.setProperty(index, 'wornEntityID', ''); + sendToScript(msg); + }; + lightboxPopup.visible = true; + } else if (msg.itemType === "avatar" && MyAvatar.skeletonModelURL === msg.itemHref) { + lightboxPopup.titleText = "Change Avatar to Default"; + lightboxPopup.bodyText = "You are currently wearing the avatar that you are trying to update.

" + + "If you proceed, your avatar will be changed to the default avatar."; + lightboxPopup.button1text = "CANCEL"; + lightboxPopup.button1method = function() { + lightboxPopup.visible = false; + } + lightboxPopup.button2text = "CONFIRM"; + lightboxPopup.button2method = function() { + MyAvatar.useFullAvatarURL(''); + sendToScript(msg); + }; + lightboxPopup.visible = true; + } else { + sendToScript(msg); + } } else if (msg.method === "giftAsset") { sendAsset.assetName = msg.itemName; sendAsset.assetCertID = msg.certId; @@ -765,14 +817,6 @@ Rectangle { } } } - - - onAtYEndChanged: { - if (purchasesContentsList.atYEnd && !purchasesContentsList.atYBeginning) { - console.log("User scrolled to the bottom of 'Purchases'."); - purchasesModel.getNextPage(); - } - } } Rectangle { @@ -957,7 +1001,7 @@ Rectangle { function updateCurrentlyWornWearables(wearables) { for (var i = 0; i < purchasesModel.count; i++) { for (var j = 0; j < wearables.length; j++) { - if (purchasesModel.get(i).itemType === "wearable" && + if (purchasesModel.get(i).item_type === "wearable" && wearables[j].entityCertID === purchasesModel.get(i).certificate_id && wearables[j].entityEdition.toString() === purchasesModel.get(i).edition_number) { purchasesModel.setProperty(i, 'wornEntityID', wearables[j].entityID); diff --git a/interface/resources/qml/hifi/commerce/wallet/Help.qml b/interface/resources/qml/hifi/commerce/wallet/Help.qml index d76fe0d8a2..b453509712 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Help.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Help.qml @@ -253,7 +253,11 @@ At the moment, there is currently no way to convert HFC to other currencies. Sta } else if (link === "#blockchain") { Qt.openUrlExternally("https://docs.highfidelity.com/high-fidelity-commerce"); } else if (link === "#bank") { - Qt.openUrlExternally("hifi://BankOfHighFidelity"); + if ((Account.metaverseServerURL).toString().indexOf("staging") >= 0) { + Qt.openUrlExternally("hifi://hifiqa-master-metaverse-staging"); // So that we can test in staging. + } else { + Qt.openUrlExternally("hifi://BankOfHighFidelity"); + } } else if (link === "#support") { Qt.openUrlExternally("mailto:support@highfidelity.com"); } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 4cf6db7889..a0c6057b3b 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -76,7 +76,7 @@ Item { anchors.top: parent.top; anchors.left: parent.left; anchors.leftMargin: 20; - width: parent.width/2; + width: parent.width/2 - anchors.leftMargin; height: 80; } @@ -212,6 +212,7 @@ Item { HifiModels.PSFListModel { id: transactionHistoryModel; listModelName: "transaction history"; // For debugging. Alternatively, we could specify endpoint for that purpose, even though it's not used directly. + listView: transactionHistory; itemsPerPage: 6; getPage: function () { console.debug('getPage', transactionHistoryModel.listModelName, transactionHistoryModel.currentPageToRetrieve); @@ -252,60 +253,6 @@ Item { anchors.bottom: parent.bottom; anchors.left: parent.left; anchors.right: parent.right; - - Item { // On empty history. We don't want to flash and then replace, so don't show until we know we're zero. - visible: transactionHistoryModel.count === 0 && transactionHistoryModel.currentPageToRetrieve < 0; - anchors.centerIn: parent; - width: parent.width - 12; - height: parent.height; - - HifiControlsUit.Separator { - colorScheme: 1; - anchors.left: parent.left; - anchors.right: parent.right; - anchors.top: parent.top; - } - - RalewayRegular { - id: noActivityText; - text: "Congrats! Your wallet is all set!

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

" + + "Where's my HFC?
" + + "High Fidelity commerce is in open beta right now. Want more HFC? Get it by meeting with a banker at " + + "BankOfHighFidelity!" + // Text size + size: 22; + // Style + color: hifi.colors.blueAccent; + anchors.top: parent.top; + anchors.topMargin: 36; + anchors.left: parent.left; + anchors.leftMargin: 12; + anchors.right: parent.right; + anchors.rightMargin: 12; + height: paintedHeight; + wrapMode: Text.WordWrap; + horizontalAlignment: Text.AlignHCenter; + + onLinkActivated: { + sendSignalToWallet({ method: "transactionHistory_goToBank" }); + } + } + + HifiControlsUit.Button { + id: bankButton; + color: hifi.buttons.blue; + colorScheme: hifi.colorSchemes.dark; + anchors.top: noActivityText.bottom; + anchors.topMargin: 30; + anchors.horizontalCenter: parent.horizontalCenter; + width: parent.width/2; + height: 50; + text: "VISIT BANK OF HIGH FIDELITY"; + onClicked: { + sendSignalToWallet({ method: "transactionHistory_goToBank" }); } } } diff --git a/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml b/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml index cb4913f999..861de001d8 100644 --- a/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml +++ b/interface/resources/qml/hifi/dialogs/GeneralPreferencesDialog.qml @@ -17,7 +17,7 @@ PreferencesDialog { id: root objectName: "GeneralPreferencesDialog" title: "General Settings" - showCategories: ["User Interface", "HMD", "Snapshots", "Privacy"] + showCategories: ["User Interface", "Mouse Sensitivity", "HMD", "Snapshots", "Privacy"] property var settings: Settings { category: root.objectName property alias x: root.x diff --git a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml index 30e03bd02e..54270c2d06 100644 --- a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml +++ b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml @@ -119,8 +119,8 @@ Item { colorScheme: hifi.colorSchemes.dark currentIndex: attachment ? model.indexOf(attachment.jointName) : -1 onCurrentIndexChanged: { - if (completed && attachment && currentIndex != -1 && currentText && currentText !== attachment.jointName) { - attachment.jointName = currentText; + if (completed && attachment && currentIndex != -1 && attachment.jointName !== model[currentIndex]) { + attachment.jointName = model[currentIndex]; updateAttachment(); } } diff --git a/interface/resources/qml/hifi/models/PSFListModel.qml b/interface/resources/qml/hifi/models/PSFListModel.qml index 1bfa2f6ae0..542145904f 100644 --- a/interface/resources/qml/hifi/models/PSFListModel.qml +++ b/interface/resources/qml/hifi/models/PSFListModel.qml @@ -33,7 +33,6 @@ ListModel { // QML fires the following changed handlers even when first instantiating the Item. So we need a guard against firing them too early. property bool initialized: false; - Component.onCompleted: initialized = true; onEndpointChanged: if (initialized) { getFirstPage('delayClear'); } onSortKeyChanged: if (initialized) { getFirstPage('delayClear'); } onSearchFilterChanged: if (initialized) { getFirstPage('delayClear'); } @@ -51,6 +50,8 @@ ListModel { if (!delayedClear) { root.clear(); } currentPageToRetrieve = 1; retrievedAtLeastOnePage = false; + totalPages = 0; + totalEntries = 0; } // Page processing. @@ -58,9 +59,41 @@ ListModel { // Override to return one property of data, and/or to transform the elements. Must return an array of model elements. property var processPage: function (data) { return data; } - property var listView; // Optional. For debugging. + property var listView; // Optional. For debugging, or for having the scroll handler automatically call getNextPage. + property var flickable: listView && (listView.flickableItem || listView); + // 2: get two pages before you need it (i.e. one full page before you reach the end). + // 1: equivalent to paging when reaching end (and not before). + // 0: don't getNextPage on scroll at all here. The application code will do it. + property real pageAhead: 2.0; + function needsEarlyYFetch() { + return flickable + && !flickable.atYBeginning + && (flickable.contentY - flickable.originY) >= (flickable.contentHeight - (pageAhead * flickable.height)); + } + function needsEarlyXFetch() { + return flickable + && !flickable.atXBeginning + && (flickable.contentX - flickable.originX) >= (flickable.contentWidth - (pageAhead * flickable.width)); + } + function getNextPageIfHorizontalScroll() { + if (needsEarlyXFetch()) { getNextPage(); } + } + function getNextPageIfVerticalScroll() { + if (needsEarlyYFetch()) { getNextPage(); } + } + Component.onCompleted: { + initialized = true; + if (flickable && pageAhead > 0.0) { + // Pun: Scrollers are usually one direction or another, such that only one of the following will actually fire. + flickable.contentXChanged.connect(getNextPageIfHorizontalScroll); + flickable.contentYChanged.connect(getNextPageIfVerticalScroll); + } + } + + property int totalPages: 0; + property int totalEntries: 0; // Check consistency and call processPage. - function handlePage(error, response) { + function handlePage(error, response, cb) { var processed; console.debug('handlePage', listModelName, additionalFirstPageRequested, error, JSON.stringify(response)); function fail(message) { @@ -79,8 +112,10 @@ ListModel { if (response.current_page && response.current_page !== currentPageToRetrieve) { // Not all endpoints specify this property. return fail("Mismatched page, expected:" + currentPageToRetrieve); } + totalPages = response.total_pages || 0; + totalEntries = response.total_entries || 0; processed = processPage(response.data || response); - if (response.total_pages && (response.total_pages === currentPageToRetrieve)) { + if (totalPages && (totalPages === currentPageToRetrieve)) { currentPageToRetrieve = -1; } @@ -99,7 +134,9 @@ ListModel { if (additionalFirstPageRequested) { console.debug('deferred getFirstPage', listModelName); additionalFirstPageRequested = false; - getFirstPage('delayedClear'); + getFirstPage('delayedClear', cb); + } else if (cb) { + cb(); } } function debugView(label) { @@ -112,7 +149,7 @@ ListModel { // Override either http or getPage. property var http; // An Item that has a request function. - property var getPage: function () { // Any override MUST call handlePage(), above, even if results empty. + property var getPage: function (cb) { // Any override MUST call handlePage(), above, even if results empty. if (!http) { return console.warn("Neither http nor getPage was set for", listModelName); } // If it is a path starting with slash, add the metaverseServer domain. var url = /^\//.test(endpoint) ? (Account.metaverseServerURL + endpoint) : endpoint; @@ -130,12 +167,12 @@ ListModel { var parametersSeparator = /\?/.test(url) ? '&' : '?'; url = url + parametersSeparator + parameters.join('&'); console.debug('getPage', listModelName, currentPageToRetrieve); - http.request({uri: url}, handlePage); + http.request({uri: url}, cb ? function (error, result) { handlePage(error, result, cb); } : handlePage); } // Start the show by retrieving data according to `getPage()`. // It can be custom-defined by this item's Parent. - property var getFirstPage: function (delayClear) { + property var getFirstPage: function (delayClear, cb) { if (requestPending) { console.debug('deferring getFirstPage', listModelName); additionalFirstPageRequested = true; @@ -145,7 +182,7 @@ ListModel { resetModel(); requestPending = true; console.debug("getFirstPage", listModelName, currentPageToRetrieve); - getPage(); + getPage(cb); } property bool additionalFirstPageRequested: false; property bool requestPending: false; // For de-bouncing getNextPage. diff --git a/interface/resources/qml/hifi/tablet/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml index da8334f831..0a45feb61f 100644 --- a/interface/resources/qml/hifi/tablet/ControllerSettings.qml +++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml @@ -69,10 +69,16 @@ Item { id: stack initialItem: inputConfiguration property alias messageVisible: imageMessageBox.visible - property alias selectedPlugin: box.currentText + property string selectedPlugin: "" Rectangle { id: inputConfiguration - anchors.fill: parent + anchors { + top: parent.top + left: parent.left + right: parent.right + } + + height: 230 HifiConstants { id: hifi } @@ -168,7 +174,7 @@ Item { text: "show all input devices" onClicked: { - inputPlugins(); + box.model = inputPlugins(); changeSource(); } } @@ -208,25 +214,28 @@ Item { anchors.leftMargin: 10 anchors.topMargin: 30 } + } + Rectangle { + id: loaderRectangle + z: -1 + color: hifi.colors.baseGray + width: parent.width + anchors.left: parent.left + anchors.right: parent.right + anchors.top: inputConfiguration.bottom + anchors.bottom: parent.bottom Loader { id: loader asynchronous: false - - width: inputConfiguration.width - anchors.left: inputConfiguration.left - anchors.right: inputConfiguration.right - anchors.top: configurationHeader.bottom - anchors.topMargin: 10 - anchors.bottom: inputConfiguration.bottom - - source: InputConfiguration.configurationLayout(box.currentText); + anchors.fill: parent + source: InputConfiguration.configurationLayout(box.textAt(box.currentIndex)); onLoaded: { if (loader.item.hasOwnProperty("pluginName")) { - if (box.currentText === "HTC Vive") { + if (box.textAt(box.currentIndex) === "HTC Vive") { loader.item.pluginName = "OpenVR"; } else { - loader.item.pluginName = box.currentText; + loader.item.pluginName = box.textAt(box.currentIndex); } } @@ -252,11 +261,12 @@ Item { function changeSource() { loader.source = ""; + var selectedDevice = box.textAt(box.currentIndex); var source = ""; - if (box.currentText == "Vive") { + if (selectedDevice == "HTC Vive") { source = InputConfiguration.configurationLayout("OpenVR"); } else { - source = InputConfiguration.configurationLayout(box.currentText); + source = InputConfiguration.configurationLayout(selectedDevice); } loader.source = source; @@ -265,6 +275,8 @@ Item { } else { box.label = ""; } + + stack.selectedPlugin = selectedDevice; } Timer { @@ -287,7 +299,13 @@ Item { anchors.fill: stackView id: controllerPrefereneces objectName: "TabletControllerPreferences" - showCategories: ["VR Movement", "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"] + showCategories: [( (HMD.active) ? "VR Movement" : "Movement"), "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"] + categoryProperties: { + "VR Movement" : { + "User real-world height (meters)" : { "anchors.right" : "undefined" }, + "RESET SENSORS" : { "width" : "180", "anchors.left" : "undefined" } + } + } } } } diff --git a/interface/resources/qml/hifi/tablet/EditEntityList.qml b/interface/resources/qml/hifi/tablet/EditEntityList.qml new file mode 100644 index 0000000000..d484885103 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/EditEntityList.qml @@ -0,0 +1,15 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtWebChannel 1.0 +import "../../controls" +import "../toolbars" +import QtGraphicalEffects 1.0 +import "../../controls-uit" as HifiControls +import "../../styles-uit" + + +WebView { + id: entityListToolWebView + url: Paths.defaultScripts + "/system/html/entityList.html" + enabled: true +} diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/interface/resources/qml/hifi/tablet/EditTabView.qml index 9a7958f95c..4ac8755570 100644 --- a/interface/resources/qml/hifi/tablet/EditTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditTabView.qml @@ -9,7 +9,6 @@ import "../../styles-uit" TabBar { id: editTabView - // anchors.fill: parent width: parent.width contentWidth: parent.width padding: 0 @@ -34,7 +33,7 @@ TabBar { width: parent.width clip: true - contentHeight: createEntitiesFlow.height + importButton.height + assetServerButton.height + + contentHeight: createEntitiesFlow.height + importButton.height + assetServerButton.height + header.anchors.topMargin + createEntitiesFlow.anchors.topMargin + assetServerButton.anchors.topMargin + importButton.anchors.topMargin + header.paintedHeight @@ -77,8 +76,9 @@ TabBar { text: "MODEL" onClicked: { editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newModelButton" } - }); + method: "newEntityButtonClicked", + params: { buttonName: "newModelButton" } + }); editTabView.currentIndex = 2 } } @@ -88,8 +88,9 @@ TabBar { text: "CUBE" onClicked: { editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newCubeButton" } - }); + method: "newEntityButtonClicked", + params: { buttonName: "newCubeButton" } + }); editTabView.currentIndex = 2 } } @@ -99,8 +100,9 @@ TabBar { text: "SPHERE" onClicked: { editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newSphereButton" } - }); + method: "newEntityButtonClicked", + params: { buttonName: "newSphereButton" } + }); editTabView.currentIndex = 2 } } @@ -110,8 +112,9 @@ TabBar { text: "LIGHT" onClicked: { editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newLightButton" } - }); + method: "newEntityButtonClicked", + params: { buttonName: "newLightButton" } + }); editTabView.currentIndex = 2 } } @@ -121,8 +124,9 @@ TabBar { text: "TEXT" onClicked: { editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newTextButton" } - }); + method: "newEntityButtonClicked", + params: { buttonName: "newTextButton" } + }); editTabView.currentIndex = 2 } } @@ -132,8 +136,9 @@ TabBar { text: "IMAGE" onClicked: { editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newImageButton" } - }); + method: "newEntityButtonClicked", + params: { buttonName: "newImageButton" } + }); editTabView.currentIndex = 2 } } @@ -143,8 +148,9 @@ TabBar { text: "WEB" onClicked: { editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newWebButton" } - }); + method: "newEntityButtonClicked", + params: { buttonName: "newWebButton" } + }); editTabView.currentIndex = 2 } } @@ -154,8 +160,9 @@ TabBar { text: "ZONE" onClicked: { editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newZoneButton" } - }); + method: "newEntityButtonClicked", + params: { buttonName: "newZoneButton" } + }); editTabView.currentIndex = 2 } } @@ -165,8 +172,9 @@ TabBar { text: "PARTICLE" onClicked: { editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newParticleButton" } - }); + method: "newEntityButtonClicked", + params: { buttonName: "newParticleButton" } + }); editTabView.currentIndex = 4 } } @@ -176,8 +184,9 @@ TabBar { text: "MATERIAL" onClicked: { editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "newMaterialButton" } - }); + method: "newEntityButtonClicked", + params: { buttonName: "newMaterialButton" } + }); editTabView.currentIndex = 2 } } @@ -196,8 +205,9 @@ TabBar { anchors.topMargin: 35 onClicked: { editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "openAssetBrowserButton" } - }); + method: "newEntityButtonClicked", + params: { buttonName: "openAssetBrowserButton" } + }); } } @@ -214,8 +224,9 @@ TabBar { anchors.topMargin: 20 onClicked: { editRoot.sendToScript({ - method: "newEntityButtonClicked", params: { buttonName: "importEntitiesButton" } - }); + method: "newEntityButtonClicked", + params: { buttonName: "importEntitiesButton" } + }); } } } diff --git a/interface/resources/qml/hifi/tablet/EditTools.qml b/interface/resources/qml/hifi/tablet/EditTools.qml new file mode 100644 index 0000000000..f989038c16 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/EditTools.qml @@ -0,0 +1,58 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.3 + +// FIXME pretty non-DRY code, should figure out a way to optionally hide one tab from the tab view, keep in sync with Edit.qml +StackView { + id: editRoot + objectName: "stack" + + signal sendToScript(var message); + + topPadding: 40 + leftPadding: 0 + rightPadding: 0 + bottomPadding: 0 + + anchors.fill: parent + + property var itemProperties: {"y": editRoot.topPadding, + "width": editRoot.availableWidth, + "height": editRoot.availableHeight } + Component.onCompleted: { + tab.currentIndex = 0 + } + + background: Rectangle { + color: "#404040" //default background color + EditToolsTabView { + id: tab + anchors.fill: parent + currentIndex: -1 + onCurrentIndexChanged: { + editRoot.replace(null, tab.itemAt(currentIndex).visualItem, + itemProperties, + StackView.Immediate) + } + } + } + + function pushSource(path) { + editRoot.push(Qt.resolvedUrl("../../" + path), itemProperties, + StackView.Immediate); + editRoot.currentItem.sendToScript.connect(editRoot.sendToScript); + } + + function popSource() { + editRoot.pop(StackView.Immediate); + } + + // Passes script messages to the item on the top of the stack + function fromScript(message) { + var currentItem = editRoot.currentItem; + if (currentItem && currentItem.fromScript) { + currentItem.fromScript(message); + } else if (tab.fromScript) { + tab.fromScript(message); + } + } +} diff --git a/interface/resources/qml/hifi/tablet/EditToolsTabView.qml b/interface/resources/qml/hifi/tablet/EditToolsTabView.qml new file mode 100644 index 0000000000..00084b8ca9 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/EditToolsTabView.qml @@ -0,0 +1,328 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.2 +import QtWebChannel 1.0 +import "../../controls" +import "../toolbars" +import QtGraphicalEffects 1.0 +import "../../controls-uit" as HifiControls +import "../../styles-uit" + +TabBar { + id: editTabView + width: parent.width + contentWidth: parent.width + padding: 0 + spacing: 0 + + readonly property QtObject tabIndex: QtObject { + readonly property int create: 0 + readonly property int properties: 1 + readonly property int grid: 2 + readonly property int particle: 3 + } + + readonly property HifiConstants hifi: HifiConstants {} + + EditTabButton { + title: "CREATE" + active: true + enabled: true + property string originalUrl: "" + + property Component visualItem: Component { + + Rectangle { + color: "#404040" + id: container + + Flickable { + height: parent.height + width: parent.width + clip: true + + contentHeight: createEntitiesFlow.height + importButton.height + assetServerButton.height + + header.anchors.topMargin + createEntitiesFlow.anchors.topMargin + + assetServerButton.anchors.topMargin + importButton.anchors.topMargin + + header.paintedHeight + + contentWidth: width + + ScrollBar.vertical : ScrollBar { + visible: parent.contentHeight > parent.height + width: 20 + background: Rectangle { + color: hifi.colors.tableScrollBackgroundDark + } + } + + Text { + id: header + color: "#ffffff" + text: "Choose an Entity Type to Create:" + font.pixelSize: 14 + font.bold: true + anchors.top: parent.top + anchors.topMargin: 28 + anchors.left: parent.left + anchors.leftMargin: 28 + } + + Flow { + id: createEntitiesFlow + spacing: 35 + anchors.right: parent.right + anchors.rightMargin: 55 + anchors.left: parent.left + anchors.leftMargin: 55 + anchors.top: parent.top + anchors.topMargin: 70 + + + NewEntityButton { + icon: "icons/create-icons/94-model-01.svg" + text: "MODEL" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", + params: { buttonName: "newModelButton" } + }); + editTabView.currentIndex = tabIndex.properties + } + } + + NewEntityButton { + icon: "icons/create-icons/21-cube-01.svg" + text: "CUBE" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", + params: { buttonName: "newCubeButton" } + }); + editTabView.currentIndex = tabIndex.properties + } + } + + NewEntityButton { + icon: "icons/create-icons/22-sphere-01.svg" + text: "SPHERE" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", + params: { buttonName: "newSphereButton" } + }); + editTabView.currentIndex = tabIndex.properties + } + } + + NewEntityButton { + icon: "icons/create-icons/24-light-01.svg" + text: "LIGHT" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", + params: { buttonName: "newLightButton" } + }); + editTabView.currentIndex = tabIndex.properties + } + } + + NewEntityButton { + icon: "icons/create-icons/20-text-01.svg" + text: "TEXT" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", + params: { buttonName: "newTextButton" } + }); + editTabView.currentIndex = tabIndex.properties + } + } + + NewEntityButton { + icon: "icons/create-icons/image.svg" + text: "IMAGE" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", + params: { buttonName: "newImageButton" } + }); + editTabView.currentIndex = tabIndex.properties + } + } + + NewEntityButton { + icon: "icons/create-icons/25-web-1-01.svg" + text: "WEB" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", + params: { buttonName: "newWebButton" } + }); + editTabView.currentIndex = tabIndex.properties + } + } + + NewEntityButton { + icon: "icons/create-icons/23-zone-01.svg" + text: "ZONE" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", + params: { buttonName: "newZoneButton" } + }); + editTabView.currentIndex = tabIndex.properties + } + } + + NewEntityButton { + icon: "icons/create-icons/90-particles-01.svg" + text: "PARTICLE" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", + params: { buttonName: "newParticleButton" } + }); + editTabView.currentIndex = tabIndex.particle + } + } + + NewEntityButton { + icon: "icons/create-icons/126-material-01.svg" + text: "MATERIAL" + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", + params: { buttonName: "newMaterialButton" } + }); + editTabView.currentIndex = tabIndex.properties + } + } + } + + HifiControls.Button { + id: assetServerButton + text: "Open This Domain's Asset Server" + color: hifi.buttons.black + colorScheme: hifi.colorSchemes.dark + anchors.right: parent.right + anchors.rightMargin: 55 + anchors.left: parent.left + anchors.leftMargin: 55 + anchors.top: createEntitiesFlow.bottom + anchors.topMargin: 35 + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", + params: { buttonName: "openAssetBrowserButton" } + }); + } + } + + HifiControls.Button { + id: importButton + text: "Import Entities (.json)" + color: hifi.buttons.black + colorScheme: hifi.colorSchemes.dark + anchors.right: parent.right + anchors.rightMargin: 55 + anchors.left: parent.left + anchors.leftMargin: 55 + anchors.top: assetServerButton.bottom + anchors.topMargin: 20 + onClicked: { + editRoot.sendToScript({ + method: "newEntityButtonClicked", + params: { buttonName: "importEntitiesButton" } + }); + } + } + } + } // Flickable + } + } + + EditTabButton { + title: "PROPERTIES" + active: true + enabled: true + property string originalUrl: "" + + property Component visualItem: Component { + WebView { + id: entityPropertiesWebView + url: Paths.defaultScripts + "/system/html/entityProperties.html" + enabled: true + } + } + } + + EditTabButton { + title: "GRID" + active: true + enabled: true + property string originalUrl: "" + + property Component visualItem: Component { + WebView { + id: gridControlsWebView + url: Paths.defaultScripts + "/system/html/gridControls.html" + enabled: true + } + } + } + + EditTabButton { + title: "P" + active: true + enabled: true + property string originalUrl: "" + + property Component visualItem: Component { + WebView { + id: particleExplorerWebView + url: Paths.defaultScripts + "/system/particle_explorer/particleExplorer.html" + enabled: true + } + } + } + + function fromScript(message) { + switch (message.method) { + case 'selectTab': + selectTab(message.params.id); + break; + default: + console.warn('Unrecognized message:', JSON.stringify(message)); + } + } + + // Changes the current tab based on tab index or title as input + function selectTab(id) { + if (typeof id === 'number') { + if (id >= tabIndex.create && id <= tabIndex.particle) { + editTabView.currentIndex = id; + } else { + console.warn('Attempt to switch to invalid tab:', id); + } + } else if (typeof id === 'string'){ + switch (id.toLowerCase()) { + case 'create': + editTabView.currentIndex = tabIndex.create; + break; + case 'properties': + editTabView.currentIndex = tabIndex.properties; + break; + case 'grid': + editTabView.currentIndex = tabIndex.grid; + break; + case 'particle': + editTabView.currentIndex = tabIndex.particle; + break; + default: + console.warn('Attempt to switch to invalid tab:', id); + } + } else { + console.warn('Attempt to switch tabs with invalid input:', JSON.stringify(id)); + } + } +} diff --git a/interface/resources/qml/hifi/tablet/EntityList.qml b/interface/resources/qml/hifi/tablet/EntityList.qml new file mode 100644 index 0000000000..f4b47c19bb --- /dev/null +++ b/interface/resources/qml/hifi/tablet/EntityList.qml @@ -0,0 +1,5 @@ +WebView { + id: entityListToolWebView + url: Paths.defaultScripts + "/system/html/entityList.html" + enabled: true +} diff --git a/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml b/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml index 6df97e67b0..526a42f8e2 100644 --- a/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewMaterialDialog.qml @@ -29,12 +29,16 @@ Rectangle { property bool keyboardRasied: false function errorMessageBox(message) { - return desktop.messageBox({ - icon: hifi.icons.warning, - defaultButton: OriginalDialogs.StandardButton.Ok, - title: "Error", - text: message - }); + try { + return desktop.messageBox({ + icon: hifi.icons.warning, + defaultButton: OriginalDialogs.StandardButton.Ok, + title: "Error", + text: message + }); + } catch(e) { + Window.alert(message); + } } Item { diff --git a/interface/resources/qml/hifi/tablet/NewMaterialWindow.qml b/interface/resources/qml/hifi/tablet/NewMaterialWindow.qml new file mode 100644 index 0000000000..def816c36e --- /dev/null +++ b/interface/resources/qml/hifi/tablet/NewMaterialWindow.qml @@ -0,0 +1,20 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.2 + +StackView { + id: stackView + anchors.fill: parent + anchors.leftMargin: 10 + anchors.rightMargin: 10 + anchors.topMargin: 40 + + signal sendToScript(var message); + + NewMaterialDialog { + id: dialog + anchors.fill: parent + Component.onCompleted:{ + dialog.sendToScript.connect(stackView.sendToScript); + } + } +} diff --git a/interface/resources/qml/hifi/tablet/NewModelDialog.qml b/interface/resources/qml/hifi/tablet/NewModelDialog.qml index a349653525..10b844c987 100644 --- a/interface/resources/qml/hifi/tablet/NewModelDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewModelDialog.qml @@ -24,16 +24,21 @@ Rectangle { color: hifi.colors.baseGray; signal sendToScript(var message); property bool keyboardEnabled: false + property bool keyboardRaised: false property bool punctuationMode: false property bool keyboardRasied: false function errorMessageBox(message) { - return desktop.messageBox({ - icon: hifi.icons.warning, - defaultButton: OriginalDialogs.StandardButton.Ok, - title: "Error", - text: message - }); + try { + return desktop.messageBox({ + icon: hifi.icons.warning, + defaultButton: OriginalDialogs.StandardButton.Ok, + title: "Error", + text: message + }); + } catch(e) { + Window.alert(message); + } } Item { @@ -235,10 +240,11 @@ Rectangle { Keyboard { id: keyboard - raised: parent.keyboardEnabled + raised: parent.keyboardEnabled && parent.keyboardRaised numeric: parent.punctuationMode anchors { bottom: parent.bottom + bottomMargin: 40 left: parent.left right: parent.right } diff --git a/interface/resources/qml/hifi/tablet/NewModelWindow.qml b/interface/resources/qml/hifi/tablet/NewModelWindow.qml new file mode 100644 index 0000000000..616a44ab7a --- /dev/null +++ b/interface/resources/qml/hifi/tablet/NewModelWindow.qml @@ -0,0 +1,20 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.2 + +StackView { + id: stackView + anchors.fill: parent + anchors.leftMargin: 10 + anchors.rightMargin: 10 + anchors.topMargin: 40 + + signal sendToScript(var message); + + NewModelDialog { + id: dialog + anchors.fill: parent + Component.onCompleted:{ + dialog.sendToScript.connect(stackView.sendToScript); + } + } +} diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index 8fb49dffc0..c2aff08e35 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -15,1092 +15,1109 @@ import "../../controls-uit" as HifiControls import "." -Rectangle { - id: openVrConfiguration - +Flickable { + id: flick width: parent.width height: parent.height anchors.fill: parent - - property int leftMargin: 75 - property int countDown: 0 + contentHeight: 550 + flickableDirection: Flickable.VerticalFlick property string pluginName: "" - property var displayInformation: null + property var page: null; - readonly property bool feetChecked: feetBox.checked - readonly property bool hipsChecked: hipBox.checked - readonly property bool chestChecked: chestBox.checked - readonly property bool shouldersChecked: shoulderBox.checked - readonly property bool hmdHead: headBox.checked - readonly property bool headPuck: headPuckBox.checked - readonly property bool handController: handBox.checked - - readonly property bool handPuck: handPuckBox.checked - readonly property bool hmdDesktop: hmdInDesktop.checked - - property int state: buttonState.disabled - property var lastConfiguration: null - - HifiConstants { id: hifi } - - Component { id: screen; CalibratingScreen {} } - QtObject { - id: buttonState - readonly property int disabled: 0 - readonly property int apply: 1 - readonly property int applyAndCalibrate: 2 - readonly property int calibrate: 3 - - } - - MouseArea { - id: mouseArea - - anchors.fill: parent - propagateComposedEvents: true - onPressed: { - parent.forceActiveFocus() - mouse.accepted = false; - } - } - - color: hifi.colors.baseGray - - RalewayBold { - id: head - - text: "Head:" - size: 12 - - color: "white" - - anchors.left: parent.left - anchors.leftMargin: leftMargin - } - - Row { - id: headConfig - anchors.top: head.bottom - anchors.topMargin: 5 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - spacing: 10 - - HifiControls.CheckBox { - id: headBox - width: 15 - height: 15 - boxRadius: 7 - - onClicked: { - if (checked) { - headPuckBox.checked = false; - hmdInDesktop.checked = false; - } else { - checked = true; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - size: 12 - text: stack.selectedPlugin + " HMD" - color: hifi.colors.lightGrayText - } - - HifiControls.CheckBox { - id: headPuckBox - width: 15 - height: 15 - boxRadius: 7 - - onClicked: { - if (checked) { - headBox.checked = false; - hmdInDesktop.checked = false; - } else { - checked = true; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - size: 12 - text: "Tracker" - color: hifi.colors.lightGrayText - } - - HifiControls.CheckBox { - id: hmdInDesktop - width: 15 - height: 15 - boxRadius: 7 - visible: viveInDesktop.checked - - anchors.top: viveInDesktop.bottom - anchors.topMargin: 5 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - - onClicked: { - if (checked) { - headBox.checked = false; - headPuckBox.checked = false; - } else { - checked = true; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - size: 12 - visible: viveInDesktop.checked - text: "None" - color: hifi.colors.lightGrayText - } - } - - Row { - id: headOffsets - anchors.top: headConfig.bottom - anchors.topMargin: 5 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - spacing: 10 - visible: headPuckBox.checked - HifiControls.SpinBox { - id: headYOffset - decimals: 1 - width: 112 - label: "Y Offset" - suffix: " cm" - minimumValue: -10 - realStepSize: 1 - realValue: -5 - colorScheme: hifi.colorSchemes.dark - - onEditingFinished: { - sendConfigurationSettings(); - } - } - - - HifiControls.SpinBox { - id: headZOffset - width: 112 - label: "Z Offset" - minimumValue: -10 - realStepSize: 1 - decimals: 1 - suffix: " cm" - realValue: -5 - colorScheme: hifi.colorSchemes.dark - - onEditingFinished: { - sendConfigurationSettings(); - } - } - } - - RalewayBold { - id: hands - - text: "Hands:" - size: 12 - - color: "white" - - anchors.top: (headOffsets.visible ? headOffsets.bottom : headConfig.bottom) - anchors.topMargin: (headOffsets.visible ? 22 : 10) - anchors.left: parent.left - anchors.leftMargin: leftMargin - } - - Row { - id: handConfig - anchors.top: hands.bottom - anchors.topMargin: 5 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - spacing: 10 - - HifiControls.CheckBox { - id: handBox - width: 15 - height: 15 - boxRadius: 7 - - onClicked: { - if (checked) { - handPuckBox.checked = false; - } else { - checked = true; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - size: 12 - text: "Controllers" - color: hifi.colors.lightGrayText - } - - HifiControls.CheckBox { - id: handPuckBox - width: 12 - height: 15 - boxRadius: 7 - - onClicked: { - if (checked) { - handBox.checked = false; - } else { - checked = true; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - size: 12 - text: "Trackers" - color: hifi.colors.lightGrayText - } - } - - Row { - id: handOffset - visible: handPuckBox.checked - anchors.top: handConfig.bottom - anchors.topMargin: 5 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - spacing: 10 - - HifiControls.SpinBox { - id: handYOffset - decimals: 1 - width: 112 - suffix: " cm" - label: "Y Offset" - minimumValue: -10 - realStepSize: 1 - colorScheme: hifi.colorSchemes.dark - - onEditingFinished: { - sendConfigurationSettings(); - } - } - - - HifiControls.SpinBox { - id: handZOffset - width: 112 - label: "Z Offset" - suffix: " cm" - minimumValue: -10 - realStepSize: 1 - decimals: 1 - colorScheme: hifi.colorSchemes.dark - - onEditingFinished: { - sendConfigurationSettings(); - } - } - } - - RalewayBold { - id: additional - - text: "Additional Trackers" - size: 12 - - color: hifi.colors.white - - anchors.top: (handOffset.visible ? handOffset.bottom : handConfig.bottom) - anchors.topMargin: (handOffset.visible ? 22 : 10) - anchors.left: parent.left - anchors.leftMargin: leftMargin - } - - RalewayRegular { - id: info - - text: "See Recommended Tracker Placement" - color: hifi.colors.blueHighlight - size: 10 - anchors { - left: additional.right - leftMargin: 10 - verticalCenter: additional.verticalCenter - } - - Rectangle { - id: selected - color: hifi.colors.blueHighlight - - width: info.width - height: 1 - - anchors { - top: info.bottom - topMargin: 1 - left: info.left - right: info.right - } - - visible: false - } - - MouseArea { - anchors.fill: parent; - hoverEnabled: true - - onEntered: { - selected.visible = true; - } - - onExited: { - selected.visible = false; - } - onClicked: { - stack.messageVisible = true; - } - } - } - - Row { - id: feetConfig - anchors.top: additional.bottom - anchors.topMargin: 15 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - spacing: 10 - - HifiControls.CheckBox { - id: feetBox - width: 15 - height: 15 - boxRadius: 7 - - onClicked: { - if (!checked) { - shoulderBox.checked = false; - chestBox.checked = false; - hipBox.checked = false; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - size: 12 - text: "Feet" - color: hifi.colors.lightGrayText - } - } - - Row { - id: hipConfig - anchors.top: feetConfig.bottom - anchors.topMargin: 15 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - spacing: 10 - - HifiControls.CheckBox { - id: hipBox - width: 15 - height: 15 - boxRadius: 7 - - onClicked: { - if (checked) { - feetBox.checked = true; - } - - if (chestChecked) { - checked = true; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - size: 12 - text: "Hips" - color: hifi.colors.lightGrayText - } - - RalewayRegular { - size: 12 - text: "requires feet" - color: hifi.colors.lightGray - } - } - - - Row { - id: chestConfig - anchors.top: hipConfig.bottom - anchors.topMargin: 15 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - spacing: 10 - - HifiControls.CheckBox { - id: chestBox - width: 15 - height: 15 - boxRadius: 7 - - onClicked: { - if (checked) { - hipBox.checked = true; - feetBox.checked = true; - shoulderBox.checked = false; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - size: 12 - text: "Chest" - color: hifi.colors.lightGrayText - } - - RalewayRegular { - size: 12 - text: "requires hips" - color: hifi.colors.lightGray - } - } - - - Row { - id: shoulderConfig - anchors.top: chestConfig.bottom - anchors.topMargin: 15 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - spacing: 10 - - HifiControls.CheckBox { - id: shoulderBox - width: 15 - height: 15 - boxRadius: 7 - - onClicked: { - if (checked) { - hipBox.checked = true; - feetBox.checked = true; - chestBox.checked = false; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - size: 12 - text: "Shoulders" - color: hifi.colors.lightGrayText - } - - RalewayRegular { - size: 12 - text: "requires hips" - color: hifi.colors.lightGray - } - } - - Row { - id: shoulderAdditionalConfig - visible: shoulderBox.checked - anchors.top: shoulderConfig.bottom - anchors.topMargin: 5 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 20 - spacing: 10 - - HifiControls.SpinBox { - id: armCircumference - decimals: 1 - width: 160 - suffix: " cm" - label: "Arm Circumference" - minimumValue: 0 - realStepSize: 1.0 - colorScheme: hifi.colorSchemes.dark - realValue: 33.0 - - onEditingFinished: { - sendConfigurationSettings(); - } - } - - HifiControls.SpinBox { - id: shoulderWidth - width: 160 - label: "Shoulder Width" - suffix: " cm" - minimumValue: 0 - realStepSize: 1.0 - decimals: 1 - colorScheme: hifi.colorSchemes.dark - realValue: 48 - - onEditingFinished: { - sendConfigurationSettings(); - } - } - } - - Separator { - id: bottomSeperator - width: parent.width - anchors.top: shoulderAdditionalConfig.visible ? shoulderAdditionalConfig.bottom : shoulderConfig.bottom - anchors.topMargin: (shoulderAdditionalConfig.visible ? 25 : 10) - } - - Rectangle { - id: calibrationButton - property int color: hifi.buttons.blue - property int colorScheme: hifi.colorSchemes.light - property string glyph: hifi.glyphs.avatar1 - property bool enabled: false - property bool pressed: false - property bool hovered: false - property int size: 32 - property string text: "apply" - property int padding: 12 - - width: glyphButton.width + calibrationText.width + padding - height: hifi.dimensions.controlLineHeight - anchors.top: bottomSeperator.bottom - anchors.topMargin: 15 - anchors.left: parent.left - anchors.leftMargin: leftMargin - - radius: hifi.buttons.radius - - gradient: Gradient { - GradientStop { - position: 0.2 - color: { - if (!calibrationButton.enabled) { - hifi.buttons.disabledColorStart[calibrationButton.colorScheme] - } else if (calibrationButton.pressed) { - hifi.buttons.pressedColor[calibrationButton.color] - } else if (calibrationButton.hovered) { - hifi.buttons.hoveredColor[calibrationButton.color] - } else { - hifi.buttons.colorStart[calibrationButton.color] - } - } - } - - GradientStop { - position: 1.0 - color: { - if (!calibrationButton.enabled) { - hifi.buttons.disabledColorFinish[calibrationButton.colorScheme] - } else if (calibrationButton.pressed) { - hifi.buttons.pressedColor[calibrationButton.color] - } else if (calibrationButton.hovered) { - hifi.buttons.hoveredColor[calibrationButton.color] - } else { - hifi.buttons.colorFinish[calibrationButton.color] - } - } - } - } - - HiFiGlyphs { - id: glyphButton - color: enabled ? hifi.buttons.textColor[calibrationButton.color] - : hifi.buttons.disabledTextColor[calibrationButton.colorScheme] - text: calibrationButton.glyph - size: calibrationButton.size - - anchors { - top: parent.top - bottom: parent.bottom - bottomMargin: 1 - } - } - - RalewayBold { - id: calibrationText - font.capitalization: Font.AllUppercase - color: enabled ? hifi.buttons.textColor[calibrationButton.color] - : hifi.buttons.disabledTextColor[calibrationButton.colorScheme] - size: hifi.fontSizes.buttonLabel - text: calibrationButton.text - - anchors { - left: glyphButton.right - top: parent.top - topMargin: 7 - } - } - - MouseArea { - anchors.fill: parent - hoverEnabled: true - onClicked: { - if (calibrationButton.enabled) { - if (openVrConfiguration.state === buttonState.apply) { - InputConfiguration.uncalibratePlugin(pluginName); - updateCalibrationButton(); - } else { - calibrationTimer.interval = timeToCalibrate.realValue * 1000 - openVrConfiguration.countDown = timeToCalibrate.realValue; - var calibratingScreen = screen.createObject(); - stack.push(calibratingScreen); - calibratingScreen.canceled.connect(cancelCalibration); - calibratingScreen.restart.connect(restartCalibration); - calibratingScreen.start(calibrationTimer.interval, timeToCalibrate.realValue); - calibrationTimer.start(); - } - } - } - - onPressed: { - calibrationButton.pressed = true; - } - - onReleased: { - calibrationButton.pressed = false; - } - - onEntered: { - calibrationButton.hovered = true; - } - - onExited: { - calibrationButton.hovered = false; - } - } - } - - Timer { - id: calibrationTimer - repeat: false - interval: 20 - onTriggered: { - InputConfiguration.calibratePlugin(pluginName) - } - } - - Timer { - id: displayTimer - repeat: false - interval: 3000 - onTriggered: { + onPluginNameChanged: { + if (page !== null) { + page.pluginName = flick.pluginName; + page.displayConfiguration(); } } Component.onCompleted: { - InputConfiguration.calibrationStatus.connect(calibrationStatusInfo); - lastConfiguration = composeConfigurationSettings(); + page = config.createObject(flick.contentItem); } + Component { + id: config + Rectangle { + id: openVrConfiguration + property int leftMargin: 75 + property int countDown: 0 + property string pluginName: "" + property var displayInformation: null - Component.onDestruction: { - var settings = InputConfiguration.configurationSettings(pluginName); - var data = { - "num_pucks": settings["puckCount"] - } - UserActivityLogger.logAction("mocap_ui_close_dialog", data); - } + readonly property bool feetChecked: feetBox.checked + readonly property bool hipsChecked: hipBox.checked + readonly property bool chestChecked: chestBox.checked + readonly property bool shouldersChecked: shoulderBox.checked + readonly property bool hmdHead: headBox.checked + readonly property bool headPuck: headPuckBox.checked + readonly property bool handController: handBox.checked - HifiControls.SpinBox { - id: timeToCalibrate - width: 70 - anchors.top: calibrationButton.bottom - anchors.topMargin: 20 - anchors.left: parent.left - anchors.leftMargin: leftMargin + readonly property bool handPuck: handPuckBox.checked + readonly property bool hmdDesktop: hmdInDesktop.checked - minimumValue: 5 - realValue: 5 - colorScheme: hifi.colorSchemes.dark + property int state: buttonState.disabled + property var lastConfiguration: null - onEditingFinished: { - calibrationTimer.interval = realValue * 1000; - openVrConfiguration.countDown = realValue; - numberAnimation.duration = calibrationTimer.interval; - } - } + HifiConstants { id: hifi } - RalewayBold { - id: delayTextInfo - size: 10 - text: "Delay Before Calibration Starts" - color: hifi.colors.white + Component { id: screen; CalibratingScreen {} } + QtObject { + id: buttonState + readonly property int disabled: 0 + readonly property int apply: 1 + readonly property int applyAndCalibrate: 2 + readonly property int calibrate: 3 - anchors { - left: timeToCalibrate.right - leftMargin: 20 - verticalCenter: timeToCalibrate.verticalCenter - } - } - - RalewayRegular { - size: 12 - text: "sec" - color: hifi.colors.lightGray - - anchors { - left: delayTextInfo.right - leftMargin: 10 - verticalCenter: delayTextInfo.verticalCenter - } - } - - Separator { - id: advanceSeperator - width: parent.width - anchors.top: timeToCalibrate.bottom - anchors.topMargin: 10 - } - - RalewayBold { - id: advanceSettings - - text: "Advanced Settings" - size: 12 - - color: hifi.colors.white - - anchors.top: advanceSeperator.bottom - anchors.topMargin: 10 - anchors.left: parent.left - anchors.leftMargin: leftMargin - } - - - HifiControls.CheckBox { - id: viveInDesktop - width: 15 - height: 15 - boxRadius: 7 - - anchors.top: advanceSettings.bottom - anchors.topMargin: 5 - anchors.left: openVrConfiguration.left - anchors.leftMargin: leftMargin + 10 - - onClicked: { - if (!checked & hmdInDesktop.checked) { - headBox.checked = true; - headPuckBox.checked = false; - hmdInDesktop.checked = false; - } - sendConfigurationSettings(); - } - } - - RalewayBold { - id: viveDesktopText - size: 10 - text: "Use " + stack.selectedPlugin + " devices in desktop mode" - color: hifi.colors.white - - anchors { - left: viveInDesktop.right - leftMargin: 5 - verticalCenter: viveInDesktop.verticalCenter - } - } - - - NumberAnimation { - id: numberAnimation - target: openVrConfiguration - property: "countDown" - to: 0 - } - - - function logAction(action, status) { - console.log("calibrated from ui"); - var data = { - "num_pucks": status["puckCount"], - "puck_configuration": status["configuration"], - "head_puck": status["head_puck"], - "hand_puck": status["hand_pucks"] - } - UserActivityLogger.logAction(action, data); - } - - function calibrationStatusInfo(status) { - var calibrationScreen = stack.currentItem; - - if (!status["UI"]) { - calibratingScreen = screen.createObject(); - stack.push(calibratingScreen); - } - - if (status["calibrated"]) { - calibrationScreen.success(); - - if (status["UI"]) { - logAction("mocap_ui_success", status); } - } else if (!status["calibrated"]) { - calibrationScreen.failure(); + MouseArea { + id: mouseArea - if (status["UI"]) { - logAction("mocap_ui_failed", status); + anchors.fill: parent + propagateComposedEvents: true + onPressed: { + parent.forceActiveFocus() + mouse.accepted = false; + } + } + + color: hifi.colors.baseGray + + RalewayBold { + id: head + + text: "Head:" + size: 12 + + color: "white" + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + } + + Row { + id: headConfig + anchors.top: head.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.CheckBox { + id: headBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + headPuckBox.checked = false; + hmdInDesktop.checked = false; + } else { + checked = true; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: stack.selectedPlugin + " HMD" + color: hifi.colors.lightGrayText + } + + HifiControls.CheckBox { + id: headPuckBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + headBox.checked = false; + hmdInDesktop.checked = false; + } else { + checked = true; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Tracker" + color: hifi.colors.lightGrayText + } + + HifiControls.CheckBox { + id: hmdInDesktop + width: 15 + height: 15 + boxRadius: 7 + visible: viveInDesktop.checked + + anchors.top: viveInDesktop.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + + onClicked: { + if (checked) { + headBox.checked = false; + headPuckBox.checked = false; + } else { + checked = true; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + visible: viveInDesktop.checked + text: "None" + color: hifi.colors.lightGrayText + } + } + + Row { + id: headOffsets + anchors.top: headConfig.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + visible: headPuckBox.checked + HifiControls.SpinBox { + id: headYOffset + decimals: 1 + width: 112 + label: "Y Offset" + suffix: " cm" + minimumValue: -10 + realStepSize: 1 + realValue: -5 + colorScheme: hifi.colorSchemes.dark + + onEditingFinished: { + sendConfigurationSettings(); + } + } + + + HifiControls.SpinBox { + id: headZOffset + width: 112 + label: "Z Offset" + minimumValue: -10 + realStepSize: 1 + decimals: 1 + suffix: " cm" + realValue: -5 + colorScheme: hifi.colorSchemes.dark + + onEditingFinished: { + sendConfigurationSettings(); + } + } + } + + RalewayBold { + id: hands + + text: "Hands:" + size: 12 + + color: "white" + + anchors.top: (headOffsets.visible ? headOffsets.bottom : headConfig.bottom) + anchors.topMargin: (headOffsets.visible ? 22 : 10) + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + } + + Row { + id: handConfig + anchors.top: hands.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.CheckBox { + id: handBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + handPuckBox.checked = false; + } else { + checked = true; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Controllers" + color: hifi.colors.lightGrayText + } + + HifiControls.CheckBox { + id: handPuckBox + width: 12 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + handBox.checked = false; + } else { + checked = true; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Trackers" + color: hifi.colors.lightGrayText + } + } + + Row { + id: handOffset + visible: handPuckBox.checked + anchors.top: handConfig.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.SpinBox { + id: handYOffset + decimals: 1 + width: 112 + suffix: " cm" + label: "Y Offset" + minimumValue: -10 + realStepSize: 1 + colorScheme: hifi.colorSchemes.dark + + onEditingFinished: { + sendConfigurationSettings(); + } + } + + + HifiControls.SpinBox { + id: handZOffset + width: 112 + label: "Z Offset" + suffix: " cm" + minimumValue: -10 + realStepSize: 1 + decimals: 1 + colorScheme: hifi.colorSchemes.dark + + onEditingFinished: { + sendConfigurationSettings(); + } + } + } + + RalewayBold { + id: additional + + text: "Additional Trackers" + size: 12 + + color: hifi.colors.white + + anchors.top: (handOffset.visible ? handOffset.bottom : handConfig.bottom) + anchors.topMargin: (handOffset.visible ? 22 : 10) + anchors.left: parent.left + anchors.leftMargin: leftMargin + } + + RalewayRegular { + id: info + + text: "See Recommended Tracker Placement" + color: hifi.colors.blueHighlight + size: 10 + anchors { + left: additional.right + leftMargin: 10 + verticalCenter: additional.verticalCenter + } + + Rectangle { + id: selected + color: hifi.colors.blueHighlight + + width: info.width + height: 1 + + anchors { + top: info.bottom + topMargin: 1 + left: info.left + right: info.right + } + + visible: false + } + + MouseArea { + anchors.fill: parent; + hoverEnabled: true + + onEntered: { + selected.visible = true; + } + + onExited: { + selected.visible = false; + } + onClicked: { + stack.messageVisible = true; + } + } + } + + Row { + id: feetConfig + anchors.top: additional.bottom + anchors.topMargin: 15 + anchors.left: parent.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.CheckBox { + id: feetBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (!checked) { + shoulderBox.checked = false; + chestBox.checked = false; + hipBox.checked = false; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Feet" + color: hifi.colors.lightGrayText + } + } + + Row { + id: hipConfig + anchors.top: feetConfig.bottom + anchors.topMargin: 15 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.CheckBox { + id: hipBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + feetBox.checked = true; + } + + if (chestChecked) { + checked = true; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Hips" + color: hifi.colors.lightGrayText + } + + RalewayRegular { + size: 12 + text: "requires feet" + color: hifi.colors.lightGray + } + } + + + Row { + id: chestConfig + anchors.top: hipConfig.bottom + anchors.topMargin: 15 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.CheckBox { + id: chestBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + hipBox.checked = true; + feetBox.checked = true; + shoulderBox.checked = false; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Chest" + color: hifi.colors.lightGrayText + } + + RalewayRegular { + size: 12 + text: "requires hips" + color: hifi.colors.lightGray + } + } + + + Row { + id: shoulderConfig + anchors.top: chestConfig.bottom + anchors.topMargin: 15 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + spacing: 10 + + HifiControls.CheckBox { + id: shoulderBox + width: 15 + height: 15 + boxRadius: 7 + + onClicked: { + if (checked) { + hipBox.checked = true; + feetBox.checked = true; + chestBox.checked = false; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + size: 12 + text: "Shoulders" + color: hifi.colors.lightGrayText + } + + RalewayRegular { + size: 12 + text: "requires hips" + color: hifi.colors.lightGray + } + } + + Row { + id: shoulderAdditionalConfig + visible: shoulderBox.checked + anchors.top: shoulderConfig.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 20 + spacing: 10 + + HifiControls.SpinBox { + id: armCircumference + decimals: 1 + width: 160 + suffix: " cm" + label: "Arm Circumference" + minimumValue: 0 + realStepSize: 1.0 + colorScheme: hifi.colorSchemes.dark + realValue: 33.0 + + onEditingFinished: { + sendConfigurationSettings(); + } + } + + HifiControls.SpinBox { + id: shoulderWidth + width: 160 + label: "Shoulder Width" + suffix: " cm" + minimumValue: 0 + realStepSize: 1.0 + decimals: 1 + colorScheme: hifi.colorSchemes.dark + realValue: 48 + + onEditingFinished: { + sendConfigurationSettings(); + } + } + } + + Separator { + id: bottomSeperator + width: parent.width + anchors.top: shoulderAdditionalConfig.visible ? shoulderAdditionalConfig.bottom : shoulderConfig.bottom + anchors.topMargin: (shoulderAdditionalConfig.visible ? 25 : 10) + } + + Rectangle { + id: calibrationButton + property int color: hifi.buttons.blue + property int colorScheme: hifi.colorSchemes.light + property string glyph: hifi.glyphs.avatar1 + property bool enabled: false + property bool pressed: false + property bool hovered: false + property int size: 32 + property string text: "apply" + property int padding: 12 + + width: glyphButton.width + calibrationText.width + padding + height: hifi.dimensions.controlLineHeight + anchors.top: bottomSeperator.bottom + anchors.topMargin: 15 + anchors.left: parent.left + anchors.leftMargin: leftMargin + + radius: hifi.buttons.radius + + gradient: Gradient { + GradientStop { + position: 0.2 + color: { + if (!calibrationButton.enabled) { + hifi.buttons.disabledColorStart[calibrationButton.colorScheme] + } else if (calibrationButton.pressed) { + hifi.buttons.pressedColor[calibrationButton.color] + } else if (calibrationButton.hovered) { + hifi.buttons.hoveredColor[calibrationButton.color] + } else { + hifi.buttons.colorStart[calibrationButton.color] + } + } + } + + GradientStop { + position: 1.0 + color: { + if (!calibrationButton.enabled) { + hifi.buttons.disabledColorFinish[calibrationButton.colorScheme] + } else if (calibrationButton.pressed) { + hifi.buttons.pressedColor[calibrationButton.color] + } else if (calibrationButton.hovered) { + hifi.buttons.hoveredColor[calibrationButton.color] + } else { + hifi.buttons.colorFinish[calibrationButton.color] + } + } + } + } + + HiFiGlyphs { + id: glyphButton + color: enabled ? hifi.buttons.textColor[calibrationButton.color] + : hifi.buttons.disabledTextColor[calibrationButton.colorScheme] + text: calibrationButton.glyph + size: calibrationButton.size + + anchors { + top: parent.top + bottom: parent.bottom + bottomMargin: 1 + } + } + + RalewayBold { + id: calibrationText + font.capitalization: Font.AllUppercase + color: enabled ? hifi.buttons.textColor[calibrationButton.color] + : hifi.buttons.disabledTextColor[calibrationButton.colorScheme] + size: hifi.fontSizes.buttonLabel + text: calibrationButton.text + + anchors { + left: glyphButton.right + top: parent.top + topMargin: 7 + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onClicked: { + if (calibrationButton.enabled) { + if (openVrConfiguration.state === buttonState.apply) { + InputConfiguration.uncalibratePlugin(openVrConfiguration.pluginName); + updateCalibrationButton(); + } else { + calibrationTimer.interval = timeToCalibrate.realValue * 1000 + openVrConfiguration.countDown = timeToCalibrate.realValue; + var calibratingScreen = screen.createObject(); + stack.push(calibratingScreen); + calibratingScreen.canceled.connect(cancelCalibration); + calibratingScreen.restart.connect(restartCalibration); + calibratingScreen.start(calibrationTimer.interval, timeToCalibrate.realValue); + calibrationTimer.start(); + } + } + } + + onPressed: { + calibrationButton.pressed = true; + } + + onReleased: { + calibrationButton.pressed = false; + } + + onEntered: { + calibrationButton.hovered = true; + } + + onExited: { + calibrationButton.hovered = false; + } + } + } + + Timer { + id: calibrationTimer + repeat: false + interval: 20 + onTriggered: { + InputConfiguration.calibratePlugin(openVrConfiguration.pluginName) + } + } + + Timer { + id: displayTimer + repeat: false + interval: 3000 + onTriggered: { + } + } + + Component.onCompleted: { + InputConfiguration.calibrationStatus.connect(calibrationStatusInfo); + lastConfiguration = composeConfigurationSettings(); + } + + Component.onDestruction: { + var settings = InputConfiguration.configurationSettings(openVrConfiguration.pluginName); + var data = { + "num_pucks": settings["puckCount"] + } + UserActivityLogger.logAction("mocap_ui_close_dialog", data); + } + + HifiControls.SpinBox { + id: timeToCalibrate + width: 70 + anchors.top: calibrationButton.bottom + anchors.topMargin: 20 + anchors.left: parent.left + anchors.leftMargin: leftMargin + + minimumValue: 5 + realValue: 5 + colorScheme: hifi.colorSchemes.dark + + onEditingFinished: { + calibrationTimer.interval = realValue * 1000; + openVrConfiguration.countDown = realValue; + numberAnimation.duration = calibrationTimer.interval; + } + } + + RalewayBold { + id: delayTextInfo + size: 10 + text: "Delay Before Calibration Starts" + color: hifi.colors.white + + anchors { + left: timeToCalibrate.right + leftMargin: 20 + verticalCenter: timeToCalibrate.verticalCenter + } + } + + RalewayRegular { + size: 12 + text: "sec" + color: hifi.colors.lightGray + + anchors { + left: delayTextInfo.right + leftMargin: 10 + verticalCenter: delayTextInfo.verticalCenter + } + } + + Separator { + id: advanceSeperator + width: parent.width + anchors.top: timeToCalibrate.bottom + anchors.topMargin: 10 + } + + RalewayBold { + id: advanceSettings + + text: "Advanced Settings" + size: 12 + + color: hifi.colors.white + + anchors.top: advanceSeperator.bottom + anchors.topMargin: 10 + anchors.left: parent.left + anchors.leftMargin: leftMargin + } + + + HifiControls.CheckBox { + id: viveInDesktop + width: 15 + height: 15 + boxRadius: 7 + + anchors.top: advanceSettings.bottom + anchors.topMargin: 5 + anchors.left: openVrConfiguration.left + anchors.leftMargin: leftMargin + 10 + + onClicked: { + if (!checked & hmdInDesktop.checked) { + headBox.checked = true; + headPuckBox.checked = false; + hmdInDesktop.checked = false; + } + sendConfigurationSettings(); + } + } + + RalewayBold { + id: viveDesktopText + size: 10 + text: "Use " + stack.selectedPlugin + " devices in desktop mode" + color: hifi.colors.white + + anchors { + left: viveInDesktop.right + leftMargin: 5 + verticalCenter: viveInDesktop.verticalCenter + } + } + + + NumberAnimation { + id: numberAnimation + target: openVrConfiguration + property: "countDown" + to: 0 + } + + + function logAction(action, status) { + var data = { + "num_pucks": status["puckCount"], + "puck_configuration": status["configuration"], + "head_puck": status["head_puck"], + "hand_puck": status["hand_pucks"] + } + UserActivityLogger.logAction(action, data); + } + + function calibrationStatusInfo(status) { + var calibrationScreen = stack.currentItem; + + if (!status["UI"]) { + calibratingScreen = screen.createObject(); + stack.push(calibratingScreen); + } + + if (status["calibrated"]) { + calibrationScreen.success(); + + if (status["UI"]) { + logAction("mocap_ui_success", status); + } + + } else if (!status["calibrated"]) { + calibrationScreen.failure(); + + if (status["UI"]) { + logAction("mocap_ui_failed", status); + } + } + updateCalibrationButton(); + } + + + function trackersForConfiguration() { + var pucksNeeded = 0; + + if (headPuckBox.checked) { + pucksNeeded++; + } + + if (feetBox.checked) { + pucksNeeded++; + } + + if (hipBox.checked) { + pucksNeeded++; + } + + if (chestBox.checked) { + pucksNeeded++; + } + + if (shoulderBox.checked) { + pucksNeeded++; + } + + return pucksNeeded; + } + + function cancelCalibration() { + calibrationTimer.stop(); + } + + function restartCalibration() { + calibrationTimer.restart(); + } + + function displayConfiguration() { + var settings = InputConfiguration.configurationSettings(openVrConfiguration.pluginName); + var configurationType = settings["trackerConfiguration"]; + displayTrackerConfiguration(configurationType); + + + var HmdHead = settings["HMDHead"]; + var viveController = settings["handController"]; + var desktopMode = settings["desktopMode"]; + var hmdDesktopPosition = settings["hmdDesktopTracking"]; + + armCircumference.realValue = settings.armCircumference; + shoulderWidth.realValue = settings.shoulderWidth; + + if (HmdHead) { + headBox.checked = true; + headPuckBox.checked = false; + } else { + headPuckBox.checked = true; + headBox.checked = false; + } + + if (viveController) { + handBox.checked = true; + handPuckBox.checked = false; + } else { + handPuckBox.checked = true; + handBox.checked = false; + } + + viveInDesktop.checked = desktopMode; + hmdInDesktop.checked = hmdDesktopPosition; + + initializeButtonState(); + updateCalibrationText(); + + var data = { + "num_pucks": settings["puckCount"] + }; + + UserActivityLogger.logAction("mocap_ui_open_dialog", data); + } + + function displayTrackerConfiguration(type) { + if (type === "Feet") { + feetBox.checked = true; + } else if (type === "FeetAndHips") { + feetBox.checked = true; + hipBox.checked = true; + } else if (type === "FeetHipsChest") { + feetBox.checked = true; + hipBox.checked = true; + chestBox.checked = true; + } else if (type === "FeetHipsAndShoulders") { + feetBox.checked = true; + hipBox.checked = true; + shoulderBox.checked = true; + } else if (type === "FeetHipsChestAndShoulders") { + feetBox.checked = true; + hipBox.checked = true; + chestBox.checked = true; + shoulderBox.checked = true; + } + } + + function updateButtonState() { + var settings = composeConfigurationSettings(); + var bodySetting = settings["bodyConfiguration"]; + var headSetting = settings["headConfiguration"]; + var headOverride = headSetting["override"]; + var handSetting = settings["handConfiguration"]; + var handOverride = handSetting["override"]; + + var settingsChanged = false; + + if (lastConfiguration["bodyConfiguration"] !== bodySetting) { + settingsChanged = true; + } + + var lastHead = lastConfiguration["headConfiguration"]; + if (lastHead["override"] !== headOverride) { + settingsChanged = true; + } + + var lastHand = lastConfiguration["handConfiguration"]; + if (lastHand["override"] !== handOverride) { + settingsChanged = true; + } + + if (settingsChanged) { + if ((!handOverride) && (!headOverride) && (bodySetting === "None")) { + state = buttonState.apply; + } else { + state = buttonState.applyAndCalibrate; + } + } else { + if (state == buttonState.apply) { + state = buttonState.disabled; + } else if (state == buttonState.applyAndCalibrate) { + state = buttonState.calibrate; + } + } + + lastConfiguration = settings; + } + + function initializeButtonState() { + var settings = composeConfigurationSettings(); + var bodySetting = settings["bodyConfiguration"]; + var headSetting = settings["headConfiguration"]; + var headOverride = headSetting["override"]; + var handSetting = settings["handConfiguration"]; + var handOverride = handSetting["override"]; + + + if ((!handOverride) && (!headOverride) && (bodySetting === "None")) { + state = buttonState.disabled; + } else { + state = buttonState.calibrate; + } + } + + function updateCalibrationButton() { + updateButtonState(); + updateCalibrationText(); + } + + function updateCalibrationText() { + if (buttonState.disabled == state) { + calibrationButton.enabled = false; + calibrationButton.text = "Apply"; + } else if (buttonState.apply == state) { + calibrationButton.enabled = true; + calibrationButton.text = "Apply"; + } else if (buttonState.applyAndCalibrate == state) { + calibrationButton.enabled = true; + calibrationButton.text = "Apply And Calibrate"; + } else if (buttonState.calibrate == state) { + calibrationButton.enabled = true; + calibrationButton.text = "Calibrate"; + } + } + + function composeConfigurationSettings() { + var trackerConfiguration = ""; + var overrideHead = false; + var overrideHandController = false; + + if (shouldersChecked && chestChecked) { + trackerConfiguration = "FeetHipsChestAndShoulders"; + } else if (shouldersChecked) { + trackerConfiguration = "FeetHipsAndShoulders"; + } else if (chestChecked) { + trackerConfiguration = "FeetHipsAndChest"; + } else if (hipsChecked) { + trackerConfiguration = "FeetAndHips"; + } else if (feetChecked) { + trackerConfiguration = "Feet"; + } else { + trackerConfiguration = "None"; + } + + if (headPuck) { + overrideHead = true; + } else if (hmdHead) { + overrideHead = false; + } + + if (handController) { + overrideHandController = false; + } else if (handPuck) { + overrideHandController = true; + } + + var headObject = { + "override": overrideHead, + "Y": headYOffset.realValue, + "Z": headZOffset.realValue + } + + var handObject = { + "override": overrideHandController, + "Y": handYOffset.realValue, + "Z": handZOffset.realValue + } + + var settingsObject = { + "bodyConfiguration": trackerConfiguration, + "headConfiguration": headObject, + "handConfiguration": handObject, + "armCircumference": armCircumference.realValue, + "shoulderWidth": shoulderWidth.realValue, + "desktopMode": viveInDesktop.checked, + "hmdDesktopTracking": hmdInDesktop.checked + } + + return settingsObject; + } + + function sendConfigurationSettings() { + var settings = composeConfigurationSettings(); + InputConfiguration.setConfigurationSettings(settings, openVrConfiguration.pluginName); + updateCalibrationButton(); } } - updateCalibrationButton(); - } - - - function trackersForConfiguration() { - var pucksNeeded = 0; - - if (headPuckBox.checked) { - pucksNeeded++; - } - - if (feetBox.checked) { - pucksNeeded++; - } - - if (hipBox.checked) { - pucksNeeded++; - } - - if (chestBox.checked) { - pucksNeeded++; - } - - if (shoulderBox.checked) { - pucksNeeded++; - } - - return pucksNeeded; - } - - function cancelCalibration() { - calibrationTimer.stop(); - } - - function restartCalibration() { - calibrationTimer.restart(); - } - - function displayConfiguration() { - var settings = InputConfiguration.configurationSettings(pluginName); - var configurationType = settings["trackerConfiguration"]; - displayTrackerConfiguration(configurationType); - - - var HmdHead = settings["HMDHead"]; - var viveController = settings["handController"]; - var desktopMode = settings["desktopMode"]; - var hmdDesktopPosition = settings["hmdDesktopTracking"]; - - armCircumference.realValue = settings.armCircumference; - shoulderWidth.realValue = settings.shoulderWidth; - - if (HmdHead) { - headBox.checked = true; - headPuckBox.checked = false; - } else { - headPuckBox.checked = true; - headBox.checked = false; - } - - if (viveController) { - handBox.checked = true; - handPuckBox.checked = false; - } else { - handPuckBox.checked = true; - handBox.checked = false; - } - - viveInDesktop.checked = desktopMode; - hmdInDesktop.checked = hmdDesktopPosition; - - initializeButtonState(); - updateCalibrationText(); - - var data = { - "num_pucks": settings["puckCount"] - }; - - UserActivityLogger.logAction("mocap_ui_open_dialog", data); - } - - function displayTrackerConfiguration(type) { - if (type === "Feet") { - feetBox.checked = true; - } else if (type === "FeetAndHips") { - feetBox.checked = true; - hipBox.checked = true; - } else if (type === "FeetHipsChest") { - feetBox.checked = true; - hipBox.checked = true; - chestBox.checked = true; - } else if (type === "FeetHipsAndShoulders") { - feetBox.checked = true; - hipBox.checked = true; - shoulderBox.checked = true; - } else if (type === "FeetHipsChestAndShoulders") { - feetBox.checked = true; - hipBox.checked = true; - chestBox.checked = true; - shoulderBox.checked = true; - } - } - - function updateButtonState() { - var settings = composeConfigurationSettings(); - var bodySetting = settings["bodyConfiguration"]; - var headSetting = settings["headConfiguration"]; - var headOverride = headSetting["override"]; - var handSetting = settings["handConfiguration"]; - var handOverride = handSetting["override"]; - - var settingsChanged = false; - - if (lastConfiguration["bodyConfiguration"] !== bodySetting) { - settingsChanged = true; - } - - var lastHead = lastConfiguration["headConfiguration"]; - if (lastHead["override"] !== headOverride) { - settingsChanged = true; - } - - var lastHand = lastConfiguration["handConfiguration"]; - if (lastHand["override"] !== handOverride) { - settingsChanged = true; - } - - if (settingsChanged) { - if ((!handOverride) && (!headOverride) && (bodySetting === "None")) { - state = buttonState.apply; - } else { - state = buttonState.applyAndCalibrate; - } - } else { - if (state == buttonState.apply) { - state = buttonState.disabled; - } else if (state == buttonState.applyAndCalibrate) { - state = buttonState.calibrate; - } - } - - lastConfiguration = settings; - } - - function initializeButtonState() { - var settings = composeConfigurationSettings(); - var bodySetting = settings["bodyConfiguration"]; - var headSetting = settings["headConfiguration"]; - var headOverride = headSetting["override"]; - var handSetting = settings["handConfiguration"]; - var handOverride = handSetting["override"]; - - - if ((!handOverride) && (!headOverride) && (bodySetting === "None")) { - state = buttonState.disabled; - } else { - state = buttonState.calibrate; - } - } - - function updateCalibrationButton() { - updateButtonState(); - updateCalibrationText(); - } - - function updateCalibrationText() { - if (buttonState.disabled == state) { - calibrationButton.enabled = false; - calibrationButton.text = "Apply"; - } else if (buttonState.apply == state) { - calibrationButton.enabled = true; - calibrationButton.text = "Apply"; - } else if (buttonState.applyAndCalibrate == state) { - calibrationButton.enabled = true; - calibrationButton.text = "Apply And Calibrate"; - } else if (buttonState.calibrate == state) { - calibrationButton.enabled = true; - calibrationButton.text = "Calibrate"; - } - } - - function composeConfigurationSettings() { - var trackerConfiguration = ""; - var overrideHead = false; - var overrideHandController = false; - - if (shouldersChecked && chestChecked) { - trackerConfiguration = "FeetHipsChestAndShoulders"; - } else if (shouldersChecked) { - trackerConfiguration = "FeetHipsAndShoulders"; - } else if (chestChecked) { - trackerConfiguration = "FeetHipsAndChest"; - } else if (hipsChecked) { - trackerConfiguration = "FeetAndHips"; - } else if (feetChecked) { - trackerConfiguration = "Feet"; - } else { - trackerConfiguration = "None"; - } - - if (headPuck) { - overrideHead = true; - } else if (hmdHead) { - overrideHead = false; - } - - if (handController) { - overrideHandController = false; - } else if (handPuck) { - overrideHandController = true; - } - - var headObject = { - "override": overrideHead, - "Y": headYOffset.realValue, - "Z": headZOffset.realValue - } - - var handObject = { - "override": overrideHandController, - "Y": handYOffset.realValue, - "Z": handZOffset.realValue - } - - var settingsObject = { - "bodyConfiguration": trackerConfiguration, - "headConfiguration": headObject, - "handConfiguration": handObject, - "armCircumference": armCircumference.realValue, - "shoulderWidth": shoulderWidth.realValue, - "desktopMode": viveInDesktop.checked, - "hmdDesktopTracking": hmdInDesktop.checked - } - - return settingsObject; - } - - function sendConfigurationSettings() { - var settings = composeConfigurationSettings(); - InputConfiguration.setConfigurationSettings(settings, pluginName); - updateCalibrationButton(); } } diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 08f86770e6..9a472de046 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -89,6 +89,7 @@ StackView { property bool keyboardEnabled: false property bool punctuationMode: false + property bool keyboardRaised: false width: parent.width height: parent.height @@ -210,6 +211,8 @@ StackView { QQC2.TextField { id: addressLine + + focus: true width: addressLineContainer.width - addressLineContainer.anchors.leftMargin - addressLineContainer.anchors.rightMargin; anchors { left: addressLineContainer.left; @@ -236,24 +239,20 @@ StackView { color: hifi.colors.text background: Item {} - QQC2.Label { - T.TextField { - id: control + } - padding: 6 // numbers taken from Qt\5.9.2\Src\qtquickcontrols2\src\imports\controls\TextField.qml - leftPadding: padding + 4 - } + QQC2.Label { + font: addressLine.font - font: parent.font + x: addressLine.x + y: addressLine.y + leftPadding: addressLine.leftPadding + topPadding: addressLine.topPadding - x: control.leftPadding - y: control.topPadding - - text: parent.placeholderText2 - verticalAlignment: "AlignVCenter" - color: 'gray' - visible: parent.text === '' - } + text: addressLine.placeholderText2 + verticalAlignment: "AlignVCenter" + color: 'gray' + visible: addressLine.text === '' } Rectangle { diff --git a/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml b/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml index 63801019b9..4f1100f20b 100644 --- a/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml @@ -32,6 +32,6 @@ StackView { TabletPreferencesDialog { id: root objectName: "TabletGeneralPreferences" - showCategories: ["User Interface", "HMD", "Snapshots", "Privacy"] + showCategories: ["User Interface", "Mouse Sensitivity", "HMD", "Snapshots", "Privacy"] } } diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml index 646a0d2c77..05f45dc61e 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/TabletPreferencesDialog.qml @@ -24,6 +24,7 @@ Item { HifiConstants { id: hifi } property var sections: [] property var showCategories: [] + property var categoryProperties: ({}) property bool keyboardEnabled: false property bool keyboardRaised: false @@ -100,7 +101,8 @@ Item { // NOTE: the sort order of items in the showCategories array is the same order in the dialog. for (i = 0; i < showCategories.length; i++) { if (categoryMap[showCategories[i]]) { - sections.push(sectionBuilder.createObject(prefControls, {name: showCategories[i]})); + var properties = categoryProperties.hasOwnProperty(showCategories[i]) ? categoryProperties[showCategories[i]] : {}; + sections.push(sectionBuilder.createObject(prefControls, {name: showCategories[i], sectionProperties: properties})); } } diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml index 15db58decc..833175f311 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/Section.qml @@ -24,6 +24,7 @@ Preference { property bool isLast: false property string name: "Header" property real spacing: 8 + property var sectionProperties: ({}) default property alias preferences: contentContainer.children HifiConstants { id: hifi } @@ -163,6 +164,28 @@ Preference { if (builder) { preferences.push(builder.createObject(contentContainer, { preference: preference, isFirstCheckBox: (checkBoxCount === 1) , z: zpos})); + + var preferenceObject = preferences[preferences.length - 1]; + var props = sectionProperties.hasOwnProperty(preference.name) ? sectionProperties[preference.name] : {}; + + for(var prop in props) { + var value = props[prop]; + if(value.indexOf('.') !== -1) { + var splittedValues = value.split('.'); + if(splittedValues[0] === 'parent') { + value = preferenceObject.parent[splittedValues[1]]; + } + } else if(value === 'undefined') { + value = undefined; + } + + if(prop.indexOf('.') !== -1) { + var splittedProps = prop.split('.'); + preferenceObject[splittedProps[0]][splittedProps[1]] = value; + } else { + preferenceObject[prop] = value; + } + } } } } diff --git a/interface/src/AndroidHelper.cpp b/interface/src/AndroidHelper.cpp index ef7319e63f..419382f2cb 100644 --- a/interface/src/AndroidHelper.cpp +++ b/interface/src/AndroidHelper.cpp @@ -10,35 +10,21 @@ // #include "AndroidHelper.h" #include -#include +#include "Application.h" + +#if defined(qApp) +#undef qApp +#endif +#define qApp (static_cast(QCoreApplication::instance())) AndroidHelper::AndroidHelper() { } AndroidHelper::~AndroidHelper() { - workerThread.quit(); - workerThread.wait(); } -void AndroidHelper::init() { - workerThread.start(); - _accountManager = QSharedPointer(new AccountManager, &QObject::deleteLater); - _accountManager->setIsAgent(true); - _accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL()); - _accountManager->setSessionID(DependencyManager::get()->getSessionID()); - connect(_accountManager.data(), &AccountManager::loginComplete, [](const QUrl& authURL) { - DependencyManager::get()->setAccountInfo(AndroidHelper::instance().getAccountManager()->getAccountInfo()); - DependencyManager::get()->setAuthURL(authURL); - }); - - connect(_accountManager.data(), &AccountManager::logoutComplete, [] () { - DependencyManager::get()->logout(); - }); - _accountManager->moveToThread(&workerThread); -} - -void AndroidHelper::requestActivity(const QString &activityName, const bool backToScene) { - emit androidActivityRequested(activityName, backToScene); +void AndroidHelper::requestActivity(const QString &activityName, const bool backToScene, QList args) { + emit androidActivityRequested(activityName, backToScene, args); } void AndroidHelper::notifyLoadComplete() { @@ -49,6 +35,10 @@ void AndroidHelper::notifyEnterForeground() { emit enterForeground(); } +void AndroidHelper::notifyBeforeEnterBackground() { + emit beforeEnterBackground(); +} + void AndroidHelper::notifyEnterBackground() { emit enterBackground(); } @@ -60,3 +50,9 @@ void AndroidHelper::performHapticFeedback(int duration) { void AndroidHelper::showLoginDialog() { emit androidActivityRequested("Login", true); } + +void AndroidHelper::processURL(const QString &url) { + if (qApp->canAcceptURL(url)) { + qApp->acceptURL(url); + } +} diff --git a/interface/src/AndroidHelper.h b/interface/src/AndroidHelper.h index 007c0db4a5..03d92f91d9 100644 --- a/interface/src/AndroidHelper.h +++ b/interface/src/AndroidHelper.h @@ -13,8 +13,6 @@ #define hifi_Android_Helper_h #include -#include -#include class AndroidHelper : public QObject { Q_OBJECT @@ -23,15 +21,15 @@ public: static AndroidHelper instance; return instance; } - void init(); - void requestActivity(const QString &activityName, const bool backToScene); + void requestActivity(const QString &activityName, const bool backToScene, QList args = QList()); void notifyLoadComplete(); void notifyEnterForeground(); + void notifyBeforeEnterBackground(); void notifyEnterBackground(); void performHapticFeedback(int duration); + void processURL(const QString &url); - QSharedPointer getAccountManager() { return _accountManager; } AndroidHelper(AndroidHelper const&) = delete; void operator=(AndroidHelper const&) = delete; @@ -39,9 +37,10 @@ public slots: void showLoginDialog(); signals: - void androidActivityRequested(const QString &activityName, const bool backToScene); + void androidActivityRequested(const QString &activityName, const bool backToScene, QList args = QList()); void qtAppLoadComplete(); void enterForeground(); + void beforeEnterBackground(); void enterBackground(); void hapticFeedbackRequested(int duration); @@ -49,8 +48,6 @@ signals: private: AndroidHelper(); ~AndroidHelper(); - QSharedPointer _accountManager; - QThread workerThread; }; #endif diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fc82198076..136e44855a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include #include @@ -129,6 +130,7 @@ #include #include #include +#include #include #include #include @@ -150,8 +152,8 @@ #include "audio/AudioScope.h" #include "avatar/AvatarManager.h" #include "avatar/MyHead.h" +#include "CrashRecoveryHandler.h" #include "CrashHandler.h" -#include "Crashpad.h" #include "devices/DdeFaceTracker.h" #include "DiscoverabilityManager.h" #include "GLCanvas.h" @@ -198,7 +200,6 @@ #include #include -#include #include #include #include @@ -270,9 +271,6 @@ public: } _renderContext->doneCurrent(); - // Deleting the object with automatically shutdown the thread - connect(qApp, &QCoreApplication::aboutToQuit, this, &QObject::deleteLater); - // Transfer to a new thread moveToNewNamedThread(this, "RenderThread", [this](QThread* renderThread) { hifi::qt::addBlockingForbiddenThread("Render", renderThread); @@ -283,7 +281,9 @@ public: private: void initialize() { + setObjectName("Render"); PROFILE_SET_THREAD_NAME("Render"); + setCrashAnnotation("render_thread_id", std::to_string((size_t)QThread::currentThreadId())); if (!_renderContext->makeCurrent()) { qFatal("Unable to make rendering context current on render thread"); } @@ -792,7 +792,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { bool previousSessionCrashed { false }; if (!inTestMode) { - previousSessionCrashed = CrashHandler::checkForResetSettings(runningMarkerExisted, suppressPrompt); + previousSessionCrashed = CrashRecoveryHandler::checkForResetSettings(runningMarkerExisted, suppressPrompt); } // get dir to use for cache @@ -813,6 +813,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { } // Tell the plugin manager about our statically linked plugins + DependencyManager::set(); auto pluginManager = PluginManager::getInstance(); pluginManager->setInputPluginProvider([] { return getInputPlugins(); }); pluginManager->setDisplayPluginProvider([] { return getDisplayPlugins(); }); @@ -905,7 +906,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -922,7 +922,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); DependencyManager::set(); DependencyManager::set(nullptr, qApp->getOcteeSceneStats()); DependencyManager::set(); @@ -1075,6 +1074,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Regular.ttf"); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-Bold.ttf"); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Raleway-SemiBold.ttf"); + QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Cairo-SemiBold.ttf"); _window->setWindowTitle("High Fidelity Interface"); Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us @@ -1082,6 +1082,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo auto nodeList = DependencyManager::get(); nodeList->startThread(); + // move the AddressManager to the NodeList thread so that domain resets due to domain changes always occur + // before we tell MyAvatar to go to a new location in the new domain + auto addressManager = DependencyManager::get(); + addressManager->moveToThread(nodeList->thread()); + const char** constArgv = const_cast(argv); if (cmdOptionExists(argc, constArgv, "--disableWatchdog")) { DISABLE_WATCHDOG = true; @@ -1099,6 +1104,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo _logger->setSessionID(accountManager->getSessionID()); setCrashAnnotation("metaverse_session_id", accountManager->getSessionID().toString().toStdString()); + setCrashAnnotation("main_thread_id", std::to_string((size_t)QThread::currentThreadId())); if (steamClient) { qCDebug(interfaceapp) << "[VERSION] SteamVR buildID:" << steamClient->getSteamVRBuildID(); @@ -1163,24 +1169,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); audioIO->startThread(); - auto audioScriptingInterface = DependencyManager::set(); - connect(audioIO.data(), &AudioClient::mutedByMixer, audioScriptingInterface.data(), &AudioScriptingInterface::mutedByMixer); - connect(audioIO.data(), &AudioClient::receivedFirstPacket, audioScriptingInterface.data(), &AudioScriptingInterface::receivedFirstPacket); - connect(audioIO.data(), &AudioClient::disconnected, audioScriptingInterface.data(), &AudioScriptingInterface::disconnected); - connect(audioIO.data(), &AudioClient::muteEnvironmentRequested, [](glm::vec3 position, float radius) { - auto audioClient = DependencyManager::get(); - auto audioScriptingInterface = DependencyManager::get(); - auto myAvatarPosition = DependencyManager::get()->getMyAvatar()->getWorldPosition(); - float distance = glm::distance(myAvatarPosition, position); - - if (distance < radius) { - audioClient->setMuted(true); - audioScriptingInterface->environmentMuted(); - } - }); - connect(this, &Application::activeDisplayPluginChanged, - reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::onContextChanged); - // Make sure we don't time out during slow operations at startup updateHeartbeat(); @@ -1244,16 +1232,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(accountManager.data(), &AccountManager::authRequired, dialogsManager.data(), &DialogsManager::showLoginDialog); #endif connect(accountManager.data(), &AccountManager::usernameChanged, this, &Application::updateWindowTitle); - connect(accountManager.data(), &AccountManager::usernameChanged, [](QString username){ - setCrashAnnotation("username", username.toStdString()); - }); // set the account manager's root URL and trigger a login request if we don't have the access token accountManager->setIsAgent(true); accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL()); - auto addressManager = DependencyManager::get(); - // use our MyAvatar position and quat for address manager path addressManager->setPositionGetter([this]{ return getMyAvatar()->getWorldPosition(); }); addressManager->setOrientationGetter([this]{ return getMyAvatar()->getWorldOrientation(); }); @@ -1369,11 +1352,37 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo initializeDisplayPlugins(); qCDebug(interfaceapp, "Initialized Display"); + // An audio device changed signal received before the display plugins are set up will cause a crash, + // so we defer the setup of the `scripting::Audio` class until this point + { + auto audioScriptingInterface = DependencyManager::set(); + connect(audioIO.data(), &AudioClient::mutedByMixer, audioScriptingInterface.data(), &AudioScriptingInterface::mutedByMixer); + connect(audioIO.data(), &AudioClient::receivedFirstPacket, audioScriptingInterface.data(), &AudioScriptingInterface::receivedFirstPacket); + connect(audioIO.data(), &AudioClient::disconnected, audioScriptingInterface.data(), &AudioScriptingInterface::disconnected); + connect(audioIO.data(), &AudioClient::muteEnvironmentRequested, [](glm::vec3 position, float radius) { + auto audioClient = DependencyManager::get(); + auto audioScriptingInterface = DependencyManager::get(); + auto myAvatarPosition = DependencyManager::get()->getMyAvatar()->getWorldPosition(); + float distance = glm::distance(myAvatarPosition, position); + + if (distance < radius) { + audioClient->setMuted(true); + audioScriptingInterface->environmentMuted(); + } + }); + connect(this, &Application::activeDisplayPluginChanged, + reinterpret_cast(audioScriptingInterface.data()), &scripting::Audio::onContextChanged); + } + // Create the rendering engine. This can be slow on some machines due to lots of // GPU pipeline creation. initializeRenderEngine(); qCDebug(interfaceapp, "Initialized Render Engine."); + // Overlays need to exist before we set the ContextOverlayInterface dependency + _overlays.init(); // do this before scripts load + DependencyManager::set(); + // Initialize the user interface and menu system // Needs to happen AFTER the render engine initialization to access its configuration initializeUi(); @@ -1439,17 +1448,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // add firstRun flag from settings to launch event Setting::Handle firstRun { Settings::firstRun, true }; - // once the settings have been loaded, check if we need to flip the default for UserActivityLogger auto& userActivityLogger = UserActivityLogger::getInstance(); - if (!userActivityLogger.isDisabledSettingSet()) { - // the user activity logger is opt-out for Interface - // but it's defaulted to disabled for other targets - // so we need to enable it here if it has never been disabled by the user - userActivityLogger.disable(false); - } - - QString machineFingerPrint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()); - if (userActivityLogger.isEnabled()) { // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. @@ -1500,13 +1499,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo properties["first_run"] = firstRun.get(); // add the user's machine ID to the launch event + QString machineFingerPrint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()); properties["machine_fingerprint"] = machineFingerPrint; userActivityLogger.logAction("launch", properties); } - setCrashAnnotation("machine_fingerprint", machineFingerPrint.toStdString()); - _entityEditSender.setMyAvatar(myAvatar.get()); // The entity octree will have to know about MyAvatar for the parentJointName import @@ -1518,10 +1516,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // allow you to move an entity around in your hand _entityEditSender.setPacketsPerSecond(3000); // super high!! - // Overlays need to exist before we set the ContextOverlayInterface dependency - _overlays.init(); // do this before scripts load - DependencyManager::set(); - // Make sure we don't time out during slow operations at startup updateHeartbeat(); @@ -1762,6 +1756,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // set the local loopback interface for local sounds AudioInjector::setLocalAudioInterface(audioIO.data()); + auto audioScriptingInterface = DependencyManager::get(); audioScriptingInterface->setLocalAudioInterface(audioIO.data()); connect(audioIO.data(), &AudioClient::noiseGateOpened, audioScriptingInterface.data(), &AudioScriptingInterface::noiseGateOpened); connect(audioIO.data(), &AudioClient::noiseGateClosed, audioScriptingInterface.data(), &AudioScriptingInterface::noiseGateClosed); @@ -1784,10 +1779,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // If launched from Steam, let it handle updates const QString HIFI_NO_UPDATER_COMMAND_LINE_KEY = "--no-updater"; bool noUpdater = arguments().indexOf(HIFI_NO_UPDATER_COMMAND_LINE_KEY) != -1; - if (!noUpdater) { + bool buildCanUpdate = BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable + || BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Master; + if (!noUpdater && buildCanUpdate) { constexpr auto INSTALLER_TYPE_CLIENT_ONLY = "client_only"; - auto applicationUpdater = DependencyManager::get(); + auto applicationUpdater = DependencyManager::set(); AutoUpdater::InstallerType type = installerType == INSTALLER_TYPE_CLIENT_ONLY ? AutoUpdater::InstallerType::CLIENT_ONLY : AutoUpdater::InstallerType::FULL; @@ -2123,6 +2120,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo || ((rightHandPose.valid || lastRightHandPose.valid) && (rightHandPose != lastRightHandPose)); lastLeftHandPose = leftHandPose; lastRightHandPose = rightHandPose; + properties["avatar_identity_requests_sent"] = DependencyManager::get()->getIdentityRequestsSent(); UserActivityLogger::getInstance().logAction("stats", properties); }); @@ -2250,9 +2248,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo DependencyManager::get()->setSetPrecisionPickingOperator([&](unsigned int rayPickID, bool value) { DependencyManager::get()->setPrecisionPicking(rayPickID, value); }); - EntityTreeRenderer::setRenderDebugHullsOperator([] { - return Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowHulls); - }); // Preload Tablet sounds DependencyManager::get()->preloadSounds(); @@ -2263,7 +2258,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo qCDebug(interfaceapp) << "Metaverse session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID()); #if defined(Q_OS_ANDROID) - AndroidHelper::instance().init(); + connect(&AndroidHelper::instance(), &AndroidHelper::beforeEnterBackground, this, &Application::beforeEnterBackground); connect(&AndroidHelper::instance(), &AndroidHelper::enterBackground, this, &Application::enterBackground); connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground); AndroidHelper::instance().notifyLoadComplete(); @@ -2301,6 +2296,7 @@ void Application::domainConnectionRefused(const QString& reasonMessage, int reas QString message = "Unable to connect to the location you are visiting.\n"; message += reasonMessage; OffscreenUi::asyncWarning("", message); + getMyAvatar()->setWorldVelocity(glm::vec3(0.0f)); break; } default: @@ -2546,6 +2542,8 @@ Application::~Application() { _main3DScene = nullptr; _renderEngine = nullptr; + _gameWorkload.shutdown(); + DependencyManager::destroy(); _entityClipboard->eraseAllOctreeElements(); @@ -2557,25 +2555,28 @@ Application::~Application() { _octreeProcessor.terminate(); _entityEditSender.terminate(); + if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { + steamClient->shutdown(); + } + DependencyManager::destroy(); + + DependencyManager::destroy(); // must be destroyed before the FramebufferCache + DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); - DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); + DependencyManager::destroy(); DependencyManager::get()->cleanup(); // remove the NodeList from the DependencyManager DependencyManager::destroy(); - if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { - steamClient->shutdown(); - } - #if 0 ConnexionClient::getInstance().destroy(); #endif @@ -2595,6 +2596,8 @@ Application::~Application() { // Can't log to file passed this point, FileLogger about to be deleted qInstallMessageHandler(LogHandler::verboseMessageHandler); + + _renderEventHandler->deleteLater(); } void Application::initializeGL() { @@ -2721,7 +2724,7 @@ void Application::initializeDisplayPlugins() { setDisplayPlugin(defaultDisplayPlugin); // Now set the desired plugin if it's not the same as the default plugin - if (targetDisplayPlugin != defaultDisplayPlugin) { + if (targetDisplayPlugin && (targetDisplayPlugin != defaultDisplayPlugin)) { setDisplayPlugin(targetDisplayPlugin); } @@ -2895,6 +2898,7 @@ void Application::initializeUi() { auto compositorHelper = DependencyManager::get(); connect(compositorHelper.data(), &CompositorHelper::allowMouseCaptureChanged, this, [=] { if (isHMDMode()) { + auto compositorHelper = DependencyManager::get(); // don't capture outer smartpointer showCursor(compositorHelper->getAllowMouseCapture() ? Cursor::Manager::lookupIcon(_preferredCursor.get()) : Cursor::Icon::SYSTEM); @@ -2977,6 +2981,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { surfaceContext->setContextProperty("Overlays", &_overlays); surfaceContext->setContextProperty("Window", DependencyManager::get().data()); + surfaceContext->setContextProperty("Desktop", DependencyManager::get().data()); surfaceContext->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance()); surfaceContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); surfaceContext->setContextProperty("ScriptDiscoveryService", DependencyManager::get().data()); @@ -3002,6 +3007,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { surfaceContext->setContextProperty("HMD", DependencyManager::get().data()); surfaceContext->setContextProperty("Scene", DependencyManager::get().data()); surfaceContext->setContextProperty("Render", _renderEngine->getConfiguration().get()); + surfaceContext->setContextProperty("Workload", _gameWorkload._engine->getConfiguration().get()); surfaceContext->setContextProperty("Reticle", getApplicationCompositor().getReticleInterface()); surfaceContext->setContextProperty("Snapshot", DependencyManager::get().data()); @@ -3215,6 +3221,7 @@ void Application::setSettingConstrainToolbarPosition(bool setting) { void Application::showHelp() { static const QString HAND_CONTROLLER_NAME_VIVE = "vive"; static const QString HAND_CONTROLLER_NAME_OCULUS_TOUCH = "oculus"; + static const QString HAND_CONTROLLER_NAME_WINDOWS_MR = "windowsMR"; static const QString TAB_KEYBOARD_MOUSE = "kbm"; static const QString TAB_GAMEPAD = "gamepad"; @@ -3229,9 +3236,13 @@ void Application::showHelp() { } else if (PluginUtils::isOculusTouchControllerAvailable()) { defaultTab = TAB_HAND_CONTROLLERS; handControllerName = HAND_CONTROLLER_NAME_OCULUS_TOUCH; + } else if (qApp->getActiveDisplayPlugin()->getName() == "WindowMS") { + defaultTab = TAB_HAND_CONTROLLERS; + handControllerName = HAND_CONTROLLER_NAME_WINDOWS_MR; } else if (PluginUtils::isXboxControllerAvailable()) { defaultTab = TAB_GAMEPAD; } + // TODO need some way to detect windowsMR to load controls reference default tab in Help > Controls Reference menu. QUrlQuery queryString; queryString.addQueryItem("handControllerName", handControllerName); @@ -3257,13 +3268,22 @@ void Application::resizeGL() { // Set the desired FBO texture size. If it hasn't changed, this does nothing. // Otherwise, it must rebuild the FBOs uvec2 framebufferSize = displayPlugin->getRecommendedRenderSize(); - float renderResolutionScale = getRenderResolutionScale(); - uvec2 renderSize = uvec2(vec2(framebufferSize) * renderResolutionScale); + uvec2 renderSize = uvec2(framebufferSize); if (_renderResolution != renderSize) { _renderResolution = renderSize; DependencyManager::get()->setFrameBufferSize(fromGlm(renderSize)); } + auto renderResolutionScale = getRenderResolutionScale(); + if (displayPlugin->getRenderResolutionScale() != renderResolutionScale) { + auto renderConfig = _renderEngine->getConfiguration(); + assert(renderConfig); + auto mainView = renderConfig->getConfig("RenderMainView.RenderDeferredTask"); + assert(mainView); + mainView->setProperty("resolutionScale", renderResolutionScale); + displayPlugin->setRenderResolutionScale(renderResolutionScale); + } + // FIXME the aspect ratio for stereo displays is incorrect based on this. float aspectRatio = displayPlugin->getRecommendedAspectRatio(); _myCamera.setProjection(glm::perspective(glm::radians(_fieldOfView.get()), aspectRatio, @@ -3275,7 +3295,6 @@ void Application::resizeGL() { } DependencyManager::get()->resize(fromGlm(displayPlugin->getRecommendedUiSize())); - displayPlugin->setRenderResolutionScale(renderResolutionScale); } void Application::handleSandboxStatus(QNetworkReply* reply) { @@ -3631,6 +3650,10 @@ bool Application::event(QEvent* event) { bool Application::eventFilter(QObject* object, QEvent* event) { + if (_aboutToQuit) { + return true; + } + if (event->type() == QEvent::Leave) { getApplicationCompositor().handleLeaveEvent(); } @@ -4016,7 +4039,18 @@ void Application::mousePressEvent(QMouseEvent* event) { return; } +#if defined(Q_OS_MAC) + // Fix for OSX right click dragging on window when coming from a native window + bool isFocussed = hasFocus(); + if (!isFocussed && event->button() == Qt::MouseButton::RightButton) { + setFocus(); + isFocussed = true; + } + + if (isFocussed) { +#else if (hasFocus()) { +#endif if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->mousePressEvent(event); } @@ -4541,6 +4575,10 @@ void Application::idle() { qFatal("Unable to make main thread context current"); } + { + _gameWorkload.updateViews(_viewFrustum, getMyAvatar()->getHeadPosition()); + _gameWorkload._engine->run(); + } { PerformanceTimer perfTimer("update"); PerformanceWarning warn(showWarnings, "Application::idle()... update()"); @@ -4805,6 +4843,7 @@ void Application::loadSettings() { } isFirstPerson = (qApp->isHMDMode()); + } else { // if this is not the first run, the camera will be initialized differently depending on user settings @@ -4936,6 +4975,9 @@ void Application::init() { avatar->setCollisionSound(sound); } }, Qt::QueuedConnection); + + _gameWorkload.startup(getEntities()->getWorkloadSpace(), _main3DScene, _entitySimulation); + _entitySimulation->setWorkloadSpace(getEntities()->getWorkloadSpace()); } void Application::loadAvatarScripts(const QVector& urls) { @@ -5249,7 +5291,7 @@ void Application::setKeyboardFocusHighlight(const glm::vec3& position, const glm _keyboardFocusHighlight->setPulseMin(0.5); _keyboardFocusHighlight->setPulseMax(1.0); _keyboardFocusHighlight->setColorPulse(1.0); - _keyboardFocusHighlight->setIgnoreRayIntersection(true); + _keyboardFocusHighlight->setIgnorePickIntersection(true); _keyboardFocusHighlight->setDrawInFront(false); _keyboardFocusHighlightID = getOverlays().addOverlay(_keyboardFocusHighlight); } @@ -5287,14 +5329,16 @@ void Application::setKeyboardFocusEntity(const EntityItemID& entityItemID) { auto entityId = _keyboardFocusedEntity.get(); if (entities->wantsKeyboardFocus(entityId)) { entities->setProxyWindow(entityId, _window->windowHandle()); - auto entity = getEntities()->getEntity(entityId); if (_keyboardMouseDevice->isActive()) { _keyboardMouseDevice->pluginFocusOutEvent(); } _lastAcceptedKeyPress = usecTimestampNow(); - setKeyboardFocusHighlight(entity->getWorldPosition(), entity->getWorldOrientation(), - entity->getScaledDimensions() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR); + auto entity = getEntities()->getEntity(entityId); + if (entity) { + setKeyboardFocusHighlight(entity->getWorldPosition(), entity->getWorldOrientation(), + entity->getScaledDimensions() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR); + } } } } @@ -5446,7 +5490,7 @@ void Application::update(float deltaTime) { // process octree stats packets are sent in between full sends of a scene (this isn't currently true). // We keep physics disabled until we've received a full scene and everything near the avatar in that // scene is ready to compute its collision shape. - if (nearbyEntitiesAreReadyForPhysics()) { + if (nearbyEntitiesAreReadyForPhysics() && getMyAvatar()->isReadyForPhysics()) { _physicsEnabled = true; getMyAvatar()->updateMotionBehaviorFromMenu(); } @@ -5622,7 +5666,10 @@ void Application::update(float deltaTime) { { PROFILE_RANGE(simulation_physics, "Simulation"); PerformanceTimer perfTimer("simulation"); + if (_physicsEnabled) { + auto t0 = std::chrono::high_resolution_clock::now(); + auto t1 = t0; { PROFILE_RANGE(simulation_physics, "PrePhysics"); PerformanceTimer perfTimer("prePhysics)"); @@ -5646,6 +5693,8 @@ void Application::update(float deltaTime) { _entitySimulation->applyDynamicChanges(); + t1 = std::chrono::high_resolution_clock::now(); + avatarManager->getObjectsToRemoveFromPhysics(motionStates); _physicsEngine->removeObjects(motionStates); avatarManager->getObjectsToAddToPhysics(motionStates); @@ -5658,6 +5707,7 @@ void Application::update(float deltaTime) { dynamic->prepareForPhysicsSimulation(); }); } + auto t2 = std::chrono::high_resolution_clock::now(); { PROFILE_RANGE(simulation_physics, "StepPhysics"); PerformanceTimer perfTimer("stepPhysics"); @@ -5665,6 +5715,7 @@ void Application::update(float deltaTime) { _physicsEngine->stepSimulation(); }); } + auto t3 = std::chrono::high_resolution_clock::now(); { if (_physicsEngine->hasOutgoingChanges()) { { @@ -5710,12 +5761,26 @@ void Application::update(float deltaTime) { // NOTE: the PhysicsEngine stats are written to stdout NOT to Qt log framework _physicsEngine->dumpStatsIfNecessary(); } + auto t4 = std::chrono::high_resolution_clock::now(); if (!_aboutToQuit) { // NOTE: the getEntities()->update() call below will wait for lock // and will provide non-physical entity motion getEntities()->update(true); // update the models... } + + auto t5 = std::chrono::high_resolution_clock::now(); + + workload::Timings timings(6); + timings[0] = (t4 - t0); + timings[1] = (t5 - t4); + timings[2] = (t4 - t3); + timings[3] = (t3 - t2); + timings[4] = (t2 - t1); + timings[5] = (t1 - t0); + + _gameWorkload.updateSimulationTimings(timings); + } } } else { @@ -5865,14 +5930,10 @@ void Application::update(float deltaTime) { { - PerformanceTimer perfTimer("limitless"); + PerformanceTimer perfTimer("AnimDebugDraw"); AnimDebugDraw::getInstance().update(); } - { - PerformanceTimer perfTimer("limitless"); - DependencyManager::get()->update(); - } { // Game loop is done, mark the end of the frame for the scene transactions and the render loop to take over PerformanceTimer perfTimer("enqueueFrame"); @@ -6134,6 +6195,9 @@ PickRay Application::computePickRay(float x, float y) const { getApplicationCompositor().computeHmdPickRay(pickPoint, result.origin, result.direction); } else { pickPoint /= getCanvasSize(); + if (_myCamera.getMode() == CameraMode::CAMERA_MODE_MIRROR) { + pickPoint.x = 1.0f - pickPoint.x; + } QMutexLocker viewLocker(&_viewMutex); _viewFrustum.computePickRay(pickPoint.x, pickPoint.y, result.origin, result.direction); } @@ -6181,6 +6245,8 @@ void Application::updateWindowTitle() const { QString connectionStatus = nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)"; QString username = accountManager->getAccountInfo().getUsername(); + setCrashAnnotation("username", username.toStdString()); + QString currentPlaceName; if (isServerlessMode()) { currentPlaceName = "serverless: " + DependencyManager::get()->getDomainURL().toString(); @@ -6467,9 +6533,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe entityScriptingInterface->setPacketSender(&_entityEditSender); entityScriptingInterface->setEntityTree(getEntities()->getTree()); - // give the script engine to the RecordingScriptingInterface for its callbacks - DependencyManager::get()->setScriptEngine(scriptEngine); - if (property(hifi::properties::TEST).isValid()) { scriptEngine->registerGlobalObject("Test", TestScriptingInterface::getInstance()); } @@ -6571,6 +6634,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("Scene", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Render", _renderEngine->getConfiguration().get()); + scriptEngine->registerGlobalObject("Workload", _gameWorkload._engine->getConfiguration().get()); GraphicsScriptingInterface::registerMetaTypes(scriptEngine.data()); scriptEngine->registerGlobalObject("Graphics", DependencyManager::get().data()); @@ -6581,7 +6645,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Users", DependencyManager::get().data()); - scriptEngine->registerGlobalObject("LimitlessSpeechRecognition", DependencyManager::get().data()); scriptEngine->registerGlobalObject("GooglePoly", DependencyManager::get().data()); if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { @@ -6605,6 +6668,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue); + registerInteractiveWindowMetaType(scriptEngine.data()); + DependencyManager::get()->registerMetaTypes(scriptEngine.data()); // connect this script engines printedMessage signal to the global ScriptEngines these various messages @@ -6952,7 +7017,9 @@ void Application::showAssetServerWidget(QString filePath) { DependencyManager::get()->show(url, "AssetServer", startUpload); } else { static const QUrl url("hifi/dialogs/TabletAssetServer.qml"); - tablet->pushOntoStack(url); + if (!tablet->isPathLoaded(url)) { + tablet->pushOntoStack(url); + } } } @@ -7589,7 +7656,6 @@ void Application::toggleEntityScriptServerLogDialog() { void Application::loadAddAvatarBookmarkDialog() const { auto avatarBookmarks = DependencyManager::get(); - avatarBookmarks->addBookmark(); } void Application::loadAvatarBrowser() const { @@ -8271,7 +8337,24 @@ void Application::saveNextPhysicsStats(QString filename) { _physicsEngine->saveNextPhysicsStats(filename); } +void Application::copyToClipboard(const QString& text) { + if (QThread::currentThread() != qApp->thread()) { + QMetaObject::invokeMethod(this, "copyToClipboard"); + return; + } + + // assume that the address is being copied because the user wants a shareable address + QApplication::clipboard()->setText(text); +} + #if defined(Q_OS_ANDROID) +void Application::beforeEnterBackground() { + auto nodeList = DependencyManager::get(); + nodeList->setSendDomainServerCheckInEnabled(false); + nodeList->reset(true); + clearDomainOctreeDetails(); +} + void Application::enterBackground() { QMetaObject::invokeMethod(DependencyManager::get().data(), "stop", Qt::BlockingQueuedConnection); @@ -8286,6 +8369,8 @@ void Application::enterForeground() { if (!getActiveDisplayPlugin() || getActiveDisplayPlugin()->isActive() || !getActiveDisplayPlugin()->activate()) { qWarning() << "Could not re-activate display plugin"; } + auto nodeList = DependencyManager::get(); + nodeList->setSendDomainServerCheckInEnabled(true); } #endif diff --git a/interface/src/Application.h b/interface/src/Application.h index 236edf8bb0..94e561e550 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -72,6 +72,8 @@ #include "ui/overlays/Overlays.h" #include "UndoStackScriptingInterface.h" +#include "workload/GameWorkload.h" + #include #include #include @@ -274,6 +276,8 @@ public: render::EnginePointer getRenderEngine() override { return _renderEngine; } gpu::ContextPointer getGPUContext() const { return _gpuContext; } + const GameWorkload& getGameWorkload() const { return _gameWorkload; } + virtual void pushPostUpdateLambda(void* key, const std::function& func) override; void updateMyAvatarLookAtPosition(); @@ -306,7 +310,10 @@ public: void loadAvatarScripts(const QVector& urls); void unloadAvatarScripts(); + Q_INVOKABLE void copyToClipboard(const QString& text); + #if defined(Q_OS_ANDROID) + void beforeEnterBackground(); void enterBackground(); void enterForeground(); #endif @@ -657,6 +664,8 @@ private: render::EnginePointer _renderEngine{ new render::RenderEngine() }; gpu::ContextPointer _gpuContext; // initialized during window creation + GameWorkload _gameWorkload; + mutable QMutex _renderArgsMutex{ QMutex::Recursive }; struct AppRenderArgs { render::Args _renderArgs; diff --git a/interface/src/Application_render.cpp b/interface/src/Application_render.cpp index 2208b3187c..6648fa2eb7 100644 --- a/interface/src/Application_render.cpp +++ b/interface/src/Application_render.cpp @@ -102,7 +102,7 @@ void Application::paintGL() { PerformanceTimer perfTimer("renderOverlay"); // NOTE: There is no batch associated with this renderArgs // the ApplicationOverlay class assumes it's viewport is setup to be the device size - renderArgs._viewport = glm::ivec4(0, 0, getDeviceSize() * getRenderResolutionScale()); + renderArgs._viewport = glm::ivec4(0, 0, getDeviceSize()); _applicationOverlay.renderOverlay(&renderArgs); } @@ -118,7 +118,7 @@ void Application::paintGL() { // Primary rendering pass auto framebufferCache = DependencyManager::get(); finalFramebufferSize = framebufferCache->getFrameBufferSize(); - // Final framebuffer that will be handled to the display-plugin + // Final framebuffer that will be handed to the display-plugin finalFramebuffer = framebufferCache->getFramebuffer(); } @@ -139,7 +139,10 @@ void Application::paintGL() { frame->frameIndex = _renderFrameCount; frame->framebuffer = finalFramebuffer; frame->framebufferRecycler = [](const gpu::FramebufferPointer& framebuffer) { - DependencyManager::get()->releaseFramebuffer(framebuffer); + auto frameBufferCache = DependencyManager::get(); + if (frameBufferCache) { + frameBufferCache->releaseFramebuffer(framebuffer); + } }; // deliver final scene rendering commands to the display plugin { @@ -205,10 +208,6 @@ void Application::runRenderFrame(RenderArgs* renderArgs) { RenderArgs::DebugFlags renderDebugFlags = RenderArgs::RENDER_DEBUG_NONE; - if (Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowHulls)) { - renderDebugFlags = static_cast(renderDebugFlags | - static_cast(RenderArgs::RENDER_DEBUG_HULLS)); - } renderArgs->_debugFlags = renderDebugFlags; } diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index 8e15de673f..3e0e643bd8 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -17,7 +17,9 @@ #include #include #include +#include +#include #include #include #include @@ -28,7 +30,6 @@ #include #include "MainWindow.h" -#include "Menu.h" #include "InterfaceLogging.h" #include "QVariantGLM.h" @@ -77,7 +78,7 @@ void addAvatarEntities(const QVariantList& avatarEntities) { entity->setLastBroadcast(usecTimestampNow()); // since we're creating this object we will immediately volunteer to own its simulation - entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY); + entity->setScriptSimulationPriority(VOLUNTEER_SIMULATION_PRIORITY); entityProperties.setLastEdited(entity->getLastEdited()); } else { qCDebug(entities) << "AvatarEntitiesBookmark failed to add new Entity to local Octree"; @@ -92,10 +93,111 @@ void addAvatarEntities(const QVariantList& avatarEntities) { } AvatarBookmarks::AvatarBookmarks() { + QDir directory(PathUtils::getAppDataPath()); + if (!directory.exists()) { + directory.mkpath("."); + } + _bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATARBOOKMARKS_FILENAME; + if(!QFile::exists(_bookmarksFilename)) { + auto defaultBookmarksFilename = PathUtils::resourcesPath() + QString("avatar/bookmarks") + "/" + AVATARBOOKMARKS_FILENAME; + if (!QFile::exists(defaultBookmarksFilename)) { + qDebug() << defaultBookmarksFilename << "doesn't exist!"; + } + + if (!QFile::copy(defaultBookmarksFilename, _bookmarksFilename)) { + qDebug() << "failed to copy" << defaultBookmarksFilename << "to" << _bookmarksFilename; + } + } readFromFile(); } +void AvatarBookmarks::addBookmark(const QString& bookmarkName) { + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "addBookmark", Q_ARG(QString, bookmarkName)); + return; + } + QVariantMap bookmark = getAvatarDataToBookmark(); + insert(bookmarkName, bookmark); + + emit bookmarkAdded(bookmarkName); +} + +void AvatarBookmarks::saveBookmark(const QString& bookmarkName) { + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "saveBookmark", Q_ARG(QString, bookmarkName)); + return; + } + if (contains(bookmarkName)) { + QVariantMap bookmark = getAvatarDataToBookmark(); + insert(bookmarkName, bookmark); + } +} + +void AvatarBookmarks::removeBookmark(const QString& bookmarkName) { + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "removeBookmark", Q_ARG(QString, bookmarkName)); + return; + } + + remove(bookmarkName); + emit bookmarkDeleted(bookmarkName); +} + +bool isWearableEntity(const EntityItemPointer& entity) { + return entity->isVisible() && entity->getParentJointIndex() != INVALID_JOINT_INDEX && + (entity->getParentID() == DependencyManager::get()->getSessionUUID() || entity->getParentID() == DependencyManager::get()->getMyAvatar()->getSelfID()); +} + +void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) { + auto myAvatar = DependencyManager::get()->getMyAvatar(); + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + myAvatar->removeAvatarEntities([&](const QUuid& entityID) { + auto entity = entityTree->findEntityByID(entityID); + return entity && isWearableEntity(entity); + }); + + addAvatarEntities(avatarEntities); +} + +void AvatarBookmarks::loadBookmark(const QString& bookmarkName) { + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(this, "loadBookmark", Q_ARG(QString, bookmarkName)); + return; + } + + auto bookmarkEntry = _bookmarks.find(bookmarkName); + + if (bookmarkEntry != _bookmarks.end()) { + QVariantMap bookmark = bookmarkEntry.value().toMap(); + if (!bookmark.empty()) { + auto myAvatar = DependencyManager::get()->getMyAvatar(); + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + myAvatar->removeAvatarEntities([&](const QUuid& entityID) { + auto entity = entityTree->findEntityByID(entityID); + return entity && isWearableEntity(entity); + }); + const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString(); + myAvatar->useFullAvatarURL(avatarUrl); + qCDebug(interfaceapp) << "Avatar On " << avatarUrl; + const QList& attachments = bookmark.value(ENTRY_AVATAR_ATTACHMENTS, QList()).toList(); + + qCDebug(interfaceapp) << "Attach " << attachments; + myAvatar->setAttachmentsVariant(attachments); + + const float& qScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat(); + myAvatar->setAvatarScale(qScale); + + const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList(); + addAvatarEntities(avatarEntities); + + emit bookmarkLoaded(bookmarkName); + } + } +} + void AvatarBookmarks::readFromFile() { // migrate old avatarbookmarks.json, used to be in 'local' folder on windows QString oldConfigPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME; @@ -115,99 +217,58 @@ void AvatarBookmarks::readFromFile() { Bookmarks::readFromFile(); } -void AvatarBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) { - // Add menus/actions - auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatar); - QObject::connect(bookmarkAction, SIGNAL(triggered()), this, SLOT(addBookmark()), Qt::QueuedConnection); - _bookmarksMenu = menu->addMenu(MenuOption::AvatarBookmarks); - _deleteBookmarksAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::DeleteAvatarBookmark); - QObject::connect(_deleteBookmarksAction, SIGNAL(triggered()), this, SLOT(deleteBookmark()), Qt::QueuedConnection); - - for (auto it = _bookmarks.begin(); it != _bookmarks.end(); ++it) { - addBookmarkToMenu(menubar, it.key(), it.value()); +QVariantMap AvatarBookmarks::getBookmark(const QString &bookmarkName) +{ + if (QThread::currentThread() != thread()) { + QVariantMap result; + BLOCKING_INVOKE_METHOD(this, "getBookmark", Q_RETURN_ARG(QVariantMap, result), Q_ARG(QString, bookmarkName)); + return result; } - Bookmarks::sortActions(menubar, _bookmarksMenu); + QVariantMap bookmark; + + auto bookmarkEntry = _bookmarks.find(bookmarkName); + + if (bookmarkEntry != _bookmarks.end()) { + bookmark = bookmarkEntry.value().toMap(); + } + + return bookmark; } -void AvatarBookmarks::changeToBookmarkedAvatar() { - QAction* action = qobject_cast(sender()); +QVariantMap AvatarBookmarks::getAvatarDataToBookmark() { auto myAvatar = DependencyManager::get()->getMyAvatar(); + const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString(); + const QVariant& avatarScale = myAvatar->getAvatarScale(); + // If Avatar attachments ever change, this is where to update them, when saving remember to also append to AVATAR_BOOKMARK_VERSION + QVariantMap bookmark; + bookmark.insert(ENTRY_VERSION, AVATAR_BOOKMARK_VERSION); + bookmark.insert(ENTRY_AVATAR_URL, avatarUrl); + bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale); + bookmark.insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant()); - if (action->data().type() == QVariant::String) { - // TODO: Phase this out eventually. - // Legacy avatar bookmark. - - myAvatar->useFullAvatarURL(action->data().toString()); - qCDebug(interfaceapp) << " Using Legacy V1 Avatar Bookmark "; - } else { - - const QMap bookmark = action->data().toMap(); - // Not magic value. This is the current made version, and if it changes this interpreter should be updated to - // handle the new one separately. - // This is where the avatar bookmark entry is parsed. If adding new Value, make sure to have backward compatability with previous - if (bookmark.value(ENTRY_VERSION) == 3) { - myAvatar->removeAvatarEntities(); - const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString(); - myAvatar->useFullAvatarURL(avatarUrl); - qCDebug(interfaceapp) << "Avatar On " << avatarUrl; - const QList& attachments = bookmark.value(ENTRY_AVATAR_ATTACHMENTS, QList()).toList(); - - qCDebug(interfaceapp) << "Attach " << attachments; - myAvatar->setAttachmentsVariant(attachments); - - const float& qScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat(); - myAvatar->setAvatarScale(qScale); - - const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList(); - addAvatarEntities(avatarEntities); - - } else { - qCDebug(interfaceapp) << " Bookmark entry does not match client version, make sure client has a handler for the new AvatarBookmark"; + QScriptEngine scriptEngine; + QVariantList wearableEntities; + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + auto avatarEntities = myAvatar->getAvatarEntityData(); + for (auto entityID : avatarEntities.keys()) { + auto entity = entityTree->findEntityByID(entityID); + if (!entity || !isWearableEntity(entity)) { + continue; } + QVariantMap avatarEntityData; + EncodeBitstreamParams params; + auto desiredProperties = entity->getEntityProperties(params); + desiredProperties += PROP_LOCAL_POSITION; + desiredProperties += PROP_LOCAL_ROTATION; + EntityItemProperties entityProperties = entity->getProperties(desiredProperties); + QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties); + avatarEntityData["properties"] = scriptProperties.toVariant(); + wearableEntities.append(QVariant(avatarEntityData)); } - -} - -void AvatarBookmarks::addBookmark() { - ModalDialogListener* dlg = OffscreenUi::getTextAsync(OffscreenUi::ICON_PLACEMARK, "Bookmark Avatar", "Name", QString()); - connect(dlg, &ModalDialogListener::response, this, [=] (QVariant response) { - disconnect(dlg, &ModalDialogListener::response, this, nullptr); - auto bookmarkName = response.toString(); - bookmarkName = bookmarkName.trimmed().replace(QRegExp("(\r\n|[\r\n\t\v ])+"), " "); - if (bookmarkName.length() == 0) { - return; - } - - auto myAvatar = DependencyManager::get()->getMyAvatar(); - - const QString& avatarUrl = myAvatar->getSkeletonModelURL().toString(); - const QVariant& avatarScale = myAvatar->getAvatarScale(); - - // If Avatar attachments ever change, this is where to update them, when saving remember to also append to AVATAR_BOOKMARK_VERSION - QVariantMap bookmark; - bookmark.insert(ENTRY_VERSION, AVATAR_BOOKMARK_VERSION); - bookmark.insert(ENTRY_AVATAR_URL, avatarUrl); - bookmark.insert(ENTRY_AVATAR_SCALE, avatarScale); - bookmark.insert(ENTRY_AVATAR_ATTACHMENTS, myAvatar->getAttachmentsVariant()); - bookmark.insert(ENTRY_AVATAR_ENTITIES, myAvatar->getAvatarEntitiesVariant()); - - Bookmarks::addBookmarkToFile(bookmarkName, bookmark); - }); - -} - -void AvatarBookmarks::addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) { - QAction* changeAction = _bookmarksMenu->newAction(); - changeAction->setData(bookmark); - connect(changeAction, SIGNAL(triggered()), this, SLOT(changeToBookmarkedAvatar())); - if (!_isMenuSorted) { - menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole); - } else { - // TODO: this is aggressive but other alternatives have proved less fruitful so far. - menubar->addActionToQMenuAndActionHash(_bookmarksMenu, changeAction, name, 0, QAction::NoRole); - Bookmarks::sortActions(menubar, _bookmarksMenu); - } + bookmark.insert(ENTRY_AVATAR_ENTITIES, wearableEntities); + return bookmark; } diff --git a/interface/src/AvatarBookmarks.h b/interface/src/AvatarBookmarks.h index 7b47ea8af7..f1bc6820eb 100644 --- a/interface/src/AvatarBookmarks.h +++ b/interface/src/AvatarBookmarks.h @@ -30,19 +30,50 @@ class AvatarBookmarks: public Bookmarks, public Dependency { public: AvatarBookmarks(); - void setupMenus(Menu* menubar, MenuWrapper* menu) override; - + void setupMenus(Menu* menubar, MenuWrapper* menu) override {}; + Q_INVOKABLE QVariantMap getBookmark(const QString& bookmarkName); public slots: /**jsdoc * Add the current Avatar to your avatar bookmarks. * @function AvatarBookmarks.addBookMark */ - void addBookmark(); + void addBookmark(const QString& bookmarkName); + void saveBookmark(const QString& bookmarkName); + void loadBookmark(const QString& bookmarkName); + void removeBookmark(const QString& bookmarkName); + void updateAvatarEntities(const QVariantList& avatarEntities); + QVariantMap getBookmarks() { return _bookmarks; } + +signals: + /**jsdoc + * This function gets triggered after avatar loaded from bookmark + * @function AvatarBookmarks.bookmarkLoaded + * @param {string} bookmarkName + * @returns {Signal} + */ + void bookmarkLoaded(const QString& bookmarkName); + + /**jsdoc + * This function gets triggered after avatar bookmark deleted + * @function AvatarBookmarks.bookmarkDeleted + * @param {string} bookmarkName + * @returns {Signal} + */ + void bookmarkDeleted(const QString& bookmarkName); + + /**jsdoc + * This function gets triggered after avatar bookmark added + * @function AvatarBookmarks.bookmarkAdded + * @param {string} bookmarkName + * @returns {Signal} + */ + void bookmarkAdded(const QString& bookmarkName); protected: - void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) override; + void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) override {}; void readFromFile() override; + QVariantMap getAvatarDataToBookmark(); private: const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json"; @@ -53,9 +84,6 @@ private: const QString ENTRY_VERSION = "version"; const int AVATAR_BOOKMARK_VERSION = 3; - -private slots: - void changeToBookmarkedAvatar(); }; #endif // hifi_AvatarBookmarks_h diff --git a/interface/src/Bookmarks.cpp b/interface/src/Bookmarks.cpp index 6e99b81e50..9a8d8eb279 100644 --- a/interface/src/Bookmarks.cpp +++ b/interface/src/Bookmarks.cpp @@ -46,6 +46,10 @@ void Bookmarks::deleteBookmark() { return; } + deleteBookmark(bookmarkName); +} + +void Bookmarks::deleteBookmark(const QString& bookmarkName) { removeBookmarkFromMenu(Menu::getInstance(), bookmarkName); remove(bookmarkName); diff --git a/interface/src/Bookmarks.h b/interface/src/Bookmarks.h index dc08d4b279..88510e4eda 100644 --- a/interface/src/Bookmarks.h +++ b/interface/src/Bookmarks.h @@ -31,6 +31,8 @@ public: QString addressForBookmark(const QString& name) const; protected: + void deleteBookmark(const QString& bookmarkName); + void addBookmarkToFile(const QString& bookmarkName, const QVariant& bookmark); virtual void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) = 0; void enableMenuItems(bool enabled); @@ -38,9 +40,10 @@ protected: void insert(const QString& name, const QVariant& address); // Overwrites any existing entry with same name. void sortActions(Menu* menubar, MenuWrapper* menu); int getMenuItemLocation(QList actions, const QString& name) const; - + void removeBookmarkFromMenu(Menu* menubar, const QString& name); bool contains(const QString& name) const; - + void remove(const QString& name); + QVariantMap _bookmarks; // { name: url, ... } QPointer _bookmarksMenu; QPointer _deleteBookmarksAction; @@ -57,12 +60,9 @@ protected slots: void deleteBookmark(); private: - void remove(const QString& name); static bool sortOrder(QAction* a, QAction* b); void persistToFile(); - - void removeBookmarkFromMenu(Menu* menubar, const QString& name); }; #endif // hifi_Bookmarks_h diff --git a/interface/src/CrashHandler.h b/interface/src/CrashHandler.h index bff8bba6dd..6f8e9c3bf6 100644 --- a/interface/src/CrashHandler.h +++ b/interface/src/CrashHandler.h @@ -2,8 +2,8 @@ // CrashHandler.h // interface/src // -// Created by David Rowe on 24 Aug 2015. -// Copyright 2015 High Fidelity, Inc. +// Created by Clement Brisset on 01/19/18. +// Copyright 2018 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 @@ -12,22 +12,9 @@ #ifndef hifi_CrashHandler_h #define hifi_CrashHandler_h -#include +#include -class CrashHandler { - -public: - static bool checkForResetSettings(bool wasLikelyCrash, bool suppressPrompt = false); - -private: - enum Action { - DELETE_INTERFACE_INI, - RETAIN_IMPORTANT_INFO, - DO_NOTHING - }; - - static Action promptUserForAction(bool showCrashMessage); - static void handleCrash(Action action); -}; +bool startCrashHandler(std::string appPath); +void setCrashAnnotation(std::string name, std::string value); #endif // hifi_CrashHandler_h diff --git a/interface/src/CrashHandler_Breakpad.cpp b/interface/src/CrashHandler_Breakpad.cpp new file mode 100644 index 0000000000..c21bfa95e0 --- /dev/null +++ b/interface/src/CrashHandler_Breakpad.cpp @@ -0,0 +1,84 @@ +// +// CrashHandler_Breakpad.cpp +// interface/src +// +// Created by Clement Brisset on 01/19/18. +// Copyright 2018 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 +// + +#if HAS_BREAKPAD + +#include "CrashHandler.h" + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +google_breakpad::ExceptionHandler* gBreakpadHandler; + +std::mutex annotationMutex; +QMap annotations; + +static bool breakpad_dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) { + return succeeded; +} + +QString obbDir() { + QAndroidJniObject mediaDir = QAndroidJniObject::callStaticObjectMethod("android/os/Environment", "getExternalStorageDirectory", "()Ljava/io/File;"); + QAndroidJniObject mediaPath = mediaDir.callObjectMethod( "getAbsolutePath", "()Ljava/lang/String;" ); + QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative", "activity", "()Landroid/app/Activity;"); + QAndroidJniObject package = activity.callObjectMethod("getPackageName", "()Ljava/lang/String;"); + QString dataAbsPath = mediaPath.toString()+"/Android/obb/" + package.toString(); + return dataAbsPath; +} + +void flushAnnotations() { + QSettings settings(obbDir() + "/annotations.json", JSON_FORMAT); + settings.clear(); + settings.beginGroup("Annotations"); + for (auto k : annotations.keys()) { + settings.setValue(k, annotations.value(k)); + } + settings.endGroup(); + settings.sync(); +} + +bool startCrashHandler(std::string appPath) { + annotations["version"] = BuildInfo::VERSION; + annotations["build_number"] = BuildInfo::BUILD_NUMBER; + annotations["build_type"] = BuildInfo::BUILD_TYPE_STRING; + + auto machineFingerPrint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()); + annotations["machine_fingerprint"] = machineFingerPrint; + + flushAnnotations(); + + gBreakpadHandler = new google_breakpad::ExceptionHandler( + google_breakpad::MinidumpDescriptor(obbDir().toStdString()), + nullptr, breakpad_dumpCallback, nullptr, true, -1); + return true; +} + +void setCrashAnnotation(std::string name, std::string value) { + std::lock_guard guard(annotationMutex); + QString qName = QString::fromStdString(name); + QString qValue = QString::fromStdString(value); + annotations[qName] = qValue; + flushAnnotations(); +} + +#endif diff --git a/interface/src/Crashpad.cpp b/interface/src/CrashHandler_Crashpad.cpp similarity index 67% rename from interface/src/Crashpad.cpp rename to interface/src/CrashHandler_Crashpad.cpp index 88651925d5..d1b5103990 100644 --- a/interface/src/Crashpad.cpp +++ b/interface/src/CrashHandler_Crashpad.cpp @@ -1,5 +1,5 @@ // -// Crashpad.cpp +// CrashHandler_Crashpad.cpp // interface/src // // Created by Clement Brisset on 01/19/18. @@ -9,21 +9,24 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "Crashpad.h" +#if HAS_CRASHPAD + +#include "CrashHandler.h" #include -#include - -#if HAS_CRASHPAD - #include #include -#include +#include #include +#include -#include + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++14-extensions" +#endif #include #include @@ -31,19 +34,32 @@ #include #include +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + #include +#include +#include using namespace crashpad; static const std::string BACKTRACE_URL { CMAKE_BACKTRACE_URL }; static const std::string BACKTRACE_TOKEN { CMAKE_BACKTRACE_TOKEN }; -extern QString qAppFileName(); - CrashpadClient* client { nullptr }; std::mutex annotationMutex; crashpad::SimpleStringDictionary* crashpadAnnotations { nullptr }; +#if defined(Q_OS_WIN) +static const QString CRASHPAD_HANDLER_NAME { "crashpad_handler.exe" }; +#else +static const QString CRASHPAD_HANDLER_NAME { "crashpad_handler" }; +#endif + +#ifdef Q_OS_WIN +#include + LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { if (!client) { return EXCEPTION_CONTINUE_SEARCH; @@ -56,8 +72,9 @@ LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { return EXCEPTION_CONTINUE_SEARCH; } +#endif -bool startCrashHandler() { +bool startCrashHandler(std::string appPath) { if (BACKTRACE_URL.empty() || BACKTRACE_TOKEN.empty()) { return false; } @@ -73,6 +90,10 @@ bool startCrashHandler() { annotations["build_number"] = BuildInfo::BUILD_NUMBER.toStdString(); annotations["build_type"] = BuildInfo::BUILD_TYPE_STRING.toStdString(); + auto machineFingerPrint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()); + annotations["machine_fingerprint"] = machineFingerPrint.toStdString(); + + arguments.push_back("--no-rate-limit"); // Setup Crashpad DB directory @@ -82,7 +103,10 @@ bool startCrashHandler() { const auto crashpadDbPath = crashpadDbDir.toStdString() + "/" + crashpadDbName; // Locate Crashpad handler - const std::string CRASHPAD_HANDLER_PATH = QFileInfo(qAppFileName()).absolutePath().toStdString() + "/crashpad_handler.exe"; + const QFileInfo interfaceBinary { QString::fromStdString(appPath) }; + const QDir interfaceDir = interfaceBinary.dir(); + assert(interfaceDir.exists(CRASHPAD_HANDLER_NAME)); + const std::string CRASHPAD_HANDLER_PATH = interfaceDir.filePath(CRASHPAD_HANDLER_NAME).toStdString(); // Setup different file paths base::FilePath::StringType dbPath; @@ -101,30 +125,24 @@ bool startCrashHandler() { // Enable automated uploads. database->GetSettings()->SetUploadsEnabled(true); +#ifdef Q_OS_WIN AddVectoredExceptionHandler(0, vectoredExceptionHandler); +#endif return client->StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true); } void setCrashAnnotation(std::string name, std::string value) { - std::lock_guard guard(annotationMutex); - if (!crashpadAnnotations) { - crashpadAnnotations = new crashpad::SimpleStringDictionary(); // don't free this, let it leak - crashpad::CrashpadInfo* crashpad_info = crashpad::CrashpadInfo::GetCrashpadInfo(); - crashpad_info->set_simple_annotations(crashpadAnnotations); + if (client) { + std::lock_guard guard(annotationMutex); + if (!crashpadAnnotations) { + crashpadAnnotations = new crashpad::SimpleStringDictionary(); // don't free this, let it leak + crashpad::CrashpadInfo* crashpad_info = crashpad::CrashpadInfo::GetCrashpadInfo(); + crashpad_info->set_simple_annotations(crashpadAnnotations); + } + std::replace(value.begin(), value.end(), ',', ';'); + crashpadAnnotations->SetKeyValue(name, value); } - std::replace(value.begin(), value.end(), ',', ';'); - crashpadAnnotations->SetKeyValue(name, value); -} - -#else - -bool startCrashHandler() { - qDebug() << "No crash handler available."; - return false; -} - -void setCrashAnnotation(std::string name, std::string value) { } #endif diff --git a/interface/src/CrashHandler_None.cpp b/interface/src/CrashHandler_None.cpp new file mode 100644 index 0000000000..77b8ab332e --- /dev/null +++ b/interface/src/CrashHandler_None.cpp @@ -0,0 +1,28 @@ +// +// CrashHandler_None.cpp +// interface/src +// +// Created by Clement Brisset on 01/19/18. +// Copyright 2018 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 +// + +#if !defined(HAS_CRASHPAD) && !defined(HAS_BREAKPAD) + +#include "CrashHandler.h" + +#include + +#include + +bool startCrashHandler(std::string appPath) { + qDebug() << "No crash handler available."; + return false; +} + +void setCrashAnnotation(std::string name, std::string value) { +} + +#endif diff --git a/interface/src/CrashHandler.cpp b/interface/src/CrashRecoveryHandler.cpp similarity index 85% rename from interface/src/CrashHandler.cpp rename to interface/src/CrashRecoveryHandler.cpp index d3079b9bf4..4bf4d04180 100644 --- a/interface/src/CrashHandler.cpp +++ b/interface/src/CrashRecoveryHandler.cpp @@ -1,5 +1,5 @@ // -// CrashHandler.cpp +// CrashRecoveryHandler.cpp // interface/src // // Created by David Rowe on 24 Aug 2015. @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "CrashHandler.h" +#include "CrashRecoveryHandler.h" #include #include @@ -30,7 +30,7 @@ #include -bool CrashHandler::checkForResetSettings(bool wasLikelyCrash, bool suppressPrompt) { +bool CrashRecoveryHandler::checkForResetSettings(bool wasLikelyCrash, bool suppressPrompt) { QSettings::setDefaultFormat(JSON_FORMAT); QSettings settings; settings.beginGroup("Developer"); @@ -59,7 +59,7 @@ bool CrashHandler::checkForResetSettings(bool wasLikelyCrash, bool suppressPromp return wasLikelyCrash; } -CrashHandler::Action CrashHandler::promptUserForAction(bool showCrashMessage) { +CrashRecoveryHandler::Action CrashRecoveryHandler::promptUserForAction(bool showCrashMessage) { QDialog crashDialog; QLabel* label; if (showCrashMessage) { @@ -94,20 +94,20 @@ CrashHandler::Action CrashHandler::promptUserForAction(bool showCrashMessage) { if (result == QDialog::Accepted) { if (option1->isChecked()) { - return CrashHandler::DELETE_INTERFACE_INI; + return CrashRecoveryHandler::DELETE_INTERFACE_INI; } if (option2->isChecked()) { - return CrashHandler::RETAIN_IMPORTANT_INFO; + return CrashRecoveryHandler::RETAIN_IMPORTANT_INFO; } } // Dialog cancelled or "do nothing" option chosen - return CrashHandler::DO_NOTHING; + return CrashRecoveryHandler::DO_NOTHING; } -void CrashHandler::handleCrash(CrashHandler::Action action) { - if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_IMPORTANT_INFO) { - // CrashHandler::DO_NOTHING or unexpected value +void CrashRecoveryHandler::handleCrash(CrashRecoveryHandler::Action action) { + if (action != CrashRecoveryHandler::DELETE_INTERFACE_INI && action != CrashRecoveryHandler::RETAIN_IMPORTANT_INFO) { + // CrashRecoveryHandler::DO_NOTHING or unexpected value return; } @@ -126,7 +126,7 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { QUrl address; bool tutorialComplete = false; - if (action == CrashHandler::RETAIN_IMPORTANT_INFO) { + if (action == CrashRecoveryHandler::RETAIN_IMPORTANT_INFO) { // Read avatar info // Location and orientation @@ -151,7 +151,7 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { settingsFile.remove(); } - if (action == CrashHandler::RETAIN_IMPORTANT_INFO) { + if (action == CrashRecoveryHandler::RETAIN_IMPORTANT_INFO) { // Write avatar info // Location and orientation diff --git a/interface/src/CrashRecoveryHandler.h b/interface/src/CrashRecoveryHandler.h new file mode 100644 index 0000000000..8df72eeb75 --- /dev/null +++ b/interface/src/CrashRecoveryHandler.h @@ -0,0 +1,33 @@ +// +// CrashRecoveryHandler.h +// interface/src +// +// Created by David Rowe on 24 Aug 2015. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_CrashRecoveryHandler_h +#define hifi_CrashRecoveryHandler_h + +#include + +class CrashRecoveryHandler { + +public: + static bool checkForResetSettings(bool wasLikelyCrash, bool suppressPrompt = false); + +private: + enum Action { + DELETE_INTERFACE_INI, + RETAIN_IMPORTANT_INFO, + DO_NOTHING + }; + + static Action promptUserForAction(bool showCrashMessage); + static void handleCrash(Action action); +}; + +#endif // hifi_CrashRecoveryHandler_h diff --git a/interface/src/Crashpad.h b/interface/src/Crashpad.h deleted file mode 100644 index a815ed701a..0000000000 --- a/interface/src/Crashpad.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// Crashpad.h -// interface/src -// -// Created by Clement Brisset on 01/19/18. -// Copyright 2018 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_Crashpad_h -#define hifi_Crashpad_h - -#include - -bool startCrashHandler(); -void setCrashAnnotation(std::string name, std::string value); - -#endif // hifi_Crashpad_h diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp index 684539145e..e3a99475ef 100644 --- a/interface/src/DiscoverabilityManager.cpp +++ b/interface/src/DiscoverabilityManager.cpp @@ -23,7 +23,7 @@ #include #include -#include "Crashpad.h" +#include "CrashHandler.h" #include "Menu.h" const Discoverability::Mode DEFAULT_DISCOVERABILITY_MODE = Discoverability::Connections; @@ -97,7 +97,7 @@ void DiscoverabilityManager::updateLocation() { locationObject.insert(AVAILABILITY_KEY_IN_LOCATION, findableByString(static_cast(_mode.get()))); JSONCallbackParameters callbackParameters; - callbackParameters.jsonCallbackReceiver = this; + callbackParameters.callbackReceiver = this; callbackParameters.jsonCallbackMethod = "handleHeartbeatResponse"; // figure out if we'll send a fresh location or just a simple heartbeat @@ -121,7 +121,7 @@ void DiscoverabilityManager::updateLocation() { // we still send a heartbeat to the metaverse server for stats collection JSONCallbackParameters callbackParameters; - callbackParameters.jsonCallbackReceiver = this; + callbackParameters.callbackReceiver = this; callbackParameters.jsonCallbackMethod = "handleHeartbeatResponse"; accountManager->sendRequest(API_USER_HEARTBEAT_PATH, AccountManagerAuth::Optional, @@ -136,7 +136,7 @@ void DiscoverabilityManager::updateLocation() { setCrashAnnotation("address", currentAddress.toString().toStdString()); } -void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply& requestReply) { +void DiscoverabilityManager::handleHeartbeatResponse(QNetworkReply* requestReply) { auto dataObject = AccountManager::dataObjectFromResponse(requestReply); if (!dataObject.isEmpty()) { diff --git a/interface/src/DiscoverabilityManager.h b/interface/src/DiscoverabilityManager.h index 0c62ad5663..fc0b1fa759 100644 --- a/interface/src/DiscoverabilityManager.h +++ b/interface/src/DiscoverabilityManager.h @@ -51,7 +51,7 @@ public: static QString findableByString(Discoverability::Mode discoverabilityMode); private slots: - void handleHeartbeatResponse(QNetworkReply& requestReply); + void handleHeartbeatResponse(QNetworkReply* requestReply); private: DiscoverabilityManager(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 6f4300862d..130c2c0b89 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -124,7 +124,7 @@ Menu::Menu() { }); // Edit > Delete - auto deleteAction =addActionToQMenuAndActionHash(editMenu, "Delete", QKeySequence::Delete); + auto deleteAction = addActionToQMenuAndActionHash(editMenu, "Delete", QKeySequence::Delete); connect(deleteAction, &QAction::triggered, [] { QKeyEvent* keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Delete, Qt::ControlModifier); QCoreApplication::postEvent(QCoreApplication::instance(), keyEvent); @@ -158,46 +158,6 @@ Menu::Menu() { // Edit > Reload All Content addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches())); - - MenuWrapper* avatarMenu = addMenu("Avatar"); - auto avatarManager = DependencyManager::get(); - auto avatar = avatarManager->getMyAvatar(); - - // Avatar > Size - MenuWrapper* avatarSizeMenu = avatarMenu->addMenu("Size"); - // Avatar > Size > Increase - addActionToQMenuAndActionHash(avatarSizeMenu, - MenuOption::IncreaseAvatarSize, - 0, // QML Qt::Key_Plus, - avatar.get(), SLOT(increaseSize())); - - // Avatar > Size > Decrease - addActionToQMenuAndActionHash(avatarSizeMenu, - MenuOption::DecreaseAvatarSize, - 0, // QML Qt::Key_Minus, - avatar.get(), SLOT(decreaseSize())); - - // Avatar > Size > Reset - addActionToQMenuAndActionHash(avatarSizeMenu, - MenuOption::ResetAvatarSize, - 0, // QML Qt::Key_Equal, - avatar.get(), SLOT(resetSize())); - - // Avatar > Reset Sensors - addActionToQMenuAndActionHash(avatarMenu, - MenuOption::ResetSensors, - 0, // QML Qt::Key_Apostrophe, - qApp, SLOT(resetSensors())); - - // Avatar > Attachments... - action = addActionToQMenuAndActionHash(avatarMenu, MenuOption::Attachments); - connect(action, &QAction::triggered, [] { - qApp->showDialog(QString("hifi/dialogs/AttachmentsDialog.qml"), - QString("hifi/tablet/TabletAttachmentsDialog.qml"), "AttachmentsDialog"); - }); - - auto avatarBookmarks = DependencyManager::get(); - avatarBookmarks->setupMenus(this, avatarMenu); // Display menu ---------------------------------- // FIXME - this is not yet matching Alan's spec because it doesn't have // menus for "2D"/"3D" - we need to add support for detecting the appropriate @@ -256,8 +216,6 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::CenterPlayerInView, 0, true, qApp, SLOT(rotationModeChanged())); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Overlays, 0, true); - // View > Enter First Person Mode in HMD addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPersonHMD, 0, true); @@ -283,7 +241,7 @@ Menu::Menu() { MenuWrapper* settingsMenu = addMenu("Settings"); // Settings > General... - action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::Preferences, Qt::CTRL | Qt::Key_G, nullptr, nullptr, QAction::PreferencesRole); + action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::Preferences, Qt::CTRL | Qt::Key_G, nullptr, nullptr); connect(action, &QAction::triggered, [] { qApp->showDialog(QString("hifi/dialogs/GeneralPreferencesDialog.qml"), QString("hifi/tablet/TabletGeneralPreferences.qml"), "GeneralPreferencesDialog"); @@ -317,11 +275,11 @@ Menu::Menu() { QString("hifi/tablet/TabletGraphicsPreferences.qml"), "GraphicsPreferencesDialog"); }); - // Settings > Avatar... - action = addActionToQMenuAndActionHash(settingsMenu, "Avatar..."); + // Settings > Attachments... + action = addActionToQMenuAndActionHash(settingsMenu, MenuOption::Attachments); connect(action, &QAction::triggered, [] { - qApp->showDialog(QString("hifi/dialogs/AvatarPreferencesDialog.qml"), - QString("hifi/tablet/TabletAvatarPreferences.qml"), "AvatarPreferencesDialog"); + qApp->showDialog(QString("hifi/dialogs/AttachmentsDialog.qml"), + QString("hifi/tablet/TabletAttachmentsDialog.qml"), "AttachmentsDialog"); }); // Settings > Developer Menu @@ -582,6 +540,9 @@ Menu::Menu() { action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowOtherLookAtVectors, 0, false); connect(action, &QAction::triggered, [this]{ Avatar::setShowOtherLookAtVectors(isOptionChecked(MenuOption::ShowOtherLookAtVectors)); }); + auto avatarManager = DependencyManager::get(); + auto avatar = avatarManager->getMyAvatar(); + action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableLookAtSnapping, 0, true); connect(action, &QAction::triggered, [this, avatar]{ avatar->setProperty("lookAtSnappingEnabled", isOptionChecked(MenuOption::EnableLookAtSnapping)); @@ -725,7 +686,6 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowOwned, 0, false, drawStatusConfig, SLOT(setShowNetwork(bool))); } - addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowHulls, 0, false, qApp->getEntities().data(), SIGNAL(setRenderDebugHulls())); addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletWireframe, 0, false, qApp, SLOT(setShowBulletWireframe(bool))); addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowBulletAABBs, 0, false, qApp, SLOT(setShowBulletAABBs(bool))); @@ -813,12 +773,15 @@ Menu::Menu() { }); essLogAction->setEnabled(nodeList->getThisNodeCanRez()); - action = addActionToQMenuAndActionHash(developerMenu, "Script Log (HMD friendly)...", Qt::NoButton, + addActionToQMenuAndActionHash(developerMenu, "Script Log (HMD friendly)...", Qt::NoButton, qApp, SLOT(showScriptLogs())); addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::VerboseLogging, 0, false, qApp, SLOT(updateVerboseLogging())); + // Developer > Show Overlays + addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Overlays, 0, true); + #if 0 /// -------------- REMOVED FOR NOW -------------- addDisabledActionAndSeparator(navigateMenu, "History"); QAction* backAction = addActionToQMenuAndActionHash(navigateMenu, MenuOption::Back, 0, addressManager.data(), SLOT(goBack())); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 6fb089acd8..1ab7faa82b 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -141,7 +141,6 @@ namespace MenuOption { const QString Overlays = "Show Overlays"; const QString PackageModel = "Package Model as .fst..."; const QString Pair = "Pair"; - const QString PhysicsShowHulls = "Draw Collision Shapes"; const QString PhysicsShowOwned = "Highlight Simulation Ownership"; const QString VerboseLogging = "Verbose Logging"; const QString PhysicsShowBulletWireframe = "Show Bullet Collision"; diff --git a/interface/src/SecondaryCamera.cpp b/interface/src/SecondaryCamera.cpp index b9a767f700..559cb140b5 100644 --- a/interface/src/SecondaryCamera.cpp +++ b/interface/src/SecondaryCamera.cpp @@ -11,25 +11,16 @@ #include "SecondaryCamera.h" +#include +#include + #include #include -#include #include "Application.h" using RenderArgsPointer = std::shared_ptr; -void MainRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred) { - task.addJob("RenderShadowTask", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1); - const auto items = task.addJob("FetchCullSort", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1); - assert(items.canCast()); - if (!isDeferred) { - task.addJob("Forward", items); - } else { - task.addJob("RenderDeferredTask", items); - } -} - class SecondaryCameraJob { // Changes renderContext for our framebuffer and view. public: using Config = SecondaryCameraJobConfig; @@ -213,10 +204,10 @@ void SecondaryCameraRenderTask::build(JobModel& task, const render::Varying& inp const auto cachedArg = task.addJob("SecondaryCamera"); const auto items = task.addJob("FetchCullSort", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1); assert(items.canCast()); - if (!isDeferred) { - task.addJob("Forward", items); + if (isDeferred) { + task.addJob("RenderDeferredTask", items, false); } else { - task.addJob("RenderDeferredTask", items); + task.addJob("Forward", items); } task.addJob("EndSecondaryCamera", cachedArg); } \ No newline at end of file diff --git a/interface/src/SecondaryCamera.h b/interface/src/SecondaryCamera.h index 3d9e52617c..3c8540c081 100644 --- a/interface/src/SecondaryCamera.h +++ b/interface/src/SecondaryCamera.h @@ -12,23 +12,11 @@ #pragma once #ifndef hifi_SecondaryCamera_h #define hifi_SecondaryCamera_h - -#include + #include -#include -#include #include #include -class MainRenderTask { -public: - using JobModel = render::Task::Model; - - MainRenderTask() {} - - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred = true); -}; - class SecondaryCameraJobConfig : public render::Task::Config { // Exposes secondary camera parameters to JavaScript. Q_OBJECT Q_PROPERTY(QUuid attachedEntityId MEMBER attachedEntityId NOTIFY dirty) // entity whose properties define camera position and orientation diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 9d568e6d73..dbc82fd70e 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -440,8 +440,8 @@ QVariantMap AvatarActionHold::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&]{ arguments["holderID"] = _holderID; - arguments["relativePosition"] = glmToQMap(_relativePosition); - arguments["relativeRotation"] = glmToQMap(_relativeRotation); + arguments["relativePosition"] = vec3ToQMap(_relativePosition); + arguments["relativeRotation"] = quatToQMap(_relativeRotation); arguments["timeScale"] = _linearTimeScale; arguments["hand"] = _hand; arguments["kinematic"] = _kinematic; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 4d133706e6..fab512f787 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -14,6 +14,9 @@ #include #include +#include + +#include "AvatarLogging.h" #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic push @@ -36,13 +39,13 @@ #include #include #include -#include #include #include "Application.h" #include "InterfaceLogging.h" #include "Menu.h" #include "MyAvatar.h" +#include "OtherAvatar.h" #include "SceneScriptingInterface.h" // 50 times per second - target is 45hz, but this helps account for any small deviations @@ -54,6 +57,13 @@ static const quint64 MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS = USECS_PER_SECOND / // We add _myAvatar into the hash with all the other AvatarData, and we use the default NULL QUid as the key. const QUuid MY_AVATAR_KEY; // NULL key +namespace { + // For an unknown avatar-data packet, wait this long before requesting the identity. + constexpr std::chrono::milliseconds REQUEST_UNKNOWN_IDENTITY_DELAY { 5 * 1000 }; + constexpr int REQUEST_UNKNOWN_IDENTITY_TRANSMITS = 3; +} +using std::chrono::steady_clock; + AvatarManager::AvatarManager(QObject* parent) : _avatarsToFade(), _myAvatar(new MyAvatar(qApp->thread()), [](MyAvatar* ptr) { ptr->deleteLater(); }) @@ -118,6 +128,7 @@ void AvatarManager::updateMyAvatar(float deltaTime) { _lastSendAvatarDataTime = now; _myAvatarSendRate.increment(); } + } @@ -186,11 +197,21 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { uint64_t updateExpiry = startTime + UPDATE_BUDGET; int numAvatarsUpdated = 0; int numAVatarsNotUpdated = 0; + bool physicsEnabled = qApp->isPhysicsEnabled(); render::Transaction transaction; while (!sortedAvatars.empty()) { const SortableAvatar& sortData = sortedAvatars.top(); const auto avatar = std::static_pointer_cast(sortData.getAvatar()); + const auto otherAvatar = std::static_pointer_cast(sortData.getAvatar()); + + // if the geometry is loaded then turn off the orb + if (avatar->getSkeletonModel()->isLoaded()) { + // remove the orb if it is there + otherAvatar->removeOrb(); + } else { + otherAvatar->updateOrbPosition(); + } bool ignoring = DependencyManager::get()->isPersonalMutingNode(avatar->getID()); if (ignoring) { @@ -202,7 +223,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { if (_shouldRender) { avatar->ensureInScene(avatar, qApp->getMain3DScene()); } - if (!avatar->isInPhysicsSimulation()) { + if (physicsEnabled && !avatar->isInPhysicsSimulation()) { ShapeInfo shapeInfo; avatar->computeShapeInfo(shapeInfo); btCollisionShape* shape = const_cast(ObjectMotionState::getShapeManager()->getShape(shapeInfo)); @@ -276,6 +297,28 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { simulateAvatarFades(deltaTime); + // Check on avatars with pending identities: + steady_clock::time_point now = steady_clock::now(); + QWriteLocker writeLock(&_hashLock); + for (auto pendingAvatar = _pendingAvatars.begin(); pendingAvatar != _pendingAvatars.end(); ++pendingAvatar) { + if (now - pendingAvatar->creationTime >= REQUEST_UNKNOWN_IDENTITY_DELAY) { + // Too long without an ID + sendIdentityRequest(pendingAvatar->avatar->getID()); + if (++pendingAvatar->transmits >= REQUEST_UNKNOWN_IDENTITY_TRANSMITS) { + qCDebug(avatars) << "Requesting identity for unknown avatar (final request)" << + pendingAvatar->avatar->getID().toString(); + + pendingAvatar = _pendingAvatars.erase(pendingAvatar); + if (pendingAvatar == _pendingAvatars.end()) { + break; + } + } else { + pendingAvatar->creationTime = now; + qCDebug(avatars) << "Requesting identity for unknown avatar" << pendingAvatar->avatar->getID().toString(); + } + } + } + _avatarSimulationTime = (float)(usecTimestampNow() - startTime) / (float)USECS_PER_MSEC; } @@ -288,6 +331,20 @@ void AvatarManager::postUpdate(float deltaTime, const render::ScenePointer& scen } } +void AvatarManager::sendIdentityRequest(const QUuid& avatarID) const { + auto nodeList = DependencyManager::get(); + nodeList->eachMatchingNode( + [&](const SharedNodePointer& node)->bool { + return node->getType() == NodeType::AvatarMixer && node->getActiveSocket(); + }, + [&](const SharedNodePointer& node) { + auto packet = NLPacket::create(PacketType::AvatarIdentityRequest, NUM_BYTES_RFC4122_UUID, true); + packet->write(avatarID.toRfc4122()); + nodeList->sendPacket(std::move(packet), *node); + ++_identityRequestsSent; + }); +} + void AvatarManager::simulateAvatarFades(float deltaTime) { if (_avatarsToFade.empty()) { return; @@ -561,6 +618,8 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic result.intersects = true; result.avatarID = avatar->getID(); result.distance = distance; + result.face = face; + result.surfaceNormal = surfaceNormal; result.extraInfo = extraInfo; } } @@ -572,6 +631,79 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic return result; } +ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector(const PickParabola& pick, + const QVector& avatarsToInclude, + const QVector& avatarsToDiscard) { + ParabolaToAvatarIntersectionResult result; + if (QThread::currentThread() != thread()) { + BLOCKING_INVOKE_METHOD(const_cast(this), "findParabolaIntersectionVector", + Q_RETURN_ARG(ParabolaToAvatarIntersectionResult, result), + Q_ARG(const PickParabola&, pick), + Q_ARG(const QVector&, avatarsToInclude), + Q_ARG(const QVector&, avatarsToDiscard)); + return result; + } + + auto avatarHashCopy = getHashCopy(); + for (auto avatarData : avatarHashCopy) { + auto avatar = std::static_pointer_cast(avatarData); + if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatar->getID())) || + (avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatar->getID()))) { + continue; + } + + float parabolicDistance; + BoxFace face; + glm::vec3 surfaceNormal; + + SkeletonModelPointer avatarModel = avatar->getSkeletonModel(); + + // It's better to intersect the parabola against the avatar's actual mesh, but this is currently difficult to + // do, because the transformed mesh data only exists over in GPU-land. As a compromise, this code + // intersects against the avatars capsule and then against the (T-pose) mesh. The end effect is that picking + // against the avatar is sort-of right, but you likely wont be able to pick against the arms. + + // TODO -- find a way to extract transformed avatar mesh data from the rendering engine. + + // if we weren't picking against the capsule, we would want to pick against the avatarBounds... + // AABox avatarBounds = avatarModel->getRenderableMeshBound(); + // if (!avatarBounds.findParabolaIntersection(pick.origin, pick.velocity, pick.acceleration, parabolicDistance, face, surfaceNormal)) { + // // parabola doesn't intersect avatar's bounding-box + // continue; + // } + + glm::vec3 start; + glm::vec3 end; + float radius; + avatar->getCapsule(start, end, radius); + bool intersects = findParabolaCapsuleIntersection(pick.origin, pick.velocity, pick.acceleration, start, end, radius, avatar->getWorldOrientation(), parabolicDistance); + if (!intersects) { + // ray doesn't intersect avatar's capsule + continue; + } + + QVariantMap extraInfo; + intersects = avatarModel->findParabolaIntersectionAgainstSubMeshes(pick.origin, pick.velocity, pick.acceleration, + parabolicDistance, face, surfaceNormal, extraInfo, true); + + if (intersects && (!result.intersects || parabolicDistance < result.parabolicDistance)) { + result.intersects = true; + result.avatarID = avatar->getID(); + result.parabolicDistance = parabolicDistance; + result.face = face; + result.surfaceNormal = surfaceNormal; + result.extraInfo = extraInfo; + } + } + + if (result.intersects) { + result.intersection = pick.origin + pick.velocity * result.parabolicDistance + 0.5f * pick.acceleration * result.parabolicDistance * result.parabolicDistance; + result.distance = glm::distance(pick.origin, result.intersection); + } + + return result; +} + // HACK float AvatarManager::getAvatarSortCoefficient(const QString& name) { if (name == "size") { @@ -612,3 +744,49 @@ void AvatarManager::setAvatarSortCoefficient(const QString& name, const QScriptV DependencyManager::get()->broadcastToNodes(std::move(packet), NodeSet() << NodeType::AvatarMixer); } } + + QVariantMap AvatarManager::getPalData(const QList specificAvatarIdentifiers) { + QJsonArray palData; + + auto avatarMap = getHashCopy(); + AvatarHash::iterator itr = avatarMap.begin(); + while (itr != avatarMap.end()) { + std::shared_ptr avatar = std::static_pointer_cast(*itr); + QString currentSessionUUID = avatar->getSessionUUID().toString(); + if (specificAvatarIdentifiers.isEmpty() || specificAvatarIdentifiers.contains(currentSessionUUID)) { + QJsonObject thisAvatarPalData; + + auto myAvatar = DependencyManager::get()->getMyAvatar(); + + if (currentSessionUUID == myAvatar->getSessionUUID().toString()) { + currentSessionUUID = ""; + } + + thisAvatarPalData.insert("sessionUUID", currentSessionUUID); + thisAvatarPalData.insert("sessionDisplayName", avatar->getSessionDisplayName()); + thisAvatarPalData.insert("audioLoudness", avatar->getAudioLoudness()); + thisAvatarPalData.insert("isReplicated", avatar->getIsReplicated()); + + glm::vec3 position = avatar->getWorldPosition(); + QJsonObject jsonPosition; + jsonPosition.insert("x", position.x); + jsonPosition.insert("y", position.y); + jsonPosition.insert("z", position.z); + thisAvatarPalData.insert("position", jsonPosition); + + float palOrbOffset = 0.2f; + int headIndex = avatar->getJointIndex("Head"); + if (headIndex > 0) { + glm::vec3 jointTranslation = avatar->getAbsoluteJointTranslationInObjectFrame(headIndex); + palOrbOffset = jointTranslation.y / 2; + } + thisAvatarPalData.insert("palOrbOffset", palOrbOffset); + + palData.append(thisAvatarPalData); + } + ++itr; + } + QJsonObject doc; + doc.insert("data", palData); + return doc.toVariantMap(); +} diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 6a3d0355f6..ecf9a2d735 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -82,6 +82,7 @@ public: void updateMyAvatar(float deltaTime); void updateOtherAvatars(float deltaTime); + void sendIdentityRequest(const QUuid& avatarID) const; void postUpdate(float deltaTime, const render::ScenePointer& scene); @@ -141,6 +142,10 @@ public: const QVector& avatarsToInclude, const QVector& avatarsToDiscard); + Q_INVOKABLE ParabolaToAvatarIntersectionResult findParabolaIntersectionVector(const PickParabola& pick, + const QVector& avatarsToInclude, + const QVector& avatarsToDiscard); + /**jsdoc * @function AvatarManager.getAvatarSortCoefficient * @param {string} name @@ -156,7 +161,19 @@ public: */ Q_INVOKABLE void setAvatarSortCoefficient(const QString& name, const QScriptValue& value); + /**jsdoc + * Used in the PAL for getting PAL-related data about avatars nearby. Using this method is faster + * than iterating over each avatar and obtaining data about them in JavaScript, as that method + * locks and unlocks each avatar's data structure potentially hundreds of times per update tick. + * @function AvatarManager.getPalData + * @param {string[]} specificAvatarIdentifiers - A list of specific Avatar Identifiers about + * which you want to get PAL data + * @returns {object} + */ + Q_INVOKABLE QVariantMap getPalData(const QList specificAvatarIdentifiers = QList()); + float getMyAvatarSendRate() const { return _myAvatarSendRate.rate(); } + int getIdentityRequestsSent() const { return _identityRequestsSent; } public slots: @@ -194,6 +211,7 @@ private: int _numAvatarsNotUpdated { 0 }; float _avatarSimulationTime { 0.0f }; bool _shouldRender { true }; + mutable int _identityRequestsSent { 0 }; }; #endif // hifi_AvatarManager_h diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index 6fc1bd8196..50c715b14a 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -154,6 +154,10 @@ const QUuid AvatarMotionState::getObjectID() const { return _avatar->getSessionUUID(); } +QString AvatarMotionState::getName() const { + return _avatar->getName(); +} + // virtual QUuid AvatarMotionState::getSimulatorID() const { return _avatar->getSessionUUID(); diff --git a/interface/src/avatar/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h index 2738aba8ee..9228641b25 100644 --- a/interface/src/avatar/AvatarMotionState.h +++ b/interface/src/avatar/AvatarMotionState.h @@ -59,6 +59,7 @@ public: virtual const QUuid getObjectID() const override; + virtual QString getName() const override; virtual QUuid getSimulatorID() const override; void setBoundingBox(const glm::vec3& corner, const glm::vec3& diagonal); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c816d1d3a1..27a52fd0b2 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -88,6 +88,10 @@ const float MyAvatar::ZOOM_MIN = 0.5f; const float MyAvatar::ZOOM_MAX = 25.0f; const float MyAvatar::ZOOM_DEFAULT = 1.5f; const float MIN_SCALE_CHANGED_DELTA = 0.001f; +const int MODE_READINGS_RING_BUFFER_SIZE = 500; +const float CENTIMETERS_PER_METER = 100.0f; + +//#define DEBUG_DRAW_HMD_MOVING_AVERAGE MyAvatar::MyAvatar(QThread* thread) : Avatar(thread), @@ -108,6 +112,7 @@ MyAvatar::MyAvatar(QThread* thread) : _hmdSensorMatrix(), _hmdSensorOrientation(), _hmdSensorPosition(), + _recentModeReadings(MODE_READINGS_RING_BUFFER_SIZE), _bodySensorMatrix(), _goToPending(false), _goToPosition(), @@ -121,6 +126,7 @@ MyAvatar::MyAvatar(QThread* thread) : _headData = new MyHead(this); _skeletonModel = std::make_shared(this, nullptr); + _skeletonModel->setLoadingPriority(MYAVATAR_LOADING_PRIORITY); connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); connect(_skeletonModel.get(), &Model::setURLFinished, this, [this](bool success) { if (success) { @@ -256,6 +262,26 @@ void MyAvatar::setDominantHand(const QString& hand) { } } +void MyAvatar::requestDisableHandTouch() { + std::lock_guard guard(_disableHandTouchMutex); + _disableHandTouchCount++; + emit shouldDisableHandTouchChanged(_disableHandTouchCount > 0); +} + +void MyAvatar::requestEnableHandTouch() { + std::lock_guard guard(_disableHandTouchMutex); + _disableHandTouchCount = std::max(_disableHandTouchCount - 1, 0); + emit shouldDisableHandTouchChanged(_disableHandTouchCount > 0); +} + +void MyAvatar::disableHandTouchForID(const QUuid& entityID) { + emit disableHandTouchForIDChanged(entityID, true); +} + +void MyAvatar::enableHandTouchForID(const QUuid& entityID) { + emit disableHandTouchForIDChanged(entityID, false); +} + void MyAvatar::registerMetaTypes(ScriptEnginePointer engine) { QScriptValue value = engine->newQObject(this, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects); engine->globalObject().setProperty("MyAvatar", value); @@ -413,7 +439,8 @@ void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) { void MyAvatar::update(float deltaTime) { // update moving average of HMD facing in xz plane. - const float HMD_FACING_TIMESCALE = 4.0f; // very slow average + const float HMD_FACING_TIMESCALE = getRotationRecenterFilterLength(); + float tau = deltaTime / HMD_FACING_TIMESCALE; _headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, _headControllerFacing, tau); @@ -422,6 +449,12 @@ void MyAvatar::update(float deltaTime) { _smoothOrientationTimer += deltaTime; } + float newHeightReading = getControllerPoseInAvatarFrame(controller::Action::HEAD).getTranslation().y; + int newHeightReadingInCentimeters = glm::floor(newHeightReading * CENTIMETERS_PER_METER); + _recentModeReadings.insert(newHeightReadingInCentimeters); + setCurrentStandingHeight(computeStandingHeightMode(getControllerPoseInAvatarFrame(controller::Action::HEAD))); + setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD))); + #ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD); glm::vec3 worldHeadPos = transformPoint(getSensorToWorldMatrix(), sensorHeadPose.getTranslation()); @@ -531,30 +564,61 @@ void MyAvatar::forgetChild(SpatiallyNestablePointer newChild) const { SpatiallyNestable::forgetChild(newChild); } -void MyAvatar::updateChildCauterization(SpatiallyNestablePointer object) { +void MyAvatar::recalculateChildCauterization() const { + _cauterizationNeedsUpdate = true; +} + +void MyAvatar::updateChildCauterization(SpatiallyNestablePointer object, bool cauterize) { if (object->getNestableType() == NestableType::Entity) { EntityItemPointer entity = std::static_pointer_cast(object); - entity->setCauterized(!_prevShouldDrawHead); + entity->setCauterized(cauterize); } } void MyAvatar::simulate(float deltaTime) { PerformanceTimer perfTimer("simulate"); - + animateScaleChanges(deltaTime); + setFlyingEnabled(getFlyingEnabled()); + if (_cauterizationNeedsUpdate) { - const std::unordered_set& headBoneSet = _skeletonModel->getCauterizeBoneSet(); + _cauterizationNeedsUpdate = false; + + // Redisplay cauterized entities that are no longer children of the avatar. + auto cauterizedChild = _cauterizedChildrenOfHead.begin(); + if (cauterizedChild != _cauterizedChildrenOfHead.end()) { + auto children = getChildren(); + while (cauterizedChild != _cauterizedChildrenOfHead.end()) { + if (!children.contains(*cauterizedChild)) { + updateChildCauterization(*cauterizedChild, false); + cauterizedChild = _cauterizedChildrenOfHead.erase(cauterizedChild); + } else { + ++cauterizedChild; + } + } + } + + // Update cauterization of entities that are children of the avatar. + auto headBoneSet = _skeletonModel->getCauterizeBoneSet(); forEachChild([&](SpatiallyNestablePointer object) { bool isChildOfHead = headBoneSet.find(object->getParentJointIndex()) != headBoneSet.end(); if (isChildOfHead) { - updateChildCauterization(object); + // Cauterize or display children of head per head drawing state. + updateChildCauterization(object, !_prevShouldDrawHead); object->forEachDescendant([&](SpatiallyNestablePointer descendant) { - updateChildCauterization(descendant); + updateChildCauterization(descendant, !_prevShouldDrawHead); }); + _cauterizedChildrenOfHead.insert(object); + } else if (_cauterizedChildrenOfHead.find(object) != _cauterizedChildrenOfHead.end()) { + // Redisplay cauterized children that are not longer children of the head. + updateChildCauterization(object, false); + object->forEachDescendant([&](SpatiallyNestablePointer descendant) { + updateChildCauterization(descendant, false); + }); + _cauterizedChildrenOfHead.erase(object); } }); - _cauterizationNeedsUpdate = false; } { @@ -662,16 +726,18 @@ void MyAvatar::simulate(float deltaTime) { properties.setQueryAACubeDirty(); properties.setLastEdited(now); - packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entity->getID(), properties); + packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, + entity->getID(), properties); entity->setLastBroadcast(usecTimestampNow()); entity->forEachDescendant([&](SpatiallyNestablePointer descendant) { - EntityItemPointer entityDescendant = std::static_pointer_cast(descendant); - if (!entityDescendant->getClientOnly() && descendant->updateQueryAACube()) { + EntityItemPointer entityDescendant = std::dynamic_pointer_cast(descendant); + if (entityDescendant && !entityDescendant->getClientOnly() && descendant->updateQueryAACube()) { EntityItemProperties descendantProperties; descendantProperties.setQueryAACube(descendant->getQueryAACube()); descendantProperties.setLastEdited(now); - packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, entityDescendant->getID(), descendantProperties); + packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree, + entityDescendant->getID(), descendantProperties); entityDescendant->setLastBroadcast(now); // for debug/physics status icons } }); @@ -684,7 +750,8 @@ void MyAvatar::simulate(float deltaTime) { entityTree->recurseTreeWithOperator(&moveOperator); } }); - _characterController.setFlyingAllowed(zoneAllowsFlying && _enableFlying); + bool isPhysicsEnabled = qApp->isPhysicsEnabled(); + _characterController.setFlyingAllowed((zoneAllowsFlying && _enableFlying) || !isPhysicsEnabled); _characterController.setCollisionlessAllowed(collisionlessAllowed); } @@ -1067,8 +1134,10 @@ void MyAvatar::saveData() { settings.setValue("displayName", _displayName); settings.setValue("collisionSoundURL", _collisionSoundURL); settings.setValue("useSnapTurn", _useSnapTurn); - settings.setValue("clearOverlayWhenMoving", _clearOverlayWhenMoving); settings.setValue("userHeight", getUserHeight()); + settings.setValue("flyingDesktop", getFlyingDesktopPref()); + settings.setValue("flyingHMD", getFlyingHMDPref()); + settings.setValue("enabledFlying", getFlyingEnabled()); settings.endGroup(); } @@ -1219,10 +1288,15 @@ void MyAvatar::loadData() { } setAvatarEntityDataChanged(true); + // Flying preferences must be loaded before calling setFlyingEnabled() + Setting::Handle firstRunVal { Settings::firstRun, true }; + setFlyingDesktopPref(firstRunVal.get() ? true : settings.value("flyingDesktop").toBool()); + setFlyingHMDPref(firstRunVal.get() ? false : settings.value("flyingHMD").toBool()); + setFlyingEnabled(getFlyingEnabled()); + setDisplayName(settings.value("displayName").toString()); setCollisionSoundURL(settings.value("collisionSoundURL", DEFAULT_AVATAR_COLLISION_SOUND_URL).toString()); setSnapTurn(settings.value("useSnapTurn", _useSnapTurn).toBool()); - setClearOverlayWhenMoving(settings.value("clearOverlayWhenMoving", _clearOverlayWhenMoving).toBool()); setDominantHand(settings.value("dominantHand", _dominantHand).toString().toLower()); setUserHeight(settings.value("userHeight", DEFAULT_AVATAR_HEIGHT).toDouble()); settings.endGroup(); @@ -1545,14 +1619,16 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { emit skeletonModelURLChanged(); } -void MyAvatar::removeAvatarEntities() { +void MyAvatar::removeAvatarEntities(const std::function& condition) { auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; if (entityTree) { entityTree->withWriteLock([&] { AvatarEntityMap avatarEntities = getAvatarEntityData(); for (auto entityID : avatarEntities.keys()) { - entityTree->deleteEntity(entityID, true, true); + if (!condition || condition(entityID)) { + entityTree->deleteEntity(entityID, true, true); + } } }); } @@ -1561,18 +1637,26 @@ void MyAvatar::removeAvatarEntities() { QVariantList MyAvatar::getAvatarEntitiesVariant() { QVariantList avatarEntitiesData; QScriptEngine scriptEngine; - forEachChild([&](SpatiallyNestablePointer child) { - if (child->getNestableType() == NestableType::Entity) { - auto modelEntity = std::dynamic_pointer_cast(child); - if (modelEntity) { - QVariantMap avatarEntityData; - EntityItemProperties entityProperties = modelEntity->getProperties(); - QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties); - avatarEntityData["properties"] = scriptProperties.toVariant(); - avatarEntitiesData.append(QVariant(avatarEntityData)); + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + if (entityTree) { + AvatarEntityMap avatarEntities = getAvatarEntityData(); + for (auto entityID : avatarEntities.keys()) { + auto entity = entityTree->findEntityByID(entityID); + if (!entity) { + continue; } + QVariantMap avatarEntityData; + EncodeBitstreamParams params; + auto desiredProperties = entity->getEntityProperties(params); + desiredProperties += PROP_LOCAL_POSITION; + desiredProperties += PROP_LOCAL_ROTATION; + EntityItemProperties entityProperties = entity->getProperties(desiredProperties); + QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties); + avatarEntityData["properties"] = scriptProperties.toVariant(); + avatarEntitiesData.append(QVariant(avatarEntityData)); } - }); + } return avatarEntitiesData; } @@ -1949,7 +2033,6 @@ QUrl MyAvatar::getAnimGraphUrl() const { } void MyAvatar::setAnimGraphUrl(const QUrl& url) { - if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "setAnimGraphUrl", Q_ARG(QUrl, url)); return; @@ -1958,6 +2041,9 @@ void MyAvatar::setAnimGraphUrl(const QUrl& url) { if (_currentAnimGraphUrl.get() == url) { return; } + + emit animGraphUrlChanged(url); + destroyAnimGraph(); _skeletonModel->reset(); // Why is this necessary? Without this, we crash in the next render. @@ -1976,6 +2062,8 @@ void MyAvatar::initAnimGraph() { graphUrl = PathUtils::resourcesUrl("avatar/avatar-animation.json"); } + emit animGraphUrlChanged(graphUrl); + _skeletonModel->getRig().initAnimGraph(graphUrl); _currentAnimGraphUrl.set(graphUrl); connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded())); @@ -2117,6 +2205,40 @@ bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const { return !defaultMode || !firstPerson || !insideHead; } +void MyAvatar::setHasScriptedBlendshapes(bool hasScriptedBlendshapes) { + if (hasScriptedBlendshapes == _hasScriptedBlendShapes) { + return; + } + if (!hasScriptedBlendshapes) { + // send a forced avatarData update to make sure the script can send neutal blendshapes on unload + // without having to wait for the update loop, make sure _hasScriptedBlendShapes is still true + // before sending the update, or else it won't send the neutal blendshapes to the receiving clients + sendAvatarDataPacket(true); + } + _hasScriptedBlendShapes = hasScriptedBlendshapes; +} + +void MyAvatar::setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement) { + _headData->setHasProceduralBlinkFaceMovement(hasProceduralBlinkFaceMovement); +} + +void MyAvatar::setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement) { + _headData->setHasProceduralEyeFaceMovement(hasProceduralEyeFaceMovement); +} + +void MyAvatar::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) { + _headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement); +} + +void MyAvatar::setRotationRecenterFilterLength(float length) { + const float MINIMUM_ROTATION_RECENTER_FILTER_LENGTH = 0.01f; + _rotationRecenterFilterLength = std::max(MINIMUM_ROTATION_RECENTER_FILTER_LENGTH, length); +} + +void MyAvatar::setRotationThreshold(float angleRadians) { + _rotationThreshold = angleRadians; +} + void MyAvatar::updateOrientation(float deltaTime) { // Smoothly rotate body with arrow keys @@ -2430,11 +2552,16 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings if (_domainMinimumHeight > _domainMaximumHeight) { std::swap(_domainMinimumHeight, _domainMaximumHeight); } + // Set avatar current scale Settings settings; settings.beginGroup("Avatar"); _targetScale = loadSetting(settings, "scale", 1.0f); + // clamp the desired _targetScale by the domain limits NOW, don't try to gracefully animate. Because + // this might cause our avatar to become embedded in the terrain. + _targetScale = getDomainLimitedScale(); + qCDebug(interfaceapp) << "This domain requires a minimum avatar scale of " << _domainMinimumHeight << " and a maximum avatar scale of " << _domainMaximumHeight; @@ -2443,6 +2570,8 @@ void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettings setModelScale(_targetScale); rebuildCollisionShape(); settings.endGroup(); + + _haveReceivedHeightLimitsFromDomain = true; } void MyAvatar::leaveDomain() { @@ -2460,6 +2589,7 @@ void MyAvatar::saveAvatarScale() { void MyAvatar::clearScaleRestriction() { _domainMinimumHeight = MIN_AVATAR_HEIGHT; _domainMaximumHeight = MAX_AVATAR_HEIGHT; + _haveReceivedHeightLimitsFromDomain = false; } void MyAvatar::goToLocation(const QVariant& propertiesVar) { @@ -2577,8 +2707,12 @@ bool MyAvatar::safeLanding(const glm::vec3& position) { // If position is not reliably safe from being stuck by physics, answer true and place a candidate better position in betterPositionOut. bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& betterPositionOut) { + // We begin with utilities and tests. The Algorithm in four parts is below. - auto halfHeight = _characterController.getCapsuleHalfHeight() + _characterController.getCapsuleRadius(); + // NOTE: we use estimated avatar height here instead of the bullet capsule halfHeight, because + // the domain avatar height limiting might not have taken effect yet on the actual bullet shape. + auto halfHeight = 0.5f * getHeight(); + if (halfHeight == 0) { return false; // zero height avatar } @@ -2587,14 +2721,13 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette return false; // no entity tree } // More utilities. - const auto offset = getWorldOrientation() *_characterController.getCapsuleLocalOffset(); - const auto capsuleCenter = positionIn + offset; + const auto capsuleCenter = positionIn; const auto up = _worldUpDirection, down = -up; glm::vec3 upperIntersection, upperNormal, lowerIntersection, lowerNormal; EntityItemID upperId, lowerId; QVector include{}, ignore{}; auto mustMove = [&] { // Place bottom of capsule at the upperIntersection, and check again based on the capsule center. - betterPositionOut = upperIntersection + (up * halfHeight) - offset; + betterPositionOut = upperIntersection + (up * halfHeight); return true; }; auto findIntersection = [&](const glm::vec3& startPointIn, const glm::vec3& directionIn, glm::vec3& intersectionOut, EntityItemID& entityIdOut, glm::vec3& normalOut) { @@ -2614,7 +2747,7 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette EntityItemID entityID = entityTree->findRayIntersection(startPointIn, directionIn, include, ignore, visibleOnly, collidableOnly, precisionPicking, element, distance, face, normalOut, extraInfo, lockType, accurateResult); if (entityID.isNull()) { - return false; + return false; } intersectionOut = startPointIn + (directionIn * distance); entityIdOut = entityID; @@ -2640,7 +2773,7 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette // I.e., we are in a clearing between two objects. if (isDown(upperNormal) && isUp(lowerNormal)) { auto spaceBetween = glm::distance(upperIntersection, lowerIntersection); - const float halfHeightFactor = 2.5f; // Until case 5003 is fixed (and maybe after?), we need a fudge factor. Also account for content modelers not being precise. + const float halfHeightFactor = 2.25f; // Until case 5003 is fixed (and maybe after?), we need a fudge factor. Also account for content modelers not being precise. if (spaceBetween > (halfHeightFactor * halfHeight)) { // There is room for us to fit in that clearing. If there wasn't, physics would oscilate us between the objects above and below. // We're now going to iterate upwards through successive upperIntersections, testing to see if we're contained within the top surface of some entity. @@ -2705,6 +2838,12 @@ void MyAvatar::setFlyingEnabled(bool enabled) { return; } + if (qApp->isHMDMode()) { + setFlyingHMDPref(enabled); + } else { + setFlyingDesktopPref(enabled); + } + _enableFlying = enabled; } @@ -2720,7 +2859,33 @@ bool MyAvatar::isInAir() { bool MyAvatar::getFlyingEnabled() { // May return true even if client is not allowed to fly in the zone. - return _enableFlying; + return (qApp->isHMDMode() ? getFlyingHMDPref() : getFlyingDesktopPref()); +} + +void MyAvatar::setFlyingDesktopPref(bool enabled) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setFlyingDesktopPref", Q_ARG(bool, enabled)); + return; + } + + _flyingPrefDesktop = enabled; +} + +bool MyAvatar::getFlyingDesktopPref() { + return _flyingPrefDesktop; +} + +void MyAvatar::setFlyingHMDPref(bool enabled) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setFlyingHMDPref", Q_ARG(bool, enabled)); + return; + } + + _flyingPrefHMD = enabled; +} + +bool MyAvatar::getFlyingHMDPref() { + return _flyingPrefHMD; } // Public interface for targetscale @@ -2746,6 +2911,7 @@ void MyAvatar::setCollisionsEnabled(bool enabled) { } _characterController.setCollisionless(!enabled); + emit collisionsEnabledChanged(enabled); } bool MyAvatar::getCollisionsEnabled() { @@ -2921,7 +3087,7 @@ static glm::vec3 dampenCgMovement(glm::vec3 cgUnderHeadHandsAvatarSpace, float b } // computeCounterBalance returns the center of gravity in Avatar space -glm::vec3 MyAvatar::computeCounterBalance() const { +glm::vec3 MyAvatar::computeCounterBalance() { struct JointMass { QString name; float weight; @@ -2939,7 +3105,8 @@ glm::vec3 MyAvatar::computeCounterBalance() const { JointMass cgLeftHandMass(QString("LeftHand"), DEFAULT_AVATAR_LEFTHAND_MASS, glm::vec3(0.0f, 0.0f, 0.0f)); JointMass cgRightHandMass(QString("RightHand"), DEFAULT_AVATAR_RIGHTHAND_MASS, glm::vec3(0.0f, 0.0f, 0.0f)); glm::vec3 tposeHead = DEFAULT_AVATAR_HEAD_POS; - glm::vec3 tposeHips = glm::vec3(0.0f, 0.0f, 0.0f); + glm::vec3 tposeHips = DEFAULT_AVATAR_HIPS_POS; + glm::vec3 tposeRightFoot = DEFAULT_AVATAR_RIGHTFOOT_POS; if (_skeletonModel->getRig().indexOfJoint(cgHeadMass.name) != -1) { cgHeadMass.position = getAbsoluteJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint(cgHeadMass.name)); @@ -2958,6 +3125,9 @@ glm::vec3 MyAvatar::computeCounterBalance() const { if (_skeletonModel->getRig().indexOfJoint("Hips") != -1) { tposeHips = getAbsoluteDefaultJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint("Hips")); } + if (_skeletonModel->getRig().indexOfJoint("RightFoot") != -1) { + tposeRightFoot = getAbsoluteDefaultJointTranslationInObjectFrame(_skeletonModel->getRig().indexOfJoint("RightFoot")); + } // find the current center of gravity position based on head and hand moments glm::vec3 sumOfMoments = (cgHeadMass.weight * cgHeadMass.position) + (cgLeftHandMass.weight * cgLeftHandMass.position) + (cgRightHandMass.weight * cgRightHandMass.position); @@ -2978,9 +3148,12 @@ glm::vec3 MyAvatar::computeCounterBalance() const { glm::vec3 counterBalancedCg = (1.0f / DEFAULT_AVATAR_HIPS_MASS) * counterBalancedForHead; // find the height of the hips + const float UPPER_LEG_FRACTION = 0.3333f; glm::vec3 xzDiff((cgHeadMass.position.x - counterBalancedCg.x), 0.0f, (cgHeadMass.position.z - counterBalancedCg.z)); float headMinusHipXz = glm::length(xzDiff); float headHipDefault = glm::length(tposeHead - tposeHips); + float hipFootDefault = tposeHips.y - tposeRightFoot.y; + float sitSquatThreshold = tposeHips.y - (UPPER_LEG_FRACTION * hipFootDefault); float hipHeight = 0.0f; if (headHipDefault > headMinusHipXz) { hipHeight = sqrtf((headHipDefault * headHipDefault) - (headMinusHipXz * headMinusHipXz)); @@ -2992,6 +3165,10 @@ glm::vec3 MyAvatar::computeCounterBalance() const { if (counterBalancedCg.y > (tposeHips.y + 0.05f)) { // if the height is higher than default hips, clamp to default hips counterBalancedCg.y = tposeHips.y + 0.05f; + } else if (counterBalancedCg.y < sitSquatThreshold) { + //do a height reset + setResetMode(true); + _follow.activate(FollowHelper::Vertical); } return counterBalancedCg; } @@ -3041,7 +3218,7 @@ static void drawBaseOfSupport(float baseOfSupportScale, float footLocal, glm::ma // this function finds the hips position using a center of gravity model that // balances the head and hands with the hips over the base of support // returns the rotation (-z forward) and position of the Avatar in Sensor space -glm::mat4 MyAvatar::deriveBodyUsingCgModel() const { +glm::mat4 MyAvatar::deriveBodyUsingCgModel() { glm::mat4 sensorToWorldMat = getSensorToWorldMatrix(); glm::mat4 worldToSensorMat = glm::inverse(sensorToWorldMat); auto headPose = getControllerPoseInSensorFrame(controller::Action::HEAD); @@ -3059,7 +3236,7 @@ glm::mat4 MyAvatar::deriveBodyUsingCgModel() const { } // get the new center of gravity - const glm::vec3 cgHipsPosition = computeCounterBalance(); + glm::vec3 cgHipsPosition = computeCounterBalance(); // find the new hips rotation using the new head-hips axis as the up axis glm::mat4 avatarHipsMat = computeNewHipsMatrix(glmExtractRotation(avatarHeadMat), extractTranslation(avatarHeadMat), cgHipsPosition); @@ -3069,6 +3246,148 @@ glm::mat4 MyAvatar::deriveBodyUsingCgModel() const { return worldToSensorMat * avatarToWorldMat * avatarHipsMat; } +static bool isInsideLine(const glm::vec3& a, const glm::vec3& b, const glm::vec3& c) { + return (((b.x - a.x) * (c.z - a.z) - (b.z - a.z) * (c.x - a.x)) > 0); +} + +static bool withinBaseOfSupport(const controller::Pose& head) { + float userScale = 1.0f; + + glm::vec3 frontLeft(-DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, -DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD); + glm::vec3 frontRight(DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, -DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD); + glm::vec3 backLeft(-DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD); + glm::vec3 backRight(DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD, 0.0f, DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD); + + bool isWithinSupport = false; + if (head.isValid()) { + bool withinFrontBase = isInsideLine(userScale * frontLeft, userScale * frontRight, head.getTranslation()); + bool withinBackBase = isInsideLine(userScale * backRight, userScale * backLeft, head.getTranslation()); + bool withinLateralBase = (isInsideLine(userScale * frontRight, userScale * backRight, head.getTranslation()) && + isInsideLine(userScale * backLeft, userScale * frontLeft, head.getTranslation())); + isWithinSupport = (withinFrontBase && withinBackBase && withinLateralBase); + } + return isWithinSupport; +} + +static bool headAngularVelocityBelowThreshold(const controller::Pose& head) { + glm::vec3 xzPlaneAngularVelocity(0.0f, 0.0f, 0.0f); + if (head.isValid()) { + xzPlaneAngularVelocity.x = head.getAngularVelocity().x; + xzPlaneAngularVelocity.z = head.getAngularVelocity().z; + } + float magnitudeAngularVelocity = glm::length(xzPlaneAngularVelocity); + bool isBelowThreshold = (magnitudeAngularVelocity < DEFAULT_AVATAR_HEAD_ANGULAR_VELOCITY_STEPPING_THRESHOLD); + + return isBelowThreshold; +} + +static bool isWithinThresholdHeightMode(const controller::Pose& head,const float& newMode) { + bool isWithinThreshold = true; + if (head.isValid()) { + isWithinThreshold = (head.getTranslation().y - newMode) > DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD; + } + return isWithinThreshold; +} + +float MyAvatar::computeStandingHeightMode(const controller::Pose& head) { + const float MODE_CORRECTION_FACTOR = 0.02f; + int greatestFrequency = 0; + int mode = 0; + // init mode in meters to the current mode + float modeInMeters = getCurrentStandingHeight(); + if (head.isValid()) { + std::map freq; + for(auto recentModeReadingsIterator = _recentModeReadings.begin(); recentModeReadingsIterator != _recentModeReadings.end(); ++recentModeReadingsIterator) { + freq[*recentModeReadingsIterator] += 1; + if (freq[*recentModeReadingsIterator] > greatestFrequency) { + greatestFrequency = freq[*recentModeReadingsIterator]; + mode = *recentModeReadingsIterator; + } + } + + modeInMeters = ((float)mode) / CENTIMETERS_PER_METER; + if (!(modeInMeters > getCurrentStandingHeight())) { + // if not greater check for a reset + if (getResetMode() && qApp->isHMDMode()) { + setResetMode(false); + float resetModeInCentimeters = glm::floor((head.getTranslation().y - MODE_CORRECTION_FACTOR)*CENTIMETERS_PER_METER); + modeInMeters = (resetModeInCentimeters / CENTIMETERS_PER_METER); + _recentModeReadings.clear(); + + } else { + // if not greater and no reset, keep the mode as it is + modeInMeters = getCurrentStandingHeight(); + + } + } + } + return modeInMeters; +} + +static bool handDirectionMatchesHeadDirection(const controller::Pose& leftHand, const controller::Pose& rightHand, const controller::Pose& head) { + const float VELOCITY_EPSILON = 0.02f; + bool leftHandDirectionMatchesHead = true; + bool rightHandDirectionMatchesHead = true; + glm::vec3 xzHeadVelocity(head.velocity.x, 0.0f, head.velocity.z); + if (leftHand.isValid() && head.isValid()) { + glm::vec3 xzLeftHandVelocity(leftHand.velocity.x, 0.0f, leftHand.velocity.z); + if ((glm::length(xzLeftHandVelocity) > VELOCITY_EPSILON) && (glm::length(xzHeadVelocity) > VELOCITY_EPSILON)) { + float handDotHeadLeft = glm::dot(glm::normalize(xzLeftHandVelocity), glm::normalize(xzHeadVelocity)); + leftHandDirectionMatchesHead = ((handDotHeadLeft > DEFAULT_HANDS_VELOCITY_DIRECTION_STEPPING_THRESHOLD)); + } else { + leftHandDirectionMatchesHead = false; + } + } + if (rightHand.isValid() && head.isValid()) { + glm::vec3 xzRightHandVelocity(rightHand.velocity.x, 0.0f, rightHand.velocity.z); + if ((glm::length(xzRightHandVelocity) > VELOCITY_EPSILON) && (glm::length(xzHeadVelocity) > VELOCITY_EPSILON)) { + float handDotHeadRight = glm::dot(glm::normalize(xzRightHandVelocity), glm::normalize(xzHeadVelocity)); + rightHandDirectionMatchesHead = (handDotHeadRight > DEFAULT_HANDS_VELOCITY_DIRECTION_STEPPING_THRESHOLD); + } else { + rightHandDirectionMatchesHead = false; + } + } + return leftHandDirectionMatchesHead && rightHandDirectionMatchesHead; +} + +static bool handAngularVelocityBelowThreshold(const controller::Pose& leftHand, const controller::Pose& rightHand) { + float leftHandXZAngularVelocity = 0.0f; + float rightHandXZAngularVelocity = 0.0f; + if (leftHand.isValid()) { + glm::vec3 xzLeftHandAngularVelocity(leftHand.angularVelocity.x, 0.0f, leftHand.angularVelocity.z); + leftHandXZAngularVelocity = glm::length(xzLeftHandAngularVelocity); + } + if (rightHand.isValid()) { + glm::vec3 xzRightHandAngularVelocity(rightHand.angularVelocity.x, 0.0f, rightHand.angularVelocity.z); + rightHandXZAngularVelocity = glm::length(xzRightHandAngularVelocity); + } + return ((leftHandXZAngularVelocity < DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD) && + (rightHandXZAngularVelocity < DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD)); +} + +static bool headVelocityGreaterThanThreshold(const controller::Pose& head) { + float headVelocityMagnitude = 0.0f; + if (head.isValid()) { + headVelocityMagnitude = glm::length(head.getVelocity()); + } + return headVelocityMagnitude > DEFAULT_HEAD_VELOCITY_STEPPING_THRESHOLD; +} + +glm::quat MyAvatar::computeAverageHeadRotation(const controller::Pose& head) { + const float AVERAGING_RATE = 0.03f; + return safeLerp(_averageHeadRotation, head.getRotation(), AVERAGING_RATE); +} + +static bool isHeadLevel(const controller::Pose& head, const glm::quat& averageHeadRotation) { + glm::vec3 diffFromAverageEulers(0.0f, 0.0f, 0.0f); + if (head.isValid()) { + glm::vec3 averageHeadEulers = glm::degrees(safeEulerAngles(averageHeadRotation)); + glm::vec3 currentHeadEulers = glm::degrees(safeEulerAngles(head.getRotation())); + diffFromAverageEulers = averageHeadEulers - currentHeadEulers; + } + return ((fabs(diffFromAverageEulers.x) < DEFAULT_HEAD_PITCH_STEPPING_TOLERANCE) && (fabs(diffFromAverageEulers.z) < DEFAULT_HEAD_ROLL_STEPPING_TOLERANCE)); +} + float MyAvatar::getUserHeight() const { return _userHeight.get(); } @@ -3090,6 +3409,10 @@ float MyAvatar::getWalkSpeed() const { return _walkSpeed.get() * _walkSpeedScalar; } +bool MyAvatar::isReadyForPhysics() const { + return qApp->isServerlessMode() || _haveReceivedHeightLimitsFromDomain; +} + void MyAvatar::setSprintMode(bool sprint) { _walkSpeedScalar = sprint ? AVATAR_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR; } @@ -3231,7 +3554,7 @@ void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) { } bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { - const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees + const float FOLLOW_ROTATION_THRESHOLD = cosf(myAvatar.getRotationThreshold()); glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix); return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD; } @@ -3258,7 +3581,37 @@ bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, } return fabs(lateralLeanAmount) > MAX_LATERAL_LEAN; +} +bool MyAvatar::FollowHelper::shouldActivateHorizontalCG(MyAvatar& myAvatar) const { + + // get the current readings + controller::Pose currentHeadPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::HEAD); + controller::Pose currentLeftHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND); + controller::Pose currentRightHandPose = myAvatar.getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND); + + bool stepDetected = false; + if (!withinBaseOfSupport(currentHeadPose) && + headAngularVelocityBelowThreshold(currentHeadPose) && + isWithinThresholdHeightMode(currentHeadPose, myAvatar.getCurrentStandingHeight()) && + handDirectionMatchesHeadDirection(currentLeftHandPose, currentRightHandPose, currentHeadPose) && + handAngularVelocityBelowThreshold(currentLeftHandPose, currentRightHandPose) && + headVelocityGreaterThanThreshold(currentHeadPose) && + isHeadLevel(currentHeadPose, myAvatar.getAverageHeadRotation())) { + // a step is detected + stepDetected = true; + } else { + glm::vec3 defaultHipsPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Hips")); + glm::vec3 defaultHeadPosition = myAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(myAvatar.getJointIndex("Head")); + glm::vec3 currentHeadPosition = currentHeadPose.getTranslation(); + float anatomicalHeadToHipsDistance = glm::length(defaultHeadPosition - defaultHipsPosition); + if (!isActive(Horizontal) && + (glm::length(currentHeadPosition - defaultHipsPosition) > (anatomicalHeadToHipsDistance + DEFAULT_AVATAR_SPINE_STRETCH_LIMIT))) { + myAvatar.setResetMode(true); + stepDetected = true; + } + } + return stepDetected; } bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const { @@ -3277,16 +3630,25 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) { if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Rotation); + myAvatar.setHeadControllerFacingMovingAverage(myAvatar._headControllerFacing); } - if (!isActive(Horizontal) && shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix)) { - activate(Horizontal); + if (myAvatar.getCenterOfGravityModelEnabled()) { + if (!isActive(Horizontal) && (shouldActivateHorizontalCG(myAvatar) || hasDriveInput)) { + activate(Horizontal); + } + } else { + if (!isActive(Horizontal) && (shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { + activate(Horizontal); + } } + if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) { activate(Vertical); } } else { if (!isActive(Rotation) && getForceActivateRotation()) { activate(Rotation); + myAvatar.setHeadControllerFacingMovingAverage(myAvatar._headControllerFacing); setForceActivateRotation(false); } if (!isActive(Horizontal) && getForceActivateHorizontal()) { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index b3ed04e8de..d36e43ca25 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -31,7 +31,9 @@ #include "AtRestDetector.h" #include "MyCharacterController.h" +#include "RingBufferHistory.h" #include +#include class AvatarActionHold; class ModelItemID; @@ -86,6 +88,10 @@ class MyAvatar : public Avatar { * @property {number} audioListenerModeCamera=1 - The audio listening position is at the camera. Read-only. * @property {number} audioListenerModeCustom=2 - The audio listening position is at a the position specified by set by the * customListenPosition and customListenOrientation property values. Read-only. + * @property {boolean} hasScriptedBlendshapes=false - Blendshapes will be transmitted over the network if set to true. + * @property {boolean} hasProceduralBlinkFaceMovement=true - procedural blinking will be turned on if set to true. + * @property {boolean} hasProceduralEyeFaceMovement=true - procedural eye movement will be turned on if set to true. + * @property {boolean} hasAudioEnabledFaceMovement=true - If set to true, voice audio will move the mouth Blendshapes while MyAvatar.hasScriptedBlendshapes is enabled. * @property {Vec3} customListenPosition=Vec3.ZERO - The listening position used when the audioListenerMode * property value is audioListenerModeCustom. * @property {Quat} customListenOrientation=Quat.IDENTITY - The listening orientation used when the @@ -187,6 +193,12 @@ class MyAvatar : public Avatar { Q_PROPERTY(AudioListenerMode audioListenerModeHead READ getAudioListenerModeHead) Q_PROPERTY(AudioListenerMode audioListenerModeCamera READ getAudioListenerModeCamera) Q_PROPERTY(AudioListenerMode audioListenerModeCustom READ getAudioListenerModeCustom) + Q_PROPERTY(bool hasScriptedBlendshapes READ getHasScriptedBlendshapes WRITE setHasScriptedBlendshapes) + Q_PROPERTY(bool hasProceduralBlinkFaceMovement READ getHasProceduralBlinkFaceMovement WRITE setHasProceduralBlinkFaceMovement) + Q_PROPERTY(bool hasProceduralEyeFaceMovement READ getHasProceduralEyeFaceMovement WRITE setHasProceduralEyeFaceMovement) + Q_PROPERTY(bool hasAudioEnabledFaceMovement READ getHasAudioEnabledFaceMovement WRITE setHasAudioEnabledFaceMovement) + Q_PROPERTY(float rotationRecenterFilterLength READ getRotationRecenterFilterLength WRITE setRotationRecenterFilterLength) + Q_PROPERTY(float rotationThreshold READ getRotationThreshold WRITE setRotationThreshold) //TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity) Q_PROPERTY(glm::vec3 leftHandPosition READ getLeftHandPosition) @@ -242,7 +254,7 @@ public: Q_ENUM(DriveKeys) explicit MyAvatar(QThread* thread); - ~MyAvatar(); + virtual ~MyAvatar(); void instantiableAvatar() override {}; void registerMetaTypes(ScriptEnginePointer engine); @@ -461,16 +473,6 @@ public: * @param {boolean} on */ Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; } - /**jsdoc - * @function MyAvatar.getClearOverlayWhenMoving - * @returns {boolean} - */ - Q_INVOKABLE bool getClearOverlayWhenMoving() const { return _clearOverlayWhenMoving; } - /**jsdoc - * @function MyAvatar.setClearOverlayWhenMoving - * @param {boolean} on - */ - Q_INVOKABLE void setClearOverlayWhenMoving(bool on) { _clearOverlayWhenMoving = on; } /**jsdoc @@ -504,6 +506,28 @@ public: * @returns {boolean} */ Q_INVOKABLE bool getHMDLeanRecenterEnabled() const { return _hmdLeanRecenterEnabled; } + /**jsdoc + * Request to enable hand touch effect globally + * @function MyAvatar.requestEnableHandTouch + */ + Q_INVOKABLE void requestEnableHandTouch(); + /**jsdoc + * Request to disable hand touch effect globally + * @function MyAvatar.requestDisableHandTouch + */ + Q_INVOKABLE void requestDisableHandTouch(); + /**jsdoc + * Disables hand touch effect on a specific entity + * @function MyAvatar.disableHandTouchForID + * @param {Uuid} entityID - ID of the entity that will disable hand touch effect + */ + Q_INVOKABLE void disableHandTouchForID(const QUuid& entityID); + /**jsdoc + * Enables hand touch effect on a specific entity + * @function MyAvatar.enableHandTouchForID + * @param {Uuid} entityID - ID of the entity that will enable hand touch effect + */ + Q_INVOKABLE void enableHandTouchForID(const QUuid& entityID); bool useAdvancedMovementControls() const { return _useAdvancedMovementControls.get(); } void setUseAdvancedMovementControls(bool useAdvancedMovementControls) @@ -589,6 +613,8 @@ public: const MyHead* getMyHead() const; + Q_INVOKABLE void toggleSmoothPoleVectors() { _skeletonModel->getRig().toggleSmoothPoleVectors(); }; + /**jsdoc * Get the current position of the avatar's "Head" joint. * @function MyAvatar.getHeadPosition @@ -882,6 +908,13 @@ public: virtual void rebuildCollisionShape() override; const glm::vec2& getHeadControllerFacingMovingAverage() const { return _headControllerFacingMovingAverage; } + void setHeadControllerFacingMovingAverage(glm::vec2 currentHeadControllerFacing) { _headControllerFacingMovingAverage = currentHeadControllerFacing; } + float getCurrentStandingHeight() const { return _currentStandingHeight; } + void setCurrentStandingHeight(float newMode) { _currentStandingHeight = newMode; } + const glm::quat getAverageHeadRotation() const { return _averageHeadRotation; } + void setAverageHeadRotation(glm::quat rotation) { _averageHeadRotation = rotation; } + bool getResetMode() const { return _resetMode; } + void setResetMode(bool hasBeenReset) { _resetMode = hasBeenReset; } void setControllerPoseInSensorFrame(controller::Action action, const controller::Pose& pose); controller::Pose getControllerPoseInSensorFrame(controller::Action action) const; @@ -890,8 +923,13 @@ public: bool hasDriveInput() const; - QVariantList getAvatarEntitiesVariant(); - void removeAvatarEntities(); + /**jsdoc + * Function returns list of avatar entities + * @function MyAvatar.getAvatarEntitiesVariant() + * @returns {object[]} + */ + Q_INVOKABLE QVariantList getAvatarEntitiesVariant(); + void removeAvatarEntities(const std::function& condition = {}); /**jsdoc * @function MyAvatar.isFlying @@ -917,6 +955,30 @@ public: */ Q_INVOKABLE bool getFlyingEnabled(); + /**jsdoc + * @function MyAvatar.setFlyingDesktopPref + * @param {boolean} enabled + */ + Q_INVOKABLE void setFlyingDesktopPref(bool enabled); + + /**jsdoc + * @function MyAvatar.getFlyingDesktopPref + * @returns {boolean} + */ + Q_INVOKABLE bool getFlyingDesktopPref(); + + /**jsdoc + * @function MyAvatar.setFlyingDesktopPref + * @param {boolean} enabled + */ + Q_INVOKABLE void setFlyingHMDPref(bool enabled); + + /**jsdoc + * @function MyAvatar.getFlyingDesktopPref + * @returns {boolean} + */ + Q_INVOKABLE bool getFlyingHMDPref(); + /**jsdoc * @function MyAvatar.getAvatarScale @@ -981,12 +1043,12 @@ public: // results are in sensor frame (-z forward) glm::mat4 deriveBodyFromHMDSensor() const; - glm::vec3 computeCounterBalance() const; + glm::vec3 computeCounterBalance(); // derive avatar body position and orientation from using the current HMD Sensor location in relation to the previous // location of the base of support of the avatar. // results are in sensor frame (-z foward) - glm::mat4 deriveBodyUsingCgModel() const; + glm::mat4 deriveBodyUsingCgModel(); /**jsdoc * @function MyAvatar.isUp @@ -1015,6 +1077,11 @@ public: QVector getScriptUrls(); + bool isReadyForPhysics() const; + + float computeStandingHeightMode(const controller::Pose& head); + glm::quat computeAverageHeadRotation(const controller::Pose& head); + public slots: /**jsdoc @@ -1294,6 +1361,22 @@ signals: */ void collisionWithEntity(const Collision& collision); + /**jsdoc + * Triggered when collisions with avatar enabled or disabled + * @function MyAvatar.collisionsEnabledChanged + * @param {boolean} enabled + * @returns {Signal} + */ + void collisionsEnabledChanged(bool enabled); + + /**jsdoc + * Triggered when avatar's animation url changes + * @function MyAvatar.animGraphUrlChanged + * @param {url} url + * @returns {Signal} + */ + void animGraphUrlChanged(const QUrl& url); + /**jsdoc * @function MyAvatar.energyChanged * @param {number} energy @@ -1358,12 +1441,30 @@ signals: */ void scaleChanged(); + /**jsdoc + * Triggered when hand touch is globally enabled or disabled + * @function MyAvatar.shouldDisableHandTouchChanged + * @param {boolean} shouldDisable + * @returns {Signal} + */ + void shouldDisableHandTouchChanged(bool shouldDisable); + + /**jsdoc + * Triggered when hand touch is enabled or disabled for an specific entity + * @function MyAvatar.disableHandTouchForIDChanged + * @param {Uuid} entityID - ID of the entity that will enable hand touch effect + * @param {boolean} disable + * @returns {Signal} + */ + void disableHandTouchForIDChanged(const QUuid& entityID, bool disable); + private slots: void leaveDomain(); protected: virtual void beParentOfChild(SpatiallyNestablePointer newChild) const override; virtual void forgetChild(SpatiallyNestablePointer newChild) const override; + virtual void recalculateChildCauterization() const override; private: @@ -1378,6 +1479,18 @@ private: virtual bool shouldRenderHead(const RenderArgs* renderArgs) const override; void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; setEnableMeshVisible(shouldRender); } bool getShouldRenderLocally() const { return _shouldRender; } + void setHasScriptedBlendshapes(bool hasScriptedBlendshapes); + bool getHasScriptedBlendshapes() const override { return _hasScriptedBlendShapes; } + void setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement); + bool getHasProceduralBlinkFaceMovement() const override { return _headData->getHasProceduralBlinkFaceMovement(); } + void setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement); + bool getHasProceduralEyeFaceMovement() const override { return _headData->getHasProceduralEyeFaceMovement(); } + void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement); + bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); } + void setRotationRecenterFilterLength(float length); + float getRotationRecenterFilterLength() const { return _rotationRecenterFilterLength; } + void setRotationThreshold(float angleRadians); + float getRotationThreshold() const { return _rotationThreshold; } bool isMyAvatar() const override { return true; } virtual int parseDataFromBuffer(const QByteArray& buffer) override; virtual glm::vec3 getSkeletonPosition() const override; @@ -1415,7 +1528,9 @@ private: std::array _driveKeys; std::bitset _disabledDriveKeys; - bool _enableFlying { true }; + bool _enableFlying { false }; + bool _flyingPrefDesktop { true }; + bool _flyingPrefHMD { false }; bool _wasPushing { false }; bool _isPushing { false }; bool _isBeingPushed { false }; @@ -1477,7 +1592,6 @@ private: ThreadSafeValueCache _prefOverrideAnimGraphUrl; QUrl _fstAnimGraphOverrideUrl; bool _useSnapTurn { true }; - bool _clearOverlayWhenMoving { true }; QString _dominantHand { DOMINANT_RIGHT_HAND }; const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // degrees @@ -1486,6 +1600,9 @@ private: bool _hmdRollControlEnabled { true }; float _hmdRollControlDeadZone { ROLL_CONTROL_DEAD_ZONE_DEFAULT }; float _hmdRollControlRate { ROLL_CONTROL_RATE_DEFAULT }; + std::atomic _hasScriptedBlendShapes { false }; + std::atomic _rotationRecenterFilterLength { 4.0f }; + std::atomic _rotationThreshold { 0.5235f }; // 30 degrees in radians // working copy -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access glm::mat4 _sensorToWorldMatrix { glm::mat4() }; @@ -1497,6 +1614,11 @@ private: // cache head controller pose in sensor space glm::vec2 _headControllerFacing; // facing vector in xz plane (sensor space) glm::vec2 _headControllerFacingMovingAverage { 0.0f, 0.0f }; // facing vector in xz plane (sensor space) + glm::quat _averageHeadRotation { 0.0f, 0.0f, 0.0f, 1.0f }; + + float _currentStandingHeight { 0.0f }; + bool _resetMode { true }; + RingBufferHistory _recentModeReadings; // cache of the current body position and orientation of the avatar's body, // in sensor space. @@ -1524,6 +1646,7 @@ private: bool shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; bool shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const; + bool shouldActivateHorizontalCG(MyAvatar& myAvatar) const; void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput); glm::mat4 postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix); bool getForceActivateRotation() const; @@ -1547,6 +1670,7 @@ private: glm::quat _goToOrientation; std::unordered_set _headBoneSet; + std::unordered_set _cauterizedChildrenOfHead; bool _prevShouldDrawHead; bool _rigEnabled { true }; @@ -1572,6 +1696,7 @@ private: // all poses are in sensor-frame std::map _controllerPoseMap; mutable std::mutex _controllerPoseMapMutex; + mutable std::mutex _disableHandTouchMutex; bool _centerOfGravityModelEnabled { true }; bool _hmdLeanRecenterEnabled { true }; @@ -1602,7 +1727,7 @@ private: // height of user in sensor space, when standing erect. ThreadSafeValueCache _userHeight { DEFAULT_AVATAR_HEIGHT }; - void updateChildCauterization(SpatiallyNestablePointer object); + void updateChildCauterization(SpatiallyNestablePointer object, bool cauterize); // max unscaled forward movement speed ThreadSafeValueCache _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED }; @@ -1610,6 +1735,9 @@ private: // load avatar scripts once when rig is ready bool _shouldLoadScripts { false }; + + bool _haveReceivedHeightLimitsFromDomain { false }; + int _disableHandTouchCount { 0 }; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); diff --git a/interface/src/avatar/MyCharacterController.cpp b/interface/src/avatar/MyCharacterController.cpp index 7e38c7763a..798dbc91ed 100755 --- a/interface/src/avatar/MyCharacterController.cpp +++ b/interface/src/avatar/MyCharacterController.cpp @@ -108,12 +108,15 @@ bool MyCharacterController::testRayShotgun(const glm::vec3& position, const glm: btScalar lengthAxis = axis.length(); if (lengthAxis > FLT_EPSILON) { // we're walking sideways - btScalar angle = acosf(lengthAxis / adjustedDirection.length()); - if (rayDirection.dot(forward) < 0.0f) { - angle = PI - angle; + btScalar cosAngle = lengthAxis / adjustedDirection.length(); + if (cosAngle < 1.0f) { + btScalar angle = acosf(cosAngle); + if (rayDirection.dot(forward) < 0.0f) { + angle = PI - angle; + } + axis /= lengthAxis; + rotation = btMatrix3x3(btQuaternion(axis, angle)) * rotation; } - axis /= lengthAxis; - rotation = btMatrix3x3(btQuaternion(axis, angle)) * rotation; } else if (rayDirection.dot(forward) < 0.0f) { // we're walking backwards rotation = btMatrix3x3(btQuaternion(_currentUp, PI)) * rotation; diff --git a/interface/src/avatar/MyHead.cpp b/interface/src/avatar/MyHead.cpp index cad2f9e5d0..9b05a26c76 100644 --- a/interface/src/avatar/MyHead.cpp +++ b/interface/src/avatar/MyHead.cpp @@ -46,32 +46,18 @@ void MyHead::simulate(float deltaTime) { auto player = DependencyManager::get(); // Only use face trackers when not playing back a recording. if (!player->isPlaying()) { - FaceTracker* faceTracker = qApp->getActiveFaceTracker(); - _isFaceTrackerConnected = faceTracker != nullptr && !faceTracker->isMuted(); + auto faceTracker = qApp->getActiveFaceTracker(); + const bool hasActualFaceTrackerConnected = faceTracker && !faceTracker->isMuted(); + _isFaceTrackerConnected = hasActualFaceTrackerConnected || _owningAvatar->getHasScriptedBlendshapes(); if (_isFaceTrackerConnected) { - _transientBlendshapeCoefficients = faceTracker->getBlendshapeCoefficients(); - - if (typeid(*faceTracker) == typeid(DdeFaceTracker)) { - - if (Menu::getInstance()->isOptionChecked(MenuOption::UseAudioForMouth)) { - calculateMouthShapes(deltaTime); - - const int JAW_OPEN_BLENDSHAPE = 21; - const int MMMM_BLENDSHAPE = 34; - const int FUNNEL_BLENDSHAPE = 40; - const int SMILE_LEFT_BLENDSHAPE = 28; - const int SMILE_RIGHT_BLENDSHAPE = 29; - _transientBlendshapeCoefficients[JAW_OPEN_BLENDSHAPE] += _audioJawOpen; - _transientBlendshapeCoefficients[SMILE_LEFT_BLENDSHAPE] += _mouth4; - _transientBlendshapeCoefficients[SMILE_RIGHT_BLENDSHAPE] += _mouth4; - _transientBlendshapeCoefficients[MMMM_BLENDSHAPE] += _mouth2; - _transientBlendshapeCoefficients[FUNNEL_BLENDSHAPE] += _mouth3; - } - applyEyelidOffset(getFinalOrientationInWorldFrame()); + if (hasActualFaceTrackerConnected) { + _blendshapeCoefficients = faceTracker->getBlendshapeCoefficients(); } } + auto eyeTracker = DependencyManager::get(); _isEyeTrackerConnected = eyeTracker->isTracking(); + // if eye tracker is connected we should get the data here. } Parent::simulate(deltaTime); } diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index c15b00ca19..0fc5e7521e 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -46,7 +46,7 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) { } glm::mat4 hipsMat; - if (myAvatar->getCenterOfGravityModelEnabled()) { + if (myAvatar->getCenterOfGravityModelEnabled() && !isFlying) { // then we use center of gravity model hipsMat = myAvatar->deriveBodyUsingCgModel(); } else { @@ -109,6 +109,11 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { AnimPose avatarToRigPose(glm::vec3(1.0f), Quaternions::Y_180, glm::vec3(0.0f)); + glm::mat4 rigToAvatarMatrix = Matrices::Y_180; + glm::mat4 avatarToWorldMatrix = createMatFromQuatAndPos(myAvatar->getWorldOrientation(), myAvatar->getWorldPosition()); + glm::mat4 sensorToWorldMatrix = myAvatar->getSensorToWorldMatrix(); + params.rigToSensorMatrix = glm::inverse(sensorToWorldMatrix) * avatarToWorldMatrix * rigToAvatarMatrix; + // input action is the highest priority source for head orientation. auto avatarHeadPose = myAvatar->getControllerPoseInAvatarFrame(controller::Action::HEAD); if (avatarHeadPose.isValid()) { diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp new file mode 100644 index 0000000000..2061df6004 --- /dev/null +++ b/interface/src/avatar/OtherAvatar.cpp @@ -0,0 +1,60 @@ +// +// Created by Bradley Austin Davis on 2017/04/27 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "OtherAvatar.h" +#include "Application.h" + +OtherAvatar::OtherAvatar(QThread* thread) : Avatar(thread) { + // give the pointer to our head to inherited _headData variable from AvatarData + _headData = new Head(this); + _skeletonModel = std::make_shared(this, nullptr); + _skeletonModel->setLoadingPriority(OTHERAVATAR_LOADING_PRIORITY); + connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); + connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); + connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset); + + // add the purple orb + createOrb(); +} + +OtherAvatar::~OtherAvatar() { + removeOrb(); +} + +void OtherAvatar::removeOrb() { + if (qApp->getOverlays().isAddedOverlay(_otherAvatarOrbMeshPlaceholderID)) { + qApp->getOverlays().deleteOverlay(_otherAvatarOrbMeshPlaceholderID); + } +} + +void OtherAvatar::updateOrbPosition() { + if (_otherAvatarOrbMeshPlaceholder != nullptr) { + _otherAvatarOrbMeshPlaceholder->setWorldPosition(getHead()->getPosition()); + } +} + +void OtherAvatar::createOrb() { + if (_otherAvatarOrbMeshPlaceholderID == UNKNOWN_OVERLAY_ID || + !qApp->getOverlays().isAddedOverlay(_otherAvatarOrbMeshPlaceholderID)) { + _otherAvatarOrbMeshPlaceholder = std::make_shared(); + _otherAvatarOrbMeshPlaceholder->setAlpha(1.0f); + _otherAvatarOrbMeshPlaceholder->setColor({ 0xFF, 0x00, 0xFF }); + _otherAvatarOrbMeshPlaceholder->setIsSolid(false); + _otherAvatarOrbMeshPlaceholder->setPulseMin(0.5); + _otherAvatarOrbMeshPlaceholder->setPulseMax(1.0); + _otherAvatarOrbMeshPlaceholder->setColorPulse(1.0); + _otherAvatarOrbMeshPlaceholder->setIgnorePickIntersection(true); + _otherAvatarOrbMeshPlaceholder->setDrawInFront(false); + _otherAvatarOrbMeshPlaceholderID = qApp->getOverlays().addOverlay(_otherAvatarOrbMeshPlaceholder); + // Position focus + _otherAvatarOrbMeshPlaceholder->setWorldOrientation(glm::quat(0.0f, 0.0f, 0.0f, 1.0)); + _otherAvatarOrbMeshPlaceholder->setWorldPosition(getHead()->getPosition()); + _otherAvatarOrbMeshPlaceholder->setDimensions(glm::vec3(0.5f, 0.5f, 0.5f)); + _otherAvatarOrbMeshPlaceholder->setVisible(true); + } +} diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h new file mode 100644 index 0000000000..f33952b78b --- /dev/null +++ b/interface/src/avatar/OtherAvatar.h @@ -0,0 +1,32 @@ +// +// Created by Bradley Austin Davis on 2017/04/27 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_OtherAvatar_h +#define hifi_OtherAvatar_h + +#include +#include "ui/overlays/Overlays.h" +#include "ui/overlays/Sphere3DOverlay.h" +#include "InterfaceLogging.h" + +class OtherAvatar : public Avatar { +public: + explicit OtherAvatar(QThread* thread); + virtual ~OtherAvatar(); + + virtual void instantiableAvatar() override { }; + virtual void createOrb() override; + void updateOrbPosition(); + void removeOrb(); + +protected: + std::shared_ptr _otherAvatarOrbMeshPlaceholder { nullptr }; + OverlayID _otherAvatarOrbMeshPlaceholderID { UNKNOWN_OVERLAY_ID }; +}; + +#endif // hifi_OtherAvatar_h diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 69698e82a6..702251f867 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -28,15 +28,15 @@ // account synthesizes a result {status: 'success', data: {keyStatus: "preexisting"|"conflicting"|"ok"}} -QJsonObject Ledger::apiResponse(const QString& label, QNetworkReply& reply) { - QByteArray response = reply.readAll(); +QJsonObject Ledger::apiResponse(const QString& label, QNetworkReply* reply) { + QByteArray response = reply->readAll(); QJsonObject data = QJsonDocument::fromJson(response).object(); qInfo(commerce) << label << "response" << QJsonDocument(data).toJson(QJsonDocument::Compact); return data; } // Non-200 responses are not json: -QJsonObject Ledger::failResponse(const QString& label, QNetworkReply& reply) { - QString response = reply.readAll(); +QJsonObject Ledger::failResponse(const QString& label, QNetworkReply* reply) { + QString response = reply->readAll(); qWarning(commerce) << "FAILED" << label << response; // tempResult will be NULL if the response isn't valid JSON. @@ -52,8 +52,8 @@ QJsonObject Ledger::failResponse(const QString& label, QNetworkReply& reply) { return tempResult.object(); } } -#define ApiHandler(NAME) void Ledger::NAME##Success(QNetworkReply& reply) { emit NAME##Result(apiResponse(#NAME, reply)); } -#define FailHandler(NAME) void Ledger::NAME##Failure(QNetworkReply& reply) { emit NAME##Result(failResponse(#NAME, reply)); } +#define ApiHandler(NAME) void Ledger::NAME##Success(QNetworkReply* reply) { emit NAME##Result(apiResponse(#NAME, reply)); } +#define FailHandler(NAME) void Ledger::NAME##Failure(QNetworkReply* reply) { emit NAME##Result(failResponse(#NAME, reply)); } #define Handler(NAME) ApiHandler(NAME) FailHandler(NAME) Handler(buy) Handler(receiveAt) @@ -68,7 +68,7 @@ Handler(updateItem) void Ledger::send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request) { auto accountManager = DependencyManager::get(); const QString URL = "/api/v1/commerce/"; - JSONCallbackParameters callbackParams(this, success, this, fail); + JSONCallbackParameters callbackParams(this, success, fail); qCInfo(commerce) << "Sending" << endpoint << QJsonDocument(request).toJson(QJsonDocument::Compact); accountManager->sendRequest(URL + endpoint, authType, @@ -223,12 +223,12 @@ QString transactionString(const QJsonObject& valueObject) { } static const QString MARKETPLACE_ITEMS_BASE_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/marketplace/items/"; -void Ledger::historySuccess(QNetworkReply& reply) { +void Ledger::historySuccess(QNetworkReply* reply) { // here we send a historyResult with some extra stuff in it // Namely, the styled text we'd like to show. The issue is the // QML cannot do that easily since it doesn't know what the wallet // public key(s) are. Let's keep it that way - QByteArray response = reply.readAll(); + QByteArray response = reply->readAll(); QJsonObject data = QJsonDocument::fromJson(response).object(); qInfo(commerce) << "history" << "response" << QJsonDocument(data).toJson(QJsonDocument::Compact); @@ -262,7 +262,7 @@ void Ledger::historySuccess(QNetworkReply& reply) { emit historyResult(newData); } -void Ledger::historyFailure(QNetworkReply& reply) { +void Ledger::historyFailure(QNetworkReply* reply) { failResponse("history", reply); } @@ -273,10 +273,10 @@ void Ledger::history(const QStringList& keys, const int& pageNumber, const int& keysQuery("history", "historySuccess", "historyFailure", params); } -void Ledger::accountSuccess(QNetworkReply& reply) { +void Ledger::accountSuccess(QNetworkReply* reply) { // lets set the appropriate stuff in the wallet now auto wallet = DependencyManager::get(); - QByteArray response = reply.readAll(); + QByteArray response = reply->readAll(); QJsonObject data = QJsonDocument::fromJson(response).object()["data"].toObject(); auto salt = QByteArray::fromBase64(data["salt"].toString().toUtf8()); @@ -312,7 +312,7 @@ void Ledger::accountSuccess(QNetworkReply& reply) { emit accountResult(responseData); } -void Ledger::accountFailure(QNetworkReply& reply) { +void Ledger::accountFailure(QNetworkReply* reply) { failResponse("account", reply); } void Ledger::account() { @@ -320,8 +320,8 @@ void Ledger::account() { } // The api/failResponse is called just for the side effect of logging. -void Ledger::updateLocationSuccess(QNetworkReply& reply) { apiResponse("updateLocation", reply); } -void Ledger::updateLocationFailure(QNetworkReply& reply) { failResponse("updateLocation", reply); } +void Ledger::updateLocationSuccess(QNetworkReply* reply) { apiResponse("updateLocation", reply); } +void Ledger::updateLocationFailure(QNetworkReply* reply) { failResponse("updateLocation", reply); } void Ledger::updateLocation(const QString& asset_id, const QString& location, const bool& alsoUpdateSiblings, const bool controlledFailure) { auto wallet = DependencyManager::get(); auto walletScriptingInterface = DependencyManager::get(); @@ -349,11 +349,11 @@ void Ledger::updateLocation(const QString& asset_id, const QString& location, co } } -void Ledger::certificateInfoSuccess(QNetworkReply& reply) { +void Ledger::certificateInfoSuccess(QNetworkReply* reply) { auto wallet = DependencyManager::get(); auto accountManager = DependencyManager::get(); - QByteArray response = reply.readAll(); + QByteArray response = reply->readAll(); QJsonObject replyObject = QJsonDocument::fromJson(response).object(); QStringList keys = wallet->listPublicKeys(); @@ -366,7 +366,7 @@ void Ledger::certificateInfoSuccess(QNetworkReply& reply) { qInfo(commerce) << "certificateInfo" << "response" << QJsonDocument(replyObject).toJson(QJsonDocument::Compact); emit certificateInfoResult(replyObject); } -void Ledger::certificateInfoFailure(QNetworkReply& reply) { +void Ledger::certificateInfoFailure(QNetworkReply* reply) { emit certificateInfoResult(failResponse("certificateInfo", reply)); } void Ledger::certificateInfo(const QString& certificateId) { diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 8a8fd2630a..ba2f167f4b 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -65,36 +65,36 @@ signals: void updateCertificateStatus(const QString& certID, uint certStatus); public slots: - void buySuccess(QNetworkReply& reply); - void buyFailure(QNetworkReply& reply); - void receiveAtSuccess(QNetworkReply& reply); - void receiveAtFailure(QNetworkReply& reply); - void balanceSuccess(QNetworkReply& reply); - void balanceFailure(QNetworkReply& reply); - void inventorySuccess(QNetworkReply& reply); - void inventoryFailure(QNetworkReply& reply); - void historySuccess(QNetworkReply& reply); - void historyFailure(QNetworkReply& reply); - void accountSuccess(QNetworkReply& reply); - void accountFailure(QNetworkReply& reply); - void updateLocationSuccess(QNetworkReply& reply); - void updateLocationFailure(QNetworkReply& reply); - void certificateInfoSuccess(QNetworkReply& reply); - void certificateInfoFailure(QNetworkReply& reply); - void transferAssetToNodeSuccess(QNetworkReply& reply); - void transferAssetToNodeFailure(QNetworkReply& reply); - void transferAssetToUsernameSuccess(QNetworkReply& reply); - void transferAssetToUsernameFailure(QNetworkReply& reply); - void alreadyOwnedSuccess(QNetworkReply& reply); - void alreadyOwnedFailure(QNetworkReply& reply); - void availableUpdatesSuccess(QNetworkReply& reply); - void availableUpdatesFailure(QNetworkReply& reply); - void updateItemSuccess(QNetworkReply& reply); - void updateItemFailure(QNetworkReply& reply); + void buySuccess(QNetworkReply* reply); + void buyFailure(QNetworkReply* reply); + void receiveAtSuccess(QNetworkReply* reply); + void receiveAtFailure(QNetworkReply* reply); + void balanceSuccess(QNetworkReply* reply); + void balanceFailure(QNetworkReply* reply); + void inventorySuccess(QNetworkReply* reply); + void inventoryFailure(QNetworkReply* reply); + void historySuccess(QNetworkReply* reply); + void historyFailure(QNetworkReply* reply); + void accountSuccess(QNetworkReply* reply); + void accountFailure(QNetworkReply* reply); + void updateLocationSuccess(QNetworkReply* reply); + void updateLocationFailure(QNetworkReply* reply); + void certificateInfoSuccess(QNetworkReply* reply); + void certificateInfoFailure(QNetworkReply* reply); + void transferAssetToNodeSuccess(QNetworkReply* reply); + void transferAssetToNodeFailure(QNetworkReply* reply); + void transferAssetToUsernameSuccess(QNetworkReply* reply); + void transferAssetToUsernameFailure(QNetworkReply* reply); + void alreadyOwnedSuccess(QNetworkReply* reply); + void alreadyOwnedFailure(QNetworkReply* reply); + void availableUpdatesSuccess(QNetworkReply* reply); + void availableUpdatesFailure(QNetworkReply* reply); + void updateItemSuccess(QNetworkReply* reply); + void updateItemFailure(QNetworkReply* reply); private: - QJsonObject apiResponse(const QString& label, QNetworkReply& reply); - QJsonObject failResponse(const QString& label, QNetworkReply& reply); + QJsonObject apiResponse(const QString& label, QNetworkReply* reply); + QJsonObject failResponse(const QString& label, QNetworkReply* reply); void send(const QString& endpoint, const QString& success, const QString& fail, QNetworkAccessManager::Operation method, AccountManagerAuth::Type authType, QJsonObject request); void keysQuery(const QString& endpoint, const QString& success, const QString& fail, QJsonObject& extraRequestParams); void keysQuery(const QString& endpoint, const QString& success, const QString& fail); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index b960c0b703..1f44343bdc 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -208,7 +208,7 @@ void QmlCommerce::alreadyOwned(const QString& marketplaceId) { ledger->alreadyOwned(marketplaceId); } -QString QmlCommerce::getInstalledApps() { +QString QmlCommerce::getInstalledApps(const QString& justInstalledAppID) { QString installedAppsFromMarketplace; QStringList runningScripts = DependencyManager::get()->getRunningScripts(); @@ -217,6 +217,18 @@ QString QmlCommerce::getInstalledApps() { foreach(QString appFileName, apps) { installedAppsFromMarketplace += appFileName; installedAppsFromMarketplace += ","; + + // If we were supplied a "justInstalledAppID" argument, that means we're entering this function + // to get the new list of installed apps immediately after installing an app. + // In that case, the app we installed may not yet have its associated script running - + // that task is asynchronous and takes a nonzero amount of time. This is especially true + // for apps that are not in Interface's script cache. + // Thus, we protect against deleting the .app.json from the user's disk (below) + // by skipping that check for the app we just installed. + if ((justInstalledAppID != "") && ((justInstalledAppID + ".app.json") == appFileName)) { + continue; + } + QFile appFile(_appsPath + appFileName); if (appFile.open(QIODevice::ReadOnly)) { QJsonDocument appFileJsonDocument = QJsonDocument::fromJson(appFile.readAll()); @@ -291,7 +303,8 @@ bool QmlCommerce::installApp(const QString& itemHref) { return false; } - emit appInstalled(itemHref); + QFileInfo appFileInfo(appFile); + emit appInstalled(appFileInfo.baseName()); return true; }); request->send(); @@ -321,7 +334,8 @@ bool QmlCommerce::uninstallApp(const QString& itemHref) { qCWarning(commerce) << "Couldn't delete local .app.json file during app uninstall. Continuing anyway. App filename is:" << appHref.fileName(); } - emit appUninstalled(itemHref); + QFileInfo appFileInfo(appFile); + emit appUninstalled(appFileInfo.baseName()); return true; } diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index a0c6916799..79d8e82e71 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -53,8 +53,8 @@ signals: void contentSetChanged(const QString& contentSetHref); - void appInstalled(const QString& appHref); - void appUninstalled(const QString& appHref); + void appInstalled(const QString& appID); + void appUninstalled(const QString& appID); protected: Q_INVOKABLE void getWalletStatus(); @@ -86,7 +86,7 @@ protected: Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID); - Q_INVOKABLE QString getInstalledApps(); + Q_INVOKABLE QString getInstalledApps(const QString& justInstalledAppID = ""); Q_INVOKABLE bool installApp(const QString& appHref); Q_INVOKABLE bool uninstallApp(const QString& appHref); Q_INVOKABLE bool openApp(const QString& appHref); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index e003ae88a0..991f1ebf3f 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -127,31 +127,40 @@ EC_KEY* readKeys(const char* filename) { bool Wallet::writeBackupInstructions() { QString inputFilename(PathUtils::resourcesPath() + "html/commerce/backup_instructions.html"); QString outputFilename = PathUtils::getAppDataFilePath(INSTRUCTIONS_FILE); + QFile inputFile(inputFilename); QFile outputFile(outputFilename); bool retval = false; - if (QFile::exists(outputFilename) || getKeyFilePath() == "") + if (getKeyFilePath() == "") { return false; } - QFile::copy(inputFilename, outputFilename); - if (QFile::exists(outputFilename) && outputFile.open(QIODevice::ReadWrite)) { + if (QFile::exists(inputFilename) && inputFile.open(QIODevice::ReadOnly)) { + if (outputFile.open(QIODevice::ReadWrite)) { + // Read the data from the original file, then close it + QByteArray fileData = inputFile.readAll(); + inputFile.close(); - QByteArray fileData = outputFile.readAll(); - QString text(fileData); + // Translate the data from the original file into a QString + QString text(fileData); - text.replace(QString("HIFIKEY_PATH_REPLACEME"), keyFilePath()); + // Replace the necessary string + text.replace(QString("HIFIKEY_PATH_REPLACEME"), keyFilePath()); - outputFile.seek(0); // go to the beginning of the file - outputFile.write(text.toUtf8()); // write the new text back to the file + // Write the new text back to the file + outputFile.write(text.toUtf8()); - outputFile.close(); // close the file handle. + // Close the output file + outputFile.close(); - retval = true; - qCDebug(commerce) << "wrote html file successfully"; + retval = true; + qCDebug(commerce) << "wrote html file successfully"; + } else { + qCDebug(commerce) << "failed to open output html file" << outputFilename; + } } else { - qCDebug(commerce) << "failed to open output html file" << outputFilename; + qCDebug(commerce) << "failed to open input html file" << inputFilename; } return retval; } diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 22db128f7e..c1ba6f0535 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -26,7 +26,7 @@ #include "AddressManager.h" #include "Application.h" -#include "Crashpad.h" +#include "CrashHandler.h" #include "InterfaceLogging.h" #include "UserActivityLogger.h" #include "MainWindow.h" @@ -81,10 +81,17 @@ int main(int argc, const char* argv[]) { // Instance UserActivityLogger now that the settings are loaded auto& ual = UserActivityLogger::getInstance(); + // once the settings have been loaded, check if we need to flip the default for UserActivityLogger + if (!ual.isDisabledSettingSet()) { + // the user activity logger is opt-out for Interface + // but it's defaulted to disabled for other targets + // so we need to enable it here if it has never been disabled by the user + ual.disable(false); + } qDebug() << "UserActivityLogger is enabled:" << ual.isEnabled(); if (ual.isEnabled()) { - auto crashHandlerStarted = startCrashHandler(); + auto crashHandlerStarted = startCrashHandler(argv[0]); qDebug() << "Crash handler started:" << crashHandlerStarted; } @@ -255,6 +262,9 @@ int main(int argc, const char* argv[]) { // Extend argv to enable WebGL rendering std::vector argvExtended(&argv[0], &argv[argc]); argvExtended.push_back("--ignore-gpu-blacklist"); +#ifdef Q_OS_ANDROID + argvExtended.push_back("--suppress-settings-reset"); +#endif int argcExtended = (int)argvExtended.size(); PROFILE_SYNC_END(startup, "main startup", ""); diff --git a/interface/src/raypick/JointParabolaPick.cpp b/interface/src/raypick/JointParabolaPick.cpp new file mode 100644 index 0000000000..11a2e90819 --- /dev/null +++ b/interface/src/raypick/JointParabolaPick.cpp @@ -0,0 +1,43 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 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 "JointParabolaPick.h" + +#include "avatar/AvatarManager.h" + +JointParabolaPick::JointParabolaPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, + float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar, PickFilter& filter, float maxDistance, bool enabled) : + ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, scaleWithAvatar, filter, maxDistance, enabled), + _jointName(jointName), + _posOffset(posOffset), + _dirOffset(dirOffset) +{ +} + +PickParabola JointParabolaPick::getMathematicalPick() 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->getWorldPosition(); + glm::quat avatarRot = myAvatar->getWorldOrientation(); + + 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 * (myAvatar->getSensorToWorldScale() * _posOffset)); + glm::vec3 dir = glm::normalize(rot * glm::normalize(_dirOffset)); + + return PickParabola(pos, getSpeed() * dir, getAcceleration()); + } + + return PickParabola(); +} diff --git a/interface/src/raypick/JointParabolaPick.h b/interface/src/raypick/JointParabolaPick.h new file mode 100644 index 0000000000..aff6bd34d8 --- /dev/null +++ b/interface/src/raypick/JointParabolaPick.h @@ -0,0 +1,32 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 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_JointParabolaPick_h +#define hifi_JointParabolaPick_h + +#include "ParabolaPick.h" + +class JointParabolaPick : public ParabolaPick { + +public: + JointParabolaPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, + float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar, + PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); + + PickParabola getMathematicalPick() const override; + + bool isLeftHand() const override { return (_jointName == "_CONTROLLER_LEFTHAND") || (_jointName == "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); } + bool isRightHand() const override { return (_jointName == "_CONTROLLER_RIGHTHAND") || (_jointName == "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"); } + +private: + std::string _jointName; + glm::vec3 _posOffset; + glm::vec3 _dirOffset; + +}; + +#endif // hifi_JointParabolaPick_h diff --git a/interface/src/raypick/JointRayPick.cpp b/interface/src/raypick/JointRayPick.cpp index 62912fdcd6..340014e7d2 100644 --- a/interface/src/raypick/JointRayPick.cpp +++ b/interface/src/raypick/JointRayPick.cpp @@ -36,7 +36,7 @@ PickRay JointRayPick::getMathematicalPick() const { // Apply offset pos = pos + (rot * (myAvatar->getSensorToWorldScale() * _posOffset)); - glm::vec3 dir = rot * glm::normalize(_dirOffset); + glm::vec3 dir = glm::normalize(rot * glm::normalize(_dirOffset)); return PickRay(pos, dir); } diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index bd71e47cf0..2382a95105 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -14,315 +14,114 @@ #include "avatar/AvatarManager.h" #include -#include -#include "PickScriptingInterface.h" #include "RayPick.h" LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, - const PointerTriggers& triggers, bool faceAvatar, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) : - Pointer(DependencyManager::get()->createRayPick(rayProps), enabled, hover), - _triggers(triggers), - _renderStates(renderStates), - _defaultRenderStates(defaultRenderStates), - _faceAvatar(faceAvatar), - _centerEndY(centerEndY), - _lockEnd(lockEnd), - _distanceScaleEnd(distanceScaleEnd), - _scaleWithAvatar(scaleWithAvatar) + const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalTime, bool centerEndY, bool lockEnd, + bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) : + PathPointer(PickQuery::Ray, rayProps, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, followNormalTime, + centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled) { - 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() { - for (auto& renderState : _renderStates) { - renderState.second.deleteOverlays(); - } - for (auto& renderState : _defaultRenderStates) { - renderState.second.second.deleteOverlays(); - } -} - -void LaserPointer::setRenderState(const std::string& state) { - withWriteLock([&] { - 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) { - withWriteLock([&] { - updateRenderStateOverlay(_renderStates[state].getStartID(), startProps); - updateRenderStateOverlay(_renderStates[state].getPathID(), pathProps); - updateRenderStateOverlay(_renderStates[state].getEndID(), endProps); - QVariant endDim = endProps.toMap()["dimensions"]; - if (endDim.isValid()) { - _renderStates[state].setEndDim(vec3FromVariant(endDim)); - } +void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) { + auto renderState = std::static_pointer_cast(_renderStates[state]); + if (renderState) { + updateRenderStateOverlay(renderState->getPathID(), pathProps); QVariant lineWidth = pathProps.toMap()["lineWidth"]; if (lineWidth.isValid()) { - _renderStates[state].setLineWidth(lineWidth.toFloat()); - } - }); -} - -PickResultPointer LaserPointer::getVisualPickResult(const PickResultPointer& pickResult) { - PickResultPointer visualPickResult = pickResult; - auto rayPickResult = std::static_pointer_cast(visualPickResult); - IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE; - - if (type != IntersectionType::HUD) { - glm::vec3 endVec; - PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay(); - if (!_lockEndObject.id.isNull()) { - glm::vec3 pos; - glm::quat rot; - glm::vec3 dim; - glm::vec3 registrationPoint; - if (_lockEndObject.isOverlay) { - pos = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "position").value); - rot = quatFromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "rotation").value); - dim = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "dimensions").value); - registrationPoint = glm::vec3(0.5f); - } else { - EntityItemProperties props = DependencyManager::get()->getEntityProperties(_lockEndObject.id); - glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition()); - glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat; - pos = extractTranslation(finalPosAndRotMat); - rot = glmExtractRotation(finalPosAndRotMat); - dim = props.getDimensions(); - registrationPoint = props.getRegistrationPoint(); - } - const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f); - endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint)); - glm::vec3 direction = endVec - pickRay.origin; - float distance = glm::distance(pickRay.origin, endVec); - glm::vec3 normalizedDirection = glm::normalize(direction); - - rayPickResult->type = _lockEndObject.isOverlay ? IntersectionType::OVERLAY : IntersectionType::ENTITY; - rayPickResult->objectID = _lockEndObject.id; - rayPickResult->intersection = endVec; - rayPickResult->distance = distance; - rayPickResult->surfaceNormal = -normalizedDirection; - rayPickResult->pickVariant["direction"] = vec3toVariant(normalizedDirection); - } else if (type != IntersectionType::NONE && _lockEnd) { - if (type == IntersectionType::ENTITY) { - endVec = DependencyManager::get()->getEntityTransform(rayPickResult->objectID)[3]; - } else if (type == IntersectionType::OVERLAY) { - endVec = vec3FromVariant(qApp->getOverlays().getProperty(rayPickResult->objectID, "position").value); - } else if (type == IntersectionType::AVATAR) { - endVec = DependencyManager::get()->getAvatar(rayPickResult->objectID)->getPosition(); - } - glm::vec3 direction = endVec - pickRay.origin; - float distance = glm::distance(pickRay.origin, endVec); - glm::vec3 normalizedDirection = glm::normalize(direction); - rayPickResult->intersection = endVec; - rayPickResult->distance = distance; - rayPickResult->surfaceNormal = -normalizedDirection; - rayPickResult->pickVariant["direction"] = vec3toVariant(normalizedDirection); + renderState->setLineWidth(lineWidth.toFloat()); } } - return visualPickResult; } -void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) { - if (!id.isNull() && props.isValid()) { - QVariantMap propMap = props.toMap(); - propMap.remove("visible"); - qApp->getOverlays().editOverlay(id, propMap); +glm::vec3 LaserPointer::getPickOrigin(const PickResultPointer& pickResult) const { + auto rayPickResult = std::static_pointer_cast(pickResult); + return (rayPickResult ? vec3FromVariant(rayPickResult->pickVariant["origin"]) : glm::vec3(0.0f)); +} + +glm::vec3 LaserPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const { + auto rayPickResult = std::static_pointer_cast(pickResult); + if (distance > 0.0f) { + PickRay pick = PickRay(rayPickResult->pickVariant); + return pick.origin + distance * pick.direction; + } else { + return rayPickResult->intersection; } } -void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay) { - 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 = pickRay.origin + pickRay.direction * distance; - - 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()); - if (_scaleWithAvatar) { - pathProps.insert("lineWidth", renderState.getLineWidth() * DependencyManager::get()->getMyAvatar()->getSensorToWorldScale()); - } - qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps); - } - if (!renderState.getEndID().isNull()) { - QVariantMap endProps; - glm::quat faceAvatarRotation = DependencyManager::get()->getMyAvatar()->getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, 180.0f, 0.0f))); - glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(renderState.getEndID(), "dimensions").value); - if (_distanceScaleEnd) { - dim = renderState.getEndDim() * glm::distance(pickRay.origin, endVec); - endProps.insert("dimensions", vec3toVariant(dim)); - } - if (_centerEndY) { - endProps.insert("position", end); - } else { - glm::vec3 currentUpVector = faceAvatarRotation * Vectors::UP; - endProps.insert("position", vec3toVariant(endVec + glm::vec3(currentUpVector.x * 0.5f * dim.y, currentUpVector.y * 0.5f * dim.y, currentUpVector.z * 0.5f * dim.y))); - } - if (_faceAvatar) { - endProps.insert("rotation", quatToVariant(faceAvatarRotation)); - } - endProps.insert("visible", true); - endProps.insert("ignoreRayIntersection", renderState.doesEndIgnoreRays()); - qApp->getOverlays().editOverlay(renderState.getEndID(), endProps); - } +glm::vec3 LaserPointer::getPickedObjectNormal(const PickResultPointer& pickResult) const { + auto rayPickResult = std::static_pointer_cast(pickResult); + return (rayPickResult ? rayPickResult->surfaceNormal : glm::vec3(0.0f)); } -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); - } +IntersectionType LaserPointer::getPickedObjectType(const PickResultPointer& pickResult) const { + auto rayPickResult = std::static_pointer_cast(pickResult); + return (rayPickResult ? rayPickResult->type : IntersectionType::NONE); } -void LaserPointer::updateVisuals(const PickResultPointer& pickResult) { - auto rayPickResult = std::static_pointer_cast(pickResult); - - IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE; - if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && - (type != IntersectionType::NONE || _laserLength > 0.0f || !_lockEndObject.id.isNull())) { - PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant): PickRay(); - QUuid uid = rayPickResult->objectID; - float distance = _laserLength > 0.0f ? _laserLength : rayPickResult->distance; - updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay); - disableRenderState(_defaultRenderStates[_currentRenderState].second); - } else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { - disableRenderState(_renderStates[_currentRenderState]); - PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay(); - updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay); - } else if (!_currentRenderState.empty()) { - disableRenderState(_renderStates[_currentRenderState]); - disableRenderState(_defaultRenderStates[_currentRenderState].second); - } +QUuid LaserPointer::getPickedObjectID(const PickResultPointer& pickResult) const { + auto rayPickResult = std::static_pointer_cast(pickResult); + return (rayPickResult ? rayPickResult->objectID : QUuid()); } -Pointer::PickedObject LaserPointer::getHoveredObject(const PickResultPointer& pickResult) { - auto rayPickResult = std::static_pointer_cast(pickResult); - if (!rayPickResult) { - return PickedObject(); - } - return PickedObject(rayPickResult->objectID, rayPickResult->type); -} - -Pointer::Buttons LaserPointer::getPressedButtons(const PickResultPointer& pickResult) { - std::unordered_set toReturn; - auto rayPickResult = std::static_pointer_cast(pickResult); - +void LaserPointer::setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, + const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) { + auto rayPickResult = std::static_pointer_cast(pickResult); if (rayPickResult) { - for (const PointerTrigger& trigger : _triggers) { - std::string button = trigger.getButton(); - TriggerState& state = _states[button]; - // TODO: right now, LaserPointers don't support axes, only on/off buttons - if (trigger.getEndpoint()->peek() >= 1.0f) { - toReturn.insert(button); - - if (_previousButtons.find(button) == _previousButtons.end()) { - // start triggering for buttons that were just pressed - state.triggeredObject = PickedObject(rayPickResult->objectID, rayPickResult->type); - state.intersection = rayPickResult->intersection; - state.triggerPos2D = findPos2D(state.triggeredObject, rayPickResult->intersection); - state.triggerStartTime = usecTimestampNow(); - state.surfaceNormal = rayPickResult->surfaceNormal; - state.deadspotExpired = false; - state.wasTriggering = true; - state.triggering = true; - _latestState = state; - } - } else { - // stop triggering for buttons that aren't pressed - state.wasTriggering = state.triggering; - state.triggering = false; - _latestState = state; - } - } - _previousButtons = toReturn; + rayPickResult->type = type; + rayPickResult->objectID = id; + rayPickResult->intersection = intersection; + rayPickResult->distance = distance; + rayPickResult->surfaceNormal = surfaceNormal; + rayPickResult->pickVariant["direction"] = vec3toVariant(-surfaceNormal); } - - return toReturn; } -void LaserPointer::setLength(float length) { - withWriteLock([&] { - _laserLength = length; - }); -} - -void LaserPointer::setLockEndUUID(const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) { - withWriteLock([&] { - _lockEndObject.id = objectID; - _lockEndObject.isOverlay = isOverlay; - _lockEndObject.offsetMat = offsetMat; - }); -} - -RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) : - _startID(startID), _pathID(pathID), _endID(endID) +LaserPointer::RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) : + StartEndRenderState(startID, endID), _pathID(pathID) { - if (!_startID.isNull()) { - _startIgnoreRays = qApp->getOverlays().getProperty(_startID, "ignoreRayIntersection").value.toBool(); - } if (!_pathID.isNull()) { _pathIgnoreRays = qApp->getOverlays().getProperty(_pathID, "ignoreRayIntersection").value.toBool(); _lineWidth = qApp->getOverlays().getProperty(_pathID, "lineWidth").value.toFloat(); } - if (!_endID.isNull()) { - _endDim = vec3FromVariant(qApp->getOverlays().getProperty(_endID, "dimensions").value); - _endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignoreRayIntersection").value.toBool(); - } } -void RenderState::deleteOverlays() { - if (!_startID.isNull()) { - qApp->getOverlays().deleteOverlay(_startID); - } +void LaserPointer::RenderState::cleanup() { + StartEndRenderState::cleanup(); if (!_pathID.isNull()) { qApp->getOverlays().deleteOverlay(_pathID); } - if (!_endID.isNull()) { - qApp->getOverlays().deleteOverlay(_endID); +} + +void LaserPointer::RenderState::disable() { + StartEndRenderState::disable(); + if (!getPathID().isNull()) { + QVariantMap pathProps; + pathProps.insert("visible", false); + pathProps.insert("ignoreRayIntersection", true); + qApp->getOverlays().editOverlay(getPathID(), pathProps); } } -RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) { +void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) { + StartEndRenderState::update(origin, end, surfaceNormal, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal, followNormalStrength, distance, pickResult); + QVariant endVariant = vec3toVariant(end); + if (!getPathID().isNull()) { + QVariantMap pathProps; + pathProps.insert("start", vec3toVariant(origin)); + pathProps.insert("end", endVariant); + pathProps.insert("visible", true); + pathProps.insert("ignoreRayIntersection", doesPathIgnoreRays()); + if (scaleWithAvatar) { + pathProps.insert("lineWidth", getLineWidth() * DependencyManager::get()->getMyAvatar()->getSensorToWorldScale()); + } + qApp->getOverlays().editOverlay(getPathID(), pathProps); + } +} + +std::shared_ptr LaserPointer::buildRenderState(const QVariantMap& propMap) { QUuid startID; if (propMap["start"].isValid()) { QVariantMap startMap = propMap["start"].toMap(); @@ -335,7 +134,7 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) { QUuid pathID; if (propMap["path"].isValid()) { QVariantMap pathMap = propMap["path"].toMap(); - // right now paths must be line3ds + // laser paths must be line3ds if (pathMap["type"].isValid() && pathMap["type"].toString() == "line3d") { pathMap.remove("visible"); pathID = qApp->getOverlays().addOverlay(pathMap["type"].toString(), pathMap); @@ -351,7 +150,7 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) { } } - return RenderState(startID, pathID, endID); + return std::make_shared(startID, pathID, endID); } PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) { @@ -391,24 +190,11 @@ PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const P glm::vec3 LaserPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction) { switch (pickedObject.type) { - case ENTITY: - return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction); - case OVERLAY: - return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction); - default: - return glm::vec3(NAN); - } -} - -glm::vec2 LaserPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) { - switch (pickedObject.type) { - case ENTITY: - return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin); - case OVERLAY: - return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin); - case HUD: - return DependencyManager::get()->calculatePos2DFromHUD(origin); - default: - return glm::vec2(NAN); + case ENTITY: + return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction); + case OVERLAY: + return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction); + default: + return glm::vec3(NAN); } } diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index 964881be42..95a5bccc6c 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -11,117 +11,54 @@ #ifndef hifi_LaserPointer_h #define hifi_LaserPointer_h -#include -#include - -#include "ui/overlays/Overlay.h" - -#include -#include - -struct LockEndObject { - QUuid id { QUuid() }; - bool isOverlay { false }; - glm::mat4 offsetMat { glm::mat4() }; -}; - -class RenderState { +#include "PathPointer.h" +class LaserPointer : public PathPointer { + using Parent = PathPointer; public: - RenderState() {} - RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID); + class RenderState : public StartEndRenderState { + 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; } + const OverlayID& getPathID() const { return _pathID; } + const bool& doesPathIgnoreRays() const { return _pathIgnoreRays; } - void setEndDim(const glm::vec3& endDim) { _endDim = endDim; } - const glm::vec3& getEndDim() const { return _endDim; } + void setLineWidth(const float& lineWidth) { _lineWidth = lineWidth; } + const float& getLineWidth() const { return _lineWidth; } - void setLineWidth(const float& lineWidth) { _lineWidth = lineWidth; } - const float& getLineWidth() const { return _lineWidth; } + void cleanup() override; + void disable() override; + void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) override; - void deleteOverlays(); + private: + OverlayID _pathID; + bool _pathIgnoreRays; -private: - OverlayID _startID; - OverlayID _pathID; - OverlayID _endID; - bool _startIgnoreRays; - bool _pathIgnoreRays; - bool _endIgnoreRays; - - glm::vec3 _endDim; - float _lineWidth; -}; - -class LaserPointer : public Pointer { - using Parent = Pointer; -public: - typedef std::unordered_map RenderStateMap; - typedef std::unordered_map> DefaultRenderStateMap; - - LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, - bool faceAvatar, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); - ~LaserPointer(); - - void setRenderState(const std::string& state) override; - // 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) override; - - void setLength(float length) override; - void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) override; - - void updateVisuals(const PickResultPointer& prevRayPickResult) override; - - static RenderState buildRenderState(const QVariantMap& propMap); - -protected: - PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override; - - PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) override; - PickedObject getHoveredObject(const PickResultPointer& pickResult) override; - Pointer::Buttons getPressedButtons(const PickResultPointer& pickResult) override; - - bool shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; } - bool shouldTrigger(const PickResultPointer& pickResult) override { return _currentRenderState != ""; } - -private: - PointerTriggers _triggers; - float _laserLength { 0.0f }; - std::string _currentRenderState { "" }; - RenderStateMap _renderStates; - DefaultRenderStateMap _defaultRenderStates; - bool _faceAvatar; - bool _centerEndY; - bool _lockEnd; - bool _distanceScaleEnd; - bool _scaleWithAvatar; - LockEndObject _lockEndObject; - - void updateRenderStateOverlay(const OverlayID& id, const QVariant& props); - void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay); - void disableRenderState(const RenderState& renderState); - - struct TriggerState { - PickedObject triggeredObject; - glm::vec3 intersection { NAN }; - glm::vec3 surfaceNormal { NAN }; - glm::vec2 triggerPos2D { NAN }; - quint64 triggerStartTime { 0 }; - bool deadspotExpired { true }; - bool triggering { false }; - bool wasTriggering { false }; + float _lineWidth; }; - Pointer::Buttons _previousButtons; - std::unordered_map _states; - TriggerState _latestState; + LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, + bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); + + static std::shared_ptr buildRenderState(const QVariantMap& propMap); + +protected: + void editRenderStatePath(const std::string& state, const QVariant& pathProps) override; + + glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const override; + glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance) const override; + glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) const override; + IntersectionType getPickedObjectType(const PickResultPointer& pickResult) const override; + QUuid getPickedObjectID(const PickResultPointer& pickResult) const override; + void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, + const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) override; + + PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override; + +private: static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction); - static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin); }; diff --git a/interface/src/raypick/MouseParabolaPick.cpp b/interface/src/raypick/MouseParabolaPick.cpp new file mode 100644 index 0000000000..66351f4520 --- /dev/null +++ b/interface/src/raypick/MouseParabolaPick.cpp @@ -0,0 +1,28 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 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 "MouseParabolaPick.h" + +#include "Application.h" +#include "display-plugins/CompositorHelper.h" + +MouseParabolaPick::MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, + bool scaleWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) : + ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, scaleWithAvatar, filter, maxDistance, enabled) +{ +} + +PickParabola MouseParabolaPick::getMathematicalPick() const { + QVariant position = qApp->getApplicationCompositor().getReticleInterface()->getPosition(); + if (position.isValid()) { + QVariantMap posMap = position.toMap(); + PickRay pickRay = qApp->getCamera().computePickRay(posMap["x"].toFloat(), posMap["y"].toFloat()); + return PickParabola(pickRay.origin, getSpeed() * pickRay.direction, getAcceleration()); + } + + return PickParabola(); +} diff --git a/interface/src/raypick/MouseParabolaPick.h b/interface/src/raypick/MouseParabolaPick.h new file mode 100644 index 0000000000..cb67c3b361 --- /dev/null +++ b/interface/src/raypick/MouseParabolaPick.h @@ -0,0 +1,24 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 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_MouseParabolaPick_h +#define hifi_MouseParabolaPick_h + +#include "ParabolaPick.h" + +class MouseParabolaPick : public ParabolaPick { + +public: + MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar, + const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); + + PickParabola getMathematicalPick() const override; + + bool isMouse() const override { return true; } +}; + +#endif // hifi_MouseParabolaPick_h diff --git a/interface/src/raypick/ParabolaPick.cpp b/interface/src/raypick/ParabolaPick.cpp new file mode 100644 index 0000000000..b3e3f16345 --- /dev/null +++ b/interface/src/raypick/ParabolaPick.cpp @@ -0,0 +1,70 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 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 "ParabolaPick.h" + +#include "Application.h" +#include "EntityScriptingInterface.h" +#include "ui/overlays/Overlays.h" +#include "avatar/AvatarManager.h" +#include "scripting/HMDScriptingInterface.h" +#include "DependencyManager.h" + +PickResultPointer ParabolaPick::getEntityIntersection(const PickParabola& pick) { + if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) { + ParabolaToEntityIntersectionResult entityRes = + DependencyManager::get()->findParabolaIntersectionVector(pick, !getFilter().doesPickCoarse(), + getIncludeItemsAs(), getIgnoreItemsAs(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable()); + if (entityRes.intersects) { + return std::make_shared(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.parabolicDistance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo); + } + } + return std::make_shared(pick.toVariantMap()); +} + +PickResultPointer ParabolaPick::getOverlayIntersection(const PickParabola& pick) { + if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) { + ParabolaToOverlayIntersectionResult overlayRes = + qApp->getOverlays().findParabolaIntersectionVector(pick, !getFilter().doesPickCoarse(), + getIncludeItemsAs(), getIgnoreItemsAs(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable()); + if (overlayRes.intersects) { + return std::make_shared(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.parabolicDistance, overlayRes.intersection, pick, overlayRes.surfaceNormal, overlayRes.extraInfo); + } + } + return std::make_shared(pick.toVariantMap()); +} + +PickResultPointer ParabolaPick::getAvatarIntersection(const PickParabola& pick) { + if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) { + ParabolaToAvatarIntersectionResult avatarRes = DependencyManager::get()->findParabolaIntersectionVector(pick, getIncludeItemsAs(), getIgnoreItemsAs()); + if (avatarRes.intersects) { + return std::make_shared(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.parabolicDistance, avatarRes.intersection, pick, avatarRes.surfaceNormal, avatarRes.extraInfo); + } + } + return std::make_shared(pick.toVariantMap()); +} + +PickResultPointer ParabolaPick::getHUDIntersection(const PickParabola& pick) { + if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) { + float parabolicDistance; + glm::vec3 hudRes = DependencyManager::get()->calculateParabolaUICollisionPoint(pick.origin, pick.velocity, pick.acceleration, parabolicDistance); + return std::make_shared(IntersectionType::HUD, QUuid(), glm::distance(pick.origin, hudRes), parabolicDistance, hudRes, pick); + } + return std::make_shared(pick.toVariantMap()); +} + +float ParabolaPick::getSpeed() const { + return (_scaleWithAvatar ? DependencyManager::get()->getMyAvatar()->getSensorToWorldScale() * _speed : _speed); +} + +glm::vec3 ParabolaPick::getAcceleration() const { + float scale = (_scaleWithAvatar ? DependencyManager::get()->getMyAvatar()->getSensorToWorldScale() : 1.0f); + if (_rotateAccelerationWithAvatar) { + return scale * (DependencyManager::get()->getMyAvatar()->getWorldOrientation() * _accelerationAxis); + } + return scale * _accelerationAxis; +} \ No newline at end of file diff --git a/interface/src/raypick/ParabolaPick.h b/interface/src/raypick/ParabolaPick.h new file mode 100644 index 0000000000..99a42a5380 --- /dev/null +++ b/interface/src/raypick/ParabolaPick.h @@ -0,0 +1,97 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 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_ParabolaPick_h +#define hifi_ParabolaPick_h + +#include +#include + +class EntityItemID; +class OverlayID; + +class ParabolaPickResult : public PickResult { +public: + ParabolaPickResult() {} + ParabolaPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {} + ParabolaPickResult(const IntersectionType type, const QUuid& objectID, float distance, float parabolicDistance, const glm::vec3& intersection, const PickParabola& parabola, + const glm::vec3& surfaceNormal = glm::vec3(NAN), const QVariantMap& extraInfo = QVariantMap()) : + PickResult(parabola.toVariantMap()), extraInfo(extraInfo), objectID(objectID), intersection(intersection), surfaceNormal(surfaceNormal), type(type), distance(distance), parabolicDistance(parabolicDistance), intersects(type != NONE) { + } + + ParabolaPickResult(const ParabolaPickResult& parabolaPickResult) : PickResult(parabolaPickResult.pickVariant) { + type = parabolaPickResult.type; + intersects = parabolaPickResult.intersects; + objectID = parabolaPickResult.objectID; + distance = parabolaPickResult.distance; + parabolicDistance = parabolaPickResult.parabolicDistance; + intersection = parabolaPickResult.intersection; + surfaceNormal = parabolaPickResult.surfaceNormal; + extraInfo = parabolaPickResult.extraInfo; + } + + QVariantMap extraInfo; + QUuid objectID; + glm::vec3 intersection { NAN }; + glm::vec3 surfaceNormal { NAN }; + IntersectionType type { NONE }; + float distance { FLT_MAX }; + float parabolicDistance { FLT_MAX }; + bool intersects { false }; + + virtual QVariantMap toVariantMap() const override { + QVariantMap toReturn; + toReturn["type"] = type; + toReturn["intersects"] = intersects; + toReturn["objectID"] = objectID; + toReturn["distance"] = distance; + toReturn["parabolicDistance"] = parabolicDistance; + toReturn["intersection"] = vec3toVariant(intersection); + toReturn["surfaceNormal"] = vec3toVariant(surfaceNormal); + toReturn["parabola"] = PickResult::toVariantMap(); + toReturn["extraInfo"] = extraInfo; + return toReturn; + } + + bool doesIntersect() const override { return intersects; } + bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return parabolicDistance < maxDistance; } + + PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override { + auto newParabolaRes = std::static_pointer_cast(newRes); + if (newParabolaRes->parabolicDistance < parabolicDistance) { + return std::make_shared(*newParabolaRes); + } else { + return std::make_shared(*this); + } + } + +}; + +class ParabolaPick : public Pick { + +public: + ParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) : + Pick(filter, maxDistance, enabled), _speed(speed), _accelerationAxis(accelerationAxis), _rotateAccelerationWithAvatar(rotateAccelerationWithAvatar), + _scaleWithAvatar(scaleWithAvatar) {} + + PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { return std::make_shared(pickVariant); } + PickResultPointer getEntityIntersection(const PickParabola& pick) override; + PickResultPointer getOverlayIntersection(const PickParabola& pick) override; + PickResultPointer getAvatarIntersection(const PickParabola& pick) override; + PickResultPointer getHUDIntersection(const PickParabola& pick) override; + +protected: + float _speed; + glm::vec3 _accelerationAxis; + bool _rotateAccelerationWithAvatar; + bool _scaleWithAvatar; + + float getSpeed() const; + glm::vec3 getAcceleration() const; +}; + +#endif // hifi_ParabolaPick_h diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp new file mode 100644 index 0000000000..9371995a2a --- /dev/null +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -0,0 +1,406 @@ +// +// Created by Sam Gondelman 7/17/2018 +// Copyright 2018 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 "ParabolaPointer.h" + +#include "Application.h" +#include "avatar/AvatarManager.h" +#include + +#include +#include "ParabolaPick.h" + +#include "render-utils/parabola_vert.h" +#include "render-utils/parabola_frag.h" + +const glm::vec4 ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR { 1.0f }; +const float ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH { 0.01f }; +const bool ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA { false }; + +gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_parabolaPipeline { nullptr }; +gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_transparentParabolaPipeline { nullptr }; + +ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, + const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd, + bool scaleWithAvatar, bool enabled) : + PathPointer(PickQuery::Parabola, rayProps, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, followNormalStrength, + centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled) +{ +} + +void ParabolaPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) { + auto renderState = std::static_pointer_cast(_renderStates[state]); + if (renderState) { + QVariantMap pathMap = pathProps.toMap(); + glm::vec3 color = glm::vec3(RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR); + float alpha = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR.a; + float width = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH; + bool isVisibleInSecondaryCamera = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA; + bool enabled = false; + if (!pathMap.isEmpty()) { + enabled = true; + if (pathMap["color"].isValid()) { + bool valid; + color = toGlm(xColorFromVariant(pathMap["color"], valid)); + } + if (pathMap["alpha"].isValid()) { + alpha = pathMap["alpha"].toFloat(); + } + if (pathMap["width"].isValid()) { + width = pathMap["width"].toFloat(); + renderState->setPathWidth(width); + } + if (pathMap["isVisibleInSecondaryCamera"].isValid()) { + isVisibleInSecondaryCamera = pathMap["isVisibleInSecondaryCamera"].toBool(); + } + } + renderState->editParabola(color, alpha, width, isVisibleInSecondaryCamera, enabled); + } +} + +glm::vec3 ParabolaPointer::getPickOrigin(const PickResultPointer& pickResult) const { + auto parabolaPickResult = std::static_pointer_cast(pickResult); + return (parabolaPickResult ? vec3FromVariant(parabolaPickResult->pickVariant["origin"]) : glm::vec3(0.0f)); +} + +glm::vec3 ParabolaPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const { + auto parabolaPickResult = std::static_pointer_cast(pickResult); + if (distance > 0.0f) { + PickParabola pick = PickParabola(parabolaPickResult->pickVariant); + return pick.origin + pick.velocity * distance + 0.5f * pick.acceleration * distance * distance; + } else { + return parabolaPickResult->intersection; + } +} + +glm::vec3 ParabolaPointer::getPickedObjectNormal(const PickResultPointer& pickResult) const { + auto parabolaPickResult = std::static_pointer_cast(pickResult); + return (parabolaPickResult ? parabolaPickResult->surfaceNormal : glm::vec3(0.0f)); +} + +IntersectionType ParabolaPointer::getPickedObjectType(const PickResultPointer& pickResult) const { + auto parabolaPickResult = std::static_pointer_cast(pickResult); + return (parabolaPickResult ? parabolaPickResult->type : IntersectionType::NONE); +} + +QUuid ParabolaPointer::getPickedObjectID(const PickResultPointer& pickResult) const { + auto parabolaPickResult = std::static_pointer_cast(pickResult); + return (parabolaPickResult ? parabolaPickResult->objectID : QUuid()); +} + +void ParabolaPointer::setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, + const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) { + auto parabolaPickResult = std::static_pointer_cast(pickResult); + if (parabolaPickResult) { + parabolaPickResult->type = type; + parabolaPickResult->objectID = id; + parabolaPickResult->intersection = intersection; + parabolaPickResult->distance = distance; + parabolaPickResult->surfaceNormal = surfaceNormal; + PickParabola parabola = PickParabola(parabolaPickResult->pickVariant); + parabolaPickResult->pickVariant["velocity"] = vec3toVariant((intersection - parabola.origin - + 0.5f * parabola.acceleration * parabolaPickResult->parabolicDistance * parabolaPickResult->parabolicDistance) / parabolaPickResult->parabolicDistance); + } +} + +ParabolaPointer::RenderState::RenderState(const OverlayID& startID, const OverlayID& endID, const glm::vec3& pathColor, float pathAlpha, float pathWidth, + bool isVisibleInSecondaryCamera, bool pathEnabled) : + StartEndRenderState(startID, endID) +{ + render::Transaction transaction; + auto scene = qApp->getMain3DScene(); + _pathID = scene->allocateID(); + _pathWidth = pathWidth; + if (render::Item::isValidID(_pathID)) { + auto renderItem = std::make_shared(pathColor, pathAlpha, pathWidth, isVisibleInSecondaryCamera, pathEnabled); + // TODO: update bounds properly + renderItem->editBound().setBox(glm::vec3(-16000.0f), 32000.0f); + transaction.resetItem(_pathID, std::make_shared(renderItem)); + scene->enqueueTransaction(transaction); + } +} + +void ParabolaPointer::RenderState::cleanup() { + StartEndRenderState::cleanup(); + if (render::Item::isValidID(_pathID)) { + render::Transaction transaction; + auto scene = qApp->getMain3DScene(); + transaction.removeItem(_pathID); + scene->enqueueTransaction(transaction); + } +} + +void ParabolaPointer::RenderState::disable() { + StartEndRenderState::disable(); + if (render::Item::isValidID(_pathID)) { + render::Transaction transaction; + auto scene = qApp->getMain3DScene(); + transaction.updateItem(_pathID, [](ParabolaRenderItem& item) { + item.setVisible(false); + }); + scene->enqueueTransaction(transaction); + } +} + +void ParabolaPointer::RenderState::editParabola(const glm::vec3& color, float alpha, float width, bool isVisibleInSecondaryCamera, bool enabled) { + if (render::Item::isValidID(_pathID)) { + render::Transaction transaction; + auto scene = qApp->getMain3DScene(); + transaction.updateItem(_pathID, [color, alpha, width, isVisibleInSecondaryCamera, enabled](ParabolaRenderItem& item) { + item.setColor(color); + item.setAlpha(alpha); + item.setWidth(width); + item.setIsVisibleInSecondaryCamera(isVisibleInSecondaryCamera); + item.setEnabled(enabled); + item.updateKey(); + }); + scene->enqueueTransaction(transaction); + } +} + +void ParabolaPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) { + StartEndRenderState::update(origin, end, surfaceNormal, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal, followNormalStrength, distance, pickResult); + auto parabolaPickResult = std::static_pointer_cast(pickResult); + if (parabolaPickResult && render::Item::isValidID(_pathID)) { + render::Transaction transaction; + auto scene = qApp->getMain3DScene(); + + PickParabola parabola = PickParabola(parabolaPickResult->pickVariant); + glm::vec3 velocity = parabola.velocity; + glm::vec3 acceleration = parabola.acceleration; + float parabolicDistance = distance > 0.0f ? distance : parabolaPickResult->parabolicDistance; + float width = scaleWithAvatar ? getPathWidth() * DependencyManager::get()->getMyAvatar()->getSensorToWorldScale() : getPathWidth(); + transaction.updateItem(_pathID, [origin, velocity, acceleration, parabolicDistance, width](ParabolaRenderItem& item) { + item.setVisible(true); + item.setOrigin(origin); + item.setVelocity(velocity); + item.setAcceleration(acceleration); + item.setParabolicDistance(parabolicDistance); + item.setWidth(width); + }); + scene->enqueueTransaction(transaction); + } +} + +std::shared_ptr ParabolaPointer::buildRenderState(const QVariantMap& propMap) { + QUuid startID; + if (propMap["start"].isValid()) { + QVariantMap startMap = propMap["start"].toMap(); + if (startMap["type"].isValid()) { + startMap.remove("visible"); + startID = qApp->getOverlays().addOverlay(startMap["type"].toString(), startMap); + } + } + + glm::vec3 color = glm::vec3(RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR); + float alpha = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR.a; + float width = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH; + bool isVisibleInSecondaryCamera = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA; + bool enabled = false; + if (propMap["path"].isValid()) { + enabled = true; + QVariantMap pathMap = propMap["path"].toMap(); + if (pathMap["color"].isValid()) { + bool valid; + color = toGlm(xColorFromVariant(pathMap["color"], valid)); + } + + if (pathMap["alpha"].isValid()) { + alpha = pathMap["alpha"].toFloat(); + } + + if (pathMap["width"].isValid()) { + width = pathMap["width"].toFloat(); + } + + if (pathMap["isVisibleInSecondaryCamera"].isValid()) { + isVisibleInSecondaryCamera = pathMap["isVisibleInSecondaryCamera"].toBool(); + } + } + + QUuid endID; + if (propMap["end"].isValid()) { + QVariantMap endMap = propMap["end"].toMap(); + if (endMap["type"].isValid()) { + endMap.remove("visible"); + endID = qApp->getOverlays().addOverlay(endMap["type"].toString(), endMap); + } + } + + return std::make_shared(startID, endID, color, alpha, width, isVisibleInSecondaryCamera, enabled); +} + +PointerEvent ParabolaPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) { + QUuid pickedID; + glm::vec3 intersection, surfaceNormal, origin, velocity, acceleration; + auto parabolaPickResult = std::static_pointer_cast(pickResult); + if (parabolaPickResult) { + intersection = parabolaPickResult->intersection; + surfaceNormal = parabolaPickResult->surfaceNormal; + const QVariantMap& parabola = parabolaPickResult->pickVariant; + origin = vec3FromVariant(parabola["origin"]); + velocity = vec3FromVariant(parabola["velocity"]); + acceleration = vec3FromVariant(parabola["acceleration"]); + pickedID = parabolaPickResult->objectID; + } + + if (pickedID != target.objectID) { + intersection = findIntersection(target, origin, velocity, acceleration); + } + glm::vec2 pos2D = findPos2D(target, intersection); + + // If we just started triggering and we haven't moved too much, don't update intersection and pos2D + TriggerState& state = hover ? _latestState : _states[button]; + float sensorToWorldScale = DependencyManager::get()->getMyAvatar()->getSensorToWorldScale(); + float deadspotSquared = TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED * sensorToWorldScale * sensorToWorldScale; + bool withinDeadspot = usecTimestampNow() - state.triggerStartTime < POINTER_MOVE_DELAY && glm::distance2(pos2D, state.triggerPos2D) < deadspotSquared; + if ((state.triggering || state.wasTriggering) && !state.deadspotExpired && withinDeadspot) { + pos2D = state.triggerPos2D; + intersection = state.intersection; + surfaceNormal = state.surfaceNormal; + } + if (!withinDeadspot) { + state.deadspotExpired = true; + } + + return PointerEvent(pos2D, intersection, surfaceNormal, velocity); +} + +glm::vec3 ParabolaPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration) { + // TODO: implement + switch (pickedObject.type) { + case ENTITY: + //return ParabolaPick::intersectParabolaWithEntityXYPlane(pickedObject.objectID, origin, velocity, acceleration); + case OVERLAY: + //return ParabolaPick::intersectParabolaWithOverlayXYPlane(pickedObject.objectID, origin, velocity, acceleration); + default: + return glm::vec3(NAN); + } +} + +ParabolaPointer::RenderState::ParabolaRenderItem::ParabolaRenderItem(const glm::vec3& color, float alpha, float width, + bool isVisibleInSecondaryCamera, bool enabled) : + _isVisibleInSecondaryCamera(isVisibleInSecondaryCamera), _enabled(enabled) +{ + _uniformBuffer->resize(sizeof(ParabolaData)); + setColor(color); + setAlpha(alpha); + setWidth(width); + updateKey(); +} + +void ParabolaPointer::RenderState::ParabolaRenderItem::setVisible(bool visible) { + if (visible && _enabled) { + _key = render::ItemKey::Builder(_key).withVisible(); + } else { + _key = render::ItemKey::Builder(_key).withInvisible(); + } + _visible = visible; +} + +void ParabolaPointer::RenderState::ParabolaRenderItem::updateKey() { + // FIXME: There's no way to designate a render item as non-shadow-reciever, and since a parabola's bounding box covers the entire domain, + // it seems to block all shadows. I think this is a bug with shadows. + //auto builder = _parabolaData.color.a < 1.0f ? render::ItemKey::Builder::transparentShape() : render::ItemKey::Builder::opaqueShape(); + auto builder = render::ItemKey::Builder::transparentShape(); + + if (_enabled && _visible) { + builder.withVisible(); + } else { + builder.withInvisible(); + } + + if (_isVisibleInSecondaryCamera) { + builder.withTagBits(render::hifi::TAG_ALL_VIEWS); + } else { + builder.withTagBits(render::hifi::TAG_MAIN_VIEW); + } + + _key = builder.build(); +} + +const gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::getParabolaPipeline() { + if (!_parabolaPipeline || !_transparentParabolaPipeline) { + auto vs = parabola_vert::getShader(); + auto ps = parabola_frag::getShader(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("parabolaData"), 0)); + gpu::Shader::makeProgram(*program, slotBindings); + + { + auto state = std::make_shared(); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setBlendFunction(false, + 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); + PrepareStencil::testMaskDrawShape(*state); + state->setCullMode(gpu::State::CULL_NONE); + _parabolaPipeline = gpu::Pipeline::create(program, state); + } + + { + auto state = std::make_shared(); + state->setDepthTest(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); + PrepareStencil::testMask(*state); + state->setCullMode(gpu::State::CULL_NONE); + _transparentParabolaPipeline = gpu::Pipeline::create(program, state); + } + } + return (_parabolaData.color.a < 1.0f ? _transparentParabolaPipeline : _parabolaPipeline); +} + +void ParabolaPointer::RenderState::ParabolaRenderItem::render(RenderArgs* args) { + if (!_visible) { + return; + } + + gpu::Batch& batch = *(args->_batch); + + Transform transform; + transform.setTranslation(_origin); + batch.setModelTransform(transform); + + batch.setPipeline(getParabolaPipeline()); + + const int MAX_SECTIONS = 100; + if (glm::length2(_parabolaData.acceleration) < EPSILON) { + _parabolaData.numSections = 1; + } else { + _parabolaData.numSections = glm::clamp((int)(_parabolaData.parabolicDistance + 1) * 10, 1, MAX_SECTIONS); + } + updateUniformBuffer(); + batch.setUniformBuffer(0, _uniformBuffer); + + // We draw 2 * n + 2 vertices for a triangle strip + batch.draw(gpu::TRIANGLE_STRIP, 2 * _parabolaData.numSections + 2, 0); +} + +namespace render { + template <> const ItemKey payloadGetKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) { + return payload->getKey(); + } + template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) { + if (payload) { + return payload->getBound(); + } + return Item::Bound(); + } + template <> void payloadRender(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload, RenderArgs* args) { + if (payload) { + payload->render(args); + } + } + template <> const ShapeKey shapeGetShapeKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) { + return ShapeKey::Builder::ownPipeline(); + } +} \ No newline at end of file diff --git a/interface/src/raypick/ParabolaPointer.h b/interface/src/raypick/ParabolaPointer.h new file mode 100644 index 0000000000..ee4977e1e4 --- /dev/null +++ b/interface/src/raypick/ParabolaPointer.h @@ -0,0 +1,125 @@ +// +// Created by Sam Gondelman 7/17/2018 +// Copyright 2018 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_ParabolaPointer_h +#define hifi_ParabolaPointer_h + +#include "PathPointer.h" + +class ParabolaPointer : public PathPointer { + using Parent = PathPointer; +public: + class RenderState : public StartEndRenderState { + public: + class ParabolaRenderItem { + public: + using Payload = render::Payload; + using Pointer = Payload::DataPointer; + + ParabolaRenderItem(const glm::vec3& color, float alpha, float width, + bool isVisibleInSecondaryCamera, bool enabled); + ~ParabolaRenderItem() {} + + static gpu::PipelinePointer _parabolaPipeline; + static gpu::PipelinePointer _transparentParabolaPipeline; + const gpu::PipelinePointer getParabolaPipeline(); + + void render(RenderArgs* args); + render::Item::Bound& editBound() { return _bound; } + const render::Item::Bound& getBound() { return _bound; } + render::ItemKey getKey() const { return _key; } + + void setVisible(bool visible); + void updateKey(); + void updateUniformBuffer() { _uniformBuffer->setSubData(0, _parabolaData); } + + void setColor(const glm::vec3& color) { _parabolaData.color = glm::vec4(color, _parabolaData.color.a); } + void setAlpha(const float& alpha) { _parabolaData.color.a = alpha; } + void setWidth(const float& width) { _parabolaData.width = width; } + void setParabolicDistance(const float& parabolicDistance) { _parabolaData.parabolicDistance = parabolicDistance; } + void setVelocity(const glm::vec3& velocity) { _parabolaData.velocity = velocity; } + void setAcceleration(const glm::vec3& acceleration) { _parabolaData.acceleration = acceleration; } + void setOrigin(const glm::vec3& origin) { _origin = origin; } + void setIsVisibleInSecondaryCamera(const bool& isVisibleInSecondaryCamera) { _isVisibleInSecondaryCamera = isVisibleInSecondaryCamera; } + void setEnabled(const bool& enabled) { _enabled = enabled; } + + static const glm::vec4 DEFAULT_PARABOLA_COLOR; + static const float DEFAULT_PARABOLA_WIDTH; + static const bool DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA; + + private: + render::Item::Bound _bound; + render::ItemKey _key; + + glm::vec3 _origin { 0.0f }; + bool _isVisibleInSecondaryCamera { DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA }; + bool _visible { false }; + bool _enabled { false }; + + struct ParabolaData { + glm::vec3 velocity { 0.0f }; + float parabolicDistance { 0.0f }; + vec3 acceleration { 0.0f }; + float width { DEFAULT_PARABOLA_WIDTH }; + vec4 color { vec4(DEFAULT_PARABOLA_COLOR)}; + int numSections { 0 }; + ivec3 spare; + }; + + ParabolaData _parabolaData; + gpu::BufferPointer _uniformBuffer { std::make_shared() }; + }; + + RenderState() {} + RenderState(const OverlayID& startID, const OverlayID& endID, const glm::vec3& pathColor, float pathAlpha, float pathWidth, + bool isVisibleInSecondaryCamera, bool pathEnabled); + + void setPathWidth(float width) { _pathWidth = width; } + float getPathWidth() const { return _pathWidth; } + + void cleanup() override; + void disable() override; + void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) override; + + void editParabola(const glm::vec3& color, float alpha, float width, bool isVisibleInSecondaryCamera, bool enabled); + + private: + int _pathID; + float _pathWidth; + }; + + ParabolaPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, + bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); + + static std::shared_ptr buildRenderState(const QVariantMap& propMap); + +protected: + void editRenderStatePath(const std::string& state, const QVariant& pathProps) override; + + glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const override; + glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance) const override; + glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) const override; + IntersectionType getPickedObjectType(const PickResultPointer& pickResult) const override; + QUuid getPickedObjectID(const PickResultPointer& pickResult) const override; + void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, + const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) override; + + PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override; + +private: + static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration); +}; + +namespace render { + template <> const ItemKey payloadGetKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload); + template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload); + template <> void payloadRender(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload, RenderArgs* args); + template <> const ShapeKey shapeGetShapeKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload); +} + +#endif // hifi_ParabolaPointer_h diff --git a/interface/src/raypick/PathPointer.cpp b/interface/src/raypick/PathPointer.cpp new file mode 100644 index 0000000000..685611d77b --- /dev/null +++ b/interface/src/raypick/PathPointer.cpp @@ -0,0 +1,353 @@ +// +// Created by Sam Gondelman 7/17/2018 +// Copyright 2018 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 "PathPointer.h" + +#include "Application.h" +#include "avatar/AvatarManager.h" + +#include +#include +#include "PickScriptingInterface.h" +#include "RayPick.h" + +PathPointer::PathPointer(PickQuery::PickType type, const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, + bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, + bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) : + Pointer(DependencyManager::get()->createPick(type, rayProps), enabled, hover), + _renderStates(renderStates), + _defaultRenderStates(defaultRenderStates), + _triggers(triggers), + _faceAvatar(faceAvatar), + _followNormal(followNormal), + _followNormalStrength(followNormalStrength), + _centerEndY(centerEndY), + _lockEnd(lockEnd), + _distanceScaleEnd(distanceScaleEnd), + _scaleWithAvatar(scaleWithAvatar) +{ + for (auto& state : _renderStates) { + if (!enabled || state.first != _currentRenderState) { + state.second->disable(); + } + } + for (auto& state : _defaultRenderStates) { + if (!enabled || state.first != _currentRenderState) { + state.second.second->disable(); + } + } +} + +PathPointer::~PathPointer() { + for (auto& renderState : _renderStates) { + renderState.second->cleanup(); + } + for (auto& renderState : _defaultRenderStates) { + renderState.second.second->cleanup(); + } +} + +void PathPointer::setRenderState(const std::string& state) { + withWriteLock([&] { + if (!_currentRenderState.empty() && state != _currentRenderState) { + if (_renderStates.find(_currentRenderState) != _renderStates.end()) { + _renderStates[_currentRenderState]->disable(); + } + if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { + _defaultRenderStates[_currentRenderState].second->disable(); + } + } + _currentRenderState = state; + }); +} + +void PathPointer::setLength(float length) { + withWriteLock([&] { + _pathLength = length; + }); +} + +void PathPointer::setLockEndUUID(const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) { + withWriteLock([&] { + _lockEndObject.id = objectID; + _lockEndObject.isOverlay = isOverlay; + _lockEndObject.offsetMat = offsetMat; + }); +} + +PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pickResult) { + PickResultPointer visualPickResult = pickResult; + glm::vec3 origin = getPickOrigin(pickResult); + IntersectionType type = getPickedObjectType(pickResult); + QUuid id; + glm::vec3 intersection; + float distance; + glm::vec3 surfaceNormal; + + if (type != IntersectionType::HUD) { + glm::vec3 endVec; + if (!_lockEndObject.id.isNull()) { + glm::vec3 pos; + glm::quat rot; + glm::vec3 dim; + glm::vec3 registrationPoint; + if (_lockEndObject.isOverlay) { + pos = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "position").value); + rot = quatFromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "rotation").value); + dim = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "dimensions").value); + registrationPoint = glm::vec3(0.5f); + } else { + EntityItemProperties props = DependencyManager::get()->getEntityProperties(_lockEndObject.id); + glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition()); + glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat; + pos = extractTranslation(finalPosAndRotMat); + rot = glmExtractRotation(finalPosAndRotMat); + dim = props.getDimensions(); + registrationPoint = props.getRegistrationPoint(); + } + const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f); + endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint)); + glm::vec3 direction = endVec - origin; + distance = glm::distance(origin, endVec); + glm::vec3 normalizedDirection = glm::normalize(direction); + + type = _lockEndObject.isOverlay ? IntersectionType::OVERLAY : IntersectionType::ENTITY; + id = _lockEndObject.id; + intersection = endVec; + surfaceNormal = -normalizedDirection; + setVisualPickResultInternal(visualPickResult, type, id, intersection, distance, surfaceNormal); + } else if (type != IntersectionType::NONE && _lockEnd) { + id = getPickedObjectID(pickResult); + if (type == IntersectionType::ENTITY) { + endVec = DependencyManager::get()->getEntityTransform(id)[3]; + } else if (type == IntersectionType::OVERLAY) { + endVec = vec3FromVariant(qApp->getOverlays().getProperty(id, "position").value); + } else if (type == IntersectionType::AVATAR) { + endVec = DependencyManager::get()->getAvatar(id)->getPosition(); + } + glm::vec3 direction = endVec - origin; + distance = glm::distance(origin, endVec); + glm::vec3 normalizedDirection = glm::normalize(direction); + intersection = endVec; + surfaceNormal = -normalizedDirection; + setVisualPickResultInternal(visualPickResult, type, id, intersection, distance, surfaceNormal); + } + } + return visualPickResult; +} + +void PathPointer::updateVisuals(const PickResultPointer& pickResult) { + IntersectionType type = getPickedObjectType(pickResult); + if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && + (type != IntersectionType::NONE || _pathLength > 0.0f)) { + glm::vec3 origin = getPickOrigin(pickResult); + glm::vec3 end = getPickEnd(pickResult, _pathLength); + glm::vec3 surfaceNormal = getPickedObjectNormal(pickResult); + _renderStates[_currentRenderState]->update(origin, end, surfaceNormal, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar, + _followNormal, _followNormalStrength, _pathLength, pickResult); + if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { + _defaultRenderStates[_currentRenderState].second->disable(); + } + } else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { + if (_renderStates.find(_currentRenderState) != _renderStates.end()) { + _renderStates[_currentRenderState]->disable(); + } + glm::vec3 origin = getPickOrigin(pickResult); + glm::vec3 end = getPickEnd(pickResult, _defaultRenderStates[_currentRenderState].first); + _defaultRenderStates[_currentRenderState].second->update(origin, end, Vectors::UP, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, + _faceAvatar, _followNormal, _followNormalStrength, _defaultRenderStates[_currentRenderState].first, pickResult); + } else if (!_currentRenderState.empty()) { + if (_renderStates.find(_currentRenderState) != _renderStates.end()) { + _renderStates[_currentRenderState]->disable(); + } + if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { + _defaultRenderStates[_currentRenderState].second->disable(); + } + } +} + +void PathPointer::editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) { + withWriteLock([&] { + updateRenderStateOverlay(_renderStates[state]->getStartID(), startProps); + updateRenderStateOverlay(_renderStates[state]->getEndID(), endProps); + QVariant startDim = startProps.toMap()["dimensions"]; + if (startDim.isValid()) { + _renderStates[state]->setStartDim(vec3FromVariant(startDim)); + } + QVariant endDim = endProps.toMap()["dimensions"]; + if (endDim.isValid()) { + _renderStates[state]->setEndDim(vec3FromVariant(endDim)); + } + QVariant rotation = endProps.toMap()["rotation"]; + if (rotation.isValid()) { + _renderStates[state]->setEndRot(quatFromVariant(rotation)); + } + + editRenderStatePath(state, pathProps); + }); +} + +void PathPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) { + if (!id.isNull() && props.isValid()) { + QVariantMap propMap = props.toMap(); + propMap.remove("visible"); + qApp->getOverlays().editOverlay(id, propMap); + } +} + +Pointer::PickedObject PathPointer::getHoveredObject(const PickResultPointer& pickResult) { + return PickedObject(getPickedObjectID(pickResult), getPickedObjectType(pickResult)); +} + +Pointer::Buttons PathPointer::getPressedButtons(const PickResultPointer& pickResult) { + std::unordered_set toReturn; + + for (const PointerTrigger& trigger : _triggers) { + std::string button = trigger.getButton(); + TriggerState& state = _states[button]; + // TODO: right now, LaserPointers don't support axes, only on/off buttons + if (trigger.getEndpoint()->peek() >= 1.0f) { + toReturn.insert(button); + + if (_previousButtons.find(button) == _previousButtons.end()) { + // start triggering for buttons that were just pressed + state.triggeredObject = PickedObject(getPickedObjectID(pickResult), getPickedObjectType(pickResult)); + state.intersection = getPickEnd(pickResult); + state.triggerPos2D = findPos2D(state.triggeredObject, state.intersection); + state.triggerStartTime = usecTimestampNow(); + state.surfaceNormal = getPickedObjectNormal(pickResult); + state.deadspotExpired = false; + state.wasTriggering = true; + state.triggering = true; + _latestState = state; + } + } else { + // stop triggering for buttons that aren't pressed + state.wasTriggering = state.triggering; + state.triggering = false; + _latestState = state; + } + } + _previousButtons = toReturn; + return toReturn; +} + +StartEndRenderState::StartEndRenderState(const OverlayID& startID, const OverlayID& endID) : + _startID(startID), _endID(endID) { + if (!_startID.isNull()) { + _startDim = vec3FromVariant(qApp->getOverlays().getProperty(_startID, "dimensions").value); + _startIgnoreRays = qApp->getOverlays().getProperty(_startID, "ignoreRayIntersection").value.toBool(); + } + if (!_endID.isNull()) { + _endDim = vec3FromVariant(qApp->getOverlays().getProperty(_endID, "dimensions").value); + _endRot = quatFromVariant(qApp->getOverlays().getProperty(_endID, "rotation").value); + _endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignoreRayIntersection").value.toBool(); + } +} + +void StartEndRenderState::cleanup() { + if (!_startID.isNull()) { + qApp->getOverlays().deleteOverlay(_startID); + } + if (!_endID.isNull()) { + qApp->getOverlays().deleteOverlay(_endID); + } +} + +void StartEndRenderState::disable() { + if (!getStartID().isNull()) { + QVariantMap startProps; + startProps.insert("visible", false); + startProps.insert("ignoreRayIntersection", true); + qApp->getOverlays().editOverlay(getStartID(), startProps); + } + if (!getEndID().isNull()) { + QVariantMap endProps; + endProps.insert("visible", false); + endProps.insert("ignoreRayIntersection", true); + qApp->getOverlays().editOverlay(getEndID(), endProps); + } +} + +void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) { + if (!getStartID().isNull()) { + QVariantMap startProps; + startProps.insert("position", vec3toVariant(origin)); + startProps.insert("visible", true); + if (scaleWithAvatar) { + startProps.insert("dimensions", vec3toVariant(getStartDim() * DependencyManager::get()->getMyAvatar()->getSensorToWorldScale())); + } + startProps.insert("ignoreRayIntersection", doesStartIgnoreRays()); + qApp->getOverlays().editOverlay(getStartID(), startProps); + } + + if (!getEndID().isNull()) { + QVariantMap endProps; + glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(getEndID(), "dimensions").value); + if (distanceScaleEnd) { + dim = getEndDim() * glm::distance(origin, end); + endProps.insert("dimensions", vec3toVariant(dim)); + } else if (scaleWithAvatar) { + dim = getEndDim() * DependencyManager::get()->getMyAvatar()->getSensorToWorldScale(); + endProps.insert("dimensions", vec3toVariant(dim)); + } + + glm::quat normalQuat = Quat().lookAtSimple(Vectors::ZERO, surfaceNormal); + normalQuat = normalQuat * glm::quat(glm::vec3(-M_PI_2, 0, 0)); + glm::vec3 avatarUp = DependencyManager::get()->getMyAvatar()->getWorldOrientation() * Vectors::UP; + glm::quat rotation = glm::rotation(Vectors::UP, avatarUp); + glm::vec3 position = end; + if (!centerEndY) { + if (followNormal) { + position = end + 0.5f * dim.y * surfaceNormal; + } else { + position = end + 0.5f * dim.y * avatarUp; + } + } + if (faceAvatar) { + glm::quat orientation = followNormal ? normalQuat : DependencyManager::get()->getMyAvatar()->getWorldOrientation(); + glm::quat lookAtWorld = Quat().lookAt(position, DependencyManager::get()->getMyAvatar()->getWorldPosition(), surfaceNormal); + glm::quat lookAtModel = glm::inverse(orientation) * lookAtWorld; + glm::quat lookAtFlatModel = Quat().cancelOutRollAndPitch(lookAtModel); + glm::quat lookAtFlatWorld = orientation * lookAtFlatModel; + rotation = lookAtFlatWorld; + } else if (followNormal) { + rotation = normalQuat; + } + if (followNormal && followNormalStrength > 0.0f && followNormalStrength < 1.0f) { + if (!_avgEndRotInitialized) { + _avgEndRot = rotation; + _avgEndRotInitialized = true; + } else { + rotation = glm::slerp(_avgEndRot, rotation, followNormalStrength); + if (!centerEndY) { + position = end + 0.5f * dim.y * (rotation * Vectors::UP); + } + _avgEndRot = rotation; + } + } + endProps.insert("position", vec3toVariant(position)); + endProps.insert("rotation", quatToVariant(rotation)); + endProps.insert("visible", true); + endProps.insert("ignoreRayIntersection", doesEndIgnoreRays()); + qApp->getOverlays().editOverlay(getEndID(), endProps); + } +} + +glm::vec2 PathPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) { + switch (pickedObject.type) { + case ENTITY: + return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin); + case OVERLAY: + return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin); + case HUD: + return DependencyManager::get()->calculatePos2DFromHUD(origin); + default: + return glm::vec2(NAN); + } +} \ No newline at end of file diff --git a/interface/src/raypick/PathPointer.h b/interface/src/raypick/PathPointer.h new file mode 100644 index 0000000000..44c1b7f82b --- /dev/null +++ b/interface/src/raypick/PathPointer.h @@ -0,0 +1,135 @@ +// +// Created by Sam Gondelman 7/17/2018 +// Copyright 2018 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_PathPointer_h +#define hifi_PathPointer_h + +#include +#include +#include + +#include "ui/overlays/Overlay.h" + +#include +#include + +struct LockEndObject { + QUuid id { QUuid() }; + bool isOverlay { false }; + glm::mat4 offsetMat { glm::mat4() }; +}; + +class StartEndRenderState { +public: + StartEndRenderState() {} + StartEndRenderState(const OverlayID& startID, const OverlayID& endID); + + const OverlayID& getStartID() const { return _startID; } + const OverlayID& getEndID() const { return _endID; } + const bool& doesStartIgnoreRays() const { return _startIgnoreRays; } + const bool& doesEndIgnoreRays() const { return _endIgnoreRays; } + + void setStartDim(const glm::vec3& startDim) { _startDim = startDim; } + const glm::vec3& getStartDim() const { return _startDim; } + + void setEndDim(const glm::vec3& endDim) { _endDim = endDim; } + const glm::vec3& getEndDim() const { return _endDim; } + + void setEndRot(const glm::quat& endRot) { _endRot = endRot; } + const glm::quat& getEndRot() const { return _endRot; } + + virtual void cleanup(); + virtual void disable(); + virtual void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, + bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult); + +protected: + OverlayID _startID; + OverlayID _endID; + bool _startIgnoreRays; + bool _endIgnoreRays; + + glm::vec3 _startDim; + glm::vec3 _endDim; + glm::quat _endRot; + + glm::quat _avgEndRot; + bool _avgEndRotInitialized { false }; +}; + +typedef std::unordered_map> RenderStateMap; +typedef std::unordered_map>> DefaultRenderStateMap; + +class PathPointer : public Pointer { + using Parent = Pointer; +public: + PathPointer(PickQuery::PickType type, const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, + bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, + bool distanceScaleEnd, bool scaleWithAvatar, bool enabled); + virtual ~PathPointer(); + + void setRenderState(const std::string& state) override; + // You cannot use editRenderState to change the type of any part of the 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) override; + + void setLength(float length) override; + void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) override; + + void updateVisuals(const PickResultPointer& prevRayPickResult) override; + +protected: + RenderStateMap _renderStates; + DefaultRenderStateMap _defaultRenderStates; + std::string _currentRenderState { "" }; + PointerTriggers _triggers; + float _pathLength { 0.0f }; + bool _faceAvatar; + bool _followNormal; + float _followNormalStrength; + bool _centerEndY; + bool _lockEnd; + bool _distanceScaleEnd; + bool _scaleWithAvatar; + LockEndObject _lockEndObject; + + struct TriggerState { + PickedObject triggeredObject; + glm::vec3 intersection { NAN }; + glm::vec3 surfaceNormal { NAN }; + glm::vec2 triggerPos2D { NAN }; + quint64 triggerStartTime { 0 }; + bool deadspotExpired { true }; + bool triggering { false }; + bool wasTriggering { false }; + }; + + Pointer::Buttons _previousButtons; + std::unordered_map _states; + TriggerState _latestState; + + bool shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; } + bool shouldTrigger(const PickResultPointer& pickResult) override { return _currentRenderState != ""; } + + void updateRenderStateOverlay(const OverlayID& id, const QVariant& props); + virtual void editRenderStatePath(const std::string& state, const QVariant& pathProps) = 0; + + PickedObject getHoveredObject(const PickResultPointer& pickResult) override; + Pointer::Buttons getPressedButtons(const PickResultPointer& pickResult) override; + + PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) override; + virtual glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const = 0; + virtual glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance = 0.0f) const = 0; + virtual glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) const = 0; + virtual IntersectionType getPickedObjectType(const PickResultPointer& pickResult) const = 0; + virtual QUuid getPickedObjectID(const PickResultPointer& pickResult) const = 0; + virtual void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id, + const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) = 0; + + static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin); +}; + +#endif // hifi_PathPointer_h diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 74459ca624..c6417d6dcf 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -17,6 +17,9 @@ #include "JointRayPick.h" #include "MouseRayPick.h" #include "StylusPick.h" +#include "StaticParabolaPick.h" +#include "JointParabolaPick.h" +#include "MouseParabolaPick.h" #include @@ -26,6 +29,8 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type, return createRayPick(properties); case PickQuery::PickType::Stylus: return createStylusPick(properties); + case PickQuery::PickType::Parabola: + return createParabolaPick(properties); default: return PickManager::INVALID_PICK_ID; } @@ -134,6 +139,101 @@ unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties return DependencyManager::get()->addPick(PickQuery::Stylus, std::make_shared(side, filter, maxDistance, enabled)); } +/**jsdoc + * A set of properties that can be passed to {@link Picks.createPick} to create a new Parabola Pick. + * @typedef {object} Picks.ParabolaPickProperties + * @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results. + * @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR. + * @property {number} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid. + * @property {string} [joint] Only for Joint or Mouse Parabola Picks. If "Mouse", it will create a Parabola Pick that follows the system mouse, in desktop or HMD. + * If "Avatar", it will create a Joint Parabola Pick that follows your avatar's head. Otherwise, it will create a Joint Parabola Pick that follows the given joint, if it + * exists on your current avatar. + * @property {Vec3} [posOffset=Vec3.ZERO] Only for Joint Parabola Picks. A local joint position offset, in meters. x = upward, y = forward, z = lateral + * @property {Vec3} [dirOffset=Vec3.UP] Only for Joint Parabola Picks. A local joint direction offset. x = upward, y = forward, z = lateral + * @property {Vec3} [position] Only for Static Parabola Picks. The world-space origin of the parabola segment. + * @property {Vec3} [direction=-Vec3.FRONT] Only for Static Parabola Picks. The world-space direction of the parabola segment. + * @property {number} [speed=1] The initial speed of the parabola, i.e. the initial speed of the projectile whose trajectory defines the parabola. + * @property {Vec3} [accelerationAxis=-Vec3.UP] The acceleration of the parabola, i.e. the acceleration of the projectile whose trajectory defines the parabola, both magnitude and direction. + * @property {boolean} [rotateAccelerationWithAvatar=true] Whether or not the acceleration axis should rotate with your avatar's local Y axis. + * @property {boolean} [scaleWithAvatar=false] If true, the velocity and acceleration of the Pick will scale linearly with your avatar. + */ +unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properties) { + QVariantMap propMap = properties.toMap(); + + bool enabled = false; + if (propMap["enabled"].isValid()) { + enabled = propMap["enabled"].toBool(); + } + + PickFilter filter = PickFilter(); + if (propMap["filter"].isValid()) { + filter = PickFilter(propMap["filter"].toUInt()); + } + + float maxDistance = 0.0f; + if (propMap["maxDistance"].isValid()) { + maxDistance = propMap["maxDistance"].toFloat(); + } + + float speed = 1.0f; + if (propMap["speed"].isValid()) { + speed = propMap["speed"].toFloat(); + } + + glm::vec3 accelerationAxis = -Vectors::UP; + if (propMap["accelerationAxis"].isValid()) { + accelerationAxis = vec3FromVariant(propMap["accelerationAxis"]); + } + + bool rotateAccelerationWithAvatar = true; + if (propMap["rotateAccelerationWithAvatar"].isValid()) { + rotateAccelerationWithAvatar = propMap["rotateAccelerationWithAvatar"].toBool(); + } + + bool scaleWithAvatar = false; + if (propMap["scaleWithAvatar"].isValid()) { + scaleWithAvatar = propMap["scaleWithAvatar"].toBool(); + } + + 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 DependencyManager::get()->addPick(PickQuery::Parabola, std::make_shared(jointName, posOffset, dirOffset, + speed, accelerationAxis, rotateAccelerationWithAvatar, + scaleWithAvatar, filter, maxDistance, enabled)); + + } else { + return DependencyManager::get()->addPick(PickQuery::Parabola, std::make_shared(speed, accelerationAxis, rotateAccelerationWithAvatar, + scaleWithAvatar, filter, maxDistance, enabled)); + } + } else if (propMap["position"].isValid()) { + glm::vec3 position = vec3FromVariant(propMap["position"]); + + glm::vec3 direction = -Vectors::FRONT; + if (propMap["direction"].isValid()) { + direction = vec3FromVariant(propMap["direction"]); + } + + return DependencyManager::get()->addPick(PickQuery::Parabola, std::make_shared(position, direction, speed, accelerationAxis, + rotateAccelerationWithAvatar, scaleWithAvatar, + filter, maxDistance, enabled)); + } + + return PickManager::INVALID_PICK_ID; +} + void PickScriptingInterface::enablePick(unsigned int uid) { DependencyManager::get()->enablePick(uid); } diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 0ee091716d..131f14d168 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -62,6 +62,7 @@ class PickScriptingInterface : public QObject, public Dependency { public: unsigned int createRayPick(const QVariant& properties); unsigned int createStylusPick(const QVariant& properties); + unsigned int createParabolaPick(const QVariant& properties); void registerMetaTypes(QScriptEngine* engine); @@ -71,7 +72,7 @@ public: * with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pick, a Mouse Ray Pick, or a Joint Ray Pick. * @function Picks.createPick * @param {PickType} type A PickType that specifies the method of picking to use - * @param {Picks.RayPickProperties|Picks.StylusPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick + * @param {Picks.RayPickProperties|Picks.StylusPickProperties|Picks.ParabolaPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick * @returns {number} The ID of the created Pick. Used for managing the Pick. 0 if invalid. */ Q_INVOKABLE unsigned int createPick(const PickQuery::PickType type, const QVariant& properties); @@ -125,6 +126,21 @@ public: * @property {StylusTip} stylusTip The StylusTip that was used. Valid even if there was no intersection. */ + /**jsdoc + * An intersection result for a Parabola Pick. + * + * @typedef {object} ParabolaPickResult + * @property {number} type The intersection type. + * @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE) + * @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections. + * @property {number} distance The distance to the intersection point from the origin of the parabola, not along the parabola. + * @property {number} parabolicDistance The distance to the intersection point from the origin of the parabola, along the parabola. + * @property {Vec3} intersection The intersection point in world-space. + * @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD. + * @property {Variant} extraInfo Additional intersection details when available for Model objects. + * @property {StylusTip} parabola The PickParabola that was used. Valid even if there was no intersection. + */ + /**jsdoc * Get the most recent pick result from this Pick. This will be updated as long as the Pick is enabled. * @function Picks.getPrevPickResult @@ -162,7 +178,7 @@ public: * Check if a Pick is associated with the left hand. * @function Picks.isLeftHand * @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}. - * @returns {boolean} True if the Pick is a Joint Ray Pick with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pick with hand == 0. + * @returns {boolean} True if the Pick is a Joint Ray or Parabola Pick with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pick with hand == 0. */ Q_INVOKABLE bool isLeftHand(unsigned int uid); @@ -170,7 +186,7 @@ public: * Check if a Pick is associated with the right hand. * @function Picks.isRightHand * @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}. - * @returns {boolean} True if the Pick is a Joint Ray Pick with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pick with hand == 1. + * @returns {boolean} True if the Pick is a Joint Ray or Parabola Pick with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pick with hand == 1. */ Q_INVOKABLE bool isRightHand(unsigned int uid); @@ -178,7 +194,7 @@ public: * Check if a Pick is associated with the system mouse. * @function Picks.isMouse * @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}. - * @returns {boolean} True if the Pick is a Mouse Ray Pick, false otherwise. + * @returns {boolean} True if the Pick is a Mouse Ray or Parabola Pick, false otherwise. */ Q_INVOKABLE bool isMouse(unsigned int uid); diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index 4e953a5cb8..5bb4293ef3 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -15,6 +15,7 @@ #include "Application.h" #include "LaserPointer.h" #include "StylusPointer.h" +#include "ParabolaPointer.h" void PointerScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) const { DependencyManager::get()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems)); @@ -37,6 +38,8 @@ unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType& return createLaserPointer(properties); case PickQuery::PickType::Stylus: return createStylus(properties); + case PickQuery::PickType::Parabola: + return createParabolaPointer(properties); default: return PointerEvent::INVALID_POINTER_ID; } @@ -84,27 +87,22 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) * @property {Overlays.OverlayProperties} [end] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). * An overlay to represent the end of the Ray Pointer, if desired. */ -/**jsdoc - * A trigger mechanism for Ray Pointers. - * - * @typedef {object} Pointers.Trigger - * @property {Controller.Standard|Controller.Actions|function} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger button. - * @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered, - * it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None". - */ /**jsdoc * A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick. * @typedef {object} Pointers.LaserPointerProperties - * @property {boolean} [faceAvatar=false] Ray Pointers only. If true, the end of the Pointer will always rotate to face the avatar. - * @property {boolean} [centerEndY=true] Ray Pointers only. If false, the end of the Pointer will be moved up by half of its height. - * @property {boolean} [lockEnd=false] Ray Pointers only. If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing. - * @property {boolean} [distanceScaleEnd=false] Ray Pointers only. If true, the dimensions of the end of the Pointer will scale linearly with distance. - * @property {boolean} [scaleWithAvatar=false] Ray Pointers only. If true, the width of the Pointer's path will scale linearly with your avatar's scale. + * @property {boolean} [faceAvatar=false] If true, the end of the Pointer will always rotate to face the avatar. + * @property {boolean} [centerEndY=true] If false, the end of the Pointer will be moved up by half of its height. + * @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing. + * @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance. + * @property {boolean} [scaleWithAvatar=false] If true, the width of the Pointer's path will scale linearly with your avatar's scale. + * @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface. + * @property {number} [followNormalStrength=0.0] The strength of the interpolation between the real normal and the visual normal if followNormal is true. 0-1. If 0 or 1, + * the normal will follow exactly. * @property {boolean} [enabled=false] - * @property {Pointers.RayPointerRenderState[]} [renderStates] Ray Pointers only. A list of different visual states to switch between. - * @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] Ray Pointers only. A list of different visual states to use if there is no intersection. + * @property {Pointers.RayPointerRenderState[]} [renderStates] A list of different visual states to switch between. + * @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] A list of different visual states to use if there is no intersection. * @property {boolean} [hover=false] If this Pointer should generate hover events. - * @property {Pointers.Trigger[]} [triggers] Ray Pointers only. A list of different triggers mechanisms that control this Pointer's click event generation. + * @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation. */ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& properties) const { QVariantMap propertyMap = properties.toMap(); @@ -134,12 +132,21 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool(); } + bool followNormal = false; + if (propertyMap["followNormal"].isValid()) { + followNormal = propertyMap["followNormal"].toBool(); + } + float followNormalStrength = 0.0f; + if (propertyMap["followNormalStrength"].isValid()) { + followNormalStrength = propertyMap["followNormalStrength"].toFloat(); + } + bool enabled = false; if (propertyMap["enabled"].isValid()) { enabled = propertyMap["enabled"].toBool(); } - LaserPointer::RenderStateMap renderStates; + RenderStateMap renderStates; if (propertyMap["renderStates"].isValid()) { QList renderStateVariants = propertyMap["renderStates"].toList(); for (const QVariant& renderStateVariant : renderStateVariants) { @@ -153,7 +160,7 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope } } - LaserPointer::DefaultRenderStateMap defaultRenderStates; + DefaultRenderStateMap defaultRenderStates; if (propertyMap["defaultRenderStates"].isValid()) { QList renderStateVariants = propertyMap["defaultRenderStates"].toList(); for (const QVariant& renderStateVariant : renderStateVariants) { @@ -162,7 +169,7 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) { std::string name = renderStateMap["name"].toString().toStdString(); float distance = renderStateMap["distance"].toFloat(); - defaultRenderStates[name] = std::pair(distance, LaserPointer::buildRenderState(renderStateMap)); + defaultRenderStates[name] = std::pair>(distance, LaserPointer::buildRenderState(renderStateMap)); } } } @@ -192,7 +199,151 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope } return DependencyManager::get()->addPointer(std::make_shared(properties, renderStates, defaultRenderStates, hover, triggers, - faceAvatar, centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled)); + faceAvatar, followNormal, followNormalStrength, centerEndY, lockEnd, + distanceScaleEnd, scaleWithAvatar, enabled)); +} + +/**jsdoc +* The rendering properties of the parabolic path +* +* @typedef {object} Pointers.ParabolaProperties +* @property {Color} color The color of the parabola. +* @property {number} alpha The alpha of the parabola. +* @property {number} width The width of the parabola, in meters. +*/ +/**jsdoc +* A set of properties used to define the visual aspect of a Parabola Pointer in the case that the Pointer is not intersecting something. Same as a {@link Pointers.ParabolaPointerRenderState}, +* but with an additional distance field. +* +* @typedef {object} Pointers.DefaultParabolaPointerRenderState +* @augments Pointers.ParabolaPointerRenderState +* @property {number} distance The distance along the parabola at which to render the end of this Parabola Pointer, if one is defined. +*/ +/**jsdoc +* A set of properties used to define the visual aspect of a Parabola Pointer in the case that the Pointer is intersecting something. +* +* @typedef {object} Pointers.ParabolaPointerRenderState +* @property {string} name The name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState} +* @property {Overlays.OverlayProperties} [start] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). +* An overlay to represent the beginning of the Parabola Pointer, if desired. +* @property {Pointers.ParabolaProperties} [path] The rendering properties of the parabolic path defined by the Parabola Pointer. +* @property {Overlays.OverlayProperties} [end] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a type field). +* An overlay to represent the end of the Parabola Pointer, if desired. +*/ +/**jsdoc +* A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick. +* @typedef {object} Pointers.LaserPointerProperties +* @property {boolean} [faceAvatar=false] If true, the end of the Pointer will always rotate to face the avatar. +* @property {boolean} [centerEndY=true] If false, the end of the Pointer will be moved up by half of its height. +* @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing. +* @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance. +* @property {boolean} [scaleWithAvatar=false] If true, the width of the Pointer's path will scale linearly with your avatar's scale. +* @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface. +* @property {number} [followNormalStrength=0.0] The strength of the interpolation between the real normal and the visual normal if followNormal is true. 0-1. If 0 or 1, +* the normal will follow exactly. +* @property {boolean} [enabled=false] +* @property {Pointers.ParabolaPointerRenderState[]} [renderStates] A list of different visual states to switch between. +* @property {Pointers.DefaultParabolaPointerRenderState[]} [defaultRenderStates] A list of different visual states to use if there is no intersection. +* @property {boolean} [hover=false] If this Pointer should generate hover events. +* @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation. +*/ +unsigned int PointerScriptingInterface::createParabolaPointer(const QVariant& properties) const { + 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 distanceScaleEnd = false; + if (propertyMap["distanceScaleEnd"].isValid()) { + distanceScaleEnd = propertyMap["distanceScaleEnd"].toBool(); + } + + bool scaleWithAvatar = false; + if (propertyMap["scaleWithAvatar"].isValid()) { + scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool(); + } + + bool followNormal = false; + if (propertyMap["followNormal"].isValid()) { + followNormal = propertyMap["followNormal"].toBool(); + } + float followNormalStrength = 0.0f; + if (propertyMap["followNormalStrength"].isValid()) { + followNormalStrength = propertyMap["followNormalStrength"].toFloat(); + } + + bool enabled = false; + if (propertyMap["enabled"].isValid()) { + enabled = propertyMap["enabled"].toBool(); + } + + RenderStateMap renderStates; + if (propertyMap["renderStates"].isValid()) { + QList renderStateVariants = propertyMap["renderStates"].toList(); + for (const QVariant& renderStateVariant : renderStateVariants) { + if (renderStateVariant.isValid()) { + QVariantMap renderStateMap = renderStateVariant.toMap(); + if (renderStateMap["name"].isValid()) { + std::string name = renderStateMap["name"].toString().toStdString(); + renderStates[name] = ParabolaPointer::buildRenderState(renderStateMap); + } + } + } + } + + DefaultRenderStateMap defaultRenderStates; + if (propertyMap["defaultRenderStates"].isValid()) { + QList renderStateVariants = propertyMap["defaultRenderStates"].toList(); + for (const 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, ParabolaPointer::buildRenderState(renderStateMap)); + } + } + } + } + + bool hover = false; + if (propertyMap["hover"].isValid()) { + hover = propertyMap["hover"].toBool(); + } + + PointerTriggers triggers; + auto userInputMapper = DependencyManager::get(); + if (propertyMap["triggers"].isValid()) { + QList triggerVariants = propertyMap["triggers"].toList(); + for (const QVariant& triggerVariant : triggerVariants) { + if (triggerVariant.isValid()) { + QVariantMap triggerMap = triggerVariant.toMap(); + if (triggerMap["action"].isValid() && triggerMap["button"].isValid()) { + controller::Endpoint::Pointer endpoint = userInputMapper->endpointFor(controller::Input(triggerMap["action"].toUInt())); + if (endpoint) { + std::string button = triggerMap["button"].toString().toStdString(); + triggers.emplace_back(endpoint, button); + } + } + } + } + } + + return DependencyManager::get()->addPointer(std::make_shared(properties, renderStates, defaultRenderStates, hover, triggers, + faceAvatar, followNormal, followNormalStrength, centerEndY, lockEnd, distanceScaleEnd, + scaleWithAvatar, enabled)); } void PointerScriptingInterface::editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const { diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 628af84790..9fe05182c7 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -31,14 +31,23 @@ class PointerScriptingInterface : public QObject, public Dependency { public: unsigned int createLaserPointer(const QVariant& properties) const; unsigned int createStylus(const QVariant& properties) const; + unsigned int createParabolaPointer(const QVariant& properties) const; + /**jsdoc + * A trigger mechanism for Ray and Parabola Pointers. + * + * @typedef {object} Pointers.Trigger + * @property {Controller.Standard|Controller.Actions|function} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger button. + * @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered, + * it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None". + */ /**jsdoc * Adds a new Pointer * Different {@link PickType}s use different properties, and within one PickType, the properties you choose can lead to a wide range of behaviors. For example, * with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pointer, a Mouse Ray Pointer, or a Joint Ray Pointer. * @function Pointers.createPointer * @param {PickType} type A PickType that specifies the method of picking to use - * @param {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer and the {@link Picks.PickProperties} for the Pick that + * @param {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties|Pointers.ParabolaPointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer and the {@link Picks.PickProperties} for the Pick that * this Pointer will use to do its picking. * @returns {number} The ID of the created Pointer. Used for managing the Pointer. 0 if invalid. * diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index 75b5e77fd8..96b41dcc72 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -39,7 +39,7 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) { PickResultPointer RayPick::getAvatarIntersection(const PickRay& pick) { RayToAvatarIntersectionResult avatarRes = DependencyManager::get()->findRayIntersectionVector(pick, getIncludeItemsAs(), getIgnoreItemsAs()); if (avatarRes.intersects) { - return std::make_shared(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, glm::vec3(NAN), avatarRes.extraInfo); + return std::make_shared(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, avatarRes.surfaceNormal, avatarRes.extraInfo); } else { return std::make_shared(pick.toVariantMap()); } diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h index 6bdc2cb5b0..9410d39c1a 100644 --- a/interface/src/raypick/RayPick.h +++ b/interface/src/raypick/RayPick.h @@ -19,7 +19,7 @@ public: RayPickResult() {} RayPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {} RayPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN), const QVariantMap& extraInfo = QVariantMap()) : - PickResult(searchRay.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal), extraInfo(extraInfo) { + PickResult(searchRay.toVariantMap()), extraInfo(extraInfo), objectID(objectID), intersection(intersection), surfaceNormal(surfaceNormal), type(type), distance(distance), intersects(type != NONE) { } RayPickResult(const RayPickResult& rayPickResult) : PickResult(rayPickResult.pickVariant) { @@ -32,13 +32,13 @@ public: extraInfo = rayPickResult.extraInfo; } - IntersectionType type { NONE }; - bool intersects { false }; + QVariantMap extraInfo; QUuid objectID; - float distance { FLT_MAX }; glm::vec3 intersection { NAN }; glm::vec3 surfaceNormal { NAN }; - QVariantMap extraInfo; + IntersectionType type { NONE }; + float distance { FLT_MAX }; + bool intersects { false }; virtual QVariantMap toVariantMap() const override { QVariantMap toReturn; diff --git a/interface/src/raypick/StaticParabolaPick.cpp b/interface/src/raypick/StaticParabolaPick.cpp new file mode 100644 index 0000000000..a4e3ccb97f --- /dev/null +++ b/interface/src/raypick/StaticParabolaPick.cpp @@ -0,0 +1,19 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 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 "StaticParabolaPick.h" + +StaticParabolaPick::StaticParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis, + bool scaleWithAvatar, bool rotateAccelerationWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) : + ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, scaleWithAvatar, filter, maxDistance, enabled), + _position(position), _velocity(direction) +{ +} + +PickParabola StaticParabolaPick::getMathematicalPick() const { + return PickParabola(_position, getSpeed() * _velocity, getAcceleration()); +} \ No newline at end of file diff --git a/interface/src/raypick/StaticParabolaPick.h b/interface/src/raypick/StaticParabolaPick.h new file mode 100644 index 0000000000..df2057a6f0 --- /dev/null +++ b/interface/src/raypick/StaticParabolaPick.h @@ -0,0 +1,26 @@ +// +// Created by Sam Gondelman 7/2/2018 +// Copyright 2018 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_StaticParabolaPick_h +#define hifi_StaticParabolaPick_h + +#include "ParabolaPick.h" + +class StaticParabolaPick : public ParabolaPick { + +public: + StaticParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, + bool scaleWithAvatar, const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false); + + PickParabola getMathematicalPick() const override; + +private: + glm::vec3 _position; + glm::vec3 _velocity; +}; + +#endif // hifi_StaticParabolaPick_h diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index b27cc344c3..bcb7a6c10f 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -56,6 +56,7 @@ Audio::Audio() : _devices(_contextIsHMD) { connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume); connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged); enableNoiseReduction(enableNoiseReductionSetting.get()); + onContextChanged(); } bool Audio::startRecording(const QString& filepath) { diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp index 34f196ac70..bda06cda48 100644 --- a/interface/src/scripting/DesktopScriptingInterface.cpp +++ b/interface/src/scripting/DesktopScriptingInterface.cpp @@ -14,6 +14,8 @@ #include #include +#include + #include "Application.h" #include "MainWindow.h" #include @@ -29,6 +31,14 @@ int DesktopScriptingInterface::getHeight() { return size.height(); } +QVariantMap DesktopScriptingInterface::getPresentationMode() { + static QVariantMap presentationModes { + { "VIRTUAL", Virtual }, + { "NATIVE", Native } + }; + return presentationModes; +} + void DesktopScriptingInterface::setHUDAlpha(float alpha) { qApp->getApplicationCompositor().setAlpha(alpha); } @@ -41,3 +51,14 @@ void DesktopScriptingInterface::show(const QString& path, const QString& title) DependencyManager::get()->show(path, title); } +InteractiveWindowPointer DesktopScriptingInterface::createWindow(const QString& sourceUrl, const QVariantMap& properties) { + if (QThread::currentThread() != thread()) { + InteractiveWindowPointer interactiveWindow = nullptr; + BLOCKING_INVOKE_METHOD(this, "createWindow", + Q_RETURN_ARG(InteractiveWindowPointer, interactiveWindow), + Q_ARG(QString, sourceUrl), + Q_ARG(QVariantMap, properties)); + return interactiveWindow; + } + return new InteractiveWindow(sourceUrl, properties);; +} diff --git a/interface/src/scripting/DesktopScriptingInterface.h b/interface/src/scripting/DesktopScriptingInterface.h index e62a3584d6..db42b5ca54 100644 --- a/interface/src/scripting/DesktopScriptingInterface.h +++ b/interface/src/scripting/DesktopScriptingInterface.h @@ -13,20 +13,48 @@ #define hifi_DesktopScriptingInterface_h #include +#include #include +#include "InteractiveWindow.h" + +/**jsdoc + * @namespace Desktop + * + * @hifi-interface + * @hifi-client-entity + * + * @property {number} width + * @property {number} height + * @property {number} ALWAYS_ON_TOP - InteractiveWindow flag for always showing a window on top + * @property {number} CLOSE_BUTTON_HIDES - InteractiveWindow flag for hiding the window instead of closing on window close by user + */ class DesktopScriptingInterface : public QObject, public Dependency { Q_OBJECT Q_PROPERTY(int width READ getWidth) // Physical width of screen(s) including task bars and system menus Q_PROPERTY(int height READ getHeight) // Physical height of screen(s) including task bars and system menus + Q_PROPERTY(QVariantMap PresentationMode READ getPresentationMode CONSTANT FINAL) + Q_PROPERTY(int ALWAYS_ON_TOP READ flagAlwaysOnTop CONSTANT FINAL) + Q_PROPERTY(int CLOSE_BUTTON_HIDES READ flagCloseButtonHides CONSTANT FINAL) + public: Q_INVOKABLE void setHUDAlpha(float alpha); Q_INVOKABLE void show(const QString& path, const QString& title); + Q_INVOKABLE InteractiveWindowPointer createWindow(const QString& sourceUrl, const QVariantMap& properties = QVariantMap()); + int getWidth(); int getHeight(); + + +private: + static int flagAlwaysOnTop() { return AlwaysOnTop; } + static int flagCloseButtonHides() { return CloseButtonHides; } + + Q_INVOKABLE static QVariantMap getPresentationMode(); }; + #endif // hifi_DesktopScriptingInterface_h diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index 31b8f74e9e..ad8e265a01 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -35,6 +35,12 @@ glm::vec3 HMDScriptingInterface::calculateRayUICollisionPoint(const glm::vec3& p return result; } +glm::vec3 HMDScriptingInterface::calculateParabolaUICollisionPoint(const glm::vec3& position, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance) const { + glm::vec3 result; + qApp->getApplicationCompositor().calculateParabolaUICollisionPoint(position, velocity, acceleration, result, parabolicDistance); + return result; +} + glm::vec2 HMDScriptingInterface::overlayFromWorldPoint(const glm::vec3& position) const { return qApp->getApplicationCompositor().overlayFromSphereSurface(position); } diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index d2a272851f..3d7cc5afb2 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -56,7 +56,8 @@ class QScriptEngine; * @property {Uuid} tabletID - The UUID of the tablet body model overlay. * @property {Uuid} tabletScreenID - The UUID of the tablet's screen overlay. * @property {Uuid} homeButtonID - The UUID of the tablet's "home" button overlay. - * @property {Uuid} homeButtonHighlightID - The UUID of the tablet's "home" button highlight overlay. + * @property {Uuid} homeButtonHighlightMaterialID - The UUID of the material entity used to highlight tablet button + * @property {Uuid} homeButtonUnhighlightMaterialID - The UUID of the material entity use to unhighlight the entity */ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Dependency { Q_OBJECT @@ -67,8 +68,9 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen Q_PROPERTY(bool tabletContextualMode READ getTabletContextualMode) Q_PROPERTY(QUuid tabletID READ getCurrentTabletFrameID WRITE setCurrentTabletFrameID) Q_PROPERTY(QUuid homeButtonID READ getCurrentHomeButtonID WRITE setCurrentHomeButtonID) - Q_PROPERTY(QUuid homeButtonHighlightID READ getCurrentHomeButtonHightlightID WRITE setCurrentHomeButtonHightlightID) Q_PROPERTY(QUuid tabletScreenID READ getCurrentTabletScreenID WRITE setCurrentTabletScreenID) + Q_PROPERTY(QUuid homeButtonHighlightMaterialID READ getCurrentHomeButtonHighlightMaterialID WRITE setCurrentHomeButtonHighlightMaterialID) + Q_PROPERTY(QUuid homeButtonUnhighlightMaterialID READ getCurrentHomeButtonUnhighlightMaterialID WRITE setCurrentHomeButtonUnhighlightMaterialID) public: @@ -98,6 +100,8 @@ public: */ Q_INVOKABLE glm::vec3 calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const; + glm::vec3 calculateParabolaUICollisionPoint(const glm::vec3& position, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance) const; + /**jsdoc * Get the 2D HUD overlay coordinates of a 3D point on the HUD overlay. * 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay. @@ -372,20 +376,24 @@ public: void setCurrentHomeButtonID(QUuid homeButtonID) { _homeButtonID = homeButtonID; } QUuid getCurrentHomeButtonID() const { return _homeButtonID; } - void setCurrentHomeButtonHightlightID(QUuid homeButtonHightlightID) { _homeButtonHightlightID = homeButtonHightlightID; } - QUuid getCurrentHomeButtonHightlightID() const { return _homeButtonHightlightID; } - void setCurrentTabletScreenID(QUuid tabletID) { _tabletScreenID = tabletID; } QUuid getCurrentTabletScreenID() const { return _tabletScreenID; } + void setCurrentHomeButtonHighlightMaterialID(QUuid homeButtonHighlightMaterialID) { _homeButtonHighlightMaterialID = homeButtonHighlightMaterialID; } + QUuid getCurrentHomeButtonHighlightMaterialID() { return _homeButtonHighlightMaterialID; } + + void setCurrentHomeButtonUnhighlightMaterialID(QUuid homeButtonUnhighlightMaterialID) { _homeButtonUnhighlightMaterialID = homeButtonUnhighlightMaterialID; } + QUuid getCurrentHomeButtonUnhighlightMaterialID() { return _homeButtonUnhighlightMaterialID; } + private: bool _showTablet { false }; bool _tabletContextualMode { false }; QUuid _tabletUIID; // this is the entityID of the tablet frame QUuid _tabletScreenID; // this is the overlayID which is part of (a child of) the tablet-ui. QUuid _homeButtonID; - QUuid _homeButtonHightlightID; QUuid _tabletEntityID; + QUuid _homeButtonHighlightMaterialID; + QUuid _homeButtonUnhighlightMaterialID; // Get the position of the HMD glm::vec3 getPosition() const; diff --git a/interface/src/scripting/LimitlessConnection.cpp b/interface/src/scripting/LimitlessConnection.cpp deleted file mode 100644 index f504136489..0000000000 --- a/interface/src/scripting/LimitlessConnection.cpp +++ /dev/null @@ -1,93 +0,0 @@ -#include "LimitlessConnection.h" - -#include -#include -#include -#include -#include -#include "LimitlessVoiceRecognitionScriptingInterface.h" - -LimitlessConnection::LimitlessConnection() : - _streamingAudioForTranscription(false) -{ -} - -void LimitlessConnection::startListening(QString authCode) { - _transcribeServerSocket.reset(new QTcpSocket(this)); - connect(_transcribeServerSocket.get(), &QTcpSocket::readyRead, this, - &LimitlessConnection::transcriptionReceived); - connect(_transcribeServerSocket.get(), &QTcpSocket::disconnected, this, [this](){stopListening();}); - - static const auto host = "gserv_devel.studiolimitless.com"; - _transcribeServerSocket->connectToHost(host, 1407); - _transcribeServerSocket->waitForConnected(); - QString requestHeader = QString::asprintf("Authorization: %s\r\nfs: %i\r\n", - authCode.toLocal8Bit().data(), AudioConstants::SAMPLE_RATE); - qCDebug(interfaceapp) << "Sending Limitless Audio Stream Request: " << requestHeader; - _transcribeServerSocket->write(requestHeader.toLocal8Bit()); - _transcribeServerSocket->waitForBytesWritten(); -} - -void LimitlessConnection::stopListening() { - emit onFinishedSpeaking(_currentTranscription); - _streamingAudioForTranscription = false; - _currentTranscription = ""; - if (!isConnected()) - return; - _transcribeServerSocket->close(); - disconnect(_transcribeServerSocket.get(), &QTcpSocket::readyRead, this, - &LimitlessConnection::transcriptionReceived); - _transcribeServerSocket.release()->deleteLater(); - disconnect(DependencyManager::get().data(), &AudioClient::inputReceived, this, - &LimitlessConnection::audioInputReceived); - qCDebug(interfaceapp) << "Connection to Limitless Voice Server closed."; -} - -void LimitlessConnection::audioInputReceived(const QByteArray& inputSamples) { - if (isConnected()) { - _transcribeServerSocket->write(inputSamples.data(), inputSamples.size()); - _transcribeServerSocket->waitForBytesWritten(); - } -} - -void LimitlessConnection::transcriptionReceived() { - while (_transcribeServerSocket && _transcribeServerSocket->bytesAvailable() > 0) { - const QByteArray data = _transcribeServerSocket->readAll(); - _serverDataBuffer.append(data); - int begin = _serverDataBuffer.indexOf('<'); - int end = _serverDataBuffer.indexOf('>'); - while (begin > -1 && end > -1) { - const int len = end - begin; - const QByteArray serverMessage = _serverDataBuffer.mid(begin+1, len-1); - if (serverMessage.contains("1407")) { - qCDebug(interfaceapp) << "Limitless Speech Server denied the request."; - // Don't spam the server with further false requests please. - DependencyManager::get()->setListeningToVoice(true); - stopListening(); - return; - } else if (serverMessage.contains("1408")) { - qCDebug(interfaceapp) << "Limitless Audio request authenticated!"; - _serverDataBuffer.clear(); - connect(DependencyManager::get().data(), &AudioClient::inputReceived, this, - &LimitlessConnection::audioInputReceived); - return; - } - QJsonObject json = QJsonDocument::fromJson(serverMessage.data()).object(); - _serverDataBuffer.remove(begin, len+1); - _currentTranscription = json["alternatives"].toArray()[0].toObject()["transcript"].toString(); - emit onReceivedTranscription(_currentTranscription); - if (json["isFinal"] == true) { - qCDebug(interfaceapp) << "Final transcription: " << _currentTranscription; - stopListening(); - return; - } - begin = _serverDataBuffer.indexOf('<'); - end = _serverDataBuffer.indexOf('>'); - } - } -} - -bool LimitlessConnection::isConnected() const { - return _transcribeServerSocket.get() && _transcribeServerSocket->isWritable() - && _transcribeServerSocket->state() != QAbstractSocket::SocketState::UnconnectedState; -} diff --git a/interface/src/scripting/LimitlessConnection.h b/interface/src/scripting/LimitlessConnection.h deleted file mode 100644 index 9ed39bd653..0000000000 --- a/interface/src/scripting/LimitlessConnection.h +++ /dev/null @@ -1,46 +0,0 @@ -// -// SpeechRecognitionScriptingInterface.h -// interface/src/scripting -// -// Created by Trevor Berninger on 3/24/17. -// Copyright 2017 Limitless ltd. -// -// 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_LimitlessConnection_h -#define hifi_LimitlessConnection_h - -#include -#include -#include - -#include - -class LimitlessConnection : public QObject { - Q_OBJECT -public: - LimitlessConnection(); - - Q_INVOKABLE void startListening(QString authCode); - Q_INVOKABLE void stopListening(); - - std::atomic _streamingAudioForTranscription; - -signals: - void onReceivedTranscription(QString speech); - void onFinishedSpeaking(QString speech); - -private: - void transcriptionReceived(); - void audioInputReceived(const QByteArray& inputSamples); - - bool isConnected() const; - - std::unique_ptr _transcribeServerSocket; - QByteArray _serverDataBuffer; - QString _currentTranscription; -}; - -#endif //hifi_LimitlessConnection_h diff --git a/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.cpp b/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.cpp deleted file mode 100644 index 2692608106..0000000000 --- a/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.cpp +++ /dev/null @@ -1,66 +0,0 @@ -// -// SpeechRecognitionScriptingInterface.h -// interface/src/scripting -// -// Created by Trevor Berninger on 3/20/17. -// Copyright 2017 Limitless ltd. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "LimitlessVoiceRecognitionScriptingInterface.h" - -#include - -#include - -#include "InterfaceLogging.h" -#include "ui/AvatarInputs.h" - -const float LimitlessVoiceRecognitionScriptingInterface::_audioLevelThreshold = 0.33f; -const int LimitlessVoiceRecognitionScriptingInterface::_voiceTimeoutDuration = 2000; - -LimitlessVoiceRecognitionScriptingInterface::LimitlessVoiceRecognitionScriptingInterface() : - _shouldStartListeningForVoice(false) -{ - _voiceTimer.setSingleShot(true); - connect(&_voiceTimer, &QTimer::timeout, this, &LimitlessVoiceRecognitionScriptingInterface::voiceTimeout); - connect(&_connection, &LimitlessConnection::onReceivedTranscription, this, [this](QString transcription){emit onReceivedTranscription(transcription);}); - connect(&_connection, &LimitlessConnection::onFinishedSpeaking, this, [this](QString transcription){emit onFinishedSpeaking(transcription);}); - moveToNewNamedThread(&_connection, "Limitless Connection"); -} - -void LimitlessVoiceRecognitionScriptingInterface::update() { - const float audioLevel = AvatarInputs::getInstance()->loudnessToAudioLevel(DependencyManager::get()->getAudioAverageInputLoudness()); - - if (_shouldStartListeningForVoice) { - if (_connection._streamingAudioForTranscription) { - if (audioLevel > _audioLevelThreshold) { - if (_voiceTimer.isActive()) { - _voiceTimer.stop(); - } - } else if (!_voiceTimer.isActive()){ - _voiceTimer.start(_voiceTimeoutDuration); - } - } else if (audioLevel > _audioLevelThreshold) { - // to make sure invoke doesn't get called twice before the method actually gets called - _connection._streamingAudioForTranscription = true; - QMetaObject::invokeMethod(&_connection, "startListening", Q_ARG(QString, authCode)); - } - } -} - -void LimitlessVoiceRecognitionScriptingInterface::setListeningToVoice(bool listening) { - _shouldStartListeningForVoice = listening; -} - -void LimitlessVoiceRecognitionScriptingInterface::setAuthKey(QString key) { - authCode = key; -} - -void LimitlessVoiceRecognitionScriptingInterface::voiceTimeout() { - if (_connection._streamingAudioForTranscription) { - QMetaObject::invokeMethod(&_connection, "stopListening"); - } -} diff --git a/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.h b/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.h deleted file mode 100644 index 2a35c37ab0..0000000000 --- a/interface/src/scripting/LimitlessVoiceRecognitionScriptingInterface.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// SpeechRecognitionScriptingInterface.h -// interface/src/scripting -// -// Created by Trevor Berninger on 3/20/17. -// Copyright 2017 Limitless ltd. -// -// 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_SpeechRecognitionScriptingInterface_h -#define hifi_SpeechRecognitionScriptingInterface_h - -#include -#include -#include -#include "LimitlessConnection.h" - -class LimitlessVoiceRecognitionScriptingInterface : public QObject, public Dependency { - Q_OBJECT -public: - LimitlessVoiceRecognitionScriptingInterface(); - - void update(); - - QString authCode; - -public slots: - void setListeningToVoice(bool listening); - void setAuthKey(QString key); - -signals: - void onReceivedTranscription(QString speech); - void onFinishedSpeaking(QString speech); - -private: - - bool _shouldStartListeningForVoice; - static const float _audioLevelThreshold; - static const int _voiceTimeoutDuration; - - QTimer _voiceTimer; - LimitlessConnection _connection; - - void voiceTimeout(); -}; - -#endif //hifi_SpeechRecognitionScriptingInterface_h diff --git a/interface/src/scripting/SelectionScriptingInterface.h b/interface/src/scripting/SelectionScriptingInterface.h index 8b5c12e3dc..83aec63ee2 100644 --- a/interface/src/scripting/SelectionScriptingInterface.h +++ b/interface/src/scripting/SelectionScriptingInterface.h @@ -182,7 +182,7 @@ public: /**jsdoc * Get the list of avatars, entities, and overlays stored in a selection list. - * @function Selection.getList + * @function Selection.getSelectedItemsList * @param {string} listName - The name of the selection list. * @returns {Selection.SelectedItemsList} The content of a selection list. If the list name doesn't exist, the function * returns an empty object with no properties. @@ -257,7 +257,7 @@ public: void onSelectedItemsListChanged(const QString& listName); signals: - /**jsoc + /**jsdoc * Triggered when a list's content changes. * @function Selection.selectedItemsListChanged * @param {string} listName - The name of the selection list that changed. diff --git a/interface/src/scripting/SettingsScriptingInterface.cpp b/interface/src/scripting/SettingsScriptingInterface.cpp index 2f14c33dc7..afafe1a350 100644 --- a/interface/src/scripting/SettingsScriptingInterface.cpp +++ b/interface/src/scripting/SettingsScriptingInterface.cpp @@ -35,5 +35,8 @@ QVariant SettingsScriptingInterface::getValue(const QString& setting, const QVar } void SettingsScriptingInterface::setValue(const QString& setting, const QVariant& value) { - Setting::Handle(setting).set(value); + // Make a deep-copy of the string. + // Dangling pointers can occur with QStrings that are implicitly shared from a QScriptEngine. + QString deepCopy = QString::fromUtf16(setting.utf16()); + Setting::Handle(deepCopy).set(value); } diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 0aea7a02c5..ba86925581 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -86,10 +86,6 @@ void WindowScriptingInterface::raise() { }); } -void WindowScriptingInterface::raiseMainWindow() { - raise(); -} - /// Display an alert box /// \param const QString& message message to display /// \return QScriptValue::UndefinedValue @@ -137,8 +133,13 @@ void WindowScriptingInterface::openUrl(const QUrl& url) { if (url.scheme() == URL_SCHEME_HIFI) { DependencyManager::get()->handleLookupString(url.toString()); } else { +#if defined(Q_OS_ANDROID) + QList args = { url.toString() }; + AndroidHelper::instance().requestActivity("WebView", true, args); +#else // address manager did not handle - ask QDesktopServices to handle QDesktopServices::openUrl(url); +#endif } } } diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 29eb6421e1..77895e0e76 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -80,13 +80,6 @@ public slots: */ void raise(); - /**jsdoc - * Raise the Interface window if it is minimized. If raised, the window gains focus. - * @function Window.raiseMainWindow - * @deprecated Use {@link Window.raise|raise} instead. - */ - void raiseMainWindow(); - /**jsdoc * Display a dialog with the specified message and an "OK" button. The dialog is non-modal; the script continues without * waiting for a user response. @@ -319,6 +312,15 @@ public slots: * {@link Window.processingGifStarted|processingGifStarted} and {@link Window.processingGifCompleted|processingGifCompleted} * are emitted. The path to store the snapshots and the length of the animated GIF to capture are specified in Settings > * General > Snapshots. + * + * If user has supplied a specific filename for the snapshot: + * If the user's requested filename has a suffix that's contained within SUPPORTED_IMAGE_FORMATS, + * DON'T append ".jpg" to the filename. QT will save the image in the format associated with the + * filename's suffix. + * If you want lossless Snapshots, supply a `.png` filename. Otherwise, use `.jpeg` or `.jpg`. + * Otherwise, ".jpg" is appended to the user's requested filename so that the image is saved in JPG format. + * If the user hasn't supplied a specific filename for the snapshot: + * Save the snapshot in JPG format according to FILENAME_PATH_FORMAT * @function Window.takeSnapshot * @param {boolean} [notify=true] - This value is passed on through the {@link Window.stillSnapshotTaken|stillSnapshotTaken} * signal. diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index ea660fb0e2..108f20b2dd 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -179,7 +179,7 @@ static const auto DEPTH_FORMAT = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPT void ApplicationOverlay::buildFramebufferObject() { PROFILE_RANGE(app, __FUNCTION__); - auto uiSize = glm::uvec2(glm::vec2(qApp->getUiSize()) * qApp->getRenderResolutionScale()); + auto uiSize = glm::uvec2(qApp->getUiSize()); if (!_overlayFramebuffer || uiSize != _overlayFramebuffer->getSize()) { _overlayFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("ApplicationOverlay")); } diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 39a5849d85..9804c6712a 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -113,9 +113,8 @@ void LoginDialog::linkSteam() { } JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; + callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "linkCompleted"; - callbackParams.errorCallbackReceiver = this; callbackParams.errorCallbackMethod = "linkFailed"; const QString LINK_STEAM_PATH = "api/v1/user/steam/link"; @@ -141,9 +140,8 @@ void LoginDialog::createAccountFromStream(QString username) { } JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; + callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "createCompleted"; - callbackParams.errorCallbackReceiver = this; callbackParams.errorCallbackMethod = "createFailed"; const QString CREATE_ACCOUNT_FROM_STEAM_PATH = "api/v1/user/steam/create"; @@ -174,39 +172,40 @@ void LoginDialog::openUrl(const QString& url) const { offscreenUi->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) { newObject->setProperty("url", url); }); + LoginDialog::hide(); } else { if (!hmd->getShouldShowTablet() && !qApp->isHMDMode()) { offscreenUi->load("Browser.qml", [=](QQmlContext* context, QObject* newObject) { newObject->setProperty("url", url); }); + LoginDialog::hide(); } else { tablet->gotoWebScreen(url); } } } -void LoginDialog::linkCompleted(QNetworkReply& reply) { +void LoginDialog::linkCompleted(QNetworkReply* reply) { emit handleLinkCompleted(); } -void LoginDialog::linkFailed(QNetworkReply& reply) { - emit handleLinkFailed(reply.errorString()); +void LoginDialog::linkFailed(QNetworkReply* reply) { + emit handleLinkFailed(reply->errorString()); } -void LoginDialog::createCompleted(QNetworkReply& reply) { +void LoginDialog::createCompleted(QNetworkReply* reply) { emit handleCreateCompleted(); } -void LoginDialog::createFailed(QNetworkReply& reply) { - emit handleCreateFailed(reply.errorString()); +void LoginDialog::createFailed(QNetworkReply* reply) { + emit handleCreateFailed(reply->errorString()); } void LoginDialog::signup(const QString& email, const QString& username, const QString& password) { JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; + callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "signupCompleted"; - callbackParams.errorCallbackReceiver = this; callbackParams.errorCallbackMethod = "signupFailed"; QJsonObject payload; @@ -228,7 +227,7 @@ void LoginDialog::signup(const QString& email, const QString& username, const QS QJsonDocument(payload).toJson()); } -void LoginDialog::signupCompleted(QNetworkReply& reply) { +void LoginDialog::signupCompleted(QNetworkReply* reply) { emit handleSignupCompleted(); } @@ -242,10 +241,10 @@ QString errorStringFromAPIObject(const QJsonValue& apiObject) { } } -void LoginDialog::signupFailed(QNetworkReply& reply) { +void LoginDialog::signupFailed(QNetworkReply* reply) { // parse the returned JSON to see what the problem was - auto jsonResponse = QJsonDocument::fromJson(reply.readAll()); + auto jsonResponse = QJsonDocument::fromJson(reply->readAll()); static const QString RESPONSE_DATA_KEY = "data"; diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h index 5ebf866fbd..ad8cab9699 100644 --- a/interface/src/ui/LoginDialog.h +++ b/interface/src/ui/LoginDialog.h @@ -42,14 +42,14 @@ signals: void handleSignupFailed(QString errorString); public slots: - void linkCompleted(QNetworkReply& reply); - void linkFailed(QNetworkReply& reply); + void linkCompleted(QNetworkReply* reply); + void linkFailed(QNetworkReply* reply); - void createCompleted(QNetworkReply& reply); - void createFailed(QNetworkReply& reply); + void createCompleted(QNetworkReply* reply); + void createFailed(QNetworkReply* reply); - void signupCompleted(QNetworkReply& reply); - void signupFailed(QNetworkReply& reply); + void signupCompleted(QNetworkReply* reply); + void signupFailed(QNetworkReply* reply); protected slots: Q_INVOKABLE bool isSteamRunning() const; diff --git a/interface/src/ui/OverlayConductor.cpp b/interface/src/ui/OverlayConductor.cpp index d131bb3467..e27001567f 100644 --- a/interface/src/ui/OverlayConductor.cpp +++ b/interface/src/ui/OverlayConductor.cpp @@ -88,38 +88,24 @@ void OverlayConductor::update(float dt) { _hmdMode = false; } - bool isAtRest = updateAvatarIsAtRest(); - bool isMoving = !isAtRest; - bool shouldRecenter = false; - if (_flags & SuppressedByMove) { - if (!isMoving) { - _flags &= ~SuppressedByMove; - shouldRecenter = true; - } - } else { - if (myAvatar->getClearOverlayWhenMoving() && isMoving) { - _flags |= SuppressedByMove; - } - } - - if (_flags & SuppressedByHead) { - if (isAtRest) { - _flags &= ~SuppressedByHead; + if (_suppressedByHead) { + if (updateAvatarIsAtRest()) { + _suppressedByHead = false; shouldRecenter = true; } } else { if (_hmdMode && headOutsideOverlay()) { - _flags |= SuppressedByHead; + _suppressedByHead = true; } } - bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && (0 == (_flags & SuppressMask)); + bool targetVisible = Menu::getInstance()->isOptionChecked(MenuOption::Overlays) && !_suppressedByHead; if (targetVisible != currentVisible) { offscreenUi->setPinned(!targetVisible); } - if (shouldRecenter && !_flags) { + if (shouldRecenter && !_suppressedByHead) { centerUI(); } } diff --git a/interface/src/ui/OverlayConductor.h b/interface/src/ui/OverlayConductor.h index cf69c32fc5..b47e23d28a 100644 --- a/interface/src/ui/OverlayConductor.h +++ b/interface/src/ui/OverlayConductor.h @@ -25,13 +25,7 @@ private: bool headOutsideOverlay() const; bool updateAvatarIsAtRest(); - enum SupressionFlags { - SuppressedByMove = 0x01, - SuppressedByHead = 0x02, - SuppressMask = 0x03, - }; - - uint8_t _flags { SuppressedByMove }; + bool _suppressedByHead { false }; bool _hmdMode { false }; // used by updateAvatarIsAtRest diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 3d3c432e92..50a4d17cae 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -161,12 +161,6 @@ void setupPreferences() { preferences->addPreference(new CheckPreference(UI_CATEGORY, "Use reticle cursor instead of arrow", getter, setter)); } - { - auto getter = [=]()->bool { return myAvatar->getClearOverlayWhenMoving(); }; - auto setter = [=](bool value) { myAvatar->setClearOverlayWhenMoving(value); }; - preferences->addPreference(new CheckPreference(UI_CATEGORY, "Clear overlays when moving", getter, setter)); - } - static const QString VIEW_CATEGORY{ "View" }; { auto getter = [=]()->float { return myAvatar->getRealWorldFieldOfView(); }; @@ -233,6 +227,8 @@ void setupPreferences() { auto getter = [=]()->float { return myAvatar->getTargetScale(); }; auto setter = [=](float value) { myAvatar->setTargetScale(value); }; auto preference = new SpinnerSliderPreference(AVATAR_TUNING, "Avatar Scale", getter, setter); + preference->setMin(0.25); + preference->setMax(4); preference->setStep(0.05f); preference->setDecimals(2); preferences->addPreference(preference); @@ -270,20 +266,15 @@ void setupPreferences() { preferences->addPreference(new SliderPreference(FACE_TRACKING, "Eye Deflection", getter, setter)); } - static const QString MOVEMENT{ "VR Movement" }; + static const QString MOVEMENT{ "Movement" }; { static const QString movementsControlChannel = QStringLiteral("Hifi-Advanced-Movement-Disabler"); auto getter = [=]()->bool { return myAvatar->useAdvancedMovementControls(); }; auto setter = [=](bool value) { myAvatar->setUseAdvancedMovementControls(value); }; preferences->addPreference(new CheckPreference(MOVEMENT, - QStringLiteral("Advanced movement for hand controllers"), - getter, setter)); - } - { - auto getter = [=]()->bool { return myAvatar->getFlyingEnabled(); }; - auto setter = [=](bool value) { myAvatar->setFlyingEnabled(value); }; - preferences->addPreference(new CheckPreference(MOVEMENT, "Flying & jumping", getter, setter)); + QStringLiteral("Advanced movement for hand controllers"), + getter, setter)); } { auto getter = [=]()->int { return myAvatar->getSnapTurn() ? 0 : 1; }; @@ -304,22 +295,73 @@ void setupPreferences() { preference->setStep(0.001f); preferences->addPreference(preference); } + { + auto preference = new ButtonPreference(MOVEMENT, "RESET SENSORS", [] { + qApp->resetSensors(); + }); + preferences->addPreference(preference); + } + + static const QString VR_MOVEMENT{ "VR Movement" }; + { + + static const QString movementsControlChannel = QStringLiteral("Hifi-Advanced-Movement-Disabler"); + auto getter = [=]()->bool { return myAvatar->useAdvancedMovementControls(); }; + auto setter = [=](bool value) { myAvatar->setUseAdvancedMovementControls(value); }; + preferences->addPreference(new CheckPreference(VR_MOVEMENT, + QStringLiteral("Advanced movement for hand controllers"), + getter, setter)); + } + { + auto getter = [=]()->bool { return myAvatar->getFlyingHMDPref(); }; + auto setter = [=](bool value) { myAvatar->setFlyingHMDPref(value); }; + preferences->addPreference(new CheckPreference(VR_MOVEMENT, "Flying & jumping", getter, setter)); + } + { + auto getter = [=]()->int { return myAvatar->getSnapTurn() ? 0 : 1; }; + auto setter = [=](int value) { myAvatar->setSnapTurn(value == 0); }; + auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Snap turn / Smooth turn", getter, setter); + QStringList items; + items << "Snap turn" << "Smooth turn"; + preference->setItems(items); + preferences->addPreference(preference); + } + { + auto getter = [=]()->float { return myAvatar->getUserHeight(); }; + auto setter = [=](float value) { myAvatar->setUserHeight(value); }; + auto preference = new SpinnerPreference(VR_MOVEMENT, "User real-world height (meters)", getter, setter); + preference->setMin(1.0f); + preference->setMax(2.2f); + preference->setDecimals(3); + preference->setStep(0.001f); + preferences->addPreference(preference); + } + { + auto preference = new ButtonPreference(VR_MOVEMENT, "RESET SENSORS", [] { + qApp->resetSensors(); + }); + preferences->addPreference(preference); + } static const QString AVATAR_CAMERA{ "Mouse Sensitivity" }; { auto getter = [=]()->float { return myAvatar->getPitchSpeed(); }; auto setter = [=](float value) { myAvatar->setPitchSpeed(value); }; - auto preference = new SpinnerPreference(AVATAR_CAMERA, "Pitch speed (degrees/second)", getter, setter); + auto preference = new SpinnerSliderPreference(AVATAR_CAMERA, "Y input:", getter, setter); preference->setMin(1.0f); preference->setMax(360.0f); + preference->setStep(1); + preference->setDecimals(1); preferences->addPreference(preference); } { auto getter = [=]()->float { return myAvatar->getYawSpeed(); }; auto setter = [=](float value) { myAvatar->setYawSpeed(value); }; - auto preference = new SpinnerPreference(AVATAR_CAMERA, "Yaw speed (degrees/second)", getter, setter); + auto preference = new SpinnerSliderPreference(AVATAR_CAMERA, "X input:", getter, setter); preference->setMin(1.0f); preference->setMax(360.0f); + preference->setStep(1); + preference->setDecimals(1); preferences->addPreference(preference); } diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index 2b306ace91..60c039ce1f 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -50,6 +50,7 @@ const QString DATETIME_FORMAT = "yyyy-MM-dd_hh-mm-ss"; const QString SNAPSHOTS_DIRECTORY = "Snapshots"; const QString URL = "highfidelity_url"; static const int SNAPSHOT_360_TIMER_INTERVAL = 350; +static const QList SUPPORTED_IMAGE_FORMATS = { "jpg", "jpeg", "png" }; Snapshot::Snapshot() { _snapshotTimer.setSingleShot(false); @@ -67,7 +68,6 @@ Snapshot::Snapshot() { } SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) { - if (!QFile(snapshotPath).exists()) { return NULL; } @@ -95,7 +95,6 @@ SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) { } QString Snapshot::saveSnapshot(QImage image, const QString& filename, const QString& pathname) { - QFile* snapshotFile = savedFileForSnapshot(image, false, filename, pathname); if (snapshotFile) { @@ -122,11 +121,15 @@ static const glm::quat CAMERA_ORIENTATION_LEFT(glm::quat(glm::radians(glm::vec3( static const glm::quat CAMERA_ORIENTATION_BACK(glm::quat(glm::radians(glm::vec3(0.0f, 180.0f, 0.0f)))); static const glm::quat CAMERA_ORIENTATION_RIGHT(glm::quat(glm::radians(glm::vec3(0.0f, 270.0f, 0.0f)))); static const glm::quat CAMERA_ORIENTATION_UP(glm::quat(glm::radians(glm::vec3(90.0f, 0.0f, 0.0f)))); -void Snapshot::save360Snapshot(const glm::vec3& cameraPosition, const bool& cubemapOutputFormat, const bool& notify, const QString& filename) { +void Snapshot::save360Snapshot(const glm::vec3& cameraPosition, + const bool& cubemapOutputFormat, + const bool& notify, + const QString& filename) { _snapshotFilename = filename; _notify360 = notify; _cubemapOutputFormat = cubemapOutputFormat; - SecondaryCameraJobConfig* secondaryCameraRenderConfig = static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera")); + SecondaryCameraJobConfig* secondaryCameraRenderConfig = + static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera")); // Save initial values of secondary camera render config _oldEnabled = secondaryCameraRenderConfig->isEnabled(); @@ -141,9 +144,11 @@ void Snapshot::save360Snapshot(const glm::vec3& cameraPosition, const bool& cube } // Initialize some secondary camera render config options for 360 snapshot capture - static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping"))->setCurve(0); + static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping")) + ->setCurve(0); - secondaryCameraRenderConfig->resetSizeSpectatorCamera(static_cast(CUBEMAP_SIDE_PIXEL_DIMENSION), static_cast(CUBEMAP_SIDE_PIXEL_DIMENSION)); + secondaryCameraRenderConfig->resetSizeSpectatorCamera(static_cast(CUBEMAP_SIDE_PIXEL_DIMENSION), + static_cast(CUBEMAP_SIDE_PIXEL_DIMENSION)); secondaryCameraRenderConfig->setProperty("attachedEntityId", ""); secondaryCameraRenderConfig->setPosition(cameraPosition); secondaryCameraRenderConfig->setProperty("vFoV", SNAPSHOT_360_FOV); @@ -159,7 +164,8 @@ void Snapshot::save360Snapshot(const glm::vec3& cameraPosition, const bool& cube } void Snapshot::takeNextSnapshot() { - SecondaryCameraJobConfig* config = static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera")); + SecondaryCameraJobConfig* config = + static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCamera")); // Order is: // 0. Down @@ -191,7 +197,9 @@ void Snapshot::takeNextSnapshot() { _snapshotTimer.stop(); // Reset secondary camera render config - static_cast(qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping"))->setCurve(1); + static_cast( + qApp->getRenderEngine()->getConfiguration()->getConfig("SecondaryCameraJob.ToneMapping")) + ->setCurve(1); config->resetSizeSpectatorCamera(qApp->getWindow()->geometry().width(), qApp->getWindow()->geometry().height()); config->setProperty("attachedEntityId", _oldAttachedEntityId); config->setProperty("vFoV", _oldvFoV); @@ -338,8 +346,10 @@ QTemporaryFile* Snapshot::saveTempSnapshot(QImage image) { return static_cast(savedFileForSnapshot(image, true)); } -QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary, const QString& userSelectedFilename, const QString& userSelectedPathname) { - +QFile* Snapshot::savedFileForSnapshot(QImage& shot, + bool isTemporary, + const QString& userSelectedFilename, + const QString& userSelectedPathname) { // adding URL to snapshot QUrl currentURL = DependencyManager::get()->currentPublicAddress(); shot.setText(URL, currentURL.toString()); @@ -350,18 +360,35 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary, const QSt QDateTime now = QDateTime::currentDateTime(); - // If user has requested specific filename then use it, else create the filename - // 'jpg" is appended, as the image is saved in jpg format. This is the case for all snapshots - // (see definition of FILENAME_PATH_FORMAT) + // If user has supplied a specific filename for the snapshot: + // If the user's requested filename has a suffix that's contained within SUPPORTED_IMAGE_FORMATS, + // DON'T append ".jpg" to the filename. QT will save the image in the format associated with the + // filename's suffix. + // If you want lossless Snapshots, supply a `.png` filename. Otherwise, use `.jpeg` or `.jpg`. + // For PNGs, we use a "quality" of "50". The output image quality is the same as "100" + // is the same as "0" -- the difference lies in the amount of compression applied to the PNG, + // which slightly affects the time it takes to save the image. + // Otherwise, ".jpg" is appended to the user's requested filename so that the image is saved in JPG format. + // If the user hasn't supplied a specific filename for the snapshot: + // Save the snapshot in JPG format at "100" quality according to FILENAME_PATH_FORMAT + int imageQuality = 100; QString filename; if (!userSelectedFilename.isNull()) { - filename = userSelectedFilename + ".jpg"; + QFileInfo snapshotFileInfo(userSelectedFilename); + QString userSelectedFilenameSuffix = snapshotFileInfo.suffix(); + userSelectedFilenameSuffix = userSelectedFilenameSuffix.toLower(); + if (SUPPORTED_IMAGE_FORMATS.contains(userSelectedFilenameSuffix)) { + filename = userSelectedFilename; + if (userSelectedFilenameSuffix == "png") { + imageQuality = 50; + } + } else { + filename = userSelectedFilename + ".jpg"; + } } else { filename = FILENAME_PATH_FORMAT.arg(username, now.toString(DATETIME_FORMAT)); } - const int IMAGE_QUALITY = 100; - if (!isTemporary) { // If user has requested specific path then use it, else use the application value QString snapshotFullPath; @@ -372,11 +399,13 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary, const QSt } if (snapshotFullPath.isEmpty()) { - snapshotFullPath = OffscreenUi::getExistingDirectory(nullptr, "Choose Snapshots Directory", QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); + snapshotFullPath = + OffscreenUi::getExistingDirectory(nullptr, "Choose Snapshots Directory", + QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); _snapshotsLocation.set(snapshotFullPath); } - if (!snapshotFullPath.isEmpty()) { // not cancelled + if (!snapshotFullPath.isEmpty()) { // not cancelled if (!snapshotFullPath.endsWith(QDir::separator())) { snapshotFullPath.append(QDir::separator()); @@ -393,7 +422,9 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary, const QSt qApp->getApplicationCompositor().getReticleInterface()->setVisible(true); qApp->getApplicationCompositor().getReticleInterface()->setAllowMouseCapture(true); - snapshotFullPath = OffscreenUi::getExistingDirectory(nullptr, "Write Error - Choose New Snapshots Directory", QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); + snapshotFullPath = + OffscreenUi::getExistingDirectory(nullptr, "Write Error - Choose New Snapshots Directory", + QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); if (snapshotFullPath.isEmpty()) { return NULL; } @@ -407,12 +438,11 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary, const QSt imageFile = new QFile(snapshotFullPath); } - shot.save(imageFile, 0, IMAGE_QUALITY); + shot.save(imageFile, 0, imageQuality); imageFile->close(); return imageFile; } - } // Either we were asked for a tempororary, or the user didn't set a directory. QTemporaryFile* imageTempFile = new QTemporaryFile(QDir::tempPath() + "/XXXXXX-" + filename); @@ -423,18 +453,17 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary, const QSt } imageTempFile->setAutoRemove(isTemporary); - shot.save(imageTempFile, 0, IMAGE_QUALITY); + shot.save(imageTempFile, 0, imageQuality); imageTempFile->close(); return imageTempFile; } void Snapshot::uploadSnapshot(const QString& filename, const QUrl& href) { - const QString SNAPSHOT_UPLOAD_URL = "/api/v1/snapshots"; QUrl url = href; if (url.isEmpty()) { - SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(filename); + SnapshotMetaData* snapshotData = parseSnapshotData(filename); if (snapshotData) { url = snapshotData->getURL(); } @@ -444,7 +473,7 @@ void Snapshot::uploadSnapshot(const QString& filename, const QUrl& href) { url = QUrl(DependencyManager::get()->currentShareableAddress()); } SnapshotUploader* uploader = new SnapshotUploader(url, filename); - + QFile* file = new QFile(filename); Q_ASSERT(file->exists()); file->open(QIODevice::ReadOnly); @@ -458,20 +487,16 @@ void Snapshot::uploadSnapshot(const QString& filename, const QUrl& href) { imagePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"image\"; filename=\"" + file->fileName() + "\"")); imagePart.setBodyDevice(file); - - QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); - file->setParent(multiPart); // we cannot delete the file now, so delete it with the multiPart - multiPart->append(imagePart); - - auto accountManager = DependencyManager::get(); - JSONCallbackParameters callbackParams(uploader, "uploadSuccess", uploader, "uploadFailure"); - accountManager->sendRequest(SNAPSHOT_UPLOAD_URL, - AccountManagerAuth::Required, - QNetworkAccessManager::PostOperation, - callbackParams, - nullptr, - multiPart); + QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + file->setParent(multiPart); // we cannot delete the file now, so delete it with the multiPart + multiPart->append(imagePart); + + auto accountManager = DependencyManager::get(); + JSONCallbackParameters callbackParams(uploader, "uploadSuccess", "uploadFailure"); + + accountManager->sendRequest(SNAPSHOT_UPLOAD_URL, AccountManagerAuth::Required, QNetworkAccessManager::PostOperation, + callbackParams, nullptr, multiPart); } QString Snapshot::getSnapshotsLocation() { diff --git a/interface/src/ui/SnapshotUploader.cpp b/interface/src/ui/SnapshotUploader.cpp index 67902c1a35..694d0fa8f8 100644 --- a/interface/src/ui/SnapshotUploader.cpp +++ b/interface/src/ui/SnapshotUploader.cpp @@ -23,11 +23,11 @@ SnapshotUploader::SnapshotUploader(QUrl inWorldLocation, QString pathname) : _pathname(pathname) { } -void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { +void SnapshotUploader::uploadSuccess(QNetworkReply* reply) { const QString STORY_UPLOAD_URL = "/api/v1/user_stories"; // parse the reply for the thumbnail_url - QByteArray contents = reply.readAll(); + QByteArray contents = reply->readAll(); QJsonParseError jsonError; auto doc = QJsonDocument::fromJson(contents, &jsonError); if (jsonError.error == QJsonParseError::NoError) { @@ -35,6 +35,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { QString thumbnailUrl = dataObject.value("thumbnail_url").toString(); QString imageUrl = dataObject.value("image_url").toString(); QString snapshotID = dataObject.value("id").toString(); + QString originalImageFileName = dataObject.value("original_image_file_name").toString(); auto addressManager = DependencyManager::get(); QString placeName = _inWorldLocation.authority(); // We currently only upload shareable places, in which case this is just host. QString currentPath = _inWorldLocation.path(); @@ -48,6 +49,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { detailsObject.insert("shareable_url", dataObject.value("shareable_url").toString()); } detailsObject.insert("snapshot_id", snapshotID); + detailsObject.insert("original_image_file_name", originalImageFileName); QString pickledDetails = QJsonDocument(detailsObject).toJson(); userStoryObject.insert("details", pickledDetails); userStoryObject.insert("thumbnail_url", thumbnailUrl); @@ -58,7 +60,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { rootObject.insert("user_story", userStoryObject); auto accountManager = DependencyManager::get(); - JSONCallbackParameters callbackParams(this, "createStorySuccess", this, "createStoryFailure"); + JSONCallbackParameters callbackParams(this, "createStorySuccess", "createStoryFailure"); accountManager->sendRequest(STORY_UPLOAD_URL, AccountManagerAuth::Required, @@ -72,11 +74,11 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { } } -void SnapshotUploader::uploadFailure(QNetworkReply& reply) { - QString replyString = reply.readAll(); - qDebug() << "Error " << reply.errorString() << " uploading snapshot " << _pathname << " from " << _inWorldLocation; +void SnapshotUploader::uploadFailure(QNetworkReply* reply) { + QString replyString = reply->readAll(); + qDebug() << "Error " << reply->errorString() << " uploading snapshot " << _pathname << " from " << _inWorldLocation; if (replyString.size() == 0) { - replyString = reply.errorString(); + replyString = reply->errorString(); } replyString = replyString.left(1000); // Only print first 1000 characters of error qDebug() << "Snapshot upload reply error (truncated):" << replyString; @@ -84,17 +86,17 @@ void SnapshotUploader::uploadFailure(QNetworkReply& reply) { this->deleteLater(); } -void SnapshotUploader::createStorySuccess(QNetworkReply& reply) { - QString replyString = reply.readAll(); +void SnapshotUploader::createStorySuccess(QNetworkReply* reply) { + QString replyString = reply->readAll(); emit DependencyManager::get()->snapshotShared(false, replyString); this->deleteLater(); } -void SnapshotUploader::createStoryFailure(QNetworkReply& reply) { - QString replyString = reply.readAll(); - qDebug() << "Error " << reply.errorString() << " uploading snapshot story " << _pathname << " from " << _inWorldLocation; +void SnapshotUploader::createStoryFailure(QNetworkReply* reply) { + QString replyString = reply->readAll(); + qDebug() << "Error " << reply->errorString() << " uploading snapshot story " << _pathname << " from " << _inWorldLocation; if (replyString.size() == 0) { - replyString = reply.errorString(); + replyString = reply->errorString(); } replyString = replyString.left(1000); // Only print first 1000 characters of error qDebug() << "Snapshot story upload reply error (truncated):" << replyString; diff --git a/interface/src/ui/SnapshotUploader.h b/interface/src/ui/SnapshotUploader.h index ae6d5d55ca..d8e72730c7 100644 --- a/interface/src/ui/SnapshotUploader.h +++ b/interface/src/ui/SnapshotUploader.h @@ -21,12 +21,12 @@ class SnapshotUploader : public QObject { public: SnapshotUploader(QUrl inWorldLocation, QString pathname); public slots: - void uploadSuccess(QNetworkReply& reply); - void uploadFailure(QNetworkReply& reply); - void createStorySuccess(QNetworkReply& reply); - void createStoryFailure(QNetworkReply& reply); + void uploadSuccess(QNetworkReply* reply); + void uploadFailure(QNetworkReply* reply); + void createStorySuccess(QNetworkReply* reply); + void createStoryFailure(QNetworkReply* reply); private: QUrl _inWorldLocation; QString _pathname; }; -#endif // hifi_SnapshotUploader_h \ No newline at end of file +#endif // hifi_SnapshotUploader_h diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 9d86745341..6bb615948c 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -333,7 +333,13 @@ void Stats::updateStats(bool force) { } auto gpuContext = qApp->getGPUContext(); - + auto displayPlugin = qApp->getActiveDisplayPlugin(); + if (displayPlugin) { + QVector2D dims(displayPlugin->getRecommendedRenderSize().x, displayPlugin->getRecommendedRenderSize().y); + dims *= displayPlugin->getRenderResolutionScale(); + STAT_UPDATE(gpuFrameSize, dims); + STAT_UPDATE(gpuFrameTimePerPixel, (float)(gpuContext->getFrameTimerGPUAverage()*1000000.0 / double(dims.x()*dims.y()))); + } // Update Frame timing (in ms) STAT_UPDATE(gpuFrameTime, (float)gpuContext->getFrameTimerGPUAverage()); STAT_UPDATE(batchFrameTime, (float)gpuContext->getFrameTimerBatchAverage()); @@ -490,9 +496,9 @@ void Stats::updateStats(bool force) { }; for (int32_t j = 0; j < categories.size(); ++j) { QString recordKey = "/idle/update/" + categories[j]; - itr = allRecords.find(recordKey); - if (itr != allRecords.end()) { - float dt = (float)itr.value().getMovingAverage() / (float)USECS_PER_MSEC; + auto record = PerformanceTimer::getTimerRecord(recordKey); + if (record.getCount()) { + float dt = (float) record.getMovingAverage() / (float)USECS_PER_MSEC; QString message = QString("\n %1 = %2").arg(categories[j]).arg(dt); idleUpdateStats.push(SortableStat(message, dt)); } diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 36e923261d..f4181f9788 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -276,7 +276,9 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, gpuTextureExternalMemory, 0) STATS_PROPERTY(QString, gpuTextureMemoryPressureState, QString()) STATS_PROPERTY(int, gpuFreeMemory, 0) + STATS_PROPERTY(QVector2D, gpuFrameSize, QVector2D(0,0)) STATS_PROPERTY(float, gpuFrameTime, 0) + STATS_PROPERTY(float, gpuFrameTimePerPixel, 0) STATS_PROPERTY(float, batchFrameTime, 0) STATS_PROPERTY(float, engineFrameTime, 0) STATS_PROPERTY(float, avatarSimulationTime, 0) @@ -962,6 +964,20 @@ signals: */ void gpuFrameTimeChanged(); + /**jsdoc + * Triggered when the value of the gpuFrameTime property changes. + * @function Stats.gpuFrameTimeChanged + * @returns {Signal} + */ + void gpuFrameSizeChanged(); + + /**jsdoc + * Triggered when the value of the gpuFrameTime property changes. + * @function Stats.gpuFrameTimeChanged + * @returns {Signal} + */ + void gpuFrameTimePerPixelChanged(); + /**jsdoc * Triggered when the value of the batchFrameTime property changes. * @function Stats.batchFrameTimeChanged diff --git a/interface/src/ui/UpdateDialog.cpp b/interface/src/ui/UpdateDialog.cpp index 2dcc0c07eb..7ff2132ab9 100644 --- a/interface/src/ui/UpdateDialog.cpp +++ b/interface/src/ui/UpdateDialog.cpp @@ -21,19 +21,31 @@ UpdateDialog::UpdateDialog(QQuickItem* parent) : OffscreenQmlDialog(parent) { auto applicationUpdater = DependencyManager::get(); - int currentVersion = QCoreApplication::applicationVersion().toInt(); - int latestVersion = applicationUpdater.data()->getBuildData().lastKey(); - _updateAvailableDetails = "v" + QString::number(latestVersion) + " released on " - + QString(applicationUpdater.data()->getBuildData()[latestVersion]["releaseTime"]).replace(" ", " "); + if (applicationUpdater) { - _releaseNotes = ""; - for (int i = latestVersion; i > currentVersion; i--) { - if (applicationUpdater.data()->getBuildData().contains(i)) { - QString releaseNotes = applicationUpdater.data()->getBuildData()[i]["releaseNotes"]; - releaseNotes.remove("
"); - releaseNotes.remove(QRegExp("^\n+")); - _releaseNotes += "\n" + QString().sprintf("%d", i) + "\n" + releaseNotes + "\n"; + auto buildData = applicationUpdater.data()->getBuildData(); + ApplicationVersion latestVersion = buildData.lastKey(); + _updateAvailableDetails = "v" + latestVersion.versionString + " released on " + + QString(buildData[latestVersion]["releaseTime"]).replace(" ", " "); + + _releaseNotes = ""; + + auto it = buildData.end(); + while (it != buildData.begin()) { + --it; + + if (applicationUpdater->getCurrentVersion() < it.key()) { + // grab the release notes for this later version + QString releaseNotes = it.value()["releaseNotes"]; + releaseNotes.remove("
"); + releaseNotes.remove(QRegExp("^\n+")); + _releaseNotes += "\n" + it.key().versionString + "\n" + releaseNotes + "\n"; + } else { + break; + } } + + } } @@ -47,5 +59,5 @@ const QString& UpdateDialog::releaseNotes() const { void UpdateDialog::triggerUpgrade() { auto applicationUpdater = DependencyManager::get(); - applicationUpdater.data()->performAutoUpdate(applicationUpdater.data()->getBuildData().lastKey()); + applicationUpdater.data()->openLatestUpdateURL(); } diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index f59b513bd5..6bce9d9283 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -23,7 +23,7 @@ Base3DOverlay::Base3DOverlay() : SpatiallyNestable(NestableType::Overlay, QUuid::createUuid()), _isSolid(DEFAULT_IS_SOLID), _isDashedLine(DEFAULT_IS_DASHED_LINE), - _ignoreRayIntersection(false), + _ignorePickIntersection(false), _drawInFront(false), _drawHUDLayer(false) { @@ -34,7 +34,7 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) : SpatiallyNestable(NestableType::Overlay, QUuid::createUuid()), _isSolid(base3DOverlay->_isSolid), _isDashedLine(base3DOverlay->_isDashedLine), - _ignoreRayIntersection(base3DOverlay->_ignoreRayIntersection), + _ignorePickIntersection(base3DOverlay->_ignorePickIntersection), _drawInFront(base3DOverlay->_drawInFront), _drawHUDLayer(base3DOverlay->_drawHUDLayer), _isGrabbable(base3DOverlay->_isGrabbable), @@ -183,8 +183,10 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { if (properties["dashed"].isValid()) { setIsDashedLine(properties["dashed"].toBool()); } - if (properties["ignoreRayIntersection"].isValid()) { - setIgnoreRayIntersection(properties["ignoreRayIntersection"].toBool()); + if (properties["ignorePickIntersection"].isValid()) { + setIgnorePickIntersection(properties["ignorePickIntersection"].toBool()); + } else if (properties["ignoreRayIntersection"].isValid()) { + setIgnorePickIntersection(properties["ignoreRayIntersection"].toBool()); } if (properties["parentID"].isValid()) { @@ -224,8 +226,7 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. @@ -260,8 +261,8 @@ QVariant Base3DOverlay::getProperty(const QString& property) { if (property == "isDashedLine" || property == "dashed") { return _isDashedLine; } - if (property == "ignoreRayIntersection") { - return _ignoreRayIntersection; + if (property == "ignorePickIntersection" || property == "ignoreRayIntersection") { + return _ignorePickIntersection; } if (property == "drawInFront") { return _drawInFront; @@ -282,11 +283,6 @@ QVariant Base3DOverlay::getProperty(const QString& property) { return Overlay::getProperty(property); } -bool Base3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal) { - return false; -} - void Base3DOverlay::locationChanged(bool tellPhysics) { SpatiallyNestable::locationChanged(tellPhysics); diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 2a63a6cb67..d44c193055 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -45,7 +45,7 @@ public: bool getIsSolid() const { return _isSolid; } bool getIsDashedLine() const { return _isDashedLine; } bool getIsSolidLine() const { return !_isDashedLine; } - bool getIgnoreRayIntersection() const { return _ignoreRayIntersection; } + bool getIgnorePickIntersection() const { return _ignorePickIntersection; } bool getDrawInFront() const { return _drawInFront; } bool getDrawHUDLayer() const { return _drawHUDLayer; } bool getIsGrabbable() const { return _isGrabbable; } @@ -53,7 +53,7 @@ public: void setIsSolid(bool isSolid) { _isSolid = isSolid; } void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; } - void setIgnoreRayIntersection(bool value) { _ignoreRayIntersection = value; } + void setIgnorePickIntersection(bool value) { _ignorePickIntersection = value; } virtual void setDrawInFront(bool value) { _drawInFront = value; } virtual void setDrawHUDLayer(bool value) { _drawHUDLayer = value; } void setIsGrabbable(bool value) { _isGrabbable = value; } @@ -69,11 +69,19 @@ public: virtual QVariant getProperty(const QString& property) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal); + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) { return false; } virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) { - return findRayIntersection(origin, direction, distance, face, surfaceNormal); + float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) { + return findRayIntersection(origin, direction, distance, face, surfaceNormal, precisionPicking); + } + + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) { return false; } + + virtual bool findParabolaIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) { + return findParabolaIntersection(origin, velocity, acceleration, parabolicDistance, face, surfaceNormal, precisionPicking); } virtual SpatialParentTree* getParentTree() const override; @@ -91,7 +99,7 @@ protected: bool _isSolid; bool _isDashedLine; - bool _ignoreRayIntersection; + bool _ignorePickIntersection; bool _drawInFront; bool _drawHUDLayer; bool _isGrabbable { false }; diff --git a/interface/src/ui/overlays/Billboardable.cpp b/interface/src/ui/overlays/Billboardable.cpp index 4ca024f2e5..a125956b5a 100644 --- a/interface/src/ui/overlays/Billboardable.cpp +++ b/interface/src/ui/overlays/Billboardable.cpp @@ -41,12 +41,16 @@ bool Billboardable::pointTransformAtCamera(Transform& transform, glm::quat offse glm::vec3 cameraPos = qApp->getCamera().getPosition(); // use the referencial from the avatar, y isn't always up glm::vec3 avatarUP = DependencyManager::get()->getMyAvatar()->getWorldOrientation()*Vectors::UP; - - glm::quat rotation(conjugate(toQuat(glm::lookAt(cameraPos, billboardPos, avatarUP)))); - - transform.setRotation(rotation); - transform.postRotate(offsetRotation); - return true; + // check to see if glm::lookAt will work / using glm::lookAt variable name + glm::highp_vec3 s(glm::cross(billboardPos - cameraPos, avatarUP)); + + // make sure s is not NaN for any component + if(glm::length2(s) > 0.0f) { + glm::quat rotation(conjugate(toQuat(glm::lookAt(cameraPos, billboardPos, avatarUP)))); + transform.setRotation(rotation); + transform.postRotate(offsetRotation); + return true; + } } return false; } diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 33f40f7c63..2e06229276 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -201,13 +201,12 @@ void Circle3DOverlay::render(RenderArgs* args) { float tickMarkAngle = getMajorTickMarksAngle(); float angle = _startAt - fmodf(_startAt, tickMarkAngle) + tickMarkAngle; - float angleInRadians = glm::radians(angle); float tickMarkLength = getMajorTickMarksLength(); float startRadius = (tickMarkLength > 0.0f) ? _innerRadius : _outerRadius; float endRadius = startRadius + tickMarkLength; while (angle <= _endAt) { - angleInRadians = glm::radians(angle); + float angleInRadians = glm::radians(angle); glm::vec2 thisPointA(cosf(angleInRadians) * startRadius, sinf(angleInRadians) * startRadius); glm::vec2 thisPointB(cosf(angleInRadians) * endRadius, sinf(angleInRadians) * endRadius); @@ -223,13 +222,12 @@ void Circle3DOverlay::render(RenderArgs* args) { float tickMarkAngle = getMinorTickMarksAngle(); float angle = _startAt - fmodf(_startAt, tickMarkAngle) + tickMarkAngle; - float angleInRadians = glm::radians(angle); float tickMarkLength = getMinorTickMarksLength(); float startRadius = (tickMarkLength > 0.0f) ? _innerRadius : _outerRadius; float endRadius = startRadius + tickMarkLength; while (angle <= _endAt) { - angleInRadians = glm::radians(angle); + float angleInRadians = glm::radians(angle); glm::vec2 thisPointA(cosf(angleInRadians) * startRadius, sinf(angleInRadians) * startRadius); glm::vec2 thisPointB(cosf(angleInRadians) * endRadius, sinf(angleInRadians) * endRadius); @@ -399,8 +397,7 @@ void Circle3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. @@ -521,23 +518,67 @@ QVariant Circle3DOverlay::getProperty(const QString& property) { } bool Circle3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) { - + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { // Scale the dimensions by the diameter glm::vec2 dimensions = getOuterRadius() * 2.0f * getDimensions(); - bool intersects = findRayRectangleIntersection(origin, direction, getWorldOrientation(), getWorldPosition(), dimensions, distance); + glm::quat rotation = getWorldOrientation(); - if (intersects) { + if (findRayRectangleIntersection(origin, direction, rotation, getWorldPosition(), dimensions, distance)) { glm::vec3 hitPosition = origin + (distance * direction); glm::vec3 localHitPosition = glm::inverse(getWorldOrientation()) * (hitPosition - getWorldPosition()); localHitPosition.x /= getDimensions().x; localHitPosition.y /= getDimensions().y; float distanceToHit = glm::length(localHitPosition); - intersects = getInnerRadius() <= distanceToHit && distanceToHit <= getOuterRadius(); + if (getInnerRadius() <= distanceToHit && distanceToHit <= getOuterRadius()) { + glm::vec3 forward = rotation * Vectors::FRONT; + if (glm::dot(forward, direction) > 0.0f) { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } else { + face = MIN_Z_FACE; + surfaceNormal = forward; + } + return true; + } } - return intersects; + return false; +} + +bool Circle3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { + // Scale the dimensions by the diameter + glm::vec2 xyDimensions = getOuterRadius() * 2.0f * getDimensions(); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition(); + + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - position); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + + if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { + glm::vec3 localHitPosition = localOrigin + localVelocity * parabolicDistance + 0.5f * localAcceleration * parabolicDistance * parabolicDistance; + localHitPosition.x /= getDimensions().x; + localHitPosition.y /= getDimensions().y; + float distanceToHit = glm::length(localHitPosition); + + if (getInnerRadius() <= distanceToHit && distanceToHit <= getOuterRadius()) { + float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + glm::vec3 forward = rotation * Vectors::FRONT; + if (localIntersectionVelocityZ > 0.0f) { + face = MIN_Z_FACE; + surfaceNormal = forward; + } else { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } + return true; + } + } + + return false; } Circle3DOverlay* Circle3DOverlay::createClone() const { diff --git a/interface/src/ui/overlays/Circle3DOverlay.h b/interface/src/ui/overlays/Circle3DOverlay.h index ef491b7f46..b3fa24fb16 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.h +++ b/interface/src/ui/overlays/Circle3DOverlay.h @@ -54,8 +54,10 @@ public: void setMajorTickMarksColor(const xColor& value) { _majorTickMarksColor = value; } void setMinorTickMarksColor(const xColor& value) { _minorTickMarksColor = value; } - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) override; + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; virtual Circle3DOverlay* createClone() const override; diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index aca186a589..789b1f9969 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -97,6 +97,10 @@ static const float CONTEXT_OVERLAY_UNHOVERED_COLORPULSE = 1.0f; void ContextOverlayInterface::setEnabled(bool enabled) { _enabled = enabled; + if (!enabled) { + // Destroy any potentially-active ContextOverlays when disabling the interface + createOrDestroyContextOverlay(EntityItemID(), PointerEvent()); + } } void ContextOverlayInterface::clickDownOnEntity(const EntityItemID& entityItemID, const PointerEvent& event) { @@ -193,7 +197,7 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& _contextOverlay->setPulseMin(CONTEXT_OVERLAY_UNHOVERED_PULSEMIN); _contextOverlay->setPulseMax(CONTEXT_OVERLAY_UNHOVERED_PULSEMAX); _contextOverlay->setColorPulse(CONTEXT_OVERLAY_UNHOVERED_COLORPULSE); - _contextOverlay->setIgnoreRayIntersection(false); + _contextOverlay->setIgnorePickIntersection(false); _contextOverlay->setDrawInFront(true); _contextOverlay->setURL(PathUtils::resourcesUrl() + "images/inspect-icon.png"); _contextOverlay->setIsFacingAvatar(true); diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index c98d9330df..38fff5f26f 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -160,8 +160,7 @@ void Cube3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/Grid3DOverlay.cpp b/interface/src/ui/overlays/Grid3DOverlay.cpp index 621c19944b..15eb9eef76 100644 --- a/interface/src/ui/overlays/Grid3DOverlay.cpp +++ b/interface/src/ui/overlays/Grid3DOverlay.cpp @@ -145,8 +145,7 @@ void Grid3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/Grid3DOverlay.h b/interface/src/ui/overlays/Grid3DOverlay.h index 5a67b21e07..64b65b3178 100644 --- a/interface/src/ui/overlays/Grid3DOverlay.h +++ b/interface/src/ui/overlays/Grid3DOverlay.h @@ -35,7 +35,10 @@ public: virtual Grid3DOverlay* createClone() const override; // Grids are UI tools, and may not be intersected (pickable) - virtual bool findRayIntersection(const glm::vec3&, const glm::vec3&, float&, BoxFace&, glm::vec3&) override { return false; } + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, + glm::vec3& surfaceNormal, bool precisionPicking = false) override { return false; } + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override { return false; } protected: Transform evalRenderTransform() override; diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index 6e9946e935..608e7eb72f 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -216,8 +216,7 @@ void Image3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. @@ -258,12 +257,9 @@ void Image3DOverlay::setURL(const QString& url) { } bool Image3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal) { + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { if (_texture && _texture->isLoaded()) { - // Make sure position and rotation is updated. Transform transform = getTransform(); - - // Don't call applyTransformTo() or setTransform() here because this code runs too frequently. // Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale. bool isNull = _fromImage.isNull(); @@ -271,12 +267,55 @@ bool Image3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec float height = isNull ? _texture->getHeight() : _fromImage.height(); float maxSize = glm::max(width, height); glm::vec2 dimensions = _dimensions * glm::vec2(width / maxSize, height / maxSize); + glm::quat rotation = transform.getRotation(); - // FIXME - face and surfaceNormal not being set - return findRayRectangleIntersection(origin, direction, - transform.getRotation(), - transform.getTranslation(), - dimensions, distance); + if (findRayRectangleIntersection(origin, direction, rotation, transform.getTranslation(), dimensions, distance)) { + glm::vec3 forward = rotation * Vectors::FRONT; + if (glm::dot(forward, direction) > 0.0f) { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } else { + face = MIN_Z_FACE; + surfaceNormal = forward; + } + return true; + } + } + + return false; +} + +bool Image3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { + if (_texture && _texture->isLoaded()) { + Transform transform = getTransform(); + + // Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale. + bool isNull = _fromImage.isNull(); + float width = isNull ? _texture->getWidth() : _fromImage.width(); + float height = isNull ? _texture->getHeight() : _fromImage.height(); + float maxSize = glm::max(width, height); + glm::vec2 dimensions = _dimensions * glm::vec2(width / maxSize, height / maxSize); + glm::quat rotation = transform.getRotation(); + glm::vec3 position = getWorldPosition(); + + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - position); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + + if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, dimensions, parabolicDistance)) { + float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + glm::vec3 forward = rotation * Vectors::FRONT; + if (localIntersectionVelocityZ > 0.0f) { + face = MIN_Z_FACE; + surfaceNormal = forward; + } else { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } + return true; + } } return false; diff --git a/interface/src/ui/overlays/Image3DOverlay.h b/interface/src/ui/overlays/Image3DOverlay.h index aa802a82a9..1ffa062d45 100644 --- a/interface/src/ui/overlays/Image3DOverlay.h +++ b/interface/src/ui/overlays/Image3DOverlay.h @@ -42,8 +42,10 @@ public: QVariant getProperty(const QString& property) override; bool isTransparent() override { return Base3DOverlay::isTransparent() || _alphaTexture; } - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) override; + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; virtual Image3DOverlay* createClone() const override; diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index c2e5ad1fb4..af6c3c2472 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -288,8 +288,7 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 875352ebb4..eee8222051 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -27,6 +27,8 @@ ModelOverlay::ModelOverlay() { _model->setLoadingPriority(_loadPriority); _isLoaded = false; + render::ScenePointer scene = qApp->getMain3DScene(); + _model->setVisibleInScene(false, scene); } ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) : @@ -101,10 +103,11 @@ void ModelOverlay::update(float deltatime) { emit DependencyManager::get()->modelAddedToScene(getID(), NestableType::Overlay, _model); } bool metaDirty = false; - if (_visibleDirty) { + if (_visibleDirty && _texturesLoaded) { _visibleDirty = false; // don't show overlays in mirrors or spectator-cam unless _isVisibleInSecondaryCamera is true uint8_t modelRenderTagMask = (_isVisibleInSecondaryCamera ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_MAIN_VIEW); + _model->setTagMask(modelRenderTagMask, scene); _model->setVisibleInScene(getVisible(), scene); metaDirty = true; @@ -129,11 +132,15 @@ void ModelOverlay::update(float deltatime) { } scene->enqueueTransaction(transaction); + if (_texturesDirty && !_modelTextures.isEmpty()) { + _texturesDirty = false; + _model->setTextures(_modelTextures); + } + if (!_texturesLoaded && _model->getGeometry() && _model->getGeometry()->areTexturesLoaded()) { _texturesLoaded = true; - if (!_modelTextures.isEmpty()) { - _model->setTextures(_modelTextures); - } + + _model->setVisibleInScene(getVisible(), scene); _model->updateRenderItems(); } } @@ -233,6 +240,7 @@ void ModelOverlay::setProperties(const QVariantMap& properties) { _texturesLoaded = false; QVariantMap textureMap = texturesValue.toMap(); _modelTextures = textureMap; + _texturesDirty = true; } auto groupCulledValue = properties["isGroupCulled"]; @@ -373,8 +381,7 @@ vectorType ModelOverlay::mapJoints(mapFunction function) const { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} isGroupCulled=false - If true, the mesh parts of the model are LOD culled as a group. @@ -509,16 +516,25 @@ QVariant ModelOverlay::getProperty(const QString& property) { } bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal) { - + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { QVariantMap extraInfo; - return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo); + return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo, precisionPicking); } bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) { + float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) { + return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo, precisionPicking); +} - return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo); +bool ModelOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { + QVariantMap extraInfo; + return _model->findParabolaIntersectionAgainstSubMeshes(origin, velocity, acceleration, parabolicDistance, face, surfaceNormal, extraInfo, precisionPicking); +} + +bool ModelOverlay::findParabolaIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) { + return _model->findParabolaIntersectionAgainstSubMeshes(origin, velocity, acceleration, parabolicDistance, face, surfaceNormal, extraInfo, precisionPicking); } ModelOverlay* ModelOverlay::createClone() const { diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index 334c9c06f1..bd922e258a 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -45,9 +45,13 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) override; + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo) override; + float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) override; + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; + virtual bool findParabolaIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) override; virtual ModelOverlay* createClone() const override; @@ -94,6 +98,7 @@ private: ModelPointer _model; QVariantMap _modelTextures; bool _texturesLoaded { false }; + bool _texturesDirty { false }; render::ItemIDs _subRenderItemIDs; diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 85041aad4e..de4ff94719 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -41,8 +41,6 @@ Q_LOGGING_CATEGORY(trace_render_overlays, "trace.render.overlays") -extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest = false); - Overlays::Overlays() { auto pointerManager = DependencyManager::get(); connect(pointerManager.data(), &PointerManager::hoverBeginOverlay, this, &Overlays::hoverEnterPointerEvent); @@ -118,7 +116,7 @@ void Overlays::renderHUD(RenderArgs* renderArgs) { auto geometryCache = DependencyManager::get(); auto textureCache = DependencyManager::get(); - auto size = glm::uvec2(glm::vec2(qApp->getUiSize()) * qApp->getRenderResolutionScale()); + auto size = glm::uvec2(qApp->getUiSize()); int width = size.x; int height = size.y; mat4 legacyProjection = glm::ortho(0, width, height, 0, -1000, 1000); @@ -548,13 +546,13 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay continue; } - if (thisOverlay && thisOverlay->getVisible() && !thisOverlay->getIgnoreRayIntersection() && thisOverlay->isLoaded()) { + if (thisOverlay && thisOverlay->getVisible() && !thisOverlay->getIgnorePickIntersection() && thisOverlay->isLoaded()) { float thisDistance; BoxFace thisFace; glm::vec3 thisSurfaceNormal; QVariantMap thisExtraInfo; if (thisOverlay->findRayIntersectionExtraInfo(ray.origin, ray.direction, thisDistance, - thisFace, thisSurfaceNormal, thisExtraInfo)) { + thisFace, thisSurfaceNormal, thisExtraInfo, precisionPicking)) { bool isDrawInFront = thisOverlay->getDrawInFront(); if ((bestIsFront && isDrawInFront && thisDistance < bestDistance) || (!bestIsFront && (isDrawInFront || thisDistance < bestDistance))) { @@ -575,76 +573,86 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay return result; } -QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value) { - auto obj = engine->newObject(); - obj.setProperty("intersects", value.intersects); - obj.setProperty("overlayID", OverlayIDtoScriptValue(engine, value.overlayID)); - obj.setProperty("distance", value.distance); +ParabolaToOverlayIntersectionResult Overlays::findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking, + const QVector& overlaysToInclude, + const QVector& overlaysToDiscard, + bool visibleOnly, bool collidableOnly) { + float bestDistance = std::numeric_limits::max(); + bool bestIsFront = false; - QString faceName = ""; - // handle BoxFace - switch (value.face) { - case MIN_X_FACE: - faceName = "MIN_X_FACE"; - break; - case MAX_X_FACE: - faceName = "MAX_X_FACE"; - break; - case MIN_Y_FACE: - faceName = "MIN_Y_FACE"; - break; - case MAX_Y_FACE: - faceName = "MAX_Y_FACE"; - break; - case MIN_Z_FACE: - faceName = "MIN_Z_FACE"; - break; - case MAX_Z_FACE: - faceName = "MAX_Z_FACE"; - break; - default: - case UNKNOWN_FACE: - faceName = "UNKNOWN_FACE"; - break; + QMutexLocker locker(&_mutex); + ParabolaToOverlayIntersectionResult result; + QMapIterator i(_overlaysWorld); + while (i.hasNext()) { + i.next(); + OverlayID thisID = i.key(); + auto thisOverlay = std::dynamic_pointer_cast(i.value()); + + if ((overlaysToDiscard.size() > 0 && overlaysToDiscard.contains(thisID)) || + (overlaysToInclude.size() > 0 && !overlaysToInclude.contains(thisID))) { + continue; + } + + if (thisOverlay && thisOverlay->getVisible() && !thisOverlay->getIgnorePickIntersection() && thisOverlay->isLoaded()) { + float thisDistance; + BoxFace thisFace; + glm::vec3 thisSurfaceNormal; + QVariantMap thisExtraInfo; + if (thisOverlay->findParabolaIntersectionExtraInfo(parabola.origin, parabola.velocity, parabola.acceleration, thisDistance, + thisFace, thisSurfaceNormal, thisExtraInfo, precisionPicking)) { + bool isDrawInFront = thisOverlay->getDrawInFront(); + if ((bestIsFront && isDrawInFront && thisDistance < bestDistance) + || (!bestIsFront && (isDrawInFront || thisDistance < bestDistance))) { + + bestIsFront = isDrawInFront; + bestDistance = thisDistance; + result.intersects = true; + result.parabolicDistance = thisDistance; + result.face = thisFace; + result.surfaceNormal = thisSurfaceNormal; + result.overlayID = thisID; + result.intersection = parabola.origin + parabola.velocity * thisDistance + 0.5f * parabola.acceleration * thisDistance * thisDistance; + result.distance = glm::distance(result.intersection, parabola.origin); + result.extraInfo = thisExtraInfo; + } + } + } } - obj.setProperty("face", faceName); - auto intersection = vec3toScriptValue(engine, value.intersection); + return result; +} + +QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value) { + QScriptValue obj = engine->newObject(); + obj.setProperty("intersects", value.intersects); + QScriptValue overlayIDValue = quuidToScriptValue(engine, value.overlayID); + obj.setProperty("overlayID", overlayIDValue); + obj.setProperty("distance", value.distance); + obj.setProperty("face", boxFaceToString(value.face)); + + QScriptValue intersection = vec3toScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); + QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal); + obj.setProperty("surfaceNormal", surfaceNormal); obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo)); return obj; } -void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& objectVar, RayToOverlayIntersectionResult& value) { - QVariantMap object = objectVar.toVariant().toMap(); - value.intersects = object["intersects"].toBool(); - value.overlayID = OverlayID(QUuid(object["overlayID"].toString())); - value.distance = object["distance"].toFloat(); +void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value) { + value.intersects = object.property("intersects").toVariant().toBool(); + QScriptValue overlayIDValue = object.property("overlayID"); + quuidFromScriptValue(overlayIDValue, value.overlayID); + value.distance = object.property("distance").toVariant().toFloat(); + value.face = boxFaceFromString(object.property("face").toVariant().toString()); - QString faceName = object["face"].toString(); - if (faceName == "MIN_X_FACE") { - value.face = MIN_X_FACE; - } else if (faceName == "MAX_X_FACE") { - value.face = MAX_X_FACE; - } else if (faceName == "MIN_Y_FACE") { - value.face = MIN_Y_FACE; - } else if (faceName == "MAX_Y_FACE") { - value.face = MAX_Y_FACE; - } else if (faceName == "MIN_Z_FACE") { - value.face = MIN_Z_FACE; - } else if (faceName == "MAX_Z_FACE") { - value.face = MAX_Z_FACE; - } else { - value.face = UNKNOWN_FACE; - }; - auto intersection = object["intersection"]; + QScriptValue intersection = object.property("intersection"); if (intersection.isValid()) { - bool valid; - auto newIntersection = vec3FromVariant(intersection, valid); - if (valid) { - value.intersection = newIntersection; - } + vec3FromScriptValue(intersection, value.intersection); } - value.extraInfo = object["extraInfo"].toMap(); + QScriptValue surfaceNormal = object.property("surfaceNormal"); + if (surfaceNormal.isValid()) { + vec3FromScriptValue(surfaceNormal, value.surfaceNormal); + } + value.extraInfo = object.property("extraInfo").toVariant().toMap(); } bool Overlays::isLoaded(OverlayID id) { @@ -1048,7 +1056,8 @@ QVector Overlays::findOverlays(const glm::vec3& center, float radius) { i.next(); OverlayID thisID = i.key(); auto overlay = std::dynamic_pointer_cast(i.value()); - if (overlay && overlay->getVisible() && !overlay->getIgnoreRayIntersection() && overlay->isLoaded()) { + // FIXME: this ignores overlays with ignorePickIntersection == true, which seems wrong + if (overlay && overlay->getVisible() && !overlay->getIgnorePickIntersection() && overlay->isLoaded()) { // get AABox in frame of overlay glm::vec3 dimensions = overlay->getDimensions(); glm::vec3 low = dimensions * -0.5f; diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 3debf74f26..21b9e93648 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -59,19 +59,28 @@ class RayToOverlayIntersectionResult { public: bool intersects { false }; OverlayID overlayID { UNKNOWN_OVERLAY_ID }; - float distance { 0 }; + float distance { 0.0f }; BoxFace face { UNKNOWN_FACE }; glm::vec3 surfaceNormal; glm::vec3 intersection; QVariantMap extraInfo; }; - - Q_DECLARE_METATYPE(RayToOverlayIntersectionResult); - QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value); void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value); +class ParabolaToOverlayIntersectionResult { +public: + bool intersects { false }; + OverlayID overlayID { UNKNOWN_OVERLAY_ID }; + float distance { 0.0f }; + float parabolicDistance { 0.0f }; + BoxFace face { UNKNOWN_FACE }; + glm::vec3 surfaceNormal; + glm::vec3 intersection; + QVariantMap extraInfo; +}; + /**jsdoc * The Overlays API provides facilities to create and interact with overlays. Overlays are 2D and 3D objects visible only to * yourself and that aren't persisted to the domain. They are used for UI. @@ -110,6 +119,11 @@ public: const QVector& overlaysToDiscard, bool visibleOnly = false, bool collidableOnly = false); + ParabolaToOverlayIntersectionResult findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking, + const QVector& overlaysToInclude, + const QVector& overlaysToDiscard, + bool visibleOnly = false, bool collidableOnly = false); + bool mousePressEvent(QMouseEvent* event); bool mouseDoublePressEvent(QMouseEvent* event); bool mouseReleaseEvent(QMouseEvent* event); diff --git a/interface/src/ui/overlays/Planar3DOverlay.cpp b/interface/src/ui/overlays/Planar3DOverlay.cpp index f53d06a239..cf2691bb13 100644 --- a/interface/src/ui/overlays/Planar3DOverlay.cpp +++ b/interface/src/ui/overlays/Planar3DOverlay.cpp @@ -71,9 +71,49 @@ QVariant Planar3DOverlay::getProperty(const QString& property) { } bool Planar3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal) { - // FIXME - face and surfaceNormal not being returned - return findRayRectangleIntersection(origin, direction, getWorldOrientation(), getWorldPosition(), getDimensions(), distance); + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { + glm::vec2 xyDimensions = getDimensions(); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition(); + + if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { + glm::vec3 forward = rotation * Vectors::FRONT; + if (glm::dot(forward, direction) > 0.0f) { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } else { + face = MIN_Z_FACE; + surfaceNormal = forward; + } + return true; + } + return false; +} + +bool Planar3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { + glm::vec2 xyDimensions = getDimensions(); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition(); + + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - position); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + + if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { + float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + glm::vec3 forward = rotation * Vectors::FRONT; + if (localIntersectionVelocityZ > 0.0f) { + face = MIN_Z_FACE; + surfaceNormal = forward; + } else { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } + return true; + } + return false; } Transform Planar3DOverlay::evalRenderTransform() { diff --git a/interface/src/ui/overlays/Planar3DOverlay.h b/interface/src/ui/overlays/Planar3DOverlay.h index 0a0e75696e..0054b0baf1 100644 --- a/interface/src/ui/overlays/Planar3DOverlay.h +++ b/interface/src/ui/overlays/Planar3DOverlay.h @@ -31,7 +31,9 @@ public: virtual QVariant getProperty(const QString& property) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) override; + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; protected: glm::vec2 _dimensions; diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.cpp b/interface/src/ui/overlays/Rectangle3DOverlay.cpp index e765f3fc18..48d89fab1c 100644 --- a/interface/src/ui/overlays/Rectangle3DOverlay.cpp +++ b/interface/src/ui/overlays/Rectangle3DOverlay.cpp @@ -140,8 +140,7 @@ const render::ShapeKey Rectangle3DOverlay::getShapeKey() { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index c27faf6f0f..b0d3cf32af 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -160,8 +160,7 @@ void Shape3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index 4743e1ed3a..00a0dd686c 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -60,8 +60,7 @@ Sphere3DOverlay::Sphere3DOverlay(const Sphere3DOverlay* Sphere3DOverlay) : * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp index b128ce7df7..fc4b8b9010 100644 --- a/interface/src/ui/overlays/Text3DOverlay.cpp +++ b/interface/src/ui/overlays/Text3DOverlay.cpp @@ -229,8 +229,7 @@ void Text3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index 3aed2a5b42..c87650a77b 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -76,7 +76,7 @@ QVariant Volume3DOverlay::getProperty(const QString& property) { } bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal) { + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { // extents is the entity relative, scaled, centered extents of the entity glm::mat4 worldToEntityMatrix; Transform transform = getTransform(); @@ -88,7 +88,34 @@ bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::ve // we can use the AABox's ray intersection by mapping our origin and direction into the overlays frame // and testing intersection there. - return _localBoundingBox.findRayIntersection(overlayFrameOrigin, overlayFrameDirection, distance, face, surfaceNormal); + bool hit = _localBoundingBox.findRayIntersection(overlayFrameOrigin, overlayFrameDirection, distance, face, surfaceNormal); + + if (hit) { + surfaceNormal = transform.getRotation() * surfaceNormal; + } + return hit; +} + +bool Volume3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) { + // extents is the entity relative, scaled, centered extents of the entity + glm::mat4 worldToEntityMatrix; + Transform transform = getTransform(); + transform.setScale(1.0f); // ignore any inherited scale from SpatiallyNestable + transform.getInverseMatrix(worldToEntityMatrix); + + glm::vec3 overlayFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); + glm::vec3 overlayFrameVelocity = glm::vec3(worldToEntityMatrix * glm::vec4(velocity, 0.0f)); + glm::vec3 overlayFrameAcceleration = glm::vec3(worldToEntityMatrix * glm::vec4(acceleration, 0.0f)); + + // we can use the AABox's ray intersection by mapping our origin and direction into the overlays frame + // and testing intersection there. + bool hit = _localBoundingBox.findParabolaIntersection(overlayFrameOrigin, overlayFrameVelocity, overlayFrameAcceleration, parabolicDistance, face, surfaceNormal); + + if (hit) { + surfaceNormal = transform.getRotation() * surfaceNormal; + } + return hit; } Transform Volume3DOverlay::evalRenderTransform() { diff --git a/interface/src/ui/overlays/Volume3DOverlay.h b/interface/src/ui/overlays/Volume3DOverlay.h index bde8c71aef..e4060ae335 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.h +++ b/interface/src/ui/overlays/Volume3DOverlay.h @@ -31,7 +31,9 @@ public: QVariant getProperty(const QString& property) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) override; + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; + virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override; protected: // Centered local bounding box diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 8af818edc6..ec4199a32d 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -42,6 +42,7 @@ #include "scripting/MenuScriptingInterface.h" #include "scripting/SettingsScriptingInterface.h" #include +#include #include #include "FileDialogHelper.h" #include "avatar/AvatarManager.h" @@ -137,11 +138,8 @@ void Web3DOverlay::destroyWebSurface() { // Fix for crash in QtWebEngineCore when rapidly switching domains // Call stop on the QWebEngineView before destroying OffscreenQMLSurface. if (rootItem) { - QObject* obj = rootItem->findChild("webEngineView"); - if (obj) { - // stop loading - QMetaObject::invokeMethod(obj, "stop"); - } + // stop loading + QMetaObject::invokeMethod(rootItem, "stop"); } _webSurface->pause(); @@ -151,6 +149,11 @@ void Web3DOverlay::destroyWebSurface() { // If the web surface was fetched out of the cache, release it back into the cache if (_cachedWebSurface) { + // If it's going back into the cache make sure to explicitly set the URL to a blank page + // in order to stop any resource consumption or audio related to the page. + if (rootItem) { + rootItem->setProperty("url", "about:blank"); + } auto offscreenCache = DependencyManager::get(); // FIXME prevents crash on shutdown, but we shoudln't have to do this check if (offscreenCache) { @@ -253,7 +256,9 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("MenuInterface", MenuScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); + _webSurface->getSurfaceContext()->setContextProperty("AvatarBookmarks", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("Render", AbstractViewStateInterface::instance()->getRenderEngine()->getConfiguration().get()); + _webSurface->getSurfaceContext()->setContextProperty("Workload", qApp->getGameWorkload()._engine->getConfiguration().get()); _webSurface->getSurfaceContext()->setContextProperty("Controller", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("Pointers", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("Web3DOverlay", this); @@ -538,8 +543,7 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { * Antonyms: isWire and wire. * @property {boolean} isDashedLine=false - If true, a dashed line is drawn on the overlay's edges. Synonym: * dashed. - * @property {boolean} ignoreRayIntersection=false - If true, - * {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay. + * @property {boolean} ignorePickIntersection=false - If true, picks ignore the overlay. ignoreRayIntersection is a synonym. * @property {boolean} drawInFront=false - If true, the overlay is rendered in front of other overlays that don't * have drawInFront set to true, and in front of entities. * @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed. @@ -622,20 +626,6 @@ void Web3DOverlay::setScriptURL(const QString& scriptURL) { } } -bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) { - glm::vec2 dimensions = getDimensions(); - glm::quat rotation = getWorldOrientation(); - glm::vec3 position = getWorldPosition(); - - if (findRayRectangleIntersection(origin, direction, rotation, position, dimensions, distance)) { - surfaceNormal = rotation * Vectors::UNIT_Z; - face = glm::dot(surfaceNormal, direction) > 0 ? MIN_Z_FACE : MAX_Z_FACE; - return true; - } else { - return false; - } -} - Web3DOverlay* Web3DOverlay::createClone() const { return new Web3DOverlay(this); } diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index d888424cbc..233f4e0d21 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -52,9 +52,6 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) override; - virtual Web3DOverlay* createClone() const override; enum InputMode { diff --git a/interface/src/workload/GameWorkload.cpp b/interface/src/workload/GameWorkload.cpp new file mode 100644 index 0000000000..afbd166c89 --- /dev/null +++ b/interface/src/workload/GameWorkload.cpp @@ -0,0 +1,82 @@ +// +// GameWorkload.cpp +// +// Created by Sam Gateau on 2/16/2018. +// Copyright 2018 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 "GameWorkload.h" +#include "GameWorkloadRenderer.h" +#include +#include +#include + +#include "PhysicsBoundary.h" + +class WorkloadEngineBuilder { +public: + using Inputs = workload::VaryingSet2; + using Outputs = workload::RegionTracker::Outputs; + using JobModel = workload::Task::ModelIO; + void build(JobModel& model, const workload::Varying& in, workload::Varying& out) { + + const auto& inViews = in.getN(0); + const auto& inTimings = in.getN(1); + + const auto usedViews = model.addJob("setupViews", inViews); + + const auto controlViewsIn = workload::ControlViews::Input(usedViews, inTimings).asVarying(); + const auto fixedViews = model.addJob("controlViews", controlViewsIn); + + const auto regionTrackerOut = model.addJob("spaceClassifier", fixedViews); + + model.addJob("PhysicsBoundary", regionTrackerOut); + + model.addJob("SpaceToRender"); + + out = regionTrackerOut; + } +}; + +GameWorkloadContext::GameWorkloadContext(const workload::SpacePointer& space, + const render::ScenePointer& scene, + const PhysicalEntitySimulationPointer& simulation): WorkloadContext(space), + _scene(scene), _simulation(simulation) +{ +} + +GameWorkloadContext::~GameWorkloadContext() { +} + + +GameWorkload::GameWorkload() : + _engine(std::make_shared(WorkloadEngineBuilder::JobModel::create("Workload"), std::shared_ptr())) +{ +} + +GameWorkload::~GameWorkload() { + shutdown(); +} + +void GameWorkload::startup(const workload::SpacePointer& space, + const render::ScenePointer& scene, + const PhysicalEntitySimulationPointer& simulation) { + _engine->reset(std::make_shared(space, scene, simulation)); +} + +void GameWorkload::shutdown() { + _engine.reset(); +} + +void GameWorkload::updateViews(const ViewFrustum& frustum, const glm::vec3& headPosition) { + workload::Views views; + views.emplace_back(workload::View::evalFromFrustum(frustum, headPosition - frustum.getPosition())); + views.emplace_back(workload::View::evalFromFrustum(frustum)); + _engine->feedInput(0, views); +} + +void GameWorkload::updateSimulationTimings(const workload::Timings& timings) { + _engine->feedInput(1, timings); +} diff --git a/interface/src/workload/GameWorkload.h b/interface/src/workload/GameWorkload.h new file mode 100644 index 0000000000..4b08be8a56 --- /dev/null +++ b/interface/src/workload/GameWorkload.h @@ -0,0 +1,45 @@ +// +// GameWorkload.h +// +// Created by Sam Gateau on 2/16/2018. +// Copyright 2018 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_GameWorkload_h +#define hifi_GameWorkload_h + +#include +#include + +#include +#include "PhysicalEntitySimulation.h" + +class GameWorkloadContext : public workload::WorkloadContext { +public: + GameWorkloadContext(const workload::SpacePointer& space, + const render::ScenePointer& scene, + const PhysicalEntitySimulationPointer& simulation); + virtual ~GameWorkloadContext(); + + render::ScenePointer _scene; + PhysicalEntitySimulationPointer _simulation; +}; + +class GameWorkload { +public: + GameWorkload(); + ~GameWorkload(); + + void startup(const workload::SpacePointer& space, + const render::ScenePointer& scene, + const PhysicalEntitySimulationPointer& simulation); + void shutdown(); + + void updateViews(const ViewFrustum& frustum, const glm::vec3& headPosition); + void updateSimulationTimings(const workload::Timings& timings); + + workload::EnginePointer _engine; +}; +#endif // hifi_GameWorkload_h diff --git a/interface/src/workload/GameWorkloadRenderer.cpp b/interface/src/workload/GameWorkloadRenderer.cpp new file mode 100644 index 0000000000..a8b65492d3 --- /dev/null +++ b/interface/src/workload/GameWorkloadRenderer.cpp @@ -0,0 +1,256 @@ +// +// GameWorkloadRender.cpp +// +// Created by Sam Gateau on 2/20/2018. +// Copyright 2018 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 "GameWorkloadRenderer.h" + +#include +#include + +#include +#include + +#include "render-utils/drawWorkloadProxy_vert.h" +#include "render-utils/drawWorkloadView_vert.h" +#include "render-utils/drawWorkloadProxy_frag.h" +#include "render-utils/drawWorkloadView_frag.h" + + +void GameSpaceToRender::configure(const Config& config) { + _freezeViews = config.freezeViews; + _showAllProxies = config.showProxies; + _showAllViews = config.showViews; +} + +void GameSpaceToRender::run(const workload::WorkloadContextPointer& runContext, Outputs& outputs) { + auto gameWorkloadContext = std::dynamic_pointer_cast(runContext); + if (!gameWorkloadContext) { + return; + } + auto space = gameWorkloadContext->_space; + if (!space) { + return; + } + + auto visible = _showAllProxies || _showAllViews; + auto showProxies = _showAllProxies; + auto showViews = _showAllViews; + + render::Transaction transaction; + auto scene = gameWorkloadContext->_scene; + + // Nothing really needed, early exit + if (!visible) { + if (render::Item::isValidID(_spaceRenderItemID)) { + transaction.updateItem(_spaceRenderItemID, [](GameWorkloadRenderItem& item) { + item.setVisible(false); + }); + scene->enqueueTransaction(transaction); + } + return; + } + + workload::Proxy::Vector proxies(space->getNumAllocatedProxies()); + space->copyProxyValues(proxies.data(), (uint32_t)proxies.size()); + + workload::Views views(space->getNumViews()); + space->copyViews(views); + + // Valid space, let's display its content + if (!render::Item::isValidID(_spaceRenderItemID)) { + _spaceRenderItemID = scene->allocateID(); + auto renderItem = std::make_shared(); + renderItem->editBound().setBox(glm::vec3(-16000.0f), 32000.0f); + renderItem->setAllProxies(proxies); + transaction.resetItem(_spaceRenderItemID, std::make_shared(renderItem)); + } + + transaction.updateItem(_spaceRenderItemID, [visible, showProxies, proxies, showViews, views](GameWorkloadRenderItem& item) { + item.setVisible(visible); + item.showProxies(showProxies); + item.setAllProxies(proxies); + item.showViews(showViews); + item.setAllViews(views); + }); + + scene->enqueueTransaction(transaction); +} + +namespace render { + template <> const ItemKey payloadGetKey(const GameWorkloadRenderItem::Pointer& payload) { + return payload->getKey(); + } + template <> const Item::Bound payloadGetBound(const GameWorkloadRenderItem::Pointer& payload) { + if (payload) { + return payload->getBound(); + } + return Item::Bound(); + } + template <> void payloadRender(const GameWorkloadRenderItem::Pointer& payload, RenderArgs* args) { + if (payload) { + payload->render(args); + } + } + template <> const ShapeKey shapeGetShapeKey(const GameWorkloadRenderItem::Pointer& payload) { + return ShapeKey::Builder::ownPipeline(); + } +} + +GameWorkloadRenderItem::GameWorkloadRenderItem() : _key(render::ItemKey::Builder::opaqueShape().withTagBits(render::ItemKey::TAG_BITS_0 | render::ItemKey::TAG_BITS_1)) { +} + +render::ItemKey GameWorkloadRenderItem::getKey() const { + return _key; +} + +void GameWorkloadRenderItem::setVisible(bool isVisible) { + if (isVisible) { + _key = render::ItemKey::Builder(_key).withVisible(); + } else { + _key = render::ItemKey::Builder(_key).withInvisible(); + } +} + +void GameWorkloadRenderItem::showProxies(bool show) { + _showProxies = show; +} + +void GameWorkloadRenderItem::showViews(bool show) { + _showViews = show; +} + + +void GameWorkloadRenderItem::setAllProxies(const workload::Proxy::Vector& proxies) { + _myOwnProxies = proxies; + static const uint32_t sizeOfProxy = sizeof(workload::Proxy); + if (!_allProxiesBuffer) { + _allProxiesBuffer = std::make_shared(sizeOfProxy); + } + + _allProxiesBuffer->setData(proxies.size() * sizeOfProxy, (const gpu::Byte*) proxies.data()); + _numAllProxies = (uint32_t) proxies.size(); +} + +void GameWorkloadRenderItem::setAllViews(const workload::Views& views) { + _myOwnViews = views; + static const uint32_t sizeOfView = sizeof(workload::View); + if (!_allViewsBuffer) { + _allViewsBuffer = std::make_shared(sizeOfView); + } + + _allViewsBuffer->setData(views.size() * sizeOfView, (const gpu::Byte*) views.data()); + _numAllViews = (uint32_t)views.size(); +} + +const gpu::PipelinePointer GameWorkloadRenderItem::getProxiesPipeline() { + if (!_drawAllProxiesPipeline) { + auto vs = drawWorkloadProxy_vert::getShader(); + auto ps = drawWorkloadProxy_frag::getShader(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding("workloadProxiesBuffer", 0)); + gpu::Shader::makeProgram(*program, slotBindings); + + auto state = std::make_shared(); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + /* state->setBlendFunction(true, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::DEST_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ZERO);*/ + + PrepareStencil::testMaskDrawShape(*state); + state->setCullMode(gpu::State::CULL_NONE); + _drawAllProxiesPipeline = gpu::Pipeline::create(program, state); + } + return _drawAllProxiesPipeline; +} + + +const gpu::PipelinePointer GameWorkloadRenderItem::getViewsPipeline() { + if (!_drawAllViewsPipeline) { + auto vs = drawWorkloadView_vert::getShader(); + auto ps = drawWorkloadView_frag::getShader(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding("workloadViewsBuffer", 1)); + slotBindings.insert(gpu::Shader::Binding("drawMeshBuffer", 0)); + gpu::Shader::makeProgram(*program, slotBindings); + + auto state = std::make_shared(); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + /* state->setBlendFunction(true, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::DEST_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ZERO);*/ + + PrepareStencil::testMaskDrawShape(*state); + state->setCullMode(gpu::State::CULL_NONE); + _drawAllViewsPipeline = gpu::Pipeline::create(program, state); + } + return _drawAllViewsPipeline; +} + +const gpu::BufferPointer GameWorkloadRenderItem::getDrawViewBuffer() { + if (!_drawViewBuffer) { + int numSegments = 64; + float angleStep = (float)M_PI * 2.0f / (float)numSegments; + + struct Vert { + glm::vec4 p; + }; + std::vector verts(numSegments + 1); + for (int i = 0; i < numSegments; i++) { + float angle = (float)i * angleStep; + verts[i].p.x = cos(angle); + verts[i].p.y = sin(angle); + verts[i].p.z = angle; + verts[i].p.w = 1.0f; + } + verts[numSegments] = verts[0]; + verts[numSegments].p.w = 0.0f; + + _drawViewBuffer = std::make_shared(verts.size() * sizeof(Vert), (const gpu::Byte*) verts.data()); + _numDrawViewVerts = numSegments + 1; + } + return _drawViewBuffer; +} + +void GameWorkloadRenderItem::render(RenderArgs* args) { + gpu::Batch& batch = *(args->_batch); + + batch.setModelTransform(Transform()); + + batch.setResourceBuffer(0, _allProxiesBuffer); + batch.setResourceBuffer(1, _allViewsBuffer); + + // Show Proxies + if (_showProxies) { + batch.setPipeline(getProxiesPipeline()); + + static const int NUM_VERTICES_PER_PROXY = 3; + batch.draw(gpu::TRIANGLES, NUM_VERTICES_PER_PROXY * _numAllProxies, 0); + } + + // Show Views + if (_showViews) { + batch.setPipeline(getViewsPipeline()); + + batch.setUniformBuffer(0, getDrawViewBuffer()); + static const int NUM_VERTICES_PER_DRAWVIEWVERT = 2; + static const int NUM_REGIONS = 3; + batch.draw(gpu::TRIANGLE_STRIP, NUM_REGIONS * NUM_VERTICES_PER_DRAWVIEWVERT * _numDrawViewVerts * _numAllViews, 0); + + } + + batch.setResourceBuffer(0, nullptr); + batch.setResourceBuffer(1, nullptr); + +} + + + diff --git a/interface/src/workload/GameWorkloadRenderer.h b/interface/src/workload/GameWorkloadRenderer.h new file mode 100644 index 0000000000..a25598821e --- /dev/null +++ b/interface/src/workload/GameWorkloadRenderer.h @@ -0,0 +1,104 @@ +// +// GameWorkloadRender.h +// +// Created by Sam Gateau on 2/20/2018. +// Copyright 2018 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_GameWorkloadRenderer_h +#define hifi_GameWorkloadRenderer_h + +#include "GameWorkload.h" + +class GameSpaceToRenderConfig : public workload::Job::Config { + Q_OBJECT + Q_PROPERTY(bool freezeViews MEMBER freezeViews NOTIFY dirty) + Q_PROPERTY(bool showProxies MEMBER showProxies NOTIFY dirty) + Q_PROPERTY(bool showViews MEMBER showViews NOTIFY dirty) +public: + + bool freezeViews{ false }; + bool showProxies{ false }; + bool showViews{ false }; +signals: + void dirty(); + +protected: +}; + +class GameSpaceToRender { +public: + using Config = GameSpaceToRenderConfig; + using Outputs = render::Transaction; + using JobModel = workload::Job::ModelO; + + GameSpaceToRender() {} + + void configure(const Config& config); + void run(const workload::WorkloadContextPointer& renderContext, Outputs& outputs); + +protected: + render::ItemID _spaceRenderItemID{ render::Item::INVALID_ITEM_ID }; + bool _freezeViews{ false }; + bool _showAllProxies{ false }; + bool _showAllViews{ false }; +}; + + +class GameWorkloadRenderItem { +public: + using Payload = render::Payload; + using Pointer = Payload::DataPointer; + + GameWorkloadRenderItem(); + ~GameWorkloadRenderItem() {} + void render(RenderArgs* args); + + render::Item::Bound& editBound() { return _bound; } + const render::Item::Bound& getBound() { return _bound; } + + void setVisible(bool visible); + void showProxies(bool show); + void showViews(bool show); + + void setAllProxies(const workload::Proxy::Vector& proxies); + void setAllViews(const workload::Views& views); + + render::ItemKey getKey() const; + +protected: + render::Item::Bound _bound; + + workload::Proxy::Vector _myOwnProxies; + gpu::BufferPointer _allProxiesBuffer; + uint32_t _numAllProxies{ 0 }; + + workload::Views _myOwnViews; + gpu::BufferPointer _allViewsBuffer; + uint32_t _numAllViews{ 0 }; + + gpu::PipelinePointer _drawAllProxiesPipeline; + const gpu::PipelinePointer getProxiesPipeline(); + + gpu::PipelinePointer _drawAllViewsPipeline; + const gpu::PipelinePointer getViewsPipeline(); + + uint32_t _numDrawViewVerts{ 0 }; + gpu::BufferPointer _drawViewBuffer; + const gpu::BufferPointer getDrawViewBuffer(); + + render::ItemKey _key; + bool _showProxies{ true }; + bool _showViews{ true }; +}; + +namespace render { + template <> const ItemKey payloadGetKey(const GameWorkloadRenderItem::Pointer& payload); + template <> const Item::Bound payloadGetBound(const GameWorkloadRenderItem::Pointer& payload); + template <> void payloadRender(const GameWorkloadRenderItem::Pointer& payload, RenderArgs* args); + template <> const ShapeKey shapeGetShapeKey(const GameWorkloadRenderItem::Pointer& payload); +} + +#endif diff --git a/interface/src/workload/PhysicsBoundary.cpp b/interface/src/workload/PhysicsBoundary.cpp new file mode 100644 index 0000000000..927121ac04 --- /dev/null +++ b/interface/src/workload/PhysicsBoundary.cpp @@ -0,0 +1,33 @@ +// +// PhysicsBoundary.h +// +// Created by Andrew Meadows 2018.04.05 +// Copyright 2018 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 "PhysicsBoundary.h" + +#include +#include + +#include "workload/GameWorkload.h" + +void PhysicsBoundary::run(const workload::WorkloadContextPointer& context, const Inputs& inputs) { + auto space = context->_space; + if (!space) { + return; + } + GameWorkloadContext* gameContext = static_cast(context.get()); + PhysicalEntitySimulationPointer simulation = gameContext->_simulation; + const auto& regionChanges = inputs.get0(); + for (uint32_t i = 0; i < (uint32_t)regionChanges.size(); ++i) { + const workload::Space::Change& change = regionChanges[i]; + auto entity = space->getOwner(change.proxyId).get(); + if (entity) { + simulation->changeEntity(entity); + } + } +} diff --git a/interface/src/workload/PhysicsBoundary.h b/interface/src/workload/PhysicsBoundary.h new file mode 100644 index 0000000000..c316fa5686 --- /dev/null +++ b/interface/src/workload/PhysicsBoundary.h @@ -0,0 +1,31 @@ +// +// PhysicsBoundary.h +// +// Created by Andrew Meadows 2018.04.05 +// Copyright 2018 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_PhysicsGatekeeper_h +#define hifi_PhysicsGatekeeper_h + +#include +#include +#include + +#include "PhysicalEntitySimulation.h" + +class PhysicsBoundary { +public: + using Config = workload::Job::Config; + using Inputs = workload::RegionTracker::Outputs; + using Outputs = bool; + using JobModel = workload::Job::ModelI; // this doesn't work + + PhysicsBoundary() {} + void configure(const Config& config) { } + void run(const workload::WorkloadContextPointer& context, const Inputs& inputs); +}; + +#endif // hifi_PhysicsGatekeeper_h diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index 9aa315beb8..f30d5605d7 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -105,8 +105,10 @@ QStringList Animation::getJointNames() const { return result; } QStringList names; - foreach (const FBXJoint& joint, _geometry->joints) { - names.append(joint.name); + if (_geometry) { + foreach (const FBXJoint& joint, _geometry->joints) { + names.append(joint.name); + } } return names; } @@ -114,11 +116,15 @@ QStringList Animation::getJointNames() const { QVector Animation::getFrames() const { if (QThread::currentThread() != thread()) { QVector result; - BLOCKING_INVOKE_METHOD(const_cast(this), "getFrames", + BLOCKING_INVOKE_METHOD(const_cast(this), "getFrames", Q_RETURN_ARG(QVector, result)); return result; } - return _geometry->animationFrames; + if (_geometry) { + return _geometry->animationFrames; + } else { + return QVector(); + } } const QVector& Animation::getFramesReference() const { diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 0833b28142..87e33ed95d 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1244,13 +1244,12 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab bool leftArmEnabled, bool rightArmEnabled, float dt, const AnimPose& leftHandPose, const AnimPose& rightHandPose, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, - const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) { + const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo, + const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) { - const bool ENABLE_POLE_VECTORS = false; + const bool ENABLE_POLE_VECTORS = true; const float ELBOW_POLE_VECTOR_BLEND_FACTOR = 0.95f; - int hipsIndex = indexOfJoint("Hips"); - if (leftHandEnabled) { glm::vec3 handPosition = leftHandPose.trans(); @@ -1269,21 +1268,33 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab int handJointIndex = _animSkeleton->nameToJointIndex("LeftHand"); int armJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); int elbowJointIndex = _animSkeleton->nameToJointIndex("LeftForeArm"); - if (ENABLE_POLE_VECTORS && !leftArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { - glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, true); + int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("RightArm"); + if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { + glm::vec3 poleVector; + bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); + if (usePoleVector) { + glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); - // smooth toward desired pole vector from previous pole vector... to reduce jitter - if (!_prevLeftHandPoleVectorValid) { - _prevLeftHandPoleVectorValid = true; - _prevLeftHandPoleVector = poleVector; + if (_smoothPoleVectors) { + // smooth toward desired pole vector from previous pole vector... to reduce jitter + if (!_prevLeftHandPoleVectorValid) { + _prevLeftHandPoleVectorValid = true; + _prevLeftHandPoleVector = sensorPoleVector; + } + glm::quat deltaRot = rotationBetween(_prevLeftHandPoleVector, sensorPoleVector); + glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); + _prevLeftHandPoleVector = smoothDeltaRot * _prevLeftHandPoleVector; + } else { + _prevLeftHandPoleVector = sensorPoleVector; + } + _animVars.set("leftHandPoleVectorEnabled", true); + _animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X); + _animVars.set("leftHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftHandPoleVector)); + } else { + _prevLeftHandPoleVectorValid = false; + _animVars.set("leftHandPoleVectorEnabled", false); } - glm::quat deltaRot = rotationBetween(_prevLeftHandPoleVector, poleVector); - glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); - _prevLeftHandPoleVector = smoothDeltaRot * _prevLeftHandPoleVector; - _animVars.set("leftHandPoleVectorEnabled", true); - _animVars.set("leftHandPoleReferenceVector", Vectors::UNIT_X); - _animVars.set("leftHandPoleVector", _prevLeftHandPoleVector); } else { _prevLeftHandPoleVectorValid = false; _animVars.set("leftHandPoleVectorEnabled", false); @@ -1295,7 +1306,6 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab _animVars.unset("leftHandPosition"); _animVars.unset("leftHandRotation"); _animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition); - } if (rightHandEnabled) { @@ -1316,21 +1326,34 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab int handJointIndex = _animSkeleton->nameToJointIndex("RightHand"); int armJointIndex = _animSkeleton->nameToJointIndex("RightArm"); int elbowJointIndex = _animSkeleton->nameToJointIndex("RightForeArm"); - if (ENABLE_POLE_VECTORS && !rightArmEnabled && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0) { - glm::vec3 poleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, hipsIndex, false); + int oppositeArmJointIndex = _animSkeleton->nameToJointIndex("LeftArm"); - // smooth toward desired pole vector from previous pole vector... to reduce jitter - if (!_prevRightHandPoleVectorValid) { - _prevRightHandPoleVectorValid = true; - _prevRightHandPoleVector = poleVector; + if (ENABLE_POLE_VECTORS && handJointIndex >= 0 && armJointIndex >= 0 && elbowJointIndex >= 0 && oppositeArmJointIndex >= 0) { + glm::vec3 poleVector; + bool usePoleVector = calculateElbowPoleVector(handJointIndex, elbowJointIndex, armJointIndex, oppositeArmJointIndex, poleVector); + if (usePoleVector) { + glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); + + if (_smoothPoleVectors) { + // smooth toward desired pole vector from previous pole vector... to reduce jitter + if (!_prevRightHandPoleVectorValid) { + _prevRightHandPoleVectorValid = true; + _prevRightHandPoleVector = sensorPoleVector; + } + glm::quat deltaRot = rotationBetween(_prevRightHandPoleVector, sensorPoleVector); + glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); + _prevRightHandPoleVector = smoothDeltaRot * _prevRightHandPoleVector; + } else { + _prevRightHandPoleVector = sensorPoleVector; + } + + _animVars.set("rightHandPoleVectorEnabled", true); + _animVars.set("rightHandPoleReferenceVector", -Vectors::UNIT_X); + _animVars.set("rightHandPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightHandPoleVector)); + } else { + _prevRightHandPoleVectorValid = false; + _animVars.set("rightHandPoleVectorEnabled", false); } - glm::quat deltaRot = rotationBetween(_prevRightHandPoleVector, poleVector); - glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, ELBOW_POLE_VECTOR_BLEND_FACTOR); - _prevRightHandPoleVector = smoothDeltaRot * _prevRightHandPoleVector; - - _animVars.set("rightHandPoleVectorEnabled", true); - _animVars.set("rightHandPoleReferenceVector", -Vectors::UNIT_X); - _animVars.set("rightHandPoleVector", _prevRightHandPoleVector); } else { _prevRightHandPoleVectorValid = false; _animVars.set("rightHandPoleVectorEnabled", false); @@ -1345,7 +1368,8 @@ void Rig::updateHands(bool leftHandEnabled, bool rightHandEnabled, bool hipsEnab } } -void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose) { +void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose, + const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix) { const float KNEE_POLE_VECTOR_BLEND_FACTOR = 0.95f; @@ -1360,19 +1384,20 @@ void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose int kneeJointIndex = _animSkeleton->nameToJointIndex("LeftLeg"); int upLegJointIndex = _animSkeleton->nameToJointIndex("LeftUpLeg"); glm::vec3 poleVector = calculateKneePoleVector(footJointIndex, kneeJointIndex, upLegJointIndex, hipsIndex, leftFootPose); + glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); - // smooth toward desired pole vector from previous pole vector... to reduce jitter + // smooth toward desired pole vector from previous pole vector... to reduce jitter, but in sensor space. if (!_prevLeftFootPoleVectorValid) { _prevLeftFootPoleVectorValid = true; - _prevLeftFootPoleVector = poleVector; + _prevLeftFootPoleVector = sensorPoleVector; } - glm::quat deltaRot = rotationBetween(_prevLeftFootPoleVector, poleVector); + glm::quat deltaRot = rotationBetween(_prevLeftFootPoleVector, sensorPoleVector); glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, KNEE_POLE_VECTOR_BLEND_FACTOR); _prevLeftFootPoleVector = smoothDeltaRot * _prevLeftFootPoleVector; _animVars.set("leftFootPoleVectorEnabled", true); _animVars.set("leftFootPoleReferenceVector", Vectors::UNIT_Z); - _animVars.set("leftFootPoleVector", _prevLeftFootPoleVector); + _animVars.set("leftFootPoleVector", transformVectorFast(sensorToRigMatrix, _prevLeftFootPoleVector)); } else { _animVars.unset("leftFootPosition"); _animVars.unset("leftFootRotation"); @@ -1390,19 +1415,20 @@ void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose int kneeJointIndex = _animSkeleton->nameToJointIndex("RightLeg"); int upLegJointIndex = _animSkeleton->nameToJointIndex("RightUpLeg"); glm::vec3 poleVector = calculateKneePoleVector(footJointIndex, kneeJointIndex, upLegJointIndex, hipsIndex, rightFootPose); + glm::vec3 sensorPoleVector = transformVectorFast(rigToSensorMatrix, poleVector); // smooth toward desired pole vector from previous pole vector... to reduce jitter if (!_prevRightFootPoleVectorValid) { _prevRightFootPoleVectorValid = true; - _prevRightFootPoleVector = poleVector; + _prevRightFootPoleVector = sensorPoleVector; } - glm::quat deltaRot = rotationBetween(_prevRightFootPoleVector, poleVector); + glm::quat deltaRot = rotationBetween(_prevRightFootPoleVector, sensorPoleVector); glm::quat smoothDeltaRot = safeMix(deltaRot, Quaternions::IDENTITY, KNEE_POLE_VECTOR_BLEND_FACTOR); _prevRightFootPoleVector = smoothDeltaRot * _prevRightFootPoleVector; _animVars.set("rightFootPoleVectorEnabled", true); _animVars.set("rightFootPoleReferenceVector", Vectors::UNIT_Z); - _animVars.set("rightFootPoleVector", _prevRightFootPoleVector); + _animVars.set("rightFootPoleVector", transformVectorFast(sensorToRigMatrix, _prevRightFootPoleVector)); } else { _animVars.unset("rightFootPosition"); _animVars.unset("rightFootRotation"); @@ -1434,9 +1460,9 @@ void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm glm::quat deltaQuat = desiredQuat * glm::inverse(headQuat); - // limit swing rotation of the deltaQuat by a 30 degree cone. + // limit swing rotation of the deltaQuat by a 25 degree cone. // TODO: use swing twist decomposition constraint instead, for off axis rotation clamping. - const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE; + const float MAX_ANGLE = 25.0f * RADIANS_PER_DEGREE; if (fabsf(glm::angle(deltaQuat)) > MAX_ANGLE) { deltaQuat = glm::angleAxis(glm::clamp(glm::angle(deltaQuat), -MAX_ANGLE, MAX_ANGLE), glm::axis(deltaQuat)); } @@ -1466,39 +1492,73 @@ static glm::quat quatLerp(const glm::quat& q1, const glm::quat& q2, float alpha) return glm::normalize(glm::lerp(q1, temp, alpha)); } -glm::vec3 Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int hipsIndex, bool isLeft) const { - AnimPose hipsPose = _externalPoseSet._absolutePoses[hipsIndex]; +bool Rig::calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int oppositeArmIndex, glm::vec3& poleVector) const { + // The resulting Pole Vector is calculated as the sum of a three vectors. + // The first is the vector with direction shoulder-hand. The module of this vector is inversely proportional to the strength of the resulting Pole Vector. + // The second vector is always perpendicular to previous vector and is part of the plane that contains a point located on the horizontal line, + // pointing forward and with height aprox to the avatar head. The position of the horizontal point will be determined by the hands Y component. + // The third vector apply a weighted correction to the resulting pole vector to avoid interpenetration and force a more natural pose. + + AnimPose oppositeArmPose = _externalPoseSet._absolutePoses[oppositeArmIndex]; AnimPose handPose = _externalPoseSet._absolutePoses[handIndex]; - AnimPose elbowPose = _externalPoseSet._absolutePoses[elbowIndex]; AnimPose armPose = _externalPoseSet._absolutePoses[armIndex]; + AnimPose elbowPose = _externalPoseSet._absolutePoses[elbowIndex]; - // ray from hand to arm. - glm::vec3 d = glm::normalize(handPose.trans() - armPose.trans()); + glm::vec3 armToHand = handPose.trans() - armPose.trans(); + glm::vec3 armToElbow = elbowPose.trans() - armPose.trans(); + glm::vec3 elbowToHand = handPose.trans() - elbowPose.trans(); - float sign = isLeft ? 1.0f : -1.0f; + glm::vec3 backVector = oppositeArmPose.trans() - armPose.trans(); + glm::vec3 backCenter = armPose.trans() + 0.5f * backVector; + + const float OVER_BACK_HEAD_PERCENTAGE = 0.2f; - // form a plane normal to the hips x-axis. - glm::vec3 n = hipsPose.rot() * Vectors::UNIT_X; - glm::vec3 y = hipsPose.rot() * Vectors::UNIT_Y; + glm::vec3 headCenter = backCenter + glm::vec3(0, OVER_BACK_HEAD_PERCENTAGE * backVector.length(), 0); + glm::vec3 frontVector = glm::normalize(glm::cross(backVector, glm::vec3(0, 1, 0))); + // Make sure is pointing forward + frontVector = frontVector.z < 0 ? -frontVector : frontVector; - // project d onto this plane - glm::vec3 dProj = d - glm::dot(d, n) * n; + float horizontalModule = glm::dot(armToHand, glm::vec3(0, -1, 0)); + glm::vec3 headForward = headCenter + horizontalModule * frontVector; - // give dProj a bit of offset away from the body. - float avatarScale = extractUniformScale(_modelOffset); - const float LATERAL_OFFSET = 1.0f * avatarScale; - const float VERTICAL_OFFSET = -0.333f * avatarScale; - glm::vec3 dProjWithOffset = dProj + sign * LATERAL_OFFSET * n + y * VERTICAL_OFFSET; + glm::vec3 armToHead = headForward - armPose.trans(); + + float armToHandDistance = glm::length(armToHand); + float armToElbowDistance = glm::length(armToElbow); + float elbowToHandDistance = glm::length(elbowToHand); + float armTotalDistance = armToElbowDistance + elbowToHandDistance; - // rotate dProj by 90 degrees to get the poleVector. - glm::vec3 poleVector = glm::angleAxis(PI / 2.0f, n) * dProjWithOffset; + glm::vec3 armToHandDir = armToHand / armToHandDistance; + glm::vec3 armToHeadPlaneNormal = glm::cross(armToHead, armToHandDir); - // blend the wrist oreintation into the pole vector to reduce the painfully bent wrist problem. - glm::quat elbowToHandDelta = handPose.rot() * glm::inverse(elbowPose.rot()); - const float WRIST_POLE_ADJUST_FACTOR = 0.5f; - glm::quat poleAdjust = quatLerp(Quaternions::IDENTITY, elbowToHandDelta, WRIST_POLE_ADJUST_FACTOR); + // How much the hand is reaching for the opposite side + float oppositeProjection = glm::dot(armToHandDir, glm::normalize(backVector)); + + // Don't use pole vector when the hands are behind + if (glm::dot(frontVector, armToHand) < 0 && oppositeProjection < 0.5f * armTotalDistance) { + return false; + } - return glm::normalize(poleAdjust * poleVector); + // The strenght of the resulting pole determined by the arm flex. + float armFlexCoeficient = armToHandDistance / armTotalDistance; + glm::vec3 attenuationVector = armFlexCoeficient * armToHandDir; + // Pole vector is perpendicular to the shoulder-hand direction and located on the plane that contains the head-forward line + glm::vec3 fullPoleVector = glm::normalize(glm::cross(armToHeadPlaneNormal, armToHandDir)); + + // Push elbow forward when hand reaches opposite side + glm::vec3 correctionVector = glm::vec3(0, 0, 0); + + const float FORWARD_TRIGGER_PERCENTAGE = 0.2f; + const float FORWARD_CORRECTOR_WEIGHT = 3.0f; + + float elbowForwardTrigger = FORWARD_TRIGGER_PERCENTAGE * armToHandDistance; + + if (oppositeProjection > -elbowForwardTrigger) { + float forwardAmount = FORWARD_CORRECTOR_WEIGHT * (elbowForwardTrigger + oppositeProjection); + correctionVector = forwardAmount * frontVector; + } + poleVector = glm::normalize(attenuationVector + fullPoleVector + correctionVector); + return true; } glm::vec3 Rig::calculateKneePoleVector(int footJointIndex, int kneeIndex, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const { @@ -1546,16 +1606,18 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo bool spine2Enabled = params.primaryControllerFlags[PrimaryControllerType_Spine2] & (uint8_t)ControllerFlags::Enabled; bool leftArmEnabled = params.secondaryControllerFlags[SecondaryControllerType_LeftArm] & (uint8_t)ControllerFlags::Enabled; bool rightArmEnabled = params.secondaryControllerFlags[SecondaryControllerType_RightArm] & (uint8_t)ControllerFlags::Enabled; + glm::mat4 sensorToRigMatrix = glm::inverse(params.rigToSensorMatrix); updateHead(headEnabled, hipsEnabled, params.primaryControllerPoses[PrimaryControllerType_Head]); updateHands(leftHandEnabled, rightHandEnabled, hipsEnabled, hipsEstimated, leftArmEnabled, rightArmEnabled, dt, params.primaryControllerPoses[PrimaryControllerType_LeftHand], params.primaryControllerPoses[PrimaryControllerType_RightHand], - params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo); + params.hipsShapeInfo, params.spineShapeInfo, params.spine1ShapeInfo, params.spine2ShapeInfo, + params.rigToSensorMatrix, sensorToRigMatrix); updateFeet(leftFootEnabled, rightFootEnabled, - params.primaryControllerPoses[PrimaryControllerType_LeftFoot], params.primaryControllerPoses[PrimaryControllerType_RightFoot]); - + params.primaryControllerPoses[PrimaryControllerType_LeftFoot], params.primaryControllerPoses[PrimaryControllerType_RightFoot], + params.rigToSensorMatrix, sensorToRigMatrix); if (headEnabled) { // Blend IK chains toward the joint limit centers, this should stablize head and hand ik. diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index e30b5d655c..1a1337fa84 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -75,6 +75,7 @@ public: }; struct ControllerParameters { + glm::mat4 rigToSensorMatrix; AnimPose primaryControllerPoses[NumPrimaryControllerTypes]; // rig space uint8_t primaryControllerFlags[NumPrimaryControllerTypes]; AnimPose secondaryControllerPoses[NumSecondaryControllerTypes]; // rig space @@ -216,7 +217,7 @@ public: // input assumed to be in rig space void computeHeadFromHMD(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut) const; - + void toggleSmoothPoleVectors() { _smoothPoleVectors = !_smoothPoleVectors; }; signals: void onLoadComplete(); @@ -231,17 +232,20 @@ protected: bool leftArmEnabled, bool rightArmEnabled, float dt, const AnimPose& leftHandPose, const AnimPose& rightHandPose, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, - const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo); - void updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose); + const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo, + const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix); + void updateFeet(bool leftFootEnabled, bool rightFootEnabled, const AnimPose& leftFootPose, const AnimPose& rightFootPose, + const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix); void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAt, const glm::vec3& saccade); void calcAnimAlpha(float speed, const std::vector& referenceSpeeds, float* alphaOut) const; - glm::vec3 calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int hipsIndex, bool isLeft) const; + bool calculateElbowPoleVector(int handIndex, int elbowIndex, int armIndex, int oppositeArmIndex, glm::vec3& poleVector) const; glm::vec3 calculateKneePoleVector(int footJointIndex, int kneeJoint, int upLegIndex, int hipsIndex, const AnimPose& targetFootPose) const; glm::vec3 deflectHandFromTorso(const glm::vec3& handPosition, const FBXJointShapeInfo& hipsShapeInfo, const FBXJointShapeInfo& spineShapeInfo, const FBXJointShapeInfo& spine1ShapeInfo, const FBXJointShapeInfo& spine2ShapeInfo) const; + AnimPose _modelOffset; // model to rig space AnimPose _geometryOffset; // geometry to model space (includes unit offset & fst offsets) AnimPose _invGeometryOffset; @@ -359,17 +363,19 @@ protected: int _nextStateHandlerId { 0 }; QMutex _stateMutex; - glm::vec3 _prevRightFootPoleVector { Vectors::UNIT_Z }; + glm::vec3 _prevRightFootPoleVector { Vectors::UNIT_Z }; // sensor space bool _prevRightFootPoleVectorValid { false }; - glm::vec3 _prevLeftFootPoleVector { Vectors::UNIT_Z }; + glm::vec3 _prevLeftFootPoleVector { Vectors::UNIT_Z }; // sensor space bool _prevLeftFootPoleVectorValid { false }; - glm::vec3 _prevRightHandPoleVector { -Vectors::UNIT_Z }; - bool _prevRightHandPoleVectorValid { false }; + glm::vec3 _prevRightHandPoleVector{ -Vectors::UNIT_Z }; // sensor space + bool _prevRightHandPoleVectorValid{ false }; - glm::vec3 _prevLeftHandPoleVector { -Vectors::UNIT_Z }; - bool _prevLeftHandPoleVectorValid { false }; + glm::vec3 _prevLeftHandPoleVector{ -Vectors::UNIT_Z }; // sensor space + bool _prevLeftHandPoleVectorValid{ false }; + + bool _smoothPoleVectors { false }; int _rigId; }; diff --git a/libraries/audio-client/CMakeLists.txt b/libraries/audio-client/CMakeLists.txt index d419a2fb7a..6ca7962c39 100644 --- a/libraries/audio-client/CMakeLists.txt +++ b/libraries/audio-client/CMakeLists.txt @@ -1,5 +1,8 @@ set(TARGET_NAME audio-client) -setup_hifi_library(Network Multimedia) +if (ANDROID) + set(PLATFORM_QT_COMPONENTS AndroidExtras) +endif () +setup_hifi_library(Network Multimedia ${PLATFORM_QT_COMPONENTS}) link_hifi_libraries(audio plugins) include_hifi_library_headers(shared) include_hifi_library_headers(networking) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index a5f79290cd..c57360b09f 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -52,6 +52,10 @@ #include "AudioLogging.h" #include "AudioHelpers.h" +#if defined(Q_OS_ANDROID) +#include +#endif + const int AudioClient::MIN_BUFFER_FRAMES = 1; const int AudioClient::MAX_BUFFER_FRAMES = 20; @@ -60,7 +64,7 @@ static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 100; #if defined(Q_OS_ANDROID) static const int CHECK_INPUT_READS_MSECS = 2000; -static const int MIN_READS_TO_CONSIDER_INPUT_ALIVE = 100; +static const int MIN_READS_TO_CONSIDER_INPUT_ALIVE = 10; #endif static const auto DEFAULT_POSITION_GETTER = []{ return Vectors::ZERO; }; @@ -235,7 +239,7 @@ AudioClient::AudioClient() : // start a thread to detect any device changes _checkDevicesTimer = new QTimer(this); - connect(_checkDevicesTimer, &QTimer::timeout, [this] { + connect(_checkDevicesTimer, &QTimer::timeout, this, [this] { QtConcurrent::run(QThreadPool::globalInstance(), [this] { checkDevices(); }); }); const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000; @@ -243,7 +247,7 @@ AudioClient::AudioClient() : // start a thread to detect peak value changes _checkPeakValuesTimer = new QTimer(this); - connect(_checkPeakValuesTimer, &QTimer::timeout, [this] { + connect(_checkPeakValuesTimer, &QTimer::timeout, this, [this] { QtConcurrent::run(QThreadPool::globalInstance(), [this] { checkPeakValues(); }); }); const unsigned long PEAK_VALUES_CHECK_INTERVAL_MSECS = 50; @@ -482,6 +486,15 @@ bool nativeFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, audioFormat.setSampleType(QAudioFormat::SignedInt); audioFormat.setByteOrder(QAudioFormat::LittleEndian); +#if defined(Q_OS_ANDROID) + // Using the HW sample rate (AUDIO_INPUT_FLAG_FAST) in some samsung phones causes a low volume at input stream + // Changing the sample rate forces a resampling that (in samsung) amplifies +18 dB + QAndroidJniObject brand = QAndroidJniObject::getStaticObjectField("android/os/Build", "BRAND"); + if (audioDevice == QAudioDeviceInfo::defaultInputDevice() && brand.toString().contains("samsung", Qt::CaseInsensitive)) { + audioFormat.setSampleRate(24000); + } +#endif + if (!audioDevice.isFormatSupported(audioFormat)) { qCWarning(audioclient) << "The native format is" << audioFormat << "but isFormatSupported() failed."; return false; @@ -635,9 +648,7 @@ void AudioClient::start() { qCDebug(audioclient) << "The closest format available is" << outputDeviceInfo.nearestFormat(_desiredOutputFormat); } #if defined(Q_OS_ANDROID) - connect(&_checkInputTimer, &QTimer::timeout, [this] { - checkInputTimeout(); - }); + connect(&_checkInputTimer, &QTimer::timeout, this, &AudioClient::checkInputTimeout); _checkInputTimer.start(CHECK_INPUT_READS_MSECS); #endif } @@ -651,6 +662,7 @@ void AudioClient::stop() { switchOutputToAudioDevice(QAudioDeviceInfo(), true); #if defined(Q_OS_ANDROID) _checkInputTimer.stop(); + disconnect(&_checkInputTimer, &QTimer::timeout, 0, 0); #endif } @@ -1426,6 +1438,8 @@ bool AudioClient::setIsStereoInput(bool isStereoInput) { // restart the input device switchInputToAudioDevice(_inputDeviceInfo); + + emit isStereoInputChanged(_isStereoInput); } return stereoInputChanged; @@ -1463,6 +1477,8 @@ void AudioClient::outputFormatChanged() { } bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo inputDeviceInfo, bool isShutdownRequest) { + Q_ASSERT_X(QThread::currentThread() == thread(), Q_FUNC_INFO, "Function invoked on wrong thread"); + qCDebug(audioclient) << __FUNCTION__ << "inputDeviceInfo: [" << inputDeviceInfo.deviceName() << "]"; bool supportedFormat = false; @@ -1554,9 +1570,7 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo inputDeviceInf #if defined(Q_OS_ANDROID) if (_audioInput) { _shouldRestartInputSetup = true; - connect(_audioInput, &QAudioInput::stateChanged, [this](QAudio::State state) { - audioInputStateChanged(state); - }); + connect(_audioInput, &QAudioInput::stateChanged, this, &AudioClient::audioInputStateChanged); } #endif _inputDevice = _audioInput->start(); @@ -1663,6 +1677,8 @@ void AudioClient::outputNotify() { } bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo outputDeviceInfo, bool isShutdownRequest) { + Q_ASSERT_X(QThread::currentThread() == thread(), Q_FUNC_INFO, "Function invoked on wrong thread"); + qCDebug(audioclient) << "AudioClient::switchOutputToAudioDevice() outputDeviceInfo: [" << outputDeviceInfo.deviceName() << "]"; bool supportedFormat = false; @@ -1758,7 +1774,7 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo outputDeviceI _outputScratchBuffer = new int16_t[_outputPeriod]; // size local output mix buffer based on resampled network frame size - int networkPeriod = _localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_STEREO); + int networkPeriod = _localToOutputResampler ? _localToOutputResampler->getMaxOutput(AudioConstants::NETWORK_FRAME_SAMPLES_STEREO) : AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; _localOutputMixBuffer = new float[networkPeriod]; // local period should be at least twice the output period, @@ -2021,7 +2037,7 @@ void AudioClient::setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 sca void AudioClient::startThread() { - moveToNewNamedThread(this, "Audio Thread", [this] { start(); }); + moveToNewNamedThread(this, "Audio Thread", [this] { start(); }, QThread::TimeCriticalPriority); } void AudioClient::setInputVolume(float volume, bool emitSignal) { diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h index 30cbceeb0e..bbfd79d0aa 100644 --- a/libraries/audio/src/AbstractAudioInterface.h +++ b/libraries/audio/src/AbstractAudioInterface.h @@ -44,6 +44,9 @@ public slots: virtual bool setIsStereoInput(bool stereo) = 0; virtual bool isStereoInput() = 0; + +signals: + void isStereoInputChanged(bool isStereo); }; Q_DECLARE_METATYPE(AbstractAudioInterface*) diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index cd93f7b52e..67f9952771 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -31,6 +31,8 @@ #include "AudioLogging.h" #include "AudioSRC.h" +#include "flump3dec.h" + QScriptValue soundSharedPointerToScriptValue(QScriptEngine* engine, const SharedSoundPointer& in) { return engine->newQObject(new SoundScriptingInterface(in), QScriptEngine::ScriptOwnership); } @@ -90,19 +92,35 @@ void SoundProcessor::run() { QString fileName = _url.fileName().toLower(); static const QString WAV_EXTENSION = ".wav"; + static const QString MP3_EXTENSION = ".mp3"; static const QString RAW_EXTENSION = ".raw"; + if (fileName.endsWith(WAV_EXTENSION)) { QByteArray outputAudioByteArray; int sampleRate = interpretAsWav(rawAudioByteArray, outputAudioByteArray); if (sampleRate == 0) { - qCDebug(audio) << "Unsupported WAV file type"; + qCWarning(audio) << "Unsupported WAV file type"; emit onError(300, "Failed to load sound file, reason: unsupported WAV file type"); return; } downSample(outputAudioByteArray, sampleRate); + + } else if (fileName.endsWith(MP3_EXTENSION)) { + + QByteArray outputAudioByteArray; + + int sampleRate = interpretAsMP3(rawAudioByteArray, outputAudioByteArray); + if (sampleRate == 0) { + qCWarning(audio) << "Unsupported MP3 file type"; + emit onError(300, "Failed to load sound file, reason: unsupported MP3 file type"); + return; + } + + downSample(outputAudioByteArray, sampleRate); + } else if (fileName.endsWith(RAW_EXTENSION)) { // check if this was a stereo raw file // since it's raw the only way for us to know that is if the file was called .stereo.raw @@ -113,8 +131,9 @@ void SoundProcessor::run() { // Process as 48khz RAW file downSample(rawAudioByteArray, 48000); + } else { - qCDebug(audio) << "Unknown sound file type"; + qCWarning(audio) << "Unknown sound file type"; emit onError(300, "Failed to load sound file, reason: unknown sound file type"); return; } @@ -204,7 +223,7 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA // Read the "RIFF" chunk RIFFHeader riff; if (waveStream.readRawData((char*)&riff, sizeof(RIFFHeader)) != sizeof(RIFFHeader)) { - qCDebug(audio) << "Not a valid WAVE file."; + qCWarning(audio) << "Not a valid WAVE file."; return 0; } @@ -212,11 +231,11 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA if (strncmp(riff.descriptor.id, "RIFF", 4) == 0) { waveStream.setByteOrder(QDataStream::LittleEndian); } else { - qCDebug(audio) << "Currently not supporting big-endian audio files."; + qCWarning(audio) << "Currently not supporting big-endian audio files."; return 0; } if (strncmp(riff.type, "WAVE", 4) != 0) { - qCDebug(audio) << "Not a valid WAVE file."; + qCWarning(audio) << "Not a valid WAVE file."; return 0; } @@ -224,7 +243,7 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA chunk fmt; while (true) { if (waveStream.readRawData((char*)&fmt, sizeof(chunk)) != sizeof(chunk)) { - qCDebug(audio) << "Not a valid WAVE file."; + qCWarning(audio) << "Not a valid WAVE file."; return 0; } if (strncmp(fmt.id, "fmt ", 4) == 0) { @@ -236,14 +255,14 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA // Read the "fmt " chunk WAVEFormat wave; if (waveStream.readRawData((char*)&wave, sizeof(WAVEFormat)) != sizeof(WAVEFormat)) { - qCDebug(audio) << "Not a valid WAVE file."; + qCWarning(audio) << "Not a valid WAVE file."; return 0; } // Parse the "fmt " chunk if (qFromLittleEndian(wave.audioFormat) != WAVEFORMAT_PCM && qFromLittleEndian(wave.audioFormat) != WAVEFORMAT_EXTENSIBLE) { - qCDebug(audio) << "Currently not supporting non PCM audio files."; + qCWarning(audio) << "Currently not supporting non PCM audio files."; return 0; } if (qFromLittleEndian(wave.numChannels) == 2) { @@ -251,11 +270,11 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA } else if (qFromLittleEndian(wave.numChannels) == 4) { _isAmbisonic = true; } else if (qFromLittleEndian(wave.numChannels) != 1) { - qCDebug(audio) << "Currently not supporting audio files with other than 1/2/4 channels."; + qCWarning(audio) << "Currently not supporting audio files with other than 1/2/4 channels."; return 0; } if (qFromLittleEndian(wave.bitsPerSample) != 16) { - qCDebug(audio) << "Currently not supporting non 16bit audio files."; + qCWarning(audio) << "Currently not supporting non 16bit audio files."; return 0; } @@ -266,7 +285,7 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA chunk data; while (true) { if (waveStream.readRawData((char*)&data, sizeof(chunk)) != sizeof(chunk)) { - qCDebug(audio) << "Not a valid WAVE file."; + qCWarning(audio) << "Not a valid WAVE file."; return 0; } if (strncmp(data.id, "data", 4) == 0) { @@ -279,10 +298,101 @@ int SoundProcessor::interpretAsWav(const QByteArray& inputAudioByteArray, QByteA quint32 outputAudioByteArraySize = qFromLittleEndian(data.size); outputAudioByteArray.resize(outputAudioByteArraySize); if (waveStream.readRawData(outputAudioByteArray.data(), outputAudioByteArraySize) != (int)outputAudioByteArraySize) { - qCDebug(audio) << "Error reading WAV file"; + qCWarning(audio) << "Error reading WAV file"; return 0; } _duration = (float)(outputAudioByteArraySize / (wave.sampleRate * wave.numChannels * wave.bitsPerSample / 8.0f)); return wave.sampleRate; } + +// returns MP3 sample rate, used for resampling +int SoundProcessor::interpretAsMP3(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) { + using namespace flump3dec; + + static const int MP3_SAMPLES_MAX = 1152; + static const int MP3_CHANNELS_MAX = 2; + static const int MP3_BUFFER_SIZE = MP3_SAMPLES_MAX * MP3_CHANNELS_MAX * sizeof(int16_t); + uint8_t mp3Buffer[MP3_BUFFER_SIZE]; + + // create bitstream + Bit_stream_struc *bitstream = bs_new(); + if (bitstream == nullptr) { + return 0; + } + + // create decoder + mp3tl *decoder = mp3tl_new(bitstream, MP3TL_MODE_16BIT); + if (decoder == nullptr) { + bs_free(bitstream); + return 0; + } + + // initialize + bs_set_data(bitstream, (uint8_t*)inputAudioByteArray.data(), inputAudioByteArray.size()); + int frameCount = 0; + int sampleRate = 0; + int numChannels = 0; + + // skip ID3 tag, if present + Mp3TlRetcode result = mp3tl_skip_id3(decoder); + + while (!(result == MP3TL_ERR_NO_SYNC || result == MP3TL_ERR_NEED_DATA)) { + + mp3tl_sync(decoder); + + // find MP3 header + const fr_header *header = nullptr; + result = mp3tl_decode_header(decoder, &header); + + if (result == MP3TL_ERR_OK) { + + if (frameCount++ == 0) { + + qCDebug(audio) << "Decoding MP3 with bitrate =" << header->bitrate + << "sample rate =" << header->sample_rate + << "channels =" << header->channels; + + // save header info + sampleRate = header->sample_rate; + numChannels = header->channels; + + // skip Xing header, if present + result = mp3tl_skip_xing(decoder, header); + } + + // decode MP3 frame + if (result == MP3TL_ERR_OK) { + + result = mp3tl_decode_frame(decoder, mp3Buffer, MP3_BUFFER_SIZE); + + // fill bad frames with silence + int len = header->frame_samples * header->channels * sizeof(int16_t); + if (result == MP3TL_ERR_BAD_FRAME) { + memset(mp3Buffer, 0, len); + } + + if (result == MP3TL_ERR_OK || result == MP3TL_ERR_BAD_FRAME) { + outputAudioByteArray.append((char*)mp3Buffer, len); + } + } + } + } + + // free decoder + mp3tl_free(decoder); + + // free bitstream + bs_free(bitstream); + + int outputAudioByteArraySize = outputAudioByteArray.size(); + if (outputAudioByteArraySize == 0) { + qCWarning(audio) << "Error decoding MP3 file"; + return 0; + } + + _isStereo = (numChannels == 2); + _isAmbisonic = false; + _duration = (float)outputAudioByteArraySize / (sampleRate * numChannels * sizeof(int16_t)); + return sampleRate; +} diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index 4cfdac7792..061c4a2417 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -62,6 +62,7 @@ public: void downSample(const QByteArray& rawAudioByteArray, int sampleRate); int interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray); + int interpretAsMP3(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray); signals: void onSuccess(QByteArray data, bool stereo, bool ambisonic, float duration); diff --git a/libraries/audio/src/flump3dec.cpp b/libraries/audio/src/flump3dec.cpp new file mode 100644 index 0000000000..935ddecabb --- /dev/null +++ b/libraries/audio/src/flump3dec.cpp @@ -0,0 +1,8205 @@ +/* + * FLUENDO S.A. + * Copyright (C) <2005 - 2011> + * + * This Source Code is licensed under MIT license and the explanations attached + * in MIT License Statements. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * MIT license Statements for Fluendo's mp3 plug-in Source Code + * ------------------------------------------------------------ + * + * Fluendo's mp3 software Source Code (the "Source Code") is licensed under the + * MIT license provisions. + * + * The MIT license is an open source license that permits the User to operate and + * use in many forms the Source Code, which would be governed under its + * regulations. + * + * The purpose of this note is to clarify the intellectual property rights granted + * over the Source Code by Fluendo, as well as other legal issues that concern + * your use of it. + * + * MIT license contents and provisions + * ----------------------------------- + * + * The MIT license allows you to do the following things with the Source Code: + * + * - Copy and use the Source Code alone or jointly with other code for any + * purposes. + * Copy of the Source Code is not limited and is royalty-free. + * + * - Merge the Source Code with other code for developing new applications with no + * limits. + * + * - Modifying the Source Code for developing the plug-in or for implementing the + * plug-in in other applications for any purposes. The MIT License does not + * require you to share these modifications with anyone. + * + * - Publish, distribute, sublicense and sell copies of the Source Code to third + * parties. + * + * - Permit anyone to whom the Source Code is licensed to enjoy the rights above + * subject to the MIT license provisions. + * + * By licensing this Source Code under the MIT License, Fluendo is offering to the + * community the rights set out above without restriction and without any + * obligation for the User of the Source Code to release his/her modifications + * back to the community. Anyone operating with the Source Code released from + * Fluendo must grant the same MIT license rights to the community, except for any + * modifications operated on the Source Code which can be granted under a + * different license (even a proprietary license). + * + * All these rights granted to the User for the Source Code hold a limitation + * which is to include MIT permission notice and the following copyright notice: + * "Copyright 2005 Fluendo, S.L. This Source Code is licensed under MIT license + * and the explanations attached in MIT License Statements". These notices shall + * be included in all copies of the Source Code or in substantial parts of the + * Source Code which may be released separately or with modifications. + * + * Patents over the plug-in and/or Source Code + * ------------------------------------------- + * + * The binaries that can be created by compiling this Source Code released by + * Fluendo might be covered by patents in various parts of the world. Fluendo + * does not own or claim to own any patents on the techniques used in the code. + * (Such patents are owned or claimed to be owned by Thompson Licensing, S.A. and + * some other entities as the case may be). + * + * Fluendo has got the relevant licenses to cover its own activities with the + * Source Code but it is not authorized to sublicense nor to grant the rights + * which it has acquired over the patents. In this sense, you can work and deal + * freely with the Source Code under MIT provisions set out above, bearing in mind + * that some activities might not be allowed under applicable patent regulations + * and that Fluendo is not granting any rights in relation to such patents. + * + * The patent license granted to Fluendo only covers Fluendo's own Software and + * Source Code activities. In any case, this software license does not allow you + * to redistribute or copy complete, ready to use mp3 software decoder binaries + * made from the Source Code as made available by Fluendo. You can of course + * distribute binaries you make yourself under any terms allowed by the MIT + * license and whatever necessary rights you have or have acquired according to + * applicable patent regulations. + * + * As Fluendo can not assure that any of the activities you undertake do not + * infringe any patents or other industrial or intellectual property rights, + * Fluendo hereby disclaims any liability for any patent infringement that may be + * claimed to you or to any other person from any legitimate right’s owner, as + * stated in MIT license. So it is your responsibility to get information and to + * acquire the necessary patent licenses to undertake your activities legally. + */ + +// +// Modifications and bug fixes copyright 2018 High Fidelity, Inc. +// Now passes ISO/IEC 11172-4 "full accuracy" compliance testing. +// + +#include "flump3dec.h" + +#include +#include + +namespace flump3dec { + +/*********************************************************************** +* +* Global Definitions +* +***********************************************************************/ + +#ifdef __GNUC__ +#define G_LIKELY(expr) (__builtin_expect ((expr), 1)) +#define G_UNLIKELY(expr) (__builtin_expect ((expr), 0)) +#else +#define G_LIKELY +#define G_UNLIKELY +#endif + +#ifdef __GNUC__ +#define ATTR_UNUSED __attribute__ ((unused)) +#else +#define ATTR_UNUSED +#endif + +#if defined(_MSC_VER) +#define STATIC_INLINE static __inline +#elif defined(__GNUC__) +#define STATIC_INLINE static __inline__ +#else +#define STATIC_INLINE static inline +#endif + +#define __CACHE_LINE_BYTES 16 +#define __CACHE_LINE_ALIGN(a) ((((gsize)a) + __CACHE_LINE_BYTES - 1) & ~(__CACHE_LINE_BYTES - 1)) + +#if defined(_MSC_VER) +#define __CACHE_LINE_DECL_ALIGN(x) __declspec (align(__CACHE_LINE_BYTES)) x +#elif defined(__GNUC__) +#define __CACHE_LINE_DECL_ALIGN(x) x __attribute__ ((aligned (__CACHE_LINE_BYTES))) +#else +#define __CACHE_LINE_DECL_ALIGN(x) x +#endif + +#if defined(_MSC_VER) +#define BYTESWAP32(p) _byteswap_ulong(*(uint32_t *)(p)) +#elif defined(__GNUC__) +#define BYTESWAP32(p) __builtin_bswap32(*(uint32_t *)(p)) +#else +#define BYTESWAP32(p) (uint32_t)(((p)[0] << 24) | ((p)[1] << 16) | ((p)[2] << 8) | ((p)[3] << 0)) +#endif + +#define SYNC_WORD_LNGTH 11 +#define HEADER_LNGTH 21 +#define SYNC_WORD ((guint32)(0x7ff)) + +/* General Definitions */ +#ifndef PI +#define PI 3.141592653589793115997963468544185161590576171875 +#endif + +#define PI4 PI/4 +#define PI64 PI/64 + +/* Maximum samples per sub-band */ +#define SSLIMIT 18 +/* Maximum number of sub-bands */ +#define SBLIMIT 32 + +#define HAN_SIZE 512 +#define SCALE_BLOCK 12 +#define SCALE_RANGE 64 +#define SCALE 32768 +#define CRC16_POLYNOMIAL 0x8005 + +/* MPEG Header Definitions - ID Bit Values */ +#define MPEG_VERSION_1 0x03 +#define MPEG_VERSION_2 0x02 +#define MPEG_VERSION_2_5 0x00 + +/* MPEG Header Definitions - Mode Values */ +#define MPG_MD_STEREO 0 +#define MPG_MD_JOINT_STEREO 1 +#define MPG_MD_DUAL_CHANNEL 2 +#define MPG_MD_MONO 3 + +/*********************************************************************** +* +* Global Type Definitions +* +***********************************************************************/ + +/* Structure for Reading Layer II Allocation Tables from File */ +typedef struct +{ + unsigned int steps; + unsigned int bits; + unsigned int group; + unsigned int quant; +} sb_alloc; + +typedef sb_alloc al_table[SBLIMIT][16]; + +/* Layer III side information. */ +typedef struct +{ + guint part2_3_length; + guint big_values; + guint global_gain; + guint scalefac_compress; + guint window_switching_flag; + guint block_type; + guint mixed_block_flag; + guint table_select[3]; + guint subblock_gain[3]; + guint region0_count; + guint region1_count; + guint preflag; + guint scalefac_scale; + guint count1table_select; +} gr_info_t; + +typedef struct +{ + guint main_data_begin; + guint private_bits; + guint scfsi[4][2]; /* [band?][channel] */ + gr_info_t gr[2][2]; /* [group][channel] */ +} III_side_info_t; + +/* Layer III scale factors. */ +typedef struct +{ + int l[22]; /* [cb] */ + int s[3][13]; /* [window][cb] */ +} III_scalefac_t[2]; /* [ch] */ + +/* top level structure for frame decoding */ +typedef struct +{ + fr_header header; /* raw header information */ + int actual_mode; /* when writing IS, may forget if 0 chs */ + int stereo; /* 1 for mono, 2 for stereo */ + int jsbound; /* first band of joint stereo coding */ + int sblimit; /* total number of sub bands */ + const al_table *alloc; /* bit allocation table for the frame */ + + /* Synthesis workspace */ + __CACHE_LINE_DECL_ALIGN(float filter[64][32]); + __CACHE_LINE_DECL_ALIGN(float synbuf[2][2 * HAN_SIZE]); + gint bufOffset[2]; + + /* scale data */ + guint scalefac_buffer[54]; +} frame_params; + +/* Huffman decoder bit buffer decls */ +#define HDBB_BUFSIZE 4096 + +typedef struct +{ + guint avail; + guint buf_byte_idx; + guint buf_bit_idx; +#if ENABLE_OPT_BS + guint remaining; + guint32 accumulator; +#endif + guint8 *buf; +} huffdec_bitbuf; + +/* Huffman Decoder bit buffer functions */ +static void h_setbuf (huffdec_bitbuf * bb, guint8 * buf, guint size); +static void h_reset (huffdec_bitbuf * bb); + +#if ENABLE_OPT_BS +static inline guint32 h_get1bit (huffdec_bitbuf * bb); +static inline void h_flushbits (huffdec_bitbuf * bb, guint N); +#else +#define h_get1bit(bb) (h_getbits ((bb), 1)) +#define h_flushbits(bb,bits) (h_getbits ((bb), (bits))) +#endif + +/* read N bits from the bit stream */ +static inline guint32 h_getbits (huffdec_bitbuf * bb, guint N); + +static void h_rewindNbits (huffdec_bitbuf * bb, guint N); + +/* Return the current bit stream position (in bits) */ +#if ENABLE_OPT_BS +#define h_sstell(bb) ((bb->buf_byte_idx * 8) - bb->buf_bit_idx) +#else +#define h_sstell(bb) ((bb->buf_byte_idx * 8) + (BS_ACUM_SIZE - bb->buf_bit_idx)) +#endif + +#if ENABLE_OPT_BS +/* This optimizazion assumes that N will be lesser than 32 */ +static inline guint32 +h_getbits (huffdec_bitbuf * bb, guint N) +{ + guint32 val = 0; + static const guint32 h_mask_table[] = { + 0x00000000, + 0x00000001, 0x00000003, 0x00000007, 0x0000000f, + 0x0000001f, 0x0000003f, 0x0000007f, 0x000000ff, + 0x000001ff, 0x000003ff, 0x000007ff, 0x00000fff, + 0x00001fff, 0x00003fff, 0x00007fff, 0x0000ffff, + 0x0001ffff, 0x0003ffff, 0x0007ffff, 0x000fffff, + 0x001fffff, 0x003fffff, 0x007fffff, 0x00ffffff, + 0x01ffffff, 0x03ffffff, 0x07ffffff, 0x0fffffff, + 0x1fffffff, 0x3fffffff, 0x7fffffff, 0xffffffff + }; + + if (N == 0) + return 0; + + /* Most common case will be when accumulator has enough bits */ + if (G_LIKELY (N <= bb->buf_bit_idx)) { + /* first reduce buf_bit_idx by the number of bits that are taken */ + bb->buf_bit_idx -= N; + /* Displace to right and mask to extract the desired number of bits */ + val = (bb->accumulator >> bb->buf_bit_idx) & h_mask_table[N]; + return (val); + } + + /* Next cases will be when there's not enough data on the accumulator + and there's atleast 4 bytes in the */ + if (bb->remaining >= 4) { + /* First take all remaining bits */ + if (bb->buf_bit_idx > 0) + val = bb->accumulator & h_mask_table[bb->buf_bit_idx]; + /* calculate how many more bits are required */ + N -= bb->buf_bit_idx; + /* reload the accumulator */ + bb->buf_bit_idx = 32 - N; /* subtract the remaining required bits */ + bb->remaining -= 4; + + /* we need reverse the byte order */ + bb->accumulator = BYTESWAP32(bb->buf + bb->buf_byte_idx); + bb->buf_byte_idx += 4; + + val <<= N; + val |= (bb->accumulator >> bb->buf_bit_idx) & h_mask_table[N]; + return (val); + } + + /* Next case when remains less that one word on the buffer */ + if (bb->remaining > 0) { + /* First take all remaining bits */ + if (bb->buf_bit_idx > 0) + val = bb->accumulator & h_mask_table[bb->buf_bit_idx]; + /* calculate how many more bits are required */ + N -= bb->buf_bit_idx; + /* reload the accumulator */ + bb->buf_bit_idx = (bb->remaining * 8) - N; /* subtract the remaining required bits */ + + bb->accumulator = 0; + /* load remaining bytes into the accumulator in the right order */ + for (; bb->remaining > 0; bb->remaining--) { + bb->accumulator <<= 8; + bb->accumulator |= (guint32) bb->buf[bb->buf_byte_idx++]; + } + val <<= N; + val |= (bb->accumulator >> bb->buf_bit_idx) & h_mask_table[N]; + return (val); + } + + return 0; +} + +static inline guint32 +h_get1bit (huffdec_bitbuf * bb) +{ + guint32 val = 0; + + /* Most common case will be when accumulator has enough bits */ + if (G_LIKELY (bb->buf_bit_idx > 0)) { + /* first reduce buf_bit_idx by the number of bits that are taken */ + bb->buf_bit_idx--; + /* Displace to right and mask to extract the desired number of bits */ + val = (bb->accumulator >> bb->buf_bit_idx) & 0x1; + return (val); + } + + /* Next cases will be when there's not enough data on the accumulator + and there's atleast 4 bytes in the */ + if (bb->remaining >= 4) { + /* reload the accumulator */ + bb->buf_bit_idx = 31; /* subtract 1 bit */ + bb->remaining -= 4; + + /* we need reverse the byte order */ + bb->accumulator = BYTESWAP32(bb->buf + bb->buf_byte_idx); + bb->buf_byte_idx += 4; + + val = (bb->accumulator >> bb->buf_bit_idx) & 0x1; + return (val); + } + + /* Next case when remains less that one word on the buffer */ + if (bb->remaining > 0) { + /* reload the accumulator */ + bb->buf_bit_idx = (bb->remaining * 8) - 1; /* subtract 1 bit */ + + bb->accumulator = 0; + /* load remaining bytes into the accumulator in the right order */ + for (; bb->remaining > 0; bb->remaining--) { + bb->accumulator <<= 8; + bb->accumulator |= (guint32) bb->buf[bb->buf_byte_idx++]; + } + + val = (bb->accumulator >> bb->buf_bit_idx) & 0x1; + return (val); + } + + return 0; +} + +static inline void +h_flushbits (huffdec_bitbuf * bb, guint N) +{ + guint bits; + guint bytes; + + if (N < 32) { + bits = N; + } else { + N -= bb->buf_bit_idx; + bytes = N >> 3; + bits = N & 0x7; + bb->buf_byte_idx += bytes; + bb->remaining -= bytes; + bb->buf_bit_idx = 0; + + if (bb->remaining >= 4) { + /* reload the accumulator */ + bb->buf_bit_idx = 32; /* subtract 1 bit */ + bb->remaining -= 4; + + /* we need reverse the byte order */ + bb->accumulator = BYTESWAP32(bb->buf + bb->buf_byte_idx); + bb->buf_byte_idx += 4; + } else if (bb->remaining > 0) { + /* reload the accumulator */ + bb->buf_bit_idx = bb->remaining * 8; + + bb->accumulator = 0; + /* load remaining bytes into the accumulator in the right order */ + for (; bb->remaining > 0; bb->remaining--) { + bb->accumulator <<= 8; + bb->accumulator |= (guint32) bb->buf[bb->buf_byte_idx++]; + } + } + } + + if (bits) + h_getbits (bb, bits); +} + +#else +static inline guint32 +h_getbits (huffdec_bitbuf * bb, guint N) +{ + guint32 val = 0; + guint j = N; + guint k, tmp; + guint mask; + + while (j > 0) { + if (!bb->buf_bit_idx) { + bb->buf_bit_idx = 8; + bb->buf_byte_idx++; + if (bb->buf_byte_idx > bb->avail) { + return 0; + } + } + k = MIN (j, bb->buf_bit_idx); + + mask = (1 << (bb->buf_bit_idx)) - 1; + tmp = bb->buf[bb->buf_byte_idx % HDBB_BUFSIZE] & mask; + tmp = tmp >> (bb->buf_bit_idx - k); + val |= tmp << (j - k); + bb->buf_bit_idx -= k; + j -= k; + } + return (val); +} +#endif + +typedef struct mp3cimpl_info mp3cimpl_info; + +struct mp3cimpl_info +{ + huffdec_bitbuf bb; /* huffman decoder bit buffer */ + guint8 hb_buf[HDBB_BUFSIZE]; /* Huffman decoder work buffer */ + guint main_data_end; /* Number of bytes in the bit reservoir at the + * end of the last frame */ + + /* Hybrid */ + gfloat prevblck[2][SBLIMIT][SSLIMIT]; + + /* scale data */ + guint scalefac_buffer[54]; +}; + +typedef short PCM[2][SSLIMIT][SBLIMIT]; +typedef unsigned int SAM[2][3][SBLIMIT]; +typedef float FRA[2][3][SBLIMIT]; + +/* Size of the temp buffer for output samples, max 2 channels * + * sub-bands * samples-per-sub-band * 2 buffers + */ +#define SAMPLE_BUF_SIZE (4 * 2 * SBLIMIT * SSLIMIT) + +struct mp3tl +{ + void * alloc_memory; + gboolean need_sync; + gboolean need_header; + gboolean at_eos; + gboolean lost_sync; + + /* Bit stream to read the data from */ + Bit_stream_struc *bs; + + /* Layer number we're decoding, 0 if we don't know yet */ + guint8 stream_layer; + + guint64 frame_num; + + /* Bits consumed from the stream so far */ + gint64 bits_used; + + /* Number of samples output so far */ + guint32 sample_frames; + + /* Total number of errors encountered */ + guint error_count; + + /* Sample size configure for. Always 16-bits, for now */ + guint sample_size; + + /* Frame decoding info */ + frame_params fr_ps; + + /* Number of granules in this frame (1 or 2) */ + guint n_granules; + /* CRC value read from the mpeg data */ + guint old_crc; + + __CACHE_LINE_DECL_ALIGN(PCM pcm_sample); + __CACHE_LINE_DECL_ALIGN(SAM sample); + __CACHE_LINE_DECL_ALIGN(FRA fraction); + + /* Output samples circular buffer and read and write ptrs */ + gint16 *sample_buf; + guint32 sample_w; + + char *reason; /* Reason why an error was returned, if known */ + + mp3cimpl_info c_impl; + + /*Free format bitrate */ + guint32 free_bitrate; + + /*Used for one time calculation of free bitrate */ + gboolean free_first; +}; + +/* Sample rates table, index by MPEG version, samplerate index */ +static const gint s_rates[4][4] = { + {11025, 12000, 8000, 0}, /* MPEG_VERSION_2_5 */ + {0, 0, 0, 0}, /* Invalid MPEG version */ + {22050, 24000, 16000, 0}, /* MPEG_VERSION_2 */ + {44100, 48000, 32000, 0} /* MPEG_VERSION_1 */ +}; + +/* MPEG version 1 bitrates. indexed by layer, bitrate index */ +static const gint bitrates_v1[3][15] = { + {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448}, + {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384}, + {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320} +}; + +/* MPEG version 2 (LSF) and 2.5 bitrates. indexed by layer, bitrate index */ +static const gint bitrates_v2[3][15] = { + {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}, + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, + {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160} +}; + +typedef struct +{ + gint sub_bands; /* Number of sub-bands in the frame */ + + const al_table alloc; /* The quantisation table itself */ +} bitalloc_table; + +/* There are 5 allocation tables available based on + * the bitrate and sample rate */ +static const bitalloc_table ba_tables[] = +{ + { + /* alloc_0 */ + 27, + { + { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 7, 3, 3, 2 }, { 15, 4, 3, 4 }, + { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, + { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, + { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 }, + { 32767, 15, 3, 15 }, { 65535, 16, 3, 16 }, + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 7, 3, 3, 2 }, { 15, 4, 3, 4 }, + { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, + { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, + { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 }, + { 32767, 15, 3, 15 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 7, 3, 3, 2 }, { 15, 4, 3, 4 }, + { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, + { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, + { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 }, + { 32767, 15, 3, 15 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, + { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, + { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, + { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, + { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, + { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, + { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + } + } + }, + { + /* alloc_1 */ + 30, + { + { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 7, 3, 3, 2 }, { 15, 4, 3, 4 }, + { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, + { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, + { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 }, + { 32767, 15, 3, 15 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 7, 3, 3, 2 }, { 15, 4, 3, 4 }, + { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, + { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, + { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 }, + { 32767, 15, 3, 15 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 7, 3, 3, 2 }, { 15, 4, 3, 4 }, + { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, + { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, + { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 }, + { 32767, 15, 3, 15 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 65535, 16, 3, 16 } + } + } + }, + { + /* alloc_2 */ + 8, + { + { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, + { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, + { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, + { 16383, 14, 3, 14 }, { 32767, 15, 3, 15 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, + { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, + { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, + { 16383, 14, 3, 14 }, { 32767, 15, 3, 15 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + } + } + }, + { + /* alloc_3 */ + 12, + { + { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, + { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, + { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, + { 16383, 14, 3, 14 }, { 32767, 15, 3, 15 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 }, + { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, { 1023, 10, 3, 10 }, + { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, { 8191, 13, 3, 13 }, + { 16383, 14, 3, 14 }, { 32767, 15, 3, 15 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + } + } +}, +{ + /* alloc_4 */ + 30, + { + { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 } + }, { + { 0, 4, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 7, 3, 3, 2 }, + { 9, 10, 1, 3 }, { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, + { 127, 7, 3, 7 }, { 255, 8, 3, 8 }, { 511, 9, 3, 9 }, + { 1023, 10, 3, 10 }, { 2047, 11, 3, 11 }, { 4095, 12, 3, 12 }, + { 8191, 13, 3, 13 }, { 16383, 14, 3, 14 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 3, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 }, + { 15, 4, 3, 4 }, { 31, 5, 3, 5 }, { 63, 6, 3, 6 }, { 127, 7, 3, 7 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + }, { + { 0, 2, 0, 0 }, { 3, 5, 1, 0 }, { 5, 7, 1, 1 }, { 9, 10, 1, 3 } + } + } + } +}; + +struct huffcodetab +{ + guint treelen; /* length of decoder tree */ + gint xlen; /* max. x-index+ */ + gint ylen; /* max. y-index+ */ + guint linbits; /* number of linbits */ + gboolean quad_table; /* TRUE for quadruple tables (32 & 33) */ + const guchar (*val)[2]; /* decoder tree data */ +}; + +/* Arrays of the table constants */ +static const guchar huffbits_1[7][2] = { + {0x02, 0x01}, {0x00, 0x00}, {0x02, 0x01}, {0x00, 0x10}, + {0x02, 0x01}, {0x00, 0x01}, {0x00, 0x11} +}; + +static const guchar huffbits_2[17][2] = { + {0x02, 0x01}, {0x00, 0x00}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x10}, {0x00, 0x01}, {0x02, 0x01}, {0x00, 0x11}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x20}, {0x00, 0x21}, + {0x02, 0x01}, {0x00, 0x12}, {0x02, 0x01}, {0x00, 0x02}, + {0x00, 0x22} +}; + +static const guchar huffbits_3[17][2] = { + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x00}, {0x00, 0x01}, + {0x02, 0x01}, {0x00, 0x11}, {0x02, 0x01}, {0x00, 0x10}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x20}, {0x00, 0x21}, + {0x02, 0x01}, {0x00, 0x12}, {0x02, 0x01}, {0x00, 0x02}, + {0x00, 0x22} +}; + +static const guchar huffbits_5[31][2] = { + {0x02, 0x01}, {0x00, 0x00}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x10}, {0x00, 0x01}, {0x02, 0x01}, {0x00, 0x11}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x20}, + {0x00, 0x02}, {0x02, 0x01}, {0x00, 0x21}, {0x00, 0x12}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x22}, + {0x00, 0x30}, {0x02, 0x01}, {0x00, 0x03}, {0x00, 0x13}, + {0x02, 0x01}, {0x00, 0x31}, {0x02, 0x01}, {0x00, 0x32}, + {0x02, 0x01}, {0x00, 0x23}, {0x00, 0x33} +}; + +static const guchar huffbits_6[31][2] = { + {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x00}, + {0x00, 0x10}, {0x00, 0x11}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x01}, {0x02, 0x01}, {0x00, 0x20}, {0x00, 0x21}, + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x12}, {0x02, 0x01}, + {0x00, 0x02}, {0x00, 0x22}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x31}, {0x00, 0x13}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x30}, {0x00, 0x32}, {0x02, 0x01}, {0x00, 0x23}, + {0x02, 0x01}, {0x00, 0x03}, {0x00, 0x33} +}; + +static const guchar huffbits_7[71][2] = { + {0x02, 0x01}, {0x00, 0x00}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x10}, {0x00, 0x01}, {0x08, 0x01}, {0x02, 0x01}, + {0x00, 0x11}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x20}, + {0x00, 0x02}, {0x00, 0x21}, {0x12, 0x01}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0x12}, {0x02, 0x01}, {0x00, 0x22}, + {0x00, 0x30}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x31}, + {0x00, 0x13}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x03}, + {0x00, 0x32}, {0x02, 0x01}, {0x00, 0x23}, {0x00, 0x04}, + {0x0a, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x40}, + {0x00, 0x41}, {0x02, 0x01}, {0x00, 0x14}, {0x02, 0x01}, + {0x00, 0x42}, {0x00, 0x24}, {0x0c, 0x01}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x33}, {0x00, 0x43}, + {0x00, 0x50}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x34}, + {0x00, 0x05}, {0x00, 0x51}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x15}, {0x02, 0x01}, {0x00, 0x52}, {0x00, 0x25}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x44}, {0x00, 0x35}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x53}, {0x00, 0x54}, + {0x02, 0x01}, {0x00, 0x45}, {0x00, 0x55} +}; + +static const guchar huffbits_8[71][2] = { + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x00}, {0x02, 0x01}, + {0x00, 0x10}, {0x00, 0x01}, {0x02, 0x01}, {0x00, 0x11}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x21}, {0x00, 0x12}, + {0x0e, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x20}, + {0x00, 0x02}, {0x02, 0x01}, {0x00, 0x22}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x30}, {0x00, 0x03}, {0x02, 0x01}, + {0x00, 0x31}, {0x00, 0x13}, {0x0e, 0x01}, {0x08, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x32}, {0x00, 0x23}, + {0x02, 0x01}, {0x00, 0x40}, {0x00, 0x04}, {0x02, 0x01}, + {0x00, 0x41}, {0x02, 0x01}, {0x00, 0x14}, {0x00, 0x42}, + {0x0c, 0x01}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x24}, + {0x02, 0x01}, {0x00, 0x33}, {0x00, 0x50}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x43}, {0x00, 0x34}, {0x00, 0x51}, + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x15}, {0x02, 0x01}, + {0x00, 0x05}, {0x00, 0x52}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x25}, {0x02, 0x01}, {0x00, 0x44}, {0x00, 0x35}, + {0x02, 0x01}, {0x00, 0x53}, {0x02, 0x01}, {0x00, 0x45}, + {0x02, 0x01}, {0x00, 0x54}, {0x00, 0x55} +}; + +static const guchar huffbits_9[71][2] = { + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x00}, + {0x00, 0x10}, {0x02, 0x01}, {0x00, 0x01}, {0x00, 0x11}, + {0x0a, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x20}, + {0x00, 0x21}, {0x02, 0x01}, {0x00, 0x12}, {0x02, 0x01}, + {0x00, 0x02}, {0x00, 0x22}, {0x0c, 0x01}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x30}, {0x00, 0x03}, + {0x00, 0x31}, {0x02, 0x01}, {0x00, 0x13}, {0x02, 0x01}, + {0x00, 0x32}, {0x00, 0x23}, {0x0c, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x41}, {0x00, 0x14}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x40}, {0x00, 0x33}, {0x02, 0x01}, + {0x00, 0x42}, {0x00, 0x24}, {0x0a, 0x01}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x04}, {0x00, 0x50}, + {0x00, 0x43}, {0x02, 0x01}, {0x00, 0x34}, {0x00, 0x51}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x15}, + {0x00, 0x52}, {0x02, 0x01}, {0x00, 0x25}, {0x00, 0x44}, + {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x05}, + {0x00, 0x54}, {0x00, 0x53}, {0x02, 0x01}, {0x00, 0x35}, + {0x02, 0x01}, {0x00, 0x45}, {0x00, 0x55} +}; + +static const guchar huffbits_10[127][2] = { + {0x02, 0x01}, {0x00, 0x00}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x10}, {0x00, 0x01}, {0x0a, 0x01}, {0x02, 0x01}, + {0x00, 0x11}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x20}, + {0x00, 0x02}, {0x02, 0x01}, {0x00, 0x21}, {0x00, 0x12}, + {0x1c, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x22}, {0x00, 0x30}, {0x02, 0x01}, {0x00, 0x31}, + {0x00, 0x13}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x03}, {0x00, 0x32}, {0x02, 0x01}, {0x00, 0x23}, + {0x00, 0x40}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x41}, + {0x00, 0x14}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x04}, + {0x00, 0x33}, {0x02, 0x01}, {0x00, 0x42}, {0x00, 0x24}, + {0x1c, 0x01}, {0x0a, 0x01}, {0x06, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x50}, {0x00, 0x05}, {0x00, 0x60}, + {0x02, 0x01}, {0x00, 0x61}, {0x00, 0x16}, {0x0c, 0x01}, + {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x43}, + {0x00, 0x34}, {0x00, 0x51}, {0x02, 0x01}, {0x00, 0x15}, + {0x02, 0x01}, {0x00, 0x52}, {0x00, 0x25}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x26}, {0x00, 0x36}, {0x00, 0x71}, + {0x14, 0x01}, {0x08, 0x01}, {0x02, 0x01}, {0x00, 0x17}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x44}, {0x00, 0x53}, + {0x00, 0x06}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x35}, {0x00, 0x45}, {0x00, 0x62}, {0x02, 0x01}, + {0x00, 0x70}, {0x02, 0x01}, {0x00, 0x07}, {0x00, 0x64}, + {0x0e, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x72}, + {0x00, 0x27}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x63}, + {0x02, 0x01}, {0x00, 0x54}, {0x00, 0x55}, {0x02, 0x01}, + {0x00, 0x46}, {0x00, 0x73}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x37}, {0x00, 0x65}, {0x02, 0x01}, + {0x00, 0x56}, {0x00, 0x74}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x47}, {0x02, 0x01}, {0x00, 0x66}, {0x00, 0x75}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x57}, {0x00, 0x76}, + {0x02, 0x01}, {0x00, 0x67}, {0x00, 0x77} +}; + +static const guchar huffbits_11[127][2] = { + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x00}, {0x02, 0x01}, + {0x00, 0x10}, {0x00, 0x01}, {0x08, 0x01}, {0x02, 0x01}, + {0x00, 0x11}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x20}, + {0x00, 0x02}, {0x00, 0x12}, {0x18, 0x01}, {0x08, 0x01}, + {0x02, 0x01}, {0x00, 0x21}, {0x02, 0x01}, {0x00, 0x22}, + {0x02, 0x01}, {0x00, 0x30}, {0x00, 0x03}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x31}, {0x00, 0x13}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x32}, {0x00, 0x23}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x40}, {0x00, 0x04}, {0x02, 0x01}, + {0x00, 0x41}, {0x00, 0x14}, {0x1e, 0x01}, {0x10, 0x01}, + {0x0a, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x42}, + {0x00, 0x24}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x33}, + {0x00, 0x43}, {0x00, 0x50}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x34}, {0x00, 0x51}, {0x00, 0x61}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0x16}, {0x02, 0x01}, {0x00, 0x06}, + {0x00, 0x26}, {0x02, 0x01}, {0x00, 0x62}, {0x02, 0x01}, + {0x00, 0x15}, {0x02, 0x01}, {0x00, 0x05}, {0x00, 0x52}, + {0x10, 0x01}, {0x0a, 0x01}, {0x06, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x25}, {0x00, 0x44}, {0x00, 0x60}, + {0x02, 0x01}, {0x00, 0x63}, {0x00, 0x36}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x70}, {0x00, 0x17}, {0x00, 0x71}, + {0x10, 0x01}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x07}, {0x00, 0x64}, {0x00, 0x72}, {0x02, 0x01}, + {0x00, 0x27}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x53}, + {0x00, 0x35}, {0x02, 0x01}, {0x00, 0x54}, {0x00, 0x45}, + {0x0a, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x46}, + {0x00, 0x73}, {0x02, 0x01}, {0x00, 0x37}, {0x02, 0x01}, + {0x00, 0x65}, {0x00, 0x56}, {0x0a, 0x01}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x55}, {0x00, 0x57}, + {0x00, 0x74}, {0x02, 0x01}, {0x00, 0x47}, {0x00, 0x66}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x75}, {0x00, 0x76}, + {0x02, 0x01}, {0x00, 0x67}, {0x00, 0x77} +}; + +static const guchar huffbits_12[127][2] = { + {0x0c, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x10}, + {0x00, 0x01}, {0x02, 0x01}, {0x00, 0x11}, {0x02, 0x01}, + {0x00, 0x00}, {0x02, 0x01}, {0x00, 0x20}, {0x00, 0x02}, + {0x10, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x21}, + {0x00, 0x12}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x22}, + {0x00, 0x31}, {0x02, 0x01}, {0x00, 0x13}, {0x02, 0x01}, + {0x00, 0x30}, {0x02, 0x01}, {0x00, 0x03}, {0x00, 0x40}, + {0x1a, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x32}, {0x00, 0x23}, {0x02, 0x01}, {0x00, 0x41}, + {0x00, 0x33}, {0x0a, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x14}, {0x00, 0x42}, {0x02, 0x01}, {0x00, 0x24}, + {0x02, 0x01}, {0x00, 0x04}, {0x00, 0x50}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x43}, {0x00, 0x34}, {0x02, 0x01}, + {0x00, 0x51}, {0x00, 0x15}, {0x1c, 0x01}, {0x0e, 0x01}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x52}, + {0x00, 0x25}, {0x02, 0x01}, {0x00, 0x53}, {0x00, 0x35}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x60}, {0x00, 0x16}, + {0x00, 0x61}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x62}, + {0x00, 0x26}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x05}, {0x00, 0x06}, {0x00, 0x44}, {0x02, 0x01}, + {0x00, 0x54}, {0x00, 0x45}, {0x12, 0x01}, {0x0a, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x63}, {0x00, 0x36}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x70}, {0x00, 0x07}, + {0x00, 0x71}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x17}, + {0x00, 0x64}, {0x02, 0x01}, {0x00, 0x46}, {0x00, 0x72}, + {0x0a, 0x01}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x27}, + {0x02, 0x01}, {0x00, 0x55}, {0x00, 0x73}, {0x02, 0x01}, + {0x00, 0x37}, {0x00, 0x56}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x65}, {0x00, 0x74}, {0x02, 0x01}, + {0x00, 0x47}, {0x00, 0x66}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x75}, {0x00, 0x57}, {0x02, 0x01}, {0x00, 0x76}, + {0x02, 0x01}, {0x00, 0x67}, {0x00, 0x77} +}; + +static const guchar huffbits_13[511][2] = { + {0x02, 0x01}, {0x00, 0x00}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x10}, {0x02, 0x01}, {0x00, 0x01}, {0x00, 0x11}, + {0x1c, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x20}, {0x00, 0x02}, {0x02, 0x01}, {0x00, 0x21}, + {0x00, 0x12}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x22}, {0x00, 0x30}, {0x02, 0x01}, {0x00, 0x03}, + {0x00, 0x31}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x13}, + {0x02, 0x01}, {0x00, 0x32}, {0x00, 0x23}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x40}, {0x00, 0x04}, {0x00, 0x41}, + {0x46, 0x01}, {0x1c, 0x01}, {0x0e, 0x01}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0x14}, {0x02, 0x01}, {0x00, 0x33}, + {0x00, 0x42}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x24}, + {0x00, 0x50}, {0x02, 0x01}, {0x00, 0x43}, {0x00, 0x34}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x51}, {0x00, 0x15}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x05}, {0x00, 0x52}, + {0x02, 0x01}, {0x00, 0x25}, {0x02, 0x01}, {0x00, 0x44}, + {0x00, 0x53}, {0x0e, 0x01}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x60}, {0x00, 0x06}, {0x02, 0x01}, + {0x00, 0x61}, {0x00, 0x16}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x80}, {0x00, 0x08}, {0x00, 0x81}, {0x10, 0x01}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x35}, + {0x00, 0x62}, {0x02, 0x01}, {0x00, 0x26}, {0x00, 0x54}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x45}, {0x00, 0x63}, + {0x02, 0x01}, {0x00, 0x36}, {0x00, 0x70}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x07}, {0x00, 0x55}, + {0x00, 0x71}, {0x02, 0x01}, {0x00, 0x17}, {0x02, 0x01}, + {0x00, 0x27}, {0x00, 0x37}, {0x48, 0x01}, {0x18, 0x01}, + {0x0c, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x18}, + {0x00, 0x82}, {0x02, 0x01}, {0x00, 0x28}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x64}, {0x00, 0x46}, {0x00, 0x72}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x84}, + {0x00, 0x48}, {0x02, 0x01}, {0x00, 0x90}, {0x00, 0x09}, + {0x02, 0x01}, {0x00, 0x91}, {0x00, 0x19}, {0x18, 0x01}, + {0x0e, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x73}, {0x00, 0x65}, {0x02, 0x01}, {0x00, 0x56}, + {0x00, 0x74}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x47}, + {0x00, 0x66}, {0x00, 0x83}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x38}, {0x02, 0x01}, {0x00, 0x75}, {0x00, 0x57}, + {0x02, 0x01}, {0x00, 0x92}, {0x00, 0x29}, {0x0e, 0x01}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x67}, + {0x00, 0x85}, {0x02, 0x01}, {0x00, 0x58}, {0x00, 0x39}, + {0x02, 0x01}, {0x00, 0x93}, {0x02, 0x01}, {0x00, 0x49}, + {0x00, 0x86}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0xa0}, + {0x02, 0x01}, {0x00, 0x68}, {0x00, 0x0a}, {0x02, 0x01}, + {0x00, 0xa1}, {0x00, 0x1a}, {0x44, 0x01}, {0x18, 0x01}, + {0x0c, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xa2}, + {0x00, 0x2a}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x95}, + {0x00, 0x59}, {0x02, 0x01}, {0x00, 0xa3}, {0x00, 0x3a}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x4a}, + {0x00, 0x96}, {0x02, 0x01}, {0x00, 0xb0}, {0x00, 0x0b}, + {0x02, 0x01}, {0x00, 0xb1}, {0x00, 0x1b}, {0x14, 0x01}, + {0x08, 0x01}, {0x02, 0x01}, {0x00, 0xb2}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x76}, {0x00, 0x77}, {0x00, 0x94}, + {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x87}, + {0x00, 0x78}, {0x00, 0xa4}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x69}, {0x00, 0xa5}, {0x00, 0x2b}, {0x0c, 0x01}, + {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x5a}, + {0x00, 0x88}, {0x00, 0xb3}, {0x02, 0x01}, {0x00, 0x3b}, + {0x02, 0x01}, {0x00, 0x79}, {0x00, 0xa6}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x6a}, {0x00, 0xb4}, + {0x00, 0xc0}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x0c}, + {0x00, 0x98}, {0x00, 0xc1}, {0x3c, 0x01}, {0x16, 0x01}, + {0x0a, 0x01}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x1c}, + {0x02, 0x01}, {0x00, 0x89}, {0x00, 0xb5}, {0x02, 0x01}, + {0x00, 0x5b}, {0x00, 0xc2}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x2c}, {0x00, 0x3c}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xb6}, {0x00, 0x6b}, {0x02, 0x01}, {0x00, 0xc4}, + {0x00, 0x4c}, {0x10, 0x01}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xa8}, {0x00, 0x8a}, {0x02, 0x01}, + {0x00, 0xd0}, {0x00, 0x0d}, {0x02, 0x01}, {0x00, 0xd1}, + {0x02, 0x01}, {0x00, 0x4b}, {0x02, 0x01}, {0x00, 0x97}, + {0x00, 0xa7}, {0x0c, 0x01}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0xc3}, {0x02, 0x01}, {0x00, 0x7a}, {0x00, 0x99}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xc5}, {0x00, 0x5c}, + {0x00, 0xb7}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x1d}, + {0x00, 0xd2}, {0x02, 0x01}, {0x00, 0x2d}, {0x02, 0x01}, + {0x00, 0x7b}, {0x00, 0xd3}, {0x34, 0x01}, {0x1c, 0x01}, + {0x0c, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x3d}, + {0x00, 0xc6}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x6c}, + {0x00, 0xa9}, {0x02, 0x01}, {0x00, 0x9a}, {0x00, 0xd4}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xb8}, + {0x00, 0x8b}, {0x02, 0x01}, {0x00, 0x4d}, {0x00, 0xc7}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x7c}, {0x00, 0xd5}, + {0x02, 0x01}, {0x00, 0x5d}, {0x00, 0xe0}, {0x0a, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xe1}, {0x00, 0x1e}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x0e}, {0x00, 0x2e}, + {0x00, 0xe2}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xe3}, {0x00, 0x6d}, {0x02, 0x01}, {0x00, 0x8c}, + {0x00, 0xe4}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xe5}, + {0x00, 0xba}, {0x00, 0xf0}, {0x26, 0x01}, {0x10, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xf1}, {0x00, 0x1f}, + {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xaa}, + {0x00, 0x9b}, {0x00, 0xb9}, {0x02, 0x01}, {0x00, 0x3e}, + {0x02, 0x01}, {0x00, 0xd6}, {0x00, 0xc8}, {0x0c, 0x01}, + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x4e}, {0x02, 0x01}, + {0x00, 0xd7}, {0x00, 0x7d}, {0x02, 0x01}, {0x00, 0xab}, + {0x02, 0x01}, {0x00, 0x5e}, {0x00, 0xc9}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0x0f}, {0x02, 0x01}, {0x00, 0x9c}, + {0x00, 0x6e}, {0x02, 0x01}, {0x00, 0xf2}, {0x00, 0x2f}, + {0x20, 0x01}, {0x10, 0x01}, {0x06, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xd8}, {0x00, 0x8d}, {0x00, 0x3f}, + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0xf3}, {0x02, 0x01}, + {0x00, 0xe6}, {0x00, 0xca}, {0x02, 0x01}, {0x00, 0xf4}, + {0x00, 0x4f}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xbb}, {0x00, 0xac}, {0x02, 0x01}, {0x00, 0xe7}, + {0x00, 0xf5}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xd9}, + {0x00, 0x9d}, {0x02, 0x01}, {0x00, 0x5f}, {0x00, 0xe8}, + {0x1e, 0x01}, {0x0c, 0x01}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x6f}, {0x02, 0x01}, {0x00, 0xf6}, {0x00, 0xcb}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xbc}, {0x00, 0xad}, + {0x00, 0xda}, {0x08, 0x01}, {0x02, 0x01}, {0x00, 0xf7}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x7e}, {0x00, 0x7f}, + {0x00, 0x8e}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x9e}, {0x00, 0xae}, {0x00, 0xcc}, {0x02, 0x01}, + {0x00, 0xf8}, {0x00, 0x8f}, {0x12, 0x01}, {0x08, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xdb}, {0x00, 0xbd}, + {0x02, 0x01}, {0x00, 0xea}, {0x00, 0xf9}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x9f}, {0x00, 0xeb}, {0x02, 0x01}, + {0x00, 0xbe}, {0x02, 0x01}, {0x00, 0xcd}, {0x00, 0xfa}, + {0x0e, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xdd}, + {0x00, 0xec}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xe9}, {0x00, 0xaf}, {0x00, 0xdc}, {0x02, 0x01}, + {0x00, 0xce}, {0x00, 0xfb}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xbf}, {0x00, 0xde}, {0x02, 0x01}, + {0x00, 0xcf}, {0x00, 0xee}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xdf}, {0x00, 0xef}, {0x02, 0x01}, {0x00, 0xff}, + {0x02, 0x01}, {0x00, 0xed}, {0x02, 0x01}, {0x00, 0xfd}, + {0x02, 0x01}, {0x00, 0xfc}, {0x00, 0xfe} +}; + +static const guchar huffbits_15[511][2] = { + {0x10, 0x01}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x00}, + {0x02, 0x01}, {0x00, 0x10}, {0x00, 0x01}, {0x02, 0x01}, + {0x00, 0x11}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x20}, + {0x00, 0x02}, {0x02, 0x01}, {0x00, 0x21}, {0x00, 0x12}, + {0x32, 0x01}, {0x10, 0x01}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x22}, {0x02, 0x01}, {0x00, 0x30}, {0x00, 0x31}, + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x13}, {0x02, 0x01}, + {0x00, 0x03}, {0x00, 0x40}, {0x02, 0x01}, {0x00, 0x32}, + {0x00, 0x23}, {0x0e, 0x01}, {0x06, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x04}, {0x00, 0x14}, {0x00, 0x41}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x33}, {0x00, 0x42}, + {0x02, 0x01}, {0x00, 0x24}, {0x00, 0x43}, {0x0a, 0x01}, + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x34}, {0x02, 0x01}, + {0x00, 0x50}, {0x00, 0x05}, {0x02, 0x01}, {0x00, 0x51}, + {0x00, 0x15}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x52}, + {0x00, 0x25}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x44}, + {0x00, 0x53}, {0x00, 0x61}, {0x5a, 0x01}, {0x24, 0x01}, + {0x12, 0x01}, {0x0a, 0x01}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x35}, {0x02, 0x01}, {0x00, 0x60}, {0x00, 0x06}, + {0x02, 0x01}, {0x00, 0x16}, {0x00, 0x62}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x26}, {0x00, 0x54}, {0x02, 0x01}, + {0x00, 0x45}, {0x00, 0x63}, {0x0a, 0x01}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0x36}, {0x02, 0x01}, {0x00, 0x70}, + {0x00, 0x07}, {0x02, 0x01}, {0x00, 0x71}, {0x00, 0x55}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x17}, {0x00, 0x64}, + {0x02, 0x01}, {0x00, 0x72}, {0x00, 0x27}, {0x18, 0x01}, + {0x10, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x46}, {0x00, 0x73}, {0x02, 0x01}, {0x00, 0x37}, + {0x00, 0x65}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x56}, + {0x00, 0x80}, {0x02, 0x01}, {0x00, 0x08}, {0x00, 0x74}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x81}, {0x00, 0x18}, + {0x02, 0x01}, {0x00, 0x82}, {0x00, 0x28}, {0x10, 0x01}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x47}, + {0x00, 0x66}, {0x02, 0x01}, {0x00, 0x83}, {0x00, 0x38}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x75}, {0x00, 0x57}, + {0x02, 0x01}, {0x00, 0x84}, {0x00, 0x48}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x90}, {0x00, 0x19}, + {0x00, 0x91}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x92}, + {0x00, 0x76}, {0x02, 0x01}, {0x00, 0x67}, {0x00, 0x29}, + {0x5c, 0x01}, {0x24, 0x01}, {0x12, 0x01}, {0x0a, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x85}, {0x00, 0x58}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x09}, {0x00, 0x77}, + {0x00, 0x93}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x39}, + {0x00, 0x94}, {0x02, 0x01}, {0x00, 0x49}, {0x00, 0x86}, + {0x0a, 0x01}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x68}, + {0x02, 0x01}, {0x00, 0xa0}, {0x00, 0x0a}, {0x02, 0x01}, + {0x00, 0xa1}, {0x00, 0x1a}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xa2}, {0x00, 0x2a}, {0x02, 0x01}, {0x00, 0x95}, + {0x00, 0x59}, {0x1a, 0x01}, {0x0e, 0x01}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0xa3}, {0x02, 0x01}, {0x00, 0x3a}, + {0x00, 0x87}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x78}, + {0x00, 0xa4}, {0x02, 0x01}, {0x00, 0x4a}, {0x00, 0x96}, + {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x69}, + {0x00, 0xb0}, {0x00, 0xb1}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x1b}, {0x00, 0xa5}, {0x00, 0xb2}, {0x0e, 0x01}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x5a}, + {0x00, 0x2b}, {0x02, 0x01}, {0x00, 0x88}, {0x00, 0x97}, + {0x02, 0x01}, {0x00, 0xb3}, {0x02, 0x01}, {0x00, 0x79}, + {0x00, 0x3b}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x6a}, {0x00, 0xb4}, {0x02, 0x01}, {0x00, 0x4b}, + {0x00, 0xc1}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x98}, + {0x00, 0x89}, {0x02, 0x01}, {0x00, 0x1c}, {0x00, 0xb5}, + {0x50, 0x01}, {0x22, 0x01}, {0x10, 0x01}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x5b}, {0x00, 0x2c}, + {0x00, 0xc2}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x0b}, {0x00, 0xc0}, {0x00, 0xa6}, {0x02, 0x01}, + {0x00, 0xa7}, {0x00, 0x7a}, {0x0a, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xc3}, {0x00, 0x3c}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x0c}, {0x00, 0x99}, {0x00, 0xb6}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x6b}, {0x00, 0xc4}, + {0x02, 0x01}, {0x00, 0x4c}, {0x00, 0xa8}, {0x14, 0x01}, + {0x0a, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x8a}, + {0x00, 0xc5}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xd0}, + {0x00, 0x5c}, {0x00, 0xd1}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xb7}, {0x00, 0x7b}, {0x02, 0x01}, {0x00, 0x1d}, + {0x02, 0x01}, {0x00, 0x0d}, {0x00, 0x2d}, {0x0c, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xd2}, {0x00, 0xd3}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x3d}, {0x00, 0xc6}, + {0x02, 0x01}, {0x00, 0x6c}, {0x00, 0xa9}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x9a}, {0x00, 0xb8}, + {0x00, 0xd4}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x8b}, + {0x00, 0x4d}, {0x02, 0x01}, {0x00, 0xc7}, {0x00, 0x7c}, + {0x44, 0x01}, {0x22, 0x01}, {0x12, 0x01}, {0x0a, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xd5}, {0x00, 0x5d}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xe0}, {0x00, 0x0e}, + {0x00, 0xe1}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x1e}, + {0x00, 0xe2}, {0x02, 0x01}, {0x00, 0xaa}, {0x00, 0x2e}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xb9}, + {0x00, 0x9b}, {0x02, 0x01}, {0x00, 0xe3}, {0x00, 0xd6}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x6d}, {0x00, 0x3e}, + {0x02, 0x01}, {0x00, 0xc8}, {0x00, 0x8c}, {0x10, 0x01}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xe4}, + {0x00, 0x4e}, {0x02, 0x01}, {0x00, 0xd7}, {0x00, 0x7d}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xe5}, {0x00, 0xba}, + {0x02, 0x01}, {0x00, 0xab}, {0x00, 0x5e}, {0x08, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xc9}, {0x00, 0x9c}, + {0x02, 0x01}, {0x00, 0xf1}, {0x00, 0x1f}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xf0}, {0x00, 0x6e}, + {0x00, 0xf2}, {0x02, 0x01}, {0x00, 0x2f}, {0x00, 0xe6}, + {0x26, 0x01}, {0x12, 0x01}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xd8}, {0x00, 0xf3}, {0x02, 0x01}, + {0x00, 0x3f}, {0x00, 0xf4}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x4f}, {0x02, 0x01}, {0x00, 0x8d}, {0x00, 0xd9}, + {0x02, 0x01}, {0x00, 0xbb}, {0x00, 0xca}, {0x08, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xac}, {0x00, 0xe7}, + {0x02, 0x01}, {0x00, 0x7e}, {0x00, 0xf5}, {0x08, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x9d}, {0x00, 0x5f}, + {0x02, 0x01}, {0x00, 0xe8}, {0x00, 0x8e}, {0x02, 0x01}, + {0x00, 0xf6}, {0x00, 0xcb}, {0x22, 0x01}, {0x12, 0x01}, + {0x0a, 0x01}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x0f}, {0x00, 0xae}, {0x00, 0x6f}, {0x02, 0x01}, + {0x00, 0xbc}, {0x00, 0xda}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xad}, {0x00, 0xf7}, {0x02, 0x01}, {0x00, 0x7f}, + {0x00, 0xe9}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x9e}, {0x00, 0xcc}, {0x02, 0x01}, {0x00, 0xf8}, + {0x00, 0x8f}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xdb}, + {0x00, 0xbd}, {0x02, 0x01}, {0x00, 0xea}, {0x00, 0xf9}, + {0x10, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x9f}, {0x00, 0xdc}, {0x02, 0x01}, {0x00, 0xcd}, + {0x00, 0xeb}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xbe}, + {0x00, 0xfa}, {0x02, 0x01}, {0x00, 0xaf}, {0x00, 0xdd}, + {0x0e, 0x01}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xec}, {0x00, 0xce}, {0x00, 0xfb}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xbf}, {0x00, 0xed}, {0x02, 0x01}, + {0x00, 0xde}, {0x00, 0xfc}, {0x06, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xcf}, {0x00, 0xfd}, {0x00, 0xee}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xdf}, {0x00, 0xfe}, + {0x02, 0x01}, {0x00, 0xef}, {0x00, 0xff} +}; + +static const guchar huffbits_16[511][2] = { + {0x02, 0x01}, {0x00, 0x00}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x10}, {0x02, 0x01}, {0x00, 0x01}, {0x00, 0x11}, + {0x2a, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x20}, {0x00, 0x02}, {0x02, 0x01}, {0x00, 0x21}, + {0x00, 0x12}, {0x0a, 0x01}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x22}, {0x02, 0x01}, {0x00, 0x30}, {0x00, 0x03}, + {0x02, 0x01}, {0x00, 0x31}, {0x00, 0x13}, {0x0a, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x32}, {0x00, 0x23}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x40}, {0x00, 0x04}, + {0x00, 0x41}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x14}, + {0x02, 0x01}, {0x00, 0x33}, {0x00, 0x42}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x24}, {0x00, 0x50}, {0x02, 0x01}, + {0x00, 0x43}, {0x00, 0x34}, {0x8a, 0x01}, {0x28, 0x01}, + {0x10, 0x01}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x05}, {0x00, 0x15}, {0x00, 0x51}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x52}, {0x00, 0x25}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x44}, {0x00, 0x35}, {0x00, 0x53}, + {0x0a, 0x01}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x60}, {0x00, 0x06}, {0x00, 0x61}, {0x02, 0x01}, + {0x00, 0x16}, {0x00, 0x62}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x26}, {0x00, 0x54}, {0x02, 0x01}, + {0x00, 0x45}, {0x00, 0x63}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x36}, {0x00, 0x70}, {0x00, 0x71}, {0x28, 0x01}, + {0x12, 0x01}, {0x08, 0x01}, {0x02, 0x01}, {0x00, 0x17}, + {0x02, 0x01}, {0x00, 0x07}, {0x02, 0x01}, {0x00, 0x55}, + {0x00, 0x64}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x72}, + {0x00, 0x27}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x46}, + {0x00, 0x65}, {0x00, 0x73}, {0x0a, 0x01}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0x37}, {0x02, 0x01}, {0x00, 0x56}, + {0x00, 0x08}, {0x02, 0x01}, {0x00, 0x80}, {0x00, 0x81}, + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x18}, {0x02, 0x01}, + {0x00, 0x74}, {0x00, 0x47}, {0x02, 0x01}, {0x00, 0x82}, + {0x02, 0x01}, {0x00, 0x28}, {0x00, 0x66}, {0x18, 0x01}, + {0x0e, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x83}, {0x00, 0x38}, {0x02, 0x01}, {0x00, 0x75}, + {0x00, 0x84}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x48}, + {0x00, 0x90}, {0x00, 0x91}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x19}, {0x02, 0x01}, {0x00, 0x09}, {0x00, 0x76}, + {0x02, 0x01}, {0x00, 0x92}, {0x00, 0x29}, {0x0e, 0x01}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x85}, + {0x00, 0x58}, {0x02, 0x01}, {0x00, 0x93}, {0x00, 0x39}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xa0}, {0x00, 0x0a}, + {0x00, 0x1a}, {0x08, 0x01}, {0x02, 0x01}, {0x00, 0xa2}, + {0x02, 0x01}, {0x00, 0x67}, {0x02, 0x01}, {0x00, 0x57}, + {0x00, 0x49}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x94}, + {0x02, 0x01}, {0x00, 0x77}, {0x00, 0x86}, {0x02, 0x01}, + {0x00, 0xa1}, {0x02, 0x01}, {0x00, 0x68}, {0x00, 0x95}, + {0xdc, 0x01}, {0x7e, 0x01}, {0x32, 0x01}, {0x1a, 0x01}, + {0x0c, 0x01}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x2a}, + {0x02, 0x01}, {0x00, 0x59}, {0x00, 0x3a}, {0x02, 0x01}, + {0x00, 0xa3}, {0x02, 0x01}, {0x00, 0x87}, {0x00, 0x78}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xa4}, + {0x00, 0x4a}, {0x02, 0x01}, {0x00, 0x96}, {0x00, 0x69}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xb0}, {0x00, 0x0b}, + {0x00, 0xb1}, {0x0a, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x1b}, {0x00, 0xb2}, {0x02, 0x01}, {0x00, 0x2b}, + {0x02, 0x01}, {0x00, 0xa5}, {0x00, 0x5a}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0xb3}, {0x02, 0x01}, {0x00, 0xa6}, + {0x00, 0x6a}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xb4}, + {0x00, 0x4b}, {0x02, 0x01}, {0x00, 0x0c}, {0x00, 0xc1}, + {0x1e, 0x01}, {0x0e, 0x01}, {0x06, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xb5}, {0x00, 0xc2}, {0x00, 0x2c}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xa7}, {0x00, 0xc3}, + {0x02, 0x01}, {0x00, 0x6b}, {0x00, 0xc4}, {0x08, 0x01}, + {0x02, 0x01}, {0x00, 0x1d}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x88}, {0x00, 0x97}, {0x00, 0x3b}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xd1}, {0x00, 0xd2}, {0x02, 0x01}, + {0x00, 0x2d}, {0x00, 0xd3}, {0x12, 0x01}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x1e}, {0x00, 0x2e}, + {0x00, 0xe2}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x79}, {0x00, 0x98}, {0x00, 0xc0}, {0x02, 0x01}, + {0x00, 0x1c}, {0x02, 0x01}, {0x00, 0x89}, {0x00, 0x5b}, + {0x0e, 0x01}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x3c}, + {0x02, 0x01}, {0x00, 0x7a}, {0x00, 0xb6}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x4c}, {0x00, 0x99}, {0x02, 0x01}, + {0x00, 0xa8}, {0x00, 0x8a}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x0d}, {0x02, 0x01}, {0x00, 0xc5}, {0x00, 0x5c}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x3d}, {0x00, 0xc6}, + {0x02, 0x01}, {0x00, 0x6c}, {0x00, 0x9a}, {0x58, 0x01}, + {0x56, 0x01}, {0x24, 0x01}, {0x10, 0x01}, {0x08, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x8b}, {0x00, 0x4d}, + {0x02, 0x01}, {0x00, 0xc7}, {0x00, 0x7c}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xd5}, {0x00, 0x5d}, {0x02, 0x01}, + {0x00, 0xe0}, {0x00, 0x0e}, {0x08, 0x01}, {0x02, 0x01}, + {0x00, 0xe3}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xd0}, + {0x00, 0xb7}, {0x00, 0x7b}, {0x06, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xa9}, {0x00, 0xb8}, {0x00, 0xd4}, + {0x02, 0x01}, {0x00, 0xe1}, {0x02, 0x01}, {0x00, 0xaa}, + {0x00, 0xb9}, {0x18, 0x01}, {0x0a, 0x01}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x9b}, {0x00, 0xd6}, + {0x00, 0x6d}, {0x02, 0x01}, {0x00, 0x3e}, {0x00, 0xc8}, + {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x8c}, + {0x00, 0xe4}, {0x00, 0x4e}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xd7}, {0x00, 0xe5}, {0x02, 0x01}, {0x00, 0xba}, + {0x00, 0xab}, {0x0c, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x9c}, {0x00, 0xe6}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x6e}, {0x00, 0xd8}, {0x02, 0x01}, {0x00, 0x8d}, + {0x00, 0xbb}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xe7}, {0x00, 0x9d}, {0x02, 0x01}, {0x00, 0xe8}, + {0x00, 0x8e}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xcb}, + {0x00, 0xbc}, {0x00, 0x9e}, {0x00, 0xf1}, {0x02, 0x01}, + {0x00, 0x1f}, {0x02, 0x01}, {0x00, 0x0f}, {0x00, 0x2f}, + {0x42, 0x01}, {0x38, 0x01}, {0x02, 0x01}, {0x00, 0xf2}, + {0x34, 0x01}, {0x32, 0x01}, {0x14, 0x01}, {0x08, 0x01}, + {0x02, 0x01}, {0x00, 0xbd}, {0x02, 0x01}, {0x00, 0x5e}, + {0x02, 0x01}, {0x00, 0x7d}, {0x00, 0xc9}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0xca}, {0x02, 0x01}, {0x00, 0xac}, + {0x00, 0x7e}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xda}, + {0x00, 0xad}, {0x00, 0xcc}, {0x0a, 0x01}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0xae}, {0x02, 0x01}, {0x00, 0xdb}, + {0x00, 0xdc}, {0x02, 0x01}, {0x00, 0xcd}, {0x00, 0xbe}, + {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xeb}, + {0x00, 0xed}, {0x00, 0xee}, {0x06, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xd9}, {0x00, 0xea}, {0x00, 0xe9}, + {0x02, 0x01}, {0x00, 0xde}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xdd}, {0x00, 0xec}, {0x00, 0xce}, {0x00, 0x3f}, + {0x00, 0xf0}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xf3}, + {0x00, 0xf4}, {0x02, 0x01}, {0x00, 0x4f}, {0x02, 0x01}, + {0x00, 0xf5}, {0x00, 0x5f}, {0x0a, 0x01}, {0x02, 0x01}, + {0x00, 0xff}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xf6}, + {0x00, 0x6f}, {0x02, 0x01}, {0x00, 0xf7}, {0x00, 0x7f}, + {0x0c, 0x01}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x8f}, + {0x02, 0x01}, {0x00, 0xf8}, {0x00, 0xf9}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x9f}, {0x00, 0xfa}, {0x00, 0xaf}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xfb}, + {0x00, 0xbf}, {0x02, 0x01}, {0x00, 0xfc}, {0x00, 0xcf}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xfd}, {0x00, 0xdf}, + {0x02, 0x01}, {0x00, 0xfe}, {0x00, 0xef} +}; + +static const guchar huffbits_24[512][2] = { + {0x3c, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x00}, {0x00, 0x10}, {0x02, 0x01}, {0x00, 0x01}, + {0x00, 0x11}, {0x0e, 0x01}, {0x06, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x20}, {0x00, 0x02}, {0x00, 0x21}, + {0x02, 0x01}, {0x00, 0x12}, {0x02, 0x01}, {0x00, 0x22}, + {0x02, 0x01}, {0x00, 0x30}, {0x00, 0x03}, {0x0e, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x31}, {0x00, 0x13}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x32}, {0x00, 0x23}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x40}, {0x00, 0x04}, + {0x00, 0x41}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x14}, {0x00, 0x33}, {0x02, 0x01}, {0x00, 0x42}, + {0x00, 0x24}, {0x06, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x43}, {0x00, 0x34}, {0x00, 0x51}, {0x06, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x50}, {0x00, 0x05}, + {0x00, 0x15}, {0x02, 0x01}, {0x00, 0x52}, {0x00, 0x25}, + {0xfa, 0x01}, {0x62, 0x01}, {0x22, 0x01}, {0x12, 0x01}, + {0x0a, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x44}, + {0x00, 0x53}, {0x02, 0x01}, {0x00, 0x35}, {0x02, 0x01}, + {0x00, 0x60}, {0x00, 0x06}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x61}, {0x00, 0x16}, {0x02, 0x01}, {0x00, 0x62}, + {0x00, 0x26}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x54}, {0x00, 0x45}, {0x02, 0x01}, {0x00, 0x63}, + {0x00, 0x36}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x71}, + {0x00, 0x55}, {0x02, 0x01}, {0x00, 0x64}, {0x00, 0x46}, + {0x20, 0x01}, {0x0e, 0x01}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x72}, {0x02, 0x01}, {0x00, 0x27}, {0x00, 0x37}, + {0x02, 0x01}, {0x00, 0x73}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x70}, {0x00, 0x07}, {0x00, 0x17}, {0x0a, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x65}, {0x00, 0x56}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x80}, {0x00, 0x08}, + {0x00, 0x81}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x74}, + {0x00, 0x47}, {0x02, 0x01}, {0x00, 0x18}, {0x00, 0x82}, + {0x10, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x28}, {0x00, 0x66}, {0x02, 0x01}, {0x00, 0x83}, + {0x00, 0x38}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x75}, + {0x00, 0x57}, {0x02, 0x01}, {0x00, 0x84}, {0x00, 0x48}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x91}, + {0x00, 0x19}, {0x02, 0x01}, {0x00, 0x92}, {0x00, 0x76}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x67}, {0x00, 0x29}, + {0x02, 0x01}, {0x00, 0x85}, {0x00, 0x58}, {0x5c, 0x01}, + {0x22, 0x01}, {0x10, 0x01}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x93}, {0x00, 0x39}, {0x02, 0x01}, + {0x00, 0x94}, {0x00, 0x49}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x77}, {0x00, 0x86}, {0x02, 0x01}, {0x00, 0x68}, + {0x00, 0xa1}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xa2}, {0x00, 0x2a}, {0x02, 0x01}, {0x00, 0x95}, + {0x00, 0x59}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xa3}, + {0x00, 0x3a}, {0x02, 0x01}, {0x00, 0x87}, {0x02, 0x01}, + {0x00, 0x78}, {0x00, 0x4a}, {0x16, 0x01}, {0x0c, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xa4}, {0x00, 0x96}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x69}, {0x00, 0xb1}, + {0x02, 0x01}, {0x00, 0x1b}, {0x00, 0xa5}, {0x06, 0x01}, + {0x02, 0x01}, {0x00, 0xb2}, {0x02, 0x01}, {0x00, 0x5a}, + {0x00, 0x2b}, {0x02, 0x01}, {0x00, 0x88}, {0x00, 0xb3}, + {0x10, 0x01}, {0x0a, 0x01}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x90}, {0x02, 0x01}, {0x00, 0x09}, {0x00, 0xa0}, + {0x02, 0x01}, {0x00, 0x97}, {0x00, 0x79}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xa6}, {0x00, 0x6a}, {0x00, 0xb4}, + {0x0c, 0x01}, {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x1a}, + {0x02, 0x01}, {0x00, 0x0a}, {0x00, 0xb0}, {0x02, 0x01}, + {0x00, 0x3b}, {0x02, 0x01}, {0x00, 0x0b}, {0x00, 0xc0}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x4b}, {0x00, 0xc1}, + {0x02, 0x01}, {0x00, 0x98}, {0x00, 0x89}, {0x43, 0x01}, + {0x22, 0x01}, {0x10, 0x01}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x1c}, {0x00, 0xb5}, {0x02, 0x01}, + {0x00, 0x5b}, {0x00, 0xc2}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x2c}, {0x00, 0xa7}, {0x02, 0x01}, {0x00, 0x7a}, + {0x00, 0xc3}, {0x0a, 0x01}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x3c}, {0x02, 0x01}, {0x00, 0x0c}, {0x00, 0xd0}, + {0x02, 0x01}, {0x00, 0xb6}, {0x00, 0x6b}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xc4}, {0x00, 0x4c}, {0x02, 0x01}, + {0x00, 0x99}, {0x00, 0xa8}, {0x10, 0x01}, {0x08, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x8a}, {0x00, 0xc5}, + {0x02, 0x01}, {0x00, 0x5c}, {0x00, 0xd1}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xb7}, {0x00, 0x7b}, {0x02, 0x01}, + {0x00, 0x1d}, {0x00, 0xd2}, {0x09, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x2d}, {0x00, 0xd3}, {0x02, 0x01}, + {0x00, 0x3d}, {0x00, 0xc6}, {0x55, 0xfa}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x6c}, {0x00, 0xa9}, {0x02, 0x01}, + {0x00, 0x9a}, {0x00, 0xd4}, {0x20, 0x01}, {0x10, 0x01}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xb8}, + {0x00, 0x8b}, {0x02, 0x01}, {0x00, 0x4d}, {0x00, 0xc7}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x7c}, {0x00, 0xd5}, + {0x02, 0x01}, {0x00, 0x5d}, {0x00, 0xe1}, {0x08, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x1e}, {0x00, 0xe2}, + {0x02, 0x01}, {0x00, 0xaa}, {0x00, 0xb9}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x9b}, {0x00, 0xe3}, {0x02, 0x01}, + {0x00, 0xd6}, {0x00, 0x6d}, {0x14, 0x01}, {0x0a, 0x01}, + {0x06, 0x01}, {0x02, 0x01}, {0x00, 0x3e}, {0x02, 0x01}, + {0x00, 0x2e}, {0x00, 0x4e}, {0x02, 0x01}, {0x00, 0xc8}, + {0x00, 0x8c}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xe4}, + {0x00, 0xd7}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x7d}, + {0x00, 0xab}, {0x00, 0xe5}, {0x0a, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xba}, {0x00, 0x5e}, {0x02, 0x01}, + {0x00, 0xc9}, {0x02, 0x01}, {0x00, 0x9c}, {0x00, 0x6e}, + {0x08, 0x01}, {0x02, 0x01}, {0x00, 0xe6}, {0x02, 0x01}, + {0x00, 0x0d}, {0x02, 0x01}, {0x00, 0xe0}, {0x00, 0x0e}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xd8}, {0x00, 0x8d}, + {0x02, 0x01}, {0x00, 0xbb}, {0x00, 0xca}, {0x4a, 0x01}, + {0x02, 0x01}, {0x00, 0xff}, {0x40, 0x01}, {0x3a, 0x01}, + {0x20, 0x01}, {0x10, 0x01}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xac}, {0x00, 0xe7}, {0x02, 0x01}, + {0x00, 0x7e}, {0x00, 0xd9}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x9d}, {0x00, 0xe8}, {0x02, 0x01}, {0x00, 0x8e}, + {0x00, 0xcb}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xbc}, {0x00, 0xda}, {0x02, 0x01}, {0x00, 0xad}, + {0x00, 0xe9}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x9e}, + {0x00, 0xcc}, {0x02, 0x01}, {0x00, 0xdb}, {0x00, 0xbd}, + {0x10, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xea}, {0x00, 0xae}, {0x02, 0x01}, {0x00, 0xdc}, + {0x00, 0xcd}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xeb}, + {0x00, 0xbe}, {0x02, 0x01}, {0x00, 0xdd}, {0x00, 0xec}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xce}, + {0x00, 0xed}, {0x02, 0x01}, {0x00, 0xde}, {0x00, 0xee}, + {0x00, 0x0f}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xf0}, + {0x00, 0x1f}, {0x00, 0xf1}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xf2}, {0x00, 0x2f}, {0x02, 0x01}, {0x00, 0xf3}, + {0x00, 0x3f}, {0x12, 0x01}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0xf4}, {0x00, 0x4f}, {0x02, 0x01}, + {0x00, 0xf5}, {0x00, 0x5f}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xf6}, {0x00, 0x6f}, {0x02, 0x01}, {0x00, 0xf7}, + {0x02, 0x01}, {0x00, 0x7f}, {0x00, 0x8f}, {0x0a, 0x01}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xf8}, {0x00, 0xf9}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x9f}, {0x00, 0xaf}, + {0x00, 0xfa}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0xfb}, {0x00, 0xbf}, {0x02, 0x01}, {0x00, 0xfc}, + {0x00, 0xcf}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0xfd}, + {0x00, 0xdf}, {0x02, 0x01}, {0x00, 0xfe}, {0x00, 0xef} + +}; + +static const guchar huffbits_32[31][2] = { + {0x02, 0x01}, {0x00, 0x00}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x08}, {0x00, 0x04}, {0x02, 0x01}, + {0x00, 0x01}, {0x00, 0x02}, {0x08, 0x01}, {0x04, 0x01}, + {0x02, 0x01}, {0x00, 0x0c}, {0x00, 0x0a}, {0x02, 0x01}, + {0x00, 0x03}, {0x00, 0x06}, {0x06, 0x01}, {0x02, 0x01}, + {0x00, 0x09}, {0x02, 0x01}, {0x00, 0x05}, {0x00, 0x07}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x0e}, {0x00, 0x0d}, + {0x02, 0x01}, {0x00, 0x0f}, {0x00, 0x0b} +}; + +static const guchar huffbits_33[31][2] = { + {0x10, 0x01}, {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, + {0x00, 0x00}, {0x00, 0x01}, {0x02, 0x01}, {0x00, 0x02}, + {0x00, 0x03}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x04}, + {0x00, 0x05}, {0x02, 0x01}, {0x00, 0x06}, {0x00, 0x07}, + {0x08, 0x01}, {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x08}, + {0x00, 0x09}, {0x02, 0x01}, {0x00, 0x0a}, {0x00, 0x0b}, + {0x04, 0x01}, {0x02, 0x01}, {0x00, 0x0c}, {0x00, 0x0d}, + {0x02, 0x01}, {0x00, 0x0e}, {0x00, 0x0f} +}; + +/* Array of decoder table structures */ +static const struct huffcodetab huff_tables[] = { + /* 0 */ {0, 0, 0, 0, FALSE, NULL}, + /* 1 */ {7, 2, 2, 0, FALSE, huffbits_1}, + /* 2 */ {17, 3, 3, 0, FALSE, huffbits_2}, + /* 3 */ {17, 3, 3, 0, FALSE, huffbits_3}, + /* 4 */ {0, 0, 0, 0, FALSE, NULL}, + /* 5 */ {31, 4, 4, 0, FALSE, huffbits_5}, + /* 6 */ {31, 4, 4, 0, FALSE, huffbits_6}, + /* 7 */ {71, 6, 6, 0, FALSE, huffbits_7}, + /* 8 */ {71, 6, 6, 0, FALSE, huffbits_8}, + /* 9 */ {71, 6, 6, 0, FALSE, huffbits_9}, + /* 10 */ {127, 8, 8, 0, FALSE, huffbits_10}, + /* 11 */ {127, 8, 8, 0, FALSE, huffbits_11}, + /* 12 */ {127, 8, 8, 0, FALSE, huffbits_12}, + /* 13 */ {511, 16, 16, 0, FALSE, huffbits_13}, + /* 14 */ {0, 0, 0, 0, FALSE, NULL}, + /* 15 */ {511, 16, 16, 0, FALSE, huffbits_15}, + /* 16 */ {511, 16, 16, 1, FALSE, huffbits_16}, + /* 17 */ {511, 16, 16, 2, FALSE, huffbits_16}, + /* 18 */ {511, 16, 16, 3, FALSE, huffbits_16}, + /* 19 */ {511, 16, 16, 4, FALSE, huffbits_16}, + /* 20 */ {511, 16, 16, 6, FALSE, huffbits_16}, + /* 21 */ {511, 16, 16, 8, FALSE, huffbits_16}, + /* 22 */ {511, 16, 16, 10, FALSE, huffbits_16}, + /* 23 */ {511, 16, 16, 13, FALSE, huffbits_16}, + /* 24 */ {512, 16, 16, 4, FALSE, huffbits_24}, + /* 25 */ {512, 16, 16, 5, FALSE, huffbits_24}, + /* 26 */ {512, 16, 16, 6, FALSE, huffbits_24}, + /* 27 */ {512, 16, 16, 7, FALSE, huffbits_24}, + /* 28 */ {512, 16, 16, 8, FALSE, huffbits_24}, + /* 29 */ {512, 16, 16, 9, FALSE, huffbits_24}, + /* 30 */ {512, 16, 16, 11, FALSE, huffbits_24}, + /* 31 */ {512, 16, 16, 13, FALSE, huffbits_24}, + /* 32 */ {31, 1, 16, 0, TRUE, huffbits_32}, + /* 33 */ {31, 1, 16, 0, TRUE, huffbits_33} +}; + +static const gfloat pow_2_table[] = { +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000000f, +0.000000000000000f, 0.000000000000000f, 0.000000000000000f, 0.000000000000001f, +0.000000000000001f, 0.000000000000001f, 0.000000000000001f, 0.000000000000001f, +0.000000000000001f, 0.000000000000001f, 0.000000000000002f, 0.000000000000002f, +0.000000000000003f, 0.000000000000003f, 0.000000000000004f, 0.000000000000004f, +0.000000000000005f, 0.000000000000006f, 0.000000000000007f, 0.000000000000008f, +0.000000000000010f, 0.000000000000012f, 0.000000000000014f, 0.000000000000017f, +0.000000000000020f, 0.000000000000024f, 0.000000000000028f, 0.000000000000034f, +0.000000000000040f, 0.000000000000048f, 0.000000000000057f, 0.000000000000068f, +0.000000000000080f, 0.000000000000096f, 0.000000000000114f, 0.000000000000135f, +0.000000000000161f, 0.000000000000191f, 0.000000000000227f, 0.000000000000270f, +0.000000000000322f, 0.000000000000382f, 0.000000000000455f, 0.000000000000541f, +0.000000000000643f, 0.000000000000765f, 0.000000000000909f, 0.000000000001082f, +0.000000000001286f, 0.000000000001530f, 0.000000000001819f, 0.000000000002163f, +0.000000000002572f, 0.000000000003059f, 0.000000000003638f, 0.000000000004326f, +0.000000000005145f, 0.000000000006118f, 0.000000000007276f, 0.000000000008653f, +0.000000000010290f, 0.000000000012237f, 0.000000000014552f, 0.000000000017305f, +0.000000000020580f, 0.000000000024473f, 0.000000000029104f, 0.000000000034610f, +0.000000000041159f, 0.000000000048947f, 0.000000000058208f, 0.000000000069221f, +0.000000000082318f, 0.000000000097893f, 0.000000000116415f, 0.000000000138442f, +0.000000000164636f, 0.000000000195786f, 0.000000000232831f, 0.000000000276884f, +0.000000000329272f, 0.000000000391573f, 0.000000000465661f, 0.000000000553768f, +0.000000000658544f, 0.000000000783146f, 0.000000000931323f, 0.000000001107535f, +0.000000001317089f, 0.000000001566292f, 0.000000001862645f, 0.000000002215071f, +0.000000002634178f, 0.000000003132583f, 0.000000003725290f, 0.000000004430142f, +0.000000005268356f, 0.000000006265167f, 0.000000007450581f, 0.000000008860283f, +0.000000010536712f, 0.000000012530333f, 0.000000014901161f, 0.000000017720566f, +0.000000021073424f, 0.000000025060666f, 0.000000029802322f, 0.000000035441133f, +0.000000042146848f, 0.000000050121333f, 0.000000059604645f, 0.000000070882265f, +0.000000084293696f, 0.000000100242666f, 0.000000119209290f, 0.000000141764531f, +0.000000168587391f, 0.000000200485331f, 0.000000238418579f, 0.000000283529062f, +0.000000337174782f, 0.000000400970663f, 0.000000476837158f, 0.000000567058123f, +0.000000674349565f, 0.000000801941326f, 0.000000953674316f, 0.000001134116246f, +0.000001348699129f, 0.000001603882652f, 0.000001907348633f, 0.000002268232492f, +0.000002697398259f, 0.000003207765303f, 0.000003814697266f, 0.000004536464985f, +0.000005394796517f, 0.000006415530606f, 0.000007629394531f, 0.000009072929970f, +0.000010789593034f, 0.000012831061213f, 0.000015258789062f, 0.000018145859940f, +0.000021579186068f, 0.000025662122425f, 0.000030517578125f, 0.000036291719880f, +0.000043158372137f, 0.000051324244851f, 0.000061035156250f, 0.000072583439760f, +0.000086316744273f, 0.000102648489701f, 0.000122070312500f, 0.000145166879520f, +0.000172633488546f, 0.000205296979402f, 0.000244140625000f, 0.000290333759040f, +0.000345266977092f, 0.000410593958804f, 0.000488281250000f, 0.000580667518079f, +0.000690533954185f, 0.000821187917609f, 0.000976562500000f, 0.001161335036159f, +0.001381067908369f, 0.001642375835218f, 0.001953125000000f, 0.002322670072317f, +0.002762135816738f, 0.003284751670435f, 0.003906250000000f, 0.004645340144634f, +0.005524271633476f, 0.006569503340870f, 0.007812500000000f, 0.009290680289268f, +0.011048543266952f, 0.013139006681740f, 0.015625000000000f, 0.018581360578537f, +0.022097086533904f, 0.026278013363481f, 0.031250000000000f, 0.037162721157074f, +0.044194173067808f, 0.052556026726961f, 0.062500000000000f, 0.074325442314148f, +0.088388346135616f, 0.105112053453922f, 0.125000000000000f, 0.148650884628296f, +0.176776692271233f, 0.210224106907845f, 0.250000000000000f, 0.297301769256592f, +0.353553384542465f, 0.420448213815689f, 0.500000000000000f, 0.594603538513184f, +0.707106769084930f, 0.840896427631378f, 1.000000000000000f, 1.189207077026367f, +1.414213538169861f, 1.681792855262756f, 2.000000000000000f, 2.378414154052734f, +2.828427076339722f, 3.363585710525513f, 4.000000000000000f, 4.756828308105469f, +5.656854152679443f, 6.727171421051025f, 8.000000000000000f, 9.513656616210938f, +11.313708305358887f, 13.454342842102051f, 16.000000000000000f, 19.027313232421875f, +22.627416610717773f, 26.908685684204102f, 32.000000000000000f, 38.054626464843750f, +45.254833221435547f, 53.817371368408203f, 64.000000000000000f, 76.109252929687500f, +90.509666442871094f, 107.634742736816406f, 128.000000000000000f, 152.218505859375000f, +181.019332885742188f, 215.269485473632812f, 256.000000000000000f, 304.437011718750000f, +362.038665771484375f, 430.538970947265625f, 512.000000000000000f, 608.874023437500000f, +724.077331542968750f, 861.077941894531250f, 1024.000000000000000f, 1217.748046875000000f, +1448.154663085937500f, 1722.155883789062500f, 2048.000000000000000f, 2435.496093750000000f +}; + +static const gfloat pow_43_table[] = { +0.0000000000f, 1.0000000000f, 2.5198421478f, 4.3267488480f, +6.3496046066f, 8.5498800278f, 10.9027242661f, 13.3905191422f, +16.0000019073f, 18.7207565308f, 21.5443496704f, 24.4637832642f, +27.4731445312f, 30.5673542023f, 33.7419967651f, 36.9931869507f, +40.3174781799f, 43.7117919922f, 47.1733512878f, 50.6996383667f, +54.2883605957f, 57.9374160767f, 61.6448745728f, 65.4089508057f, +69.2279891968f, 73.1004562378f, 77.0249099731f, 81.0000076294f, +85.0245056152f, 89.0971984863f, 93.2169876099f, 97.3828125000f, +101.5936813354f, 105.8486480713f, 110.1468200684f, 114.4873352051f, +118.8694000244f, 123.2922286987f, 127.7550811768f, 132.2572631836f, +136.7980957031f, 141.3769226074f, 145.9931335449f, 150.6461334229f, +155.3353576660f, 160.0602264404f, 164.8202209473f, 169.6148529053f, +174.4436035156f, 179.3060150146f, 184.2015991211f, 189.1299438477f, +194.0906066895f, 199.0831756592f, 204.1072387695f, 209.1624145508f, +214.2483215332f, 219.3645935059f, 224.5108795166f, 229.6868286133f, +234.8920898438f, 240.1263732910f, 245.3893127441f, 250.6806488037f, +256.0000305176f, 261.3472290039f, 266.7218933105f, 272.1237792969f, +277.5525817871f, 283.0080871582f, 288.4900207520f, 293.9981079102f, +299.5321350098f, 305.0918273926f, 310.6769409180f, 316.2872924805f, +321.9226379395f, 327.5827636719f, 333.2674255371f, 338.9764404297f, +344.7096252441f, 350.4667053223f, 356.2475585938f, 362.0519409180f, +367.8796691895f, 373.7305908203f, 379.6044921875f, 385.5012207031f, +391.4205627441f, 397.3623962402f, 403.3265075684f, 409.3127441406f, +415.3209533691f, 421.3509826660f, 427.4026489258f, 433.4758300781f, +439.5703430176f, 445.6860656738f, 451.8228454590f, 457.9805297852f, +464.1589660645f, 470.3580322266f, 476.5776062012f, 482.8175354004f, +489.0776977539f, 495.3579711914f, 501.6581726074f, 507.9782409668f, +514.3180541992f, 520.6774291992f, 527.0562744141f, 533.4545288086f, +539.8719482422f, 546.3085327148f, 552.7641601562f, 559.2387084961f, +565.7319946289f, 572.2439575195f, 578.7745361328f, 585.3236083984f, +591.8909912109f, 598.4766845703f, 605.0805664062f, 611.7024536133f, +618.3423461914f, 625.0001220703f, 631.6756591797f, 638.3688964844f, +645.0797119141f, 651.8080444336f, 658.5537109375f, 665.3167724609f, +672.0970458984f, 678.8944702148f, 685.7089233398f, 692.5404052734f, +699.3887329102f, 706.2538452148f, 713.1357421875f, 720.0342407227f, +726.9493408203f, 733.8808593750f, 740.8288574219f, 747.7931518555f, +754.7736816406f, 761.7703857422f, 768.7832031250f, 775.8120727539f, +782.8568725586f, 789.9176025391f, 796.9940795898f, 804.0863647461f, +811.1942749023f, 818.3178100586f, 825.4568481445f, 832.6113891602f, +839.7813110352f, 846.9666137695f, 854.1671752930f, 861.3829345703f, +868.6138305664f, 875.8598022461f, 883.1207885742f, 890.3967285156f, +897.6875610352f, 904.9932861328f, 912.3137207031f, 919.6488647461f, +926.9987182617f, 934.3631591797f, 941.7420654297f, 949.1355590820f, +956.5433959961f, 963.9656372070f, 971.4022216797f, 978.8530273438f, +986.3180541992f, 993.7972412109f, 1001.2904663086f, 1008.7977905273f, +1016.3190917969f, 1023.8543701172f, 1031.4035644531f, 1038.9665527344f, +1046.5432128906f, 1054.1337890625f, 1061.7379150391f, 1069.3558349609f, +1076.9871826172f, 1084.6322021484f, 1092.2906494141f, 1099.9626464844f, +1107.6479492188f, 1115.3465576172f, 1123.0585937500f, 1130.7838134766f, +1138.5222167969f, 1146.2739257812f, 1154.0385742188f, 1161.8164062500f, +1169.6072998047f, 1177.4112548828f, 1185.2280273438f, 1193.0577392578f, +1200.9003906250f, 1208.7558593750f, 1216.6240234375f, 1224.5050048828f, +1232.3986816406f, 1240.3049316406f, 1248.2238769531f, 1256.1553955078f, +1264.0994873047f, 1272.0560302734f, 1280.0250244141f, 1288.0064697266f, +1296.0002441406f, 1304.0064697266f, 1312.0249023438f, 1320.0556640625f, +1328.0986328125f, 1336.1538085938f, 1344.2211914062f, 1352.3006591797f, +1360.3922119141f, 1368.4957275391f, 1376.6113281250f, 1384.7388916016f, +1392.8784179688f, 1401.0299072266f, 1409.1932373047f, 1417.3684082031f, +1425.5552978516f, 1433.7540283203f, 1441.9644775391f, 1450.1866455078f, +1458.4205322266f, 1466.6660156250f, 1474.9230957031f, 1483.1917724609f, +1491.4719238281f, 1499.7636718750f, 1508.0667724609f, 1516.3814697266f, +1524.7075195312f, 1533.0449218750f, 1541.3936767578f, 1549.7537841797f, +1558.1251220703f, 1566.5078125000f, 1574.9016113281f, 1583.3067626953f, +1591.7230224609f, 1600.1503906250f, 1608.5888671875f, 1617.0384521484f, +1625.4990234375f, 1633.9707031250f, 1642.4533691406f, 1650.9468994141f, +1659.4515380859f, 1667.9669189453f, 1676.4932861328f, 1685.0305175781f, +1693.5784912109f, 1702.1373291016f, 1710.7069091797f, 1719.2872314453f, +1727.8782958984f, 1736.4801025391f, 1745.0925292969f, 1753.7155761719f, +1762.3492431641f, 1770.9934082031f, 1779.6483154297f, 1788.3135986328f, +1796.9895019531f, 1805.6759033203f, 1814.3726806641f, 1823.0798339844f, +1831.7974853516f, 1840.5256347656f, 1849.2639160156f, 1858.0126953125f, +1866.7717285156f, 1875.5410156250f, 1884.3206787109f, 1893.1104736328f, +1901.9105224609f, 1910.7207031250f, 1919.5411376953f, 1928.3717041016f, +1937.2124023438f, 1946.0631103516f, 1954.9239501953f, 1963.7949218750f, +1972.6757812500f, 1981.5666503906f, 1990.4676513672f, 1999.3785400391f, +2008.2993164062f, 2017.2299804688f, 2026.1706542969f, 2035.1212158203f, +2044.0815429688f, 2053.0517578125f, 2062.0317382812f, 2071.0214843750f, +2080.0209960938f, 2089.0302734375f, 2098.0493164062f, 2107.0781250000f, +2116.1164550781f, 2125.1645507812f, 2134.2221679688f, 2143.2895507812f, +2152.3664550781f, 2161.4528808594f, 2170.5490722656f, 2179.6545410156f, +2188.7697753906f, 2197.8942871094f, 2207.0283203125f, 2216.1718750000f, +2225.3249511719f, 2234.4873046875f, 2243.6591796875f, 2252.8405761719f, +2262.0310058594f, 2271.2309570312f, 2280.4401855469f, 2289.6586914062f, +2298.8864746094f, 2308.1237792969f, 2317.3701171875f, 2326.6257324219f, +2335.8903808594f, 2345.1645507812f, 2354.4475097656f, 2363.7399902344f, +2373.0415039062f, 2382.3520507812f, 2391.6718750000f, 2401.0004882812f, +2410.3383789062f, 2419.6853027344f, 2429.0412597656f, 2438.4062500000f, +2447.7802734375f, 2457.1630859375f, 2466.5551757812f, 2475.9560546875f, +2485.3657226562f, 2494.7844238281f, 2504.2121582031f, 2513.6486816406f, +2523.0939941406f, 2532.5483398438f, 2542.0112304688f, 2551.4831542969f, +2560.9638671875f, 2570.4531250000f, 2579.9514160156f, 2589.4584960938f, +2598.9741210938f, 2608.4985351562f, 2618.0314941406f, 2627.5734863281f, +2637.1237792969f, 2646.6828613281f, 2656.2507324219f, 2665.8271484375f, +2675.4121093750f, 2685.0056152344f, 2694.6079101562f, 2704.2185058594f, +2713.8378906250f, 2723.4655761719f, 2733.1020507812f, 2742.7468261719f, +2752.4001464844f, 2762.0617675781f, 2771.7321777344f, 2781.4108886719f, +2791.0979003906f, 2800.7934570312f, 2810.4973144531f, 2820.2097167969f, +2829.9301757812f, 2839.6594238281f, 2849.3967285156f, 2859.1423339844f, +2868.8962402344f, 2878.6586914062f, 2888.4291992188f, 2898.2080078125f, +2907.9951171875f, 2917.7905273438f, 2927.5942382812f, 2937.4060058594f, +2947.2258300781f, 2957.0541992188f, 2966.8903808594f, 2976.7348632812f, +2986.5876464844f, 2996.4484863281f, 3006.3173828125f, 3016.1943359375f, +3026.0793457031f, 3035.9726562500f, 3045.8737792969f, 3055.7832031250f, +3065.7004394531f, 3075.6259765625f, 3085.5593261719f, 3095.5007324219f, +3105.4499511719f, 3115.4074707031f, 3125.3728027344f, 3135.3459472656f, +3145.3271484375f, 3155.3161621094f, 3165.3132324219f, 3175.3183593750f, +3185.3310546875f, 3195.3518066406f, 3205.3803710938f, 3215.4167480469f, +3225.4609375000f, 3235.5131835938f, 3245.5729980469f, 3255.6406250000f, +3265.7160644531f, 3275.7993164062f, 3285.8903808594f, 3295.9892578125f, +3306.0957031250f, 3316.2099609375f, 3326.3320312500f, 3336.4616699219f, +3346.5988769531f, 3356.7441406250f, 3366.8967285156f, 3377.0571289062f, +3387.2250976562f, 3397.4008789062f, 3407.5839843750f, 3417.7749023438f, +3427.9736328125f, 3438.1796875000f, 3448.3933105469f, 3458.6145019531f, +3468.8432617188f, 3479.0795898438f, 3489.3234863281f, 3499.5749511719f, +3509.8339843750f, 3520.1003417969f, 3530.3742675781f, 3540.6555175781f, +3550.9445800781f, 3561.2407226562f, 3571.5446777344f, 3581.8557128906f, +3592.1743164062f, 3602.5004882812f, 3612.8339843750f, 3623.1748046875f, +3633.5229492188f, 3643.8786621094f, 3654.2414550781f, 3664.6118164062f, +3674.9895019531f, 3685.3745117188f, 3695.7668457031f, 3706.1665039062f, +3716.5732421875f, 3726.9875488281f, 3737.4091796875f, 3747.8378906250f, +3758.2739257812f, 3768.7170410156f, 3779.1677246094f, 3789.6254882812f, +3800.0903320312f, 3810.5625000000f, 3821.0419921875f, 3831.5285644531f, +3842.0222167969f, 3852.5231933594f, 3863.0312500000f, 3873.5463867188f, +3884.0688476562f, 3894.5981445312f, 3905.1347656250f, 3915.6787109375f, +3926.2294921875f, 3936.7873535156f, 3947.3522949219f, 3957.9245605469f, +3968.5036621094f, 3979.0898437500f, 3989.6831054688f, 4000.2834472656f, +4010.8906250000f, 4021.5048828125f, 4032.1262207031f, 4042.7546386719f, +4053.3898925781f, 4064.0322265625f, 4074.6816406250f, 4085.3378906250f, +4096.0009765625f, 4106.6713867188f, 4117.3481445312f, 4128.0322265625f, +4138.7231445312f, 4149.4208984375f, 4160.1254882812f, 4170.8374023438f, +4181.5556640625f, 4192.2812500000f, 4203.0136718750f, 4213.7524414062f, +4224.4985351562f, 4235.2514648438f, 4246.0107421875f, 4256.7773437500f, +4267.5502929688f, 4278.3305664062f, 4289.1171875000f, 4299.9111328125f, +4310.7114257812f, 4321.5185546875f, 4332.3325195312f, 4343.1533203125f, +4353.9804687500f, 4364.8149414062f, 4375.6557617188f, 4386.5034179688f, +4397.3574218750f, 4408.2187500000f, 4419.0864257812f, 4429.9609375000f, +4440.8417968750f, 4451.7294921875f, 4462.6240234375f, 4473.5249023438f, +4484.4326171875f, 4495.3471679688f, 4506.2680664062f, 4517.1958007812f, +4528.1298828125f, 4539.0708007812f, 4550.0180664062f, 4560.9721679688f, +4571.9326171875f, 4582.8999023438f, 4593.8735351562f, 4604.8540039062f, +4615.8408203125f, 4626.8339843750f, 4637.8339843750f, 4648.8403320312f, +4659.8535156250f, 4670.8725585938f, 4681.8989257812f, 4692.9311523438f, +4703.9702148438f, 4715.0156250000f, 4726.0673828125f, 4737.1259765625f, +4748.1904296875f, 4759.2617187500f, 4770.3398437500f, 4781.4238281250f, +4792.5141601562f, 4803.6113281250f, 4814.7148437500f, 4825.8247070312f, +4836.9409179688f, 4848.0634765625f, 4859.1923828125f, 4870.3276367188f, +4881.4692382812f, 4892.6176757812f, 4903.7719726562f, 4914.9326171875f, +4926.1000976562f, 4937.2734375000f, 4948.4531250000f, 4959.6391601562f, +4970.8315429688f, 4982.0302734375f, 4993.2353515625f, 5004.4467773438f, +5015.6640625000f, 5026.8881835938f, 5038.1181640625f, 5049.3544921875f, +5060.5971679688f, 5071.8461914062f, 5083.1010742188f, 5094.3627929688f, +5105.6303710938f, 5116.9042968750f, 5128.1840820312f, 5139.4702148438f, +5150.7626953125f, 5162.0615234375f, 5173.3662109375f, 5184.6772460938f, +5195.9946289062f, 5207.3178710938f, 5218.6469726562f, 5229.9829101562f, +5241.3247070312f, 5252.6723632812f, 5264.0268554688f, 5275.3867187500f, +5286.7529296875f, 5298.1254882812f, 5309.5039062500f, 5320.8886718750f, +5332.2792968750f, 5343.6762695312f, 5355.0791015625f, 5366.4882812500f, +5377.9028320312f, 5389.3242187500f, 5400.7514648438f, 5412.1845703125f, +5423.6235351562f, 5435.0688476562f, 5446.5200195312f, 5457.9775390625f, +5469.4409179688f, 5480.9101562500f, 5492.3857421875f, 5503.8666992188f, +5515.3540039062f, 5526.8476562500f, 5538.3466796875f, 5549.8520507812f, +5561.3632812500f, 5572.8803710938f, 5584.4038085938f, 5595.9326171875f, +5607.4677734375f, 5619.0087890625f, 5630.5556640625f, 5642.1083984375f, +5653.6669921875f, 5665.2319335938f, 5676.8022460938f, 5688.3789062500f, +5699.9609375000f, 5711.5493164062f, 5723.1435546875f, 5734.7436523438f, +5746.3491210938f, 5757.9609375000f, 5769.5786132812f, 5781.2021484375f, +5792.8315429688f, 5804.4663085938f, 5816.1074218750f, 5827.7543945312f, +5839.4067382812f, 5851.0649414062f, 5862.7294921875f, 5874.3994140625f, +5886.0751953125f, 5897.7568359375f, 5909.4443359375f, 5921.1376953125f, +5932.8364257812f, 5944.5410156250f, 5956.2514648438f, 5967.9677734375f, +5979.6899414062f, 5991.4174804688f, 6003.1513671875f, 6014.8906250000f, +6026.6352539062f, 6038.3862304688f, 6050.1425781250f, 6061.9047851562f, +6073.6723632812f, 6085.4458007812f, 6097.2250976562f, 6109.0102539062f, +6120.8007812500f, 6132.5971679688f, 6144.3989257812f, 6156.2065429688f, +6168.0200195312f, 6179.8388671875f, 6191.6635742188f, 6203.4936523438f, +6215.3295898438f, 6227.1713867188f, 6239.0185546875f, 6250.8710937500f, +6262.7294921875f, 6274.5937500000f, 6286.4633789062f, 6298.3383789062f, +6310.2192382812f, 6322.1059570312f, 6333.9980468750f, 6345.8955078125f, +6357.7988281250f, 6369.7075195312f, 6381.6215820312f, 6393.5415039062f, +6405.4672851562f, 6417.3984375000f, 6429.3349609375f, 6441.2768554688f, +6453.2246093750f, 6465.1777343750f, 6477.1362304688f, 6489.1005859375f, +6501.0703125000f, 6513.0454101562f, 6525.0263671875f, 6537.0126953125f, +6549.0043945312f, 6561.0019531250f, 6573.0043945312f, 6585.0126953125f, +6597.0263671875f, 6609.0454101562f, 6621.0703125000f, 6633.1000976562f, +6645.1357421875f, 6657.1767578125f, 6669.2231445312f, 6681.2753906250f, +6693.3325195312f, 6705.3955078125f, 6717.4633789062f, 6729.5371093750f, +6741.6162109375f, 6753.7006835938f, 6765.7905273438f, 6777.8857421875f, +6789.9863281250f, 6802.0927734375f, 6814.2041015625f, 6826.3208007812f, +6838.4428710938f, 6850.5708007812f, 6862.7036132812f, 6874.8417968750f, +6886.9858398438f, 6899.1347656250f, 6911.2890625000f, 6923.4487304688f, +6935.6137695312f, 6947.7841796875f, 6959.9599609375f, 6972.1411132812f, +6984.3276367188f, 6996.5190429688f, 7008.7163085938f, 7020.9184570312f, +7033.1259765625f, 7045.3388671875f, 7057.5571289062f, 7069.7807617188f, +7082.0097656250f, 7094.2436523438f, 7106.4829101562f, 7118.7275390625f, +7130.9775390625f, 7143.2329101562f, 7155.4931640625f, 7167.7587890625f, +7180.0297851562f, 7192.3061523438f, 7204.5874023438f, 7216.8740234375f, +7229.1660156250f, 7241.4628906250f, 7253.7656250000f, 7266.0732421875f, +7278.3857421875f, 7290.7036132812f, 7303.0268554688f, 7315.3554687500f, +7327.6889648438f, 7340.0278320312f, 7352.3715820312f, 7364.7207031250f, +7377.0751953125f, 7389.4345703125f, 7401.7993164062f, 7414.1689453125f, +7426.5439453125f, 7438.9243164062f, 7451.3095703125f, 7463.7001953125f, +7476.0957031250f, 7488.4965820312f, 7500.9023437500f, 7513.3134765625f, +7525.7294921875f, 7538.1503906250f, 7550.5771484375f, 7563.0083007812f, +7575.4453125000f, 7587.8867187500f, 7600.3334960938f, 7612.7856445312f, +7625.2426757812f, 7637.7045898438f, 7650.1718750000f, 7662.6440429688f, +7675.1215820312f, 7687.6040039062f, 7700.0913085938f, 7712.5839843750f, +7725.0815429688f, 7737.5839843750f, 7750.0917968750f, 7762.6044921875f, +7775.1225585938f, 7787.6450195312f, 7800.1728515625f, 7812.7060546875f, +7825.2441406250f, 7837.7871093750f, 7850.3349609375f, 7862.8876953125f, +7875.4458007812f, 7888.0087890625f, 7900.5771484375f, 7913.1499023438f, +7925.7280273438f, 7938.3110351562f, 7950.8989257812f, 7963.4921875000f, +7976.0898437500f, 7988.6928710938f, 8001.3007812500f, 8013.9135742188f, +8026.5317382812f, 8039.1542968750f, 8051.7822265625f, 8064.4150390625f, +8077.0527343750f, 8089.6953125000f, 8102.3427734375f, 8114.9951171875f, +8127.6528320312f, 8140.3149414062f, 8152.9824218750f, 8165.6542968750f, +8178.3315429688f, 8191.0136718750f, 8203.7001953125f, 8216.3925781250f, +8229.0888671875f, 8241.7910156250f, 8254.4970703125f, 8267.2089843750f, +8279.9248046875f, 8292.6464843750f, 8305.3730468750f, 8318.1035156250f, +8330.8398437500f, 8343.5800781250f, 8356.3261718750f, 8369.0761718750f, +8381.8310546875f, 8394.5917968750f, 8407.3564453125f, 8420.1269531250f, +8432.9013671875f, 8445.6806640625f, 8458.4648437500f, 8471.2539062500f, +8484.0488281250f, 8496.8476562500f, 8509.6513671875f, 8522.4589843750f, +8535.2724609375f, 8548.0908203125f, 8560.9140625000f, 8573.7412109375f, +8586.5742187500f, 8599.4111328125f, 8612.2539062500f, 8625.1005859375f, +8637.9521484375f, 8650.8085937500f, 8663.6699218750f, 8676.5361328125f, +8689.4072265625f, 8702.2822265625f, 8715.1630859375f, 8728.0478515625f, +8740.9375000000f, 8753.8320312500f, 8766.7314453125f, 8779.6357421875f, +8792.5449218750f, 8805.4580078125f, 8818.3769531250f, 8831.2998046875f, +8844.2275390625f, 8857.1601562500f, 8870.0976562500f, 8883.0390625000f, +8895.9853515625f, 8908.9375000000f, 8921.8935546875f, 8934.8544921875f, +8947.8193359375f, 8960.7900390625f, 8973.7646484375f, 8986.7441406250f, +8999.7285156250f, 9012.7177734375f, 9025.7109375000f, 9038.7099609375f, +9051.7128906250f, 9064.7197265625f, 9077.7324218750f, 9090.7500000000f, +9103.7714843750f, 9116.7978515625f, 9129.8281250000f, 9142.8642578125f, +9155.9042968750f, 9168.9492187500f, 9181.9990234375f, 9195.0527343750f, +9208.1123046875f, 9221.1757812500f, 9234.2431640625f, 9247.3164062500f, +9260.3935546875f, 9273.4755859375f, 9286.5625000000f, 9299.6533203125f, +9312.7490234375f, 9325.8496093750f, 9338.9541015625f, 9352.0644531250f, +9365.1787109375f, 9378.2968750000f, 9391.4208984375f, 9404.5488281250f, +9417.6806640625f, 9430.8183593750f, 9443.9599609375f, 9457.1064453125f, +9470.2568359375f, 9483.4121093750f, 9496.5722656250f, 9509.7373046875f, +9522.9062500000f, 9536.0800781250f, 9549.2578125000f, 9562.4404296875f, +9575.6279296875f, 9588.8193359375f, 9602.0166015625f, 9615.2167968750f, +9628.4228515625f, 9641.6328125000f, 9654.8466796875f, 9668.0664062500f, +9681.2890625000f, 9694.5175781250f, 9707.7500000000f, 9720.9873046875f, +9734.2285156250f, 9747.4746093750f, 9760.7255859375f, 9773.9804687500f, +9787.2402343750f, 9800.5039062500f, 9813.7724609375f, 9827.0458984375f, +9840.3232421875f, 9853.6054687500f, 9866.8916015625f, 9880.1826171875f, +9893.4785156250f, 9906.7783203125f, 9920.0830078125f, 9933.3916015625f, +9946.7050781250f, 9960.0224609375f, 9973.3447265625f, 9986.6718750000f, +10000.0029296875f, 10013.3378906250f, 10026.6787109375f, 10040.0224609375f, +10053.3720703125f, 10066.7246093750f, 10080.0830078125f, 10093.4453125000f, +10106.8115234375f, 10120.1826171875f, 10133.5576171875f, 10146.9375000000f, +10160.3222656250f, 10173.7109375000f, 10187.1035156250f, 10200.5009765625f, +10213.9033203125f, 10227.3095703125f, 10240.7197265625f, 10254.1347656250f, +10267.5546875000f, 10280.9785156250f, 10294.4062500000f, 10307.8388671875f, +10321.2763671875f, 10334.7177734375f, 10348.1630859375f, 10361.6132812500f, +10375.0673828125f, 10388.5263671875f, 10401.9892578125f, 10415.4570312500f, +10428.9287109375f, 10442.4052734375f, 10455.8857421875f, 10469.3710937500f, +10482.8603515625f, 10496.3535156250f, 10509.8515625000f, 10523.3544921875f, +10536.8603515625f, 10550.3720703125f, 10563.8867187500f, 10577.4062500000f, +10590.9306640625f, 10604.4589843750f, 10617.9912109375f, 10631.5283203125f, +10645.0693359375f, 10658.6152343750f, 10672.1650390625f, 10685.7187500000f, +10699.2773437500f, 10712.8398437500f, 10726.4072265625f, 10739.9785156250f, +10753.5537109375f, 10767.1337890625f, 10780.7177734375f, 10794.3066406250f, +10807.8984375000f, 10821.4960937500f, 10835.0966796875f, 10848.7031250000f, +10862.3125000000f, 10875.9267578125f, 10889.5449218750f, 10903.1669921875f, +10916.7939453125f, 10930.4257812500f, 10944.0605468750f, 10957.7001953125f, +10971.3437500000f, 10984.9921875000f, 10998.6445312500f, 11012.3007812500f, +11025.9619140625f, 11039.6269531250f, 11053.2958984375f, 11066.9697265625f, +11080.6474609375f, 11094.3291015625f, 11108.0156250000f, 11121.7060546875f, +11135.4003906250f, 11149.0986328125f, 11162.8017578125f, 11176.5087890625f, +11190.2207031250f, 11203.9365234375f, 11217.6562500000f, 11231.3798828125f, +11245.1083984375f, 11258.8408203125f, 11272.5771484375f, 11286.3183593750f, +11300.0625000000f, 11313.8115234375f, 11327.5654296875f, 11341.3232421875f, +11355.0839843750f, 11368.8505859375f, 11382.6201171875f, 11396.3945312500f, +11410.1728515625f, 11423.9550781250f, 11437.7421875000f, 11451.5322265625f, +11465.3271484375f, 11479.1269531250f, 11492.9296875000f, 11506.7373046875f, +11520.5488281250f, 11534.3642578125f, 11548.1845703125f, 11562.0087890625f, +11575.8359375000f, 11589.6689453125f, 11603.5048828125f, 11617.3457031250f, +11631.1904296875f, 11645.0390625000f, 11658.8916015625f, 11672.7480468750f, +11686.6093750000f, 11700.4746093750f, 11714.3437500000f, 11728.2177734375f, +11742.0947265625f, 11755.9765625000f, 11769.8623046875f, 11783.7519531250f, +11797.6455078125f, 11811.5439453125f, 11825.4462890625f, 11839.3525390625f, +11853.2626953125f, 11867.1767578125f, 11881.0947265625f, 11895.0175781250f, +11908.9443359375f, 11922.8750000000f, 11936.8095703125f, 11950.7480468750f, +11964.6914062500f, 11978.6376953125f, 11992.5888671875f, 12006.5439453125f, +12020.5029296875f, 12034.4658203125f, 12048.4335937500f, 12062.4042968750f, +12076.3798828125f, 12090.3593750000f, 12104.3427734375f, 12118.3300781250f, +12132.3212890625f, 12146.3164062500f, 12160.3164062500f, 12174.3193359375f, +12188.3271484375f, 12202.3388671875f, 12216.3544921875f, 12230.3740234375f, +12244.3974609375f, 12258.4257812500f, 12272.4570312500f, 12286.4931640625f, +12300.5322265625f, 12314.5761718750f, 12328.6240234375f, 12342.6757812500f, +12356.7314453125f, 12370.7910156250f, 12384.8544921875f, 12398.9228515625f, +12412.9941406250f, 12427.0703125000f, 12441.1494140625f, 12455.2333984375f, +12469.3212890625f, 12483.4121093750f, 12497.5078125000f, 12511.6074218750f, +12525.7109375000f, 12539.8183593750f, 12553.9296875000f, 12568.0458984375f, +12582.1650390625f, 12596.2880859375f, 12610.4160156250f, 12624.5468750000f, +12638.6826171875f, 12652.8212890625f, 12666.9648437500f, 12681.1113281250f, +12695.2626953125f, 12709.4179687500f, 12723.5771484375f, 12737.7392578125f, +12751.9062500000f, 12766.0771484375f, 12780.2519531250f, 12794.4306640625f, +12808.6132812500f, 12822.7998046875f, 12836.9902343750f, 12851.1845703125f, +12865.3828125000f, 12879.5849609375f, 12893.7910156250f, 12908.0009765625f, +12922.2148437500f, 12936.4326171875f, 12950.6542968750f, 12964.8798828125f, +12979.1093750000f, 12993.3427734375f, 13007.5800781250f, 13021.8212890625f, +13036.0664062500f, 13050.3154296875f, 13064.5683593750f, 13078.8251953125f, +13093.0859375000f, 13107.3505859375f, 13121.6191406250f, 13135.8906250000f, +13150.1669921875f, 13164.4472656250f, 13178.7314453125f, 13193.0195312500f, +13207.3105468750f, 13221.6064453125f, 13235.9062500000f, 13250.2089843750f, +13264.5166015625f, 13278.8271484375f, 13293.1425781250f, 13307.4609375000f, +13321.7832031250f, 13336.1103515625f, 13350.4404296875f, 13364.7744140625f, +13379.1123046875f, 13393.4541015625f, 13407.7998046875f, 13422.1494140625f, +13436.5029296875f, 13450.8593750000f, 13465.2207031250f, 13479.5849609375f, +13493.9541015625f, 13508.3261718750f, 13522.7031250000f, 13537.0830078125f, +13551.4667968750f, 13565.8544921875f, 13580.2460937500f, 13594.6416015625f, +13609.0410156250f, 13623.4433593750f, 13637.8505859375f, 13652.2617187500f, +13666.6757812500f, 13681.0937500000f, 13695.5156250000f, 13709.9414062500f, +13724.3710937500f, 13738.8046875000f, 13753.2421875000f, 13767.6826171875f, +13782.1279296875f, 13796.5761718750f, 13811.0283203125f, 13825.4843750000f, +13839.9443359375f, 13854.4082031250f, 13868.8759765625f, 13883.3466796875f, +13897.8222656250f, 13912.3007812500f, 13926.7832031250f, 13941.2695312500f, +13955.7597656250f, 13970.2539062500f, 13984.7509765625f, 13999.2529296875f, +14013.7578125000f, 14028.2666015625f, 14042.7792968750f, 14057.2958984375f, +14071.8154296875f, 14086.3398437500f, 14100.8671875000f, 14115.3984375000f, +14129.9335937500f, 14144.4726562500f, 14159.0156250000f, 14173.5615234375f, +14188.1113281250f, 14202.6650390625f, 14217.2226562500f, 14231.7841796875f, +14246.3486328125f, 14260.9179687500f, 14275.4902343750f, 14290.0664062500f, +14304.6464843750f, 14319.2294921875f, 14333.8164062500f, 14348.4082031250f, +14363.0029296875f, 14377.6005859375f, 14392.2031250000f, 14406.8085937500f, +14421.4179687500f, 14436.0312500000f, 14450.6484375000f, 14465.2695312500f, +14479.8935546875f, 14494.5214843750f, 14509.1533203125f, 14523.7880859375f, +14538.4277343750f, 14553.0703125000f, 14567.7167968750f, 14582.3671875000f, +14597.0205078125f, 14611.6777343750f, 14626.3388671875f, 14641.0039062500f, +14655.6728515625f, 14670.3447265625f, 14685.0205078125f, 14699.7001953125f, +14714.3837890625f, 14729.0703125000f, 14743.7607421875f, 14758.4550781250f, +14773.1523437500f, 14787.8544921875f, 14802.5595703125f, 14817.2685546875f, +14831.9804687500f, 14846.6962890625f, 14861.4160156250f, 14876.1396484375f, +14890.8671875000f, 14905.5976562500f, 14920.3320312500f, 14935.0693359375f, +14949.8115234375f, 14964.5566406250f, 14979.3056640625f, 14994.0576171875f, +15008.8144531250f, 15023.5742187500f, 15038.3369140625f, 15053.1044921875f, +15067.8750000000f, 15082.6494140625f, 15097.4267578125f, 15112.2080078125f, +15126.9931640625f, 15141.7822265625f, 15156.5742187500f, 15171.3701171875f, +15186.1699218750f, 15200.9726562500f, 15215.7792968750f, 15230.5898437500f, +15245.4042968750f, 15260.2216796875f, 15275.0429687500f, 15289.8671875000f, +15304.6962890625f, 15319.5273437500f, 15334.3632812500f, 15349.2021484375f, +15364.0449218750f, 15378.8916015625f, 15393.7412109375f, 15408.5947265625f, +15423.4521484375f, 15438.3125000000f, 15453.1767578125f, 15468.0439453125f, +15482.9160156250f, 15497.7900390625f, 15512.6689453125f, 15527.5507812500f, +15542.4365234375f, 15557.3261718750f, 15572.2187500000f, 15587.1152343750f, +15602.0146484375f, 15616.9179687500f, 15631.8251953125f, 15646.7353515625f, +15661.6494140625f, 15676.5673828125f, 15691.4882812500f, 15706.4130859375f, +15721.3417968750f, 15736.2734375000f, 15751.2089843750f, 15766.1474609375f, +15781.0898437500f, 15796.0361328125f, 15810.9853515625f, 15825.9384765625f, +15840.8955078125f, 15855.8554687500f, 15870.8193359375f, 15885.7861328125f, +15900.7568359375f, 15915.7314453125f, 15930.7089843750f, 15945.6904296875f, +15960.6748046875f, 15975.6630859375f, 15990.6552734375f, 16005.6503906250f, +16020.6494140625f, 16035.6513671875f, 16050.6572265625f, 16065.6669921875f, +16080.6796875000f, 16095.6962890625f, 16110.7158203125f, 16125.7392578125f, +16140.7666015625f, 16155.7968750000f, 16170.8310546875f, 16185.8681640625f, +16200.9091796875f, 16215.9531250000f, 16231.0009765625f, 16246.0527343750f, +16261.1074218750f, 16276.1660156250f, 16291.2275390625f, 16306.2929687500f, +16321.3613281250f, 16336.4335937500f, 16351.5097656250f, 16366.5888671875f, +16381.6708984375f, 16396.7578125000f, 16411.8476562500f, 16426.9394531250f, +16442.0371093750f, 16457.1367187500f, 16472.2402343750f, 16487.3476562500f, +16502.4570312500f, 16517.5722656250f, 16532.6894531250f, 16547.8105468750f, +16562.9335937500f, 16578.0605468750f, 16593.1933593750f, 16608.3281250000f, +16623.4648437500f, 16638.6074218750f, 16653.7519531250f, 16668.9003906250f, +16684.0527343750f, 16699.2070312500f, 16714.3652343750f, 16729.5273437500f, +16744.6933593750f, 16759.8632812500f, 16775.0351562500f, 16790.2109375000f, +16805.3906250000f, 16820.5722656250f, 16835.7597656250f, 16850.9492187500f, +16866.1425781250f, 16881.3378906250f, 16896.5371093750f, 16911.7421875000f, +16926.9472656250f, 16942.1582031250f, 16957.3710937500f, 16972.5878906250f, +16987.8085937500f, 17003.0332031250f, 17018.2597656250f, 17033.4902343750f, +17048.7246093750f, 17063.9609375000f, 17079.2031250000f, 17094.4472656250f, +17109.6933593750f, 17124.9453125000f, 17140.1992187500f, 17155.4570312500f, +17170.7187500000f, 17185.9824218750f, 17201.2519531250f, 17216.5234375000f, +17231.7968750000f, 17247.0761718750f, 17262.3574218750f, 17277.6425781250f, +17292.9296875000f, 17308.2207031250f, 17323.5156250000f, 17338.8144531250f, +17354.1171875000f, 17369.4218750000f, 17384.7304687500f, 17400.0429687500f, +17415.3574218750f, 17430.6757812500f, 17445.9980468750f, 17461.3242187500f, +17476.6523437500f, 17491.9843750000f, 17507.3203125000f, 17522.6582031250f, +17538.0000000000f, 17553.3457031250f, 17568.6953125000f, 17584.0468750000f, +17599.4023437500f, 17614.7617187500f, 17630.1250000000f, 17645.4902343750f, +17660.8593750000f, 17676.2304687500f, 17691.6074218750f, 17706.9863281250f, +17722.3671875000f, 17737.7539062500f, 17753.1425781250f, 17768.5351562500f, +17783.9296875000f, 17799.3300781250f, 17814.7324218750f, 17830.1367187500f, +17845.5468750000f, 17860.9589843750f, 17876.3750000000f, 17891.7929687500f, +17907.2167968750f, 17922.6406250000f, 17938.0703125000f, 17953.5019531250f, +17968.9375000000f, 17984.3769531250f, 17999.8183593750f, 18015.2656250000f, +18030.7128906250f, 18046.1660156250f, 18061.6210937500f, 18077.0800781250f, +18092.5429687500f, 18108.0078125000f, 18123.4765625000f, 18138.9472656250f, +18154.4238281250f, 18169.9023437500f, 18185.3828125000f, 18200.8691406250f, +18216.3574218750f, 18231.8496093750f, 18247.3437500000f, 18262.8417968750f, +18278.3437500000f, 18293.8496093750f, 18309.3574218750f, 18324.8691406250f, +18340.3828125000f, 18355.9023437500f, 18371.4218750000f, 18386.9472656250f, +18402.4746093750f, 18418.0058593750f, 18433.5410156250f, 18449.0781250000f, +18464.6191406250f, 18480.1640625000f, 18495.7109375000f, 18511.2617187500f, +18526.8164062500f, 18542.3730468750f, 18557.9335937500f, 18573.4980468750f, +18589.0644531250f, 18604.6347656250f, 18620.2089843750f, 18635.7851562500f, +18651.3652343750f, 18666.9492187500f, 18682.5351562500f, 18698.1250000000f, +18713.7187500000f, 18729.3144531250f, 18744.9160156250f, 18760.5175781250f, +18776.1250000000f, 18791.7343750000f, 18807.3457031250f, 18822.9609375000f, +18838.5800781250f, 18854.2031250000f, 18869.8281250000f, 18885.4570312500f, +18901.0898437500f, 18916.7246093750f, 18932.3632812500f, 18948.0058593750f, +18963.6503906250f, 18979.2988281250f, 18994.9492187500f, 19010.6035156250f, +19026.2617187500f, 19041.9238281250f, 19057.5878906250f, 19073.2558593750f, +19088.9257812500f, 19104.5996093750f, 19120.2773437500f, 19135.9570312500f, +19151.6406250000f, 19167.3281250000f, 19183.0175781250f, 19198.7109375000f, +19214.4082031250f, 19230.1074218750f, 19245.8105468750f, 19261.5156250000f, +19277.2246093750f, 19292.9375000000f, 19308.6542968750f, 19324.3730468750f, +19340.0937500000f, 19355.8203125000f, 19371.5488281250f, 19387.2792968750f, +19403.0156250000f, 19418.7519531250f, 19434.4941406250f, 19450.2382812500f, +19465.9863281250f, 19481.7363281250f, 19497.4902343750f, 19513.2480468750f, +19529.0078125000f, 19544.7714843750f, 19560.5390625000f, 19576.3085937500f, +19592.0820312500f, 19607.8574218750f, 19623.6367187500f, 19639.4199218750f, +19655.2050781250f, 19670.9941406250f, 19686.7851562500f, 19702.5820312500f, +19718.3789062500f, 19734.1816406250f, 19749.9863281250f, 19765.7929687500f, +19781.6054687500f, 19797.4199218750f, 19813.2363281250f, 19829.0566406250f, +19844.8808593750f, 19860.7070312500f, 19876.5371093750f, 19892.3710937500f, +19908.2070312500f, 19924.0468750000f, 19939.8886718750f, 19955.7343750000f, +19971.5839843750f, 19987.4355468750f, 20003.2910156250f, 20019.1484375000f, +20035.0097656250f, 20050.8750000000f, 20066.7421875000f, 20082.6132812500f, +20098.4882812500f, 20114.3652343750f, 20130.2460937500f, 20146.1289062500f, +20162.0156250000f, 20177.9042968750f, 20193.7968750000f, 20209.6933593750f, +20225.5937500000f, 20241.4941406250f, 20257.4003906250f, 20273.3085937500f, +20289.2207031250f, 20305.1347656250f, 20321.0527343750f, 20336.9746093750f, +20352.8984375000f, 20368.8242187500f, 20384.7558593750f, 20400.6875000000f, +20416.6250000000f, 20432.5644531250f, 20448.5078125000f, 20464.4531250000f, +20480.4023437500f, 20496.3535156250f, 20512.3085937500f, 20528.2675781250f, +20544.2285156250f, 20560.1933593750f, 20576.1601562500f, 20592.1308593750f, +20608.1054687500f, 20624.0820312500f, 20640.0625000000f, 20656.0449218750f, +20672.0312500000f, 20688.0195312500f, 20704.0117187500f, 20720.0078125000f, +20736.0058593750f, 20752.0078125000f, 20768.0117187500f, 20784.0195312500f, +20800.0312500000f, 20816.0449218750f, 20832.0625000000f, 20848.0820312500f, +20864.1054687500f, 20880.1308593750f, 20896.1601562500f, 20912.1933593750f, +20928.2285156250f, 20944.2656250000f, 20960.3085937500f, 20976.3535156250f, +20992.4003906250f, 21008.4511718750f, 21024.5058593750f, 21040.5625000000f, +21056.6210937500f, 21072.6855468750f, 21088.7519531250f, 21104.8203125000f, +21120.8925781250f, 21136.9667968750f, 21153.0468750000f, 21169.1269531250f, +21185.2109375000f, 21201.2988281250f, 21217.3906250000f, 21233.4843750000f, +21249.5800781250f, 21265.6796875000f, 21281.7832031250f, 21297.8886718750f, +21313.9980468750f, 21330.1093750000f, 21346.2246093750f, 21362.3417968750f, +21378.4628906250f, 21394.5878906250f, 21410.7148437500f, 21426.8437500000f, +21442.9765625000f, 21459.1132812500f, 21475.2519531250f, 21491.3945312500f, +21507.5410156250f, 21523.6894531250f, 21539.8398437500f, 21555.9941406250f, +21572.1523437500f, 21588.3125000000f, 21604.4746093750f, 21620.6425781250f, +21636.8125000000f, 21652.9843750000f, 21669.1601562500f, 21685.3378906250f, +21701.5195312500f, 21717.7050781250f, 21733.8925781250f, 21750.0820312500f, +21766.2753906250f, 21782.4726562500f, 21798.6718750000f, 21814.8750000000f, +21831.0800781250f, 21847.2890625000f, 21863.5019531250f, 21879.7167968750f, +21895.9335937500f, 21912.1542968750f, 21928.3789062500f, 21944.6054687500f, +21960.8339843750f, 21977.0664062500f, 21993.3027343750f, 22009.5410156250f, +22025.7832031250f, 22042.0273437500f, 22058.2753906250f, 22074.5273437500f, +22090.7792968750f, 22107.0371093750f, 22123.2968750000f, 22139.5585937500f, +22155.8242187500f, 22172.0937500000f, 22188.3652343750f, 22204.6386718750f, +22220.9179687500f, 22237.1972656250f, 22253.4804687500f, 22269.7675781250f, +22286.0566406250f, 22302.3496093750f, 22318.6445312500f, 22334.9433593750f, +22351.2441406250f, 22367.5488281250f, 22383.8574218750f, 22400.1660156250f, +22416.4804687500f, 22432.7968750000f, 22449.1152343750f, 22465.4375000000f, +22481.7636718750f, 22498.0917968750f, 22514.4218750000f, 22530.7558593750f, +22547.0937500000f, 22563.4335937500f, 22579.7753906250f, 22596.1210937500f, +22612.4707031250f, 22628.8222656250f, 22645.1777343750f, 22661.5351562500f, +22677.8964843750f, 22694.2597656250f, 22710.6250000000f, 22726.9960937500f, +22743.3671875000f, 22759.7421875000f, 22776.1210937500f, 22792.5019531250f, +22808.8867187500f, 22825.2734375000f, 22841.6640625000f, 22858.0566406250f, +22874.4531250000f, 22890.8515625000f, 22907.2539062500f, 22923.6582031250f, +22940.0664062500f, 22956.4765625000f, 22972.8906250000f, 22989.3066406250f, +23005.7265625000f, 23022.1484375000f, 23038.5742187500f, 23055.0019531250f, +23071.4335937500f, 23087.8671875000f, 23104.3046875000f, 23120.7460937500f, +23137.1875000000f, 23153.6347656250f, 23170.0820312500f, 23186.5332031250f, +23202.9882812500f, 23219.4453125000f, 23235.9062500000f, 23252.3691406250f, +23268.8359375000f, 23285.3046875000f, 23301.7773437500f, 23318.2519531250f, +23334.7304687500f, 23351.2109375000f, 23367.6953125000f, 23384.1816406250f, +23400.6699218750f, 23417.1640625000f, 23433.6582031250f, 23450.1562500000f, +23466.6582031250f, 23483.1621093750f, 23499.6679687500f, 23516.1777343750f, +23532.6914062500f, 23549.2070312500f, 23565.7246093750f, 23582.2460937500f, +23598.7714843750f, 23615.2988281250f, 23631.8281250000f, 23648.3613281250f, +23664.8964843750f, 23681.4355468750f, 23697.9785156250f, 23714.5214843750f, +23731.0703125000f, 23747.6191406250f, 23764.1738281250f, 23780.7285156250f, +23797.2890625000f, 23813.8496093750f, 23830.4140625000f, 23846.9824218750f, +23863.5527343750f, 23880.1269531250f, 23896.7031250000f, 23913.2812500000f, +23929.8632812500f, 23946.4492187500f, 23963.0351562500f, 23979.6269531250f, +23996.2207031250f, 24012.8164062500f, 24029.4160156250f, 24046.0175781250f, +24062.6230468750f, 24079.2304687500f, 24095.8417968750f, 24112.4550781250f, +24129.0703125000f, 24145.6894531250f, 24162.3125000000f, 24178.9375000000f, +24195.5644531250f, 24212.1953125000f, 24228.8300781250f, 24245.4648437500f, +24262.1054687500f, 24278.7480468750f, 24295.3925781250f, 24312.0390625000f, +24328.6914062500f, 24345.3437500000f, 24362.0000000000f, 24378.6601562500f, +24395.3222656250f, 24411.9863281250f, 24428.6542968750f, 24445.3242187500f, +24461.9980468750f, 24478.6738281250f, 24495.3535156250f, 24512.0351562500f, +24528.7207031250f, 24545.4082031250f, 24562.0976562500f, 24578.7910156250f, +24595.4882812500f, 24612.1875000000f, 24628.8886718750f, 24645.5937500000f, +24662.3007812500f, 24679.0117187500f, 24695.7246093750f, 24712.4394531250f, +24729.1582031250f, 24745.8808593750f, 24762.6054687500f, 24779.3320312500f, +24796.0625000000f, 24812.7949218750f, 24829.5312500000f, 24846.2695312500f, +24863.0117187500f, 24879.7558593750f, 24896.5019531250f, 24913.2519531250f, +24930.0039062500f, 24946.7597656250f, 24963.5175781250f, 24980.2792968750f, +24997.0429687500f, 25013.8105468750f, 25030.5800781250f, 25047.3515625000f, +25064.1269531250f, 25080.9042968750f, 25097.6855468750f, 25114.4687500000f, +25131.2558593750f, 25148.0449218750f, 25164.8359375000f, 25181.6308593750f, +25198.4277343750f, 25215.2285156250f, 25232.0312500000f, 25248.8378906250f, +25265.6464843750f, 25282.4589843750f, 25299.2734375000f, 25316.0898437500f, +25332.9101562500f, 25349.7324218750f, 25366.5585937500f, 25383.3867187500f, +25400.2167968750f, 25417.0507812500f, 25433.8886718750f, 25450.7265625000f, +25467.5703125000f, 25484.4140625000f, 25501.2617187500f, 25518.1132812500f, +25534.9667968750f, 25551.8222656250f, 25568.6816406250f, 25585.5429687500f, +25602.4082031250f, 25619.2753906250f, 25636.1445312500f, 25653.0175781250f, +25669.8925781250f, 25686.7714843750f, 25703.6523437500f, 25720.5371093750f, +25737.4238281250f, 25754.3125000000f, 25771.2050781250f, 25788.0996093750f, +25804.9980468750f, 25821.8984375000f, 25838.8027343750f, 25855.7070312500f, +25872.6171875000f, 25889.5273437500f, 25906.4433593750f, 25923.3593750000f, +25940.2792968750f, 25957.2031250000f, 25974.1269531250f, 25991.0566406250f, +26007.9863281250f, 26024.9199218750f, 26041.8574218750f, 26058.7968750000f, +26075.7382812500f, 26092.6816406250f, 26109.6308593750f, 26126.5800781250f, +26143.5332031250f, 26160.4882812500f, 26177.4472656250f, 26194.4082031250f, +26211.3730468750f, 26228.3398437500f, 26245.3085937500f, 26262.2812500000f, +26279.2558593750f, 26296.2324218750f, 26313.2128906250f, 26330.1972656250f, +26347.1816406250f, 26364.1718750000f, 26381.1621093750f, 26398.1562500000f, +26415.1523437500f, 26432.1523437500f, 26449.1542968750f, 26466.1601562500f, +26483.1679687500f, 26500.1777343750f, 26517.1914062500f, 26534.2070312500f, +26551.2265625000f, 26568.2480468750f, 26585.2714843750f, 26602.2988281250f, +26619.3281250000f, 26636.3593750000f, 26653.3945312500f, 26670.4335937500f, +26687.4726562500f, 26704.5156250000f, 26721.5625000000f, 26738.6113281250f, +26755.6621093750f, 26772.7167968750f, 26789.7734375000f, 26806.8320312500f, +26823.8945312500f, 26840.9589843750f, 26858.0273437500f, 26875.0976562500f, +26892.1699218750f, 26909.2460937500f, 26926.3242187500f, 26943.4062500000f, +26960.4902343750f, 26977.5761718750f, 26994.6660156250f, 27011.7578125000f, +27028.8515625000f, 27045.9492187500f, 27063.0507812500f, 27080.1523437500f, +27097.2578125000f, 27114.3671875000f, 27131.4765625000f, 27148.5917968750f, +27165.7070312500f, 27182.8261718750f, 27199.9472656250f, 27217.0722656250f, +27234.1992187500f, 27251.3300781250f, 27268.4609375000f, 27285.5976562500f, +27302.7343750000f, 27319.8750000000f, 27337.0175781250f, 27354.1640625000f, +27371.3125000000f, 27388.4648437500f, 27405.6191406250f, 27422.7753906250f, +27439.9335937500f, 27457.0957031250f, 27474.2617187500f, 27491.4277343750f, +27508.5976562500f, 27525.7714843750f, 27542.9472656250f, 27560.1250000000f, +27577.3046875000f, 27594.4882812500f, 27611.6757812500f, 27628.8632812500f, +27646.0546875000f, 27663.2500000000f, 27680.4472656250f, 27697.6464843750f, +27714.8476562500f, 27732.0527343750f, 27749.2597656250f, 27766.4707031250f, +27783.6835937500f, 27800.8984375000f, 27818.1171875000f, 27835.3378906250f, +27852.5605468750f, 27869.7871093750f, 27887.0156250000f, 27904.2480468750f, +27921.4824218750f, 27938.7187500000f, 27955.9589843750f, 27973.2011718750f, +27990.4453125000f, 28007.6933593750f, 28024.9433593750f, 28042.1953125000f, +28059.4511718750f, 28076.7089843750f, 28093.9707031250f, 28111.2324218750f, +28128.5000000000f, 28145.7675781250f, 28163.0390625000f, 28180.3125000000f, +28197.5898437500f, 28214.8691406250f, 28232.1503906250f, 28249.4355468750f, +28266.7226562500f, 28284.0117187500f, 28301.3046875000f, 28318.5996093750f, +28335.8984375000f, 28353.1992187500f, 28370.5019531250f, 28387.8066406250f, +28405.1152343750f, 28422.4257812500f, 28439.7402343750f, 28457.0566406250f, +28474.3750000000f, 28491.6972656250f, 28509.0214843750f, 28526.3476562500f, +28543.6757812500f, 28561.0078125000f, 28578.3437500000f, 28595.6816406250f, +28613.0214843750f, 28630.3632812500f, 28647.7089843750f, 28665.0566406250f, +28682.4062500000f, 28699.7597656250f, 28717.1152343750f, 28734.4726562500f, +28751.8339843750f, 28769.1972656250f, 28786.5644531250f, 28803.9335937500f, +28821.3046875000f, 28838.6777343750f, 28856.0546875000f, 28873.4335937500f, +28890.8164062500f, 28908.2011718750f, 28925.5878906250f, 28942.9765625000f, +28960.3691406250f, 28977.7636718750f, 28995.1621093750f, 29012.5625000000f, +29029.9648437500f, 29047.3710937500f, 29064.7773437500f, 29082.1894531250f, +29099.6015625000f, 29117.0175781250f, 29134.4355468750f, 29151.8574218750f, +29169.2812500000f, 29186.7070312500f, 29204.1347656250f, 29221.5664062500f, +29239.0019531250f, 29256.4375000000f, 29273.8769531250f, 29291.3183593750f, +29308.7636718750f, 29326.2109375000f, 29343.6601562500f, 29361.1113281250f, +29378.5664062500f, 29396.0234375000f, 29413.4843750000f, 29430.9472656250f, +29448.4121093750f, 29465.8789062500f, 29483.3496093750f, 29500.8222656250f, +29518.2988281250f, 29535.7753906250f, 29553.2578125000f, 29570.7402343750f, +29588.2265625000f, 29605.7148437500f, 29623.2050781250f, 29640.6992187500f, +29658.1953125000f, 29675.6933593750f, 29693.1953125000f, 29710.6992187500f, +29728.2050781250f, 29745.7148437500f, 29763.2265625000f, 29780.7402343750f, +29798.2578125000f, 29815.7773437500f, 29833.2988281250f, 29850.8222656250f, +29868.3496093750f, 29885.8808593750f, 29903.4121093750f, 29920.9472656250f, +29938.4843750000f, 29956.0234375000f, 29973.5664062500f, 29991.1113281250f, +30008.6601562500f, 30026.2089843750f, 30043.7617187500f, 30061.3183593750f, +30078.8750000000f, 30096.4355468750f, 30114.0000000000f, 30131.5644531250f, +30149.1328125000f, 30166.7031250000f, 30184.2773437500f, 30201.8535156250f, +30219.4316406250f, 30237.0117187500f, 30254.5957031250f, 30272.1816406250f, +30289.7695312500f, 30307.3613281250f, 30324.9550781250f, 30342.5507812500f, +30360.1503906250f, 30377.7519531250f, 30395.3554687500f, 30412.9609375000f, +30430.5703125000f, 30448.1816406250f, 30465.7968750000f, 30483.4140625000f, +30501.0332031250f, 30518.6542968750f, 30536.2792968750f, 30553.9042968750f, +30571.5351562500f, 30589.1660156250f, 30606.8007812500f, 30624.4375000000f, +30642.0781250000f, 30659.7187500000f, 30677.3632812500f, 30695.0117187500f, +30712.6601562500f, 30730.3125000000f, 30747.9687500000f, 30765.6250000000f, +30783.2851562500f, 30800.9472656250f, 30818.6132812500f, 30836.2792968750f, +30853.9492187500f, 30871.6230468750f, 30889.2968750000f, 30906.9746093750f, +30924.6542968750f, 30942.3378906250f, 30960.0234375000f, 30977.7109375000f, +30995.4003906250f, 31013.0937500000f, 31030.7890625000f, 31048.4863281250f, +31066.1855468750f, 31083.8886718750f, 31101.5937500000f, 31119.3027343750f, +31137.0117187500f, 31154.7246093750f, 31172.4414062500f, 31190.1582031250f, +31207.8789062500f, 31225.6015625000f, 31243.3281250000f, 31261.0546875000f, +31278.7851562500f, 31296.5195312500f, 31314.2539062500f, 31331.9921875000f, +31349.7324218750f, 31367.4765625000f, 31385.2226562500f, 31402.9707031250f, +31420.7207031250f, 31438.4726562500f, 31456.2285156250f, 31473.9863281250f, +31491.7480468750f, 31509.5117187500f, 31527.2773437500f, 31545.0449218750f, +31562.8144531250f, 31580.5878906250f, 31598.3632812500f, 31616.1425781250f, +31633.9218750000f, 31651.7050781250f, 31669.4921875000f, 31687.2792968750f, +31705.0703125000f, 31722.8632812500f, 31740.6582031250f, 31758.4570312500f, +31776.2578125000f, 31794.0605468750f, 31811.8652343750f, 31829.6738281250f, +31847.4843750000f, 31865.2968750000f, 31883.1132812500f, 31900.9316406250f, +31918.7519531250f, 31936.5742187500f, 31954.4003906250f, 31972.2285156250f, +31990.0585937500f, 32007.8906250000f, 32025.7265625000f, 32043.5644531250f, +32061.4042968750f, 32079.2480468750f, 32097.0937500000f, 32114.9414062500f, +32132.7910156250f, 32150.6445312500f, 32168.5000000000f, 32186.3574218750f, +32204.2167968750f, 32222.0800781250f, 32239.9453125000f, 32257.8125000000f, +32275.6835937500f, 32293.5566406250f, 32311.4316406250f, 32329.3085937500f, +32347.1875000000f, 32365.0703125000f, 32382.9550781250f, 32400.8437500000f, +32418.7324218750f, 32436.6250000000f, 32454.5195312500f, 32472.4179687500f, +32490.3183593750f, 32508.2207031250f, 32526.1250000000f, 32544.0312500000f, +32561.9414062500f, 32579.8535156250f, 32597.7675781250f, 32615.6855468750f, +32633.6035156250f, 32651.5253906250f, 32669.4511718750f, 32687.3769531250f, +32705.3066406250f, 32723.2382812500f, 32741.1738281250f, 32759.1093750000f, +32777.0468750000f, 32794.9921875000f, 32812.9335937500f, 32830.8789062500f, +32848.8281250000f, 32866.7812500000f, 32884.7343750000f, 32902.6914062500f, +32920.6484375000f, 32938.6132812500f, 32956.5742187500f, 32974.5429687500f, +32992.5078125000f, 33010.4804687500f, 33028.4531250000f, 33046.4296875000f, +33064.4101562500f, 33082.3906250000f, 33100.3710937500f, 33118.3593750000f, +33136.3476562500f, 33154.3359375000f, 33172.3281250000f, 33190.3242187500f, +33208.3242187500f, 33226.3242187500f, 33244.3242187500f, 33262.3320312500f, +33280.3398437500f, 33298.3476562500f, 33316.3632812500f, 33334.3750000000f, +33352.3945312500f, 33370.4140625000f, 33388.4375000000f, 33406.4609375000f, +33424.4882812500f, 33442.5156250000f, 33460.5507812500f, 33478.5820312500f, +33496.6210937500f, 33514.6601562500f, 33532.6992187500f, 33550.7460937500f, +33568.7929687500f, 33586.8398437500f, 33604.8906250000f, 33622.9453125000f, +33641.0039062500f, 33659.0625000000f, 33677.1210937500f, 33695.1835937500f, +33713.2500000000f, 33731.3203125000f, 33749.3906250000f, 33767.4648437500f, +33785.5390625000f, 33803.6171875000f, 33821.6992187500f, 33839.7812500000f, +33857.8671875000f, 33875.9531250000f, 33894.0429687500f, 33912.1367187500f, +33930.2304687500f, 33948.3281250000f, 33966.4296875000f, 33984.5312500000f, +34002.6328125000f, 34020.7421875000f, 34038.8515625000f, 34056.9609375000f, +34075.0781250000f, 34093.1953125000f, 34111.3125000000f, 34129.4335937500f, +34147.5585937500f, 34165.6835937500f, 34183.8125000000f, 34201.9453125000f, +34220.0781250000f, 34238.2148437500f, 34256.3515625000f, 34274.4921875000f, +34292.6367187500f, 34310.7812500000f, 34328.9296875000f, 34347.0781250000f, +34365.2304687500f, 34383.3867187500f, 34401.5429687500f, 34419.7031250000f, +34437.8671875000f, 34456.0312500000f, 34474.1992187500f, 34492.3671875000f, +34510.5390625000f, 34528.7109375000f, 34546.8906250000f, 34565.0703125000f, +34583.2500000000f, 34601.4335937500f, 34619.6210937500f, 34637.8085937500f, +34656.0000000000f, 34674.1914062500f, 34692.3867187500f, 34710.5859375000f, +34728.7851562500f, 34746.9882812500f, 34765.1953125000f, 34783.4023437500f, +34801.6132812500f, 34819.8242187500f, 34838.0390625000f, 34856.2578125000f, +34874.4765625000f, 34892.6992187500f, 34910.9218750000f, 34929.1484375000f, +34947.3789062500f, 34965.6093750000f, 34983.8437500000f, 35002.0781250000f, +35020.3164062500f, 35038.5585937500f, 35056.8007812500f, 35075.0468750000f, +35093.2968750000f, 35111.5468750000f, 35129.8007812500f, 35148.0546875000f, +35166.3125000000f, 35184.5703125000f, 35202.8359375000f, 35221.0976562500f, +35239.3671875000f, 35257.6367187500f, 35275.9062500000f, 35294.1796875000f, +35312.4570312500f, 35330.7382812500f, 35349.0195312500f, 35367.3007812500f, +35385.5859375000f, 35403.8750000000f, 35422.1679687500f, 35440.4609375000f, +35458.7539062500f, 35477.0507812500f, 35495.3515625000f, 35513.6562500000f, +35531.9609375000f, 35550.2656250000f, 35568.5781250000f, 35586.8867187500f, +35605.2031250000f, 35623.5195312500f, 35641.8398437500f, 35660.1601562500f, +35678.4843750000f, 35696.8085937500f, 35715.1367187500f, 35733.4687500000f, +35751.8007812500f, 35770.1367187500f, 35788.4765625000f, 35806.8164062500f, +35825.1562500000f, 35843.5039062500f, 35861.8476562500f, 35880.1992187500f, +35898.5507812500f, 35916.9062500000f, 35935.2617187500f, 35953.6210937500f, +35971.9804687500f, 35990.3437500000f, 36008.7109375000f, 36027.0781250000f, +36045.4492187500f, 36063.8242187500f, 36082.1992187500f, 36100.5781250000f, +36118.9570312500f, 36137.3398437500f, 36155.7226562500f, 36174.1093750000f, +36192.5000000000f, 36210.8906250000f, 36229.2851562500f, 36247.6796875000f, +36266.0820312500f, 36284.4804687500f, 36302.8828125000f, 36321.2890625000f, +36339.6992187500f, 36358.1093750000f, 36376.5195312500f, 36394.9375000000f, +36413.3515625000f, 36431.7734375000f, 36450.1953125000f, 36468.6210937500f, +36487.0468750000f, 36505.4765625000f, 36523.9062500000f, 36542.3398437500f, +36560.7773437500f, 36579.2148437500f, 36597.6562500000f, 36616.0976562500f, +36634.5429687500f, 36652.9921875000f, 36671.4414062500f, 36689.8906250000f, +36708.3476562500f, 36726.8046875000f, 36745.2617187500f, 36763.7226562500f, +36782.1875000000f, 36800.6562500000f, 36819.1210937500f, 36837.5937500000f, +36856.0664062500f, 36874.5429687500f, 36893.0195312500f, 36911.5000000000f, +36929.9804687500f, 36948.4648437500f, 36966.9531250000f, 36985.4414062500f, +37003.9335937500f, 37022.4296875000f, 37040.9257812500f, 37059.4218750000f, +37077.9218750000f, 37096.4257812500f, 37114.9335937500f, 37133.4414062500f, +37151.9492187500f, 37170.4609375000f, 37188.9765625000f, 37207.4921875000f, +37226.0117187500f, 37244.5351562500f, 37263.0585937500f, 37281.5859375000f, +37300.1132812500f, 37318.6445312500f, 37337.1757812500f, 37355.7109375000f, +37374.2500000000f, 37392.7890625000f, 37411.3320312500f, 37429.8789062500f, +37448.4257812500f, 37466.9726562500f, 37485.5234375000f, 37504.0781250000f, +37522.6328125000f, 37541.1914062500f, 37559.7539062500f, 37578.3164062500f, +37596.8828125000f, 37615.4492187500f, 37634.0195312500f, 37652.5898437500f, +37671.1640625000f, 37689.7421875000f, 37708.3203125000f, 37726.9023437500f, +37745.4843750000f, 37764.0703125000f, 37782.6601562500f, 37801.2500000000f, +37819.8437500000f, 37838.4375000000f, 37857.0351562500f, 37875.6328125000f, +37894.2343750000f, 37912.8398437500f, 37931.4453125000f, 37950.0546875000f, +37968.6679687500f, 37987.2812500000f, 38005.8945312500f, 38024.5117187500f, +38043.1328125000f, 38061.7539062500f, 38080.3789062500f, 38099.0078125000f, +38117.6367187500f, 38136.2656250000f, 38154.9023437500f, 38173.5390625000f, +38192.1757812500f, 38210.8164062500f, 38229.4570312500f, 38248.1054687500f, +38266.7500000000f, 38285.4023437500f, 38304.0546875000f, 38322.7070312500f, +38341.3632812500f, 38360.0234375000f, 38378.6835937500f, 38397.3476562500f, +38416.0117187500f, 38434.6796875000f, 38453.3515625000f, 38472.0234375000f, +38490.6953125000f, 38509.3750000000f, 38528.0546875000f, 38546.7343750000f, +38565.4179687500f, 38584.1054687500f, 38602.7929687500f, 38621.4843750000f, +38640.1757812500f, 38658.8710937500f, 38677.5664062500f, 38696.2656250000f, +38714.9687500000f, 38733.6718750000f, 38752.3789062500f, 38771.0859375000f, +38789.7968750000f, 38808.5117187500f, 38827.2265625000f, 38845.9453125000f, +38864.6640625000f, 38883.3867187500f, 38902.1093750000f, 38920.8359375000f, +38939.5664062500f, 38958.2968750000f, 38977.0312500000f, 38995.7656250000f, +39014.5039062500f, 39033.2421875000f, 39051.9843750000f, 39070.7304687500f, +39089.4765625000f, 39108.2265625000f, 39126.9765625000f, 39145.7304687500f, +39164.4882812500f, 39183.2460937500f, 39202.0039062500f, 39220.7695312500f, +39239.5312500000f, 39258.3007812500f, 39277.0703125000f, 39295.8398437500f, +39314.6132812500f, 39333.3906250000f, 39352.1679687500f, 39370.9492187500f, +39389.7304687500f, 39408.5156250000f, 39427.3046875000f, 39446.0937500000f, +39464.8867187500f, 39483.6796875000f, 39502.4765625000f, 39521.2734375000f, +39540.0742187500f, 39558.8789062500f, 39577.6835937500f, 39596.4882812500f, +39615.3007812500f, 39634.1093750000f, 39652.9257812500f, 39671.7421875000f, +39690.5585937500f, 39709.3789062500f, 39728.2031250000f, 39747.0273437500f, +39765.8554687500f, 39784.6875000000f, 39803.5195312500f, 39822.3515625000f, +39841.1875000000f, 39860.0273437500f, 39878.8671875000f, 39897.7109375000f, +39916.5546875000f, 39935.4023437500f, 39954.2539062500f, 39973.1054687500f, +39991.9570312500f, 40010.8164062500f, 40029.6718750000f, 40048.5351562500f, +40067.3984375000f, 40086.2617187500f, 40105.1289062500f, 40124.0000000000f, +40142.8710937500f, 40161.7460937500f, 40180.6210937500f, 40199.5000000000f, +40218.3828125000f, 40237.2656250000f, 40256.1484375000f, 40275.0390625000f, +40293.9257812500f, 40312.8203125000f, 40331.7109375000f, 40350.6093750000f, +40369.5078125000f, 40388.4062500000f, 40407.3125000000f, 40426.2148437500f, +40445.1250000000f, 40464.0312500000f, 40482.9453125000f, 40501.8593750000f, +40520.7734375000f, 40539.6914062500f, 40558.6132812500f, 40577.5351562500f, +40596.4609375000f, 40615.3906250000f, 40634.3164062500f, 40653.2500000000f, +40672.1835937500f, 40691.1210937500f, 40710.0585937500f, 40729.0000000000f, +40747.9414062500f, 40766.8867187500f, 40785.8320312500f, 40804.7812500000f, +40823.7343750000f, 40842.6875000000f, 40861.6445312500f, 40880.6015625000f, +40899.5625000000f, 40918.5234375000f, 40937.4882812500f, 40956.4531250000f, +40975.4257812500f, 40994.3945312500f, 41013.3671875000f, 41032.3437500000f, +41051.3203125000f, 41070.3007812500f, 41089.2851562500f, 41108.2695312500f, +41127.2539062500f, 41146.2421875000f, 41165.2343750000f, 41184.2265625000f, +41203.2226562500f, 41222.2187500000f, 41241.2187500000f, 41260.2226562500f, +41279.2265625000f, 41298.2343750000f, 41317.2421875000f, 41336.2500000000f, +41355.2656250000f, 41374.2812500000f, 41393.2968750000f, 41412.3164062500f, +41431.3359375000f, 41450.3593750000f, 41469.3867187500f, 41488.4140625000f, +41507.4453125000f, 41526.4765625000f, 41545.5117187500f, 41564.5507812500f, +41583.5898437500f, 41602.6289062500f, 41621.6718750000f, 41640.7187500000f, +41659.7656250000f, 41678.8164062500f, 41697.8671875000f, 41716.9218750000f, +41735.9804687500f, 41755.0390625000f, 41774.0976562500f, 41793.1601562500f, +41812.2265625000f, 41831.2929687500f, 41850.3632812500f, 41869.4335937500f, +41888.5078125000f, 41907.5859375000f, 41926.6640625000f, 41945.7421875000f, +41964.8242187500f, 41983.9101562500f, 42002.9960937500f, 42022.0859375000f, +42041.1757812500f, 42060.2695312500f, 42079.3671875000f, 42098.4648437500f, +42117.5625000000f, 42136.6679687500f, 42155.7695312500f, 42174.8750000000f, +42193.9843750000f, 42213.0976562500f, 42232.2070312500f, 42251.3242187500f, +42270.4414062500f, 42289.5585937500f, 42308.6796875000f, 42327.8046875000f, +42346.9296875000f, 42366.0585937500f, 42385.1875000000f, 42404.3203125000f, +42423.4570312500f, 42442.5937500000f, 42461.7304687500f, 42480.8710937500f, +42500.0156250000f, 42519.1601562500f, 42538.3085937500f, 42557.4570312500f, +42576.6093750000f, 42595.7617187500f, 42614.9179687500f, 42634.0781250000f, +42653.2382812500f, 42672.3984375000f, 42691.5664062500f, 42710.7304687500f, +42729.8984375000f, 42749.0703125000f, 42768.2460937500f, 42787.4179687500f, +42806.5976562500f, 42825.7773437500f, 42844.9570312500f, 42864.1445312500f, +42883.3281250000f, 42902.5156250000f, 42921.7070312500f, 42940.8984375000f, +42960.0937500000f, 42979.2929687500f, 42998.4921875000f, 43017.6914062500f, +43036.8945312500f, 43056.1015625000f, 43075.3085937500f, 43094.5156250000f, +43113.7304687500f, 43132.9414062500f, 43152.1601562500f, 43171.3789062500f, +43190.5976562500f, 43209.8203125000f, 43229.0468750000f, 43248.2734375000f, +43267.5000000000f, 43286.7304687500f, 43305.9648437500f, 43325.1992187500f, +43344.4375000000f, 43363.6757812500f, 43382.9179687500f, 43402.1640625000f, +43421.4101562500f, 43440.6562500000f, 43459.9062500000f, 43479.1601562500f, +43498.4140625000f, 43517.6718750000f, 43536.9296875000f, 43556.1914062500f, +43575.4531250000f, 43594.7187500000f, 43613.9882812500f, 43633.2578125000f, +43652.5273437500f, 43671.8007812500f, 43691.0781250000f, 43710.3554687500f, +43729.6367187500f, 43748.9179687500f, 43768.2031250000f, 43787.4882812500f, +43806.7773437500f, 43826.0664062500f, 43845.3593750000f, 43864.6562500000f, +43883.9531250000f, 43903.2500000000f, 43922.5546875000f, 43941.8554687500f, +43961.1601562500f, 43980.4687500000f, 43999.7773437500f, 44019.0898437500f, +44038.4062500000f, 44057.7226562500f, 44077.0390625000f, 44096.3593750000f, +44115.6835937500f, 44135.0078125000f, 44154.3320312500f, 44173.6640625000f, +44192.9921875000f, 44212.3281250000f, 44231.6601562500f, 44251.0000000000f, +44270.3398437500f, 44289.6796875000f, 44309.0234375000f, 44328.3710937500f, +44347.7187500000f, 44367.0664062500f, 44386.4179687500f, 44405.7734375000f, +44425.1289062500f, 44444.4882812500f, 44463.8476562500f, 44483.2109375000f, +44502.5781250000f, 44521.9414062500f, 44541.3125000000f, 44560.6835937500f, +44580.0546875000f, 44599.4296875000f, 44618.8085937500f, 44638.1875000000f, +44657.5703125000f, 44676.9531250000f, 44696.3398437500f, 44715.7265625000f, +44735.1171875000f, 44754.5078125000f, 44773.9023437500f, 44793.3007812500f, +44812.6992187500f, 44832.0976562500f, 44851.5000000000f, 44870.9062500000f, +44890.3125000000f, 44909.7226562500f, 44929.1328125000f, 44948.5468750000f, +44967.9609375000f, 44987.3789062500f, 45006.7968750000f, 45026.2187500000f, +45045.6406250000f, 45065.0664062500f, 45084.4960937500f, 45103.9257812500f, +45123.3593750000f, 45142.7929687500f, 45162.2265625000f, 45181.6640625000f, +45201.1054687500f, 45220.5468750000f, 45239.9921875000f, 45259.4414062500f, +45278.8867187500f, 45298.3398437500f, 45317.7929687500f, 45337.2460937500f, +45356.7031250000f, 45376.1640625000f, 45395.6250000000f, 45415.0859375000f, +45434.5507812500f, 45454.0195312500f, 45473.4882812500f, 45492.9609375000f, +45512.4335937500f, 45531.9101562500f, 45551.3867187500f, 45570.8671875000f, +45590.3515625000f, 45609.8359375000f, 45629.3203125000f, 45648.8085937500f, +45668.3007812500f, 45687.7929687500f, 45707.2851562500f, 45726.7812500000f, +45746.2812500000f, 45765.7812500000f, 45785.2851562500f, 45804.7890625000f, +45824.2968750000f, 45843.8046875000f, 45863.3164062500f, 45882.8320312500f, +45902.3437500000f, 45921.8632812500f, 45941.3828125000f, 45960.9023437500f, +45980.4257812500f, 45999.9531250000f, 46019.4804687500f, 46039.0117187500f, +46058.5429687500f, 46078.0781250000f, 46097.6132812500f, 46117.1484375000f, +46136.6914062500f, 46156.2343750000f, 46175.7773437500f, 46195.3242187500f, +46214.8710937500f, 46234.4218750000f, 46253.9765625000f, 46273.5273437500f, +46293.0859375000f, 46312.6445312500f, 46332.2070312500f, 46351.7695312500f, +46371.3320312500f, 46390.8984375000f, 46410.4687500000f, 46430.0390625000f, +46449.6132812500f, 46469.1875000000f, 46488.7656250000f, 46508.3437500000f, +46527.9257812500f, 46547.5117187500f, 46567.0976562500f, 46586.6835937500f, +46606.2734375000f, 46625.8632812500f, 46645.4570312500f, 46665.0546875000f, +46684.6523437500f, 46704.2539062500f, 46723.8554687500f, 46743.4570312500f, +46763.0664062500f, 46782.6718750000f, 46802.2812500000f, 46821.8945312500f, +46841.5117187500f, 46861.1250000000f, 46880.7460937500f, 46900.3632812500f, +46919.9882812500f, 46939.6132812500f, 46959.2382812500f, 46978.8671875000f, +46998.5000000000f, 47018.1328125000f, 47037.7656250000f, 47057.4023437500f, +47077.0429687500f, 47096.6835937500f, 47116.3242187500f, 47135.9726562500f, +47155.6171875000f, 47175.2656250000f, 47194.9179687500f, 47214.5703125000f, +47234.2265625000f, 47253.8828125000f, 47273.5429687500f, 47293.2070312500f, +47312.8710937500f, 47332.5351562500f, 47352.2031250000f, 47371.8710937500f, +47391.5429687500f, 47411.2187500000f, 47430.8945312500f, 47450.5703125000f, +47470.2500000000f, 47489.9335937500f, 47509.6171875000f, 47529.3007812500f, +47548.9921875000f, 47568.6796875000f, 47588.3710937500f, 47608.0664062500f, +47627.7617187500f, 47647.4609375000f, 47667.1601562500f, 47686.8632812500f, +47706.5664062500f, 47726.2734375000f, 47745.9843750000f, 47765.6914062500f, +47785.4062500000f, 47805.1210937500f, 47824.8359375000f, 47844.5546875000f, +47864.2773437500f, 47884.0000000000f, 47903.7226562500f, 47923.4492187500f, +47943.1796875000f, 47962.9101562500f, 47982.6406250000f, 48002.3750000000f, +48022.1132812500f, 48041.8515625000f, 48061.5937500000f, 48081.3359375000f, +48101.0820312500f, 48120.8281250000f, 48140.5781250000f, 48160.3281250000f, +48180.0820312500f, 48199.8359375000f, 48219.5937500000f, 48239.3515625000f, +48259.1132812500f, 48278.8750000000f, 48298.6406250000f, 48318.4101562500f, +48338.1757812500f, 48357.9492187500f, 48377.7226562500f, 48397.4960937500f, +48417.2734375000f, 48437.0546875000f, 48456.8359375000f, 48476.6171875000f, +48496.4023437500f, 48516.1914062500f, 48535.9804687500f, 48555.7734375000f, +48575.5664062500f, 48595.3593750000f, 48615.1601562500f, 48634.9570312500f, +48654.7578125000f, 48674.5625000000f, 48694.3671875000f, 48714.1757812500f, +48733.9843750000f, 48753.7968750000f, 48773.6093750000f, 48793.4257812500f, +48813.2421875000f, 48833.0625000000f, 48852.8867187500f, 48872.7070312500f, +48892.5351562500f, 48912.3632812500f, 48932.1914062500f, 48952.0234375000f, +48971.8554687500f, 48991.6914062500f, 49011.5312500000f, 49031.3710937500f, +49051.2109375000f, 49071.0546875000f, 49090.9023437500f, 49110.7500000000f, +49130.5976562500f, 49150.4492187500f, 49170.3046875000f, 49190.1601562500f, +49210.0195312500f, 49229.8789062500f, 49249.7382812500f, 49269.6015625000f, +49289.4687500000f, 49309.3359375000f, 49329.2070312500f, 49349.0781250000f, +49368.9531250000f, 49388.8281250000f, 49408.7070312500f, 49428.5859375000f, +49448.4687500000f, 49468.3515625000f, 49488.2382812500f, 49508.1250000000f, +49528.0156250000f, 49547.9062500000f, 49567.8007812500f, 49587.6953125000f, +49607.5937500000f, 49627.4921875000f, 49647.3945312500f, 49667.2968750000f, +49687.2031250000f, 49707.1132812500f, 49727.0234375000f, 49746.9335937500f, +49766.8476562500f, 49786.7617187500f, 49806.6796875000f, 49826.6015625000f, +49846.5234375000f, 49866.4453125000f, 49886.3710937500f, 49906.3007812500f, +49926.2304687500f, 49946.1601562500f, 49966.0937500000f, 49986.0312500000f, +50005.9687500000f, 50025.9062500000f, 50045.8476562500f, 50065.7929687500f, +50085.7382812500f, 50105.6835937500f, 50125.6328125000f, 50145.5859375000f, +50165.5390625000f, 50185.4960937500f, 50205.4531250000f, 50225.4101562500f, +50245.3750000000f, 50265.3359375000f, 50285.3007812500f, 50305.2695312500f, +50325.2382812500f, 50345.2109375000f, 50365.1835937500f, 50385.1601562500f, +50405.1367187500f, 50425.1132812500f, 50445.0976562500f, 50465.0781250000f, +50485.0664062500f, 50505.0507812500f, 50525.0390625000f, 50545.0312500000f, +50565.0234375000f, 50585.0195312500f, 50605.0156250000f, 50625.0156250000f, +50645.0156250000f, 50665.0195312500f, 50685.0234375000f, 50705.0312500000f, +50725.0429687500f, 50745.0507812500f, 50765.0664062500f, 50785.0781250000f, +50805.0976562500f, 50825.1132812500f, 50845.1367187500f, 50865.1601562500f, +50885.1835937500f, 50905.2109375000f, 50925.2382812500f, 50945.2695312500f, +50965.3007812500f, 50985.3359375000f, 51005.3710937500f, 51025.4101562500f, +51045.4531250000f, 51065.4921875000f, 51085.5390625000f, 51105.5859375000f, +51125.6328125000f, 51145.6835937500f, 51165.7343750000f, 51185.7890625000f, +51205.8437500000f, 51225.9023437500f, 51245.9648437500f, 51266.0273437500f, +51286.0898437500f, 51306.1562500000f, 51326.2226562500f, 51346.2929687500f, +51366.3671875000f, 51386.4375000000f, 51406.5156250000f, 51426.5937500000f, +51446.6718750000f, 51466.7539062500f, 51486.8359375000f, 51506.9218750000f, +51527.0117187500f, 51547.1015625000f, 51567.1914062500f, 51587.2851562500f, +51607.3789062500f, 51627.4765625000f, 51647.5781250000f, 51667.6796875000f, +51687.7812500000f, 51707.8867187500f, 51727.9921875000f, 51748.1015625000f, +51768.2148437500f, 51788.3281250000f, 51808.4414062500f, 51828.5585937500f, +51848.6757812500f, 51868.7968750000f, 51888.9218750000f, 51909.0468750000f, +51929.1718750000f, 51949.3007812500f, 51969.4296875000f, 51989.5625000000f, +52009.6992187500f, 52029.8359375000f, 52049.9726562500f, 52070.1132812500f, +52090.2539062500f, 52110.3984375000f, 52130.5468750000f, 52150.6914062500f, +52170.8437500000f, 52190.9960937500f, 52211.1484375000f, 52231.3046875000f, +52251.4609375000f, 52271.6210937500f, 52291.7851562500f, 52311.9492187500f, +52332.1132812500f, 52352.2812500000f, 52372.4492187500f, 52392.6210937500f, +52412.7929687500f, 52432.9687500000f, 52453.1484375000f, 52473.3281250000f, +52493.5078125000f, 52513.6914062500f, 52533.8750000000f, 52554.0625000000f, +52574.2500000000f, 52594.4414062500f, 52614.6328125000f, 52634.8281250000f, +52655.0273437500f, 52675.2226562500f, 52695.4257812500f, 52715.6289062500f, +52735.8320312500f, 52756.0390625000f, 52776.2460937500f, 52796.4570312500f, +52816.6679687500f, 52836.8828125000f, 52857.0976562500f, 52877.3164062500f, +52897.5351562500f, 52917.7578125000f, 52937.9804687500f, 52958.2070312500f, +52978.4335937500f, 52998.6640625000f, 53018.8945312500f, 53039.1289062500f, +53059.3632812500f, 53079.6015625000f, 53099.8398437500f, 53120.0820312500f, +53140.3242187500f, 53160.5703125000f, 53180.8164062500f, 53201.0664062500f, +53221.3164062500f, 53241.5664062500f, 53261.8242187500f, 53282.0781250000f, +53302.3359375000f, 53322.5976562500f, 53342.8593750000f, 53363.1250000000f, +53383.3906250000f, 53403.6562500000f, 53423.9296875000f, 53444.1992187500f, +53464.4726562500f, 53484.7500000000f, 53505.0273437500f, 53525.3046875000f, +53545.5898437500f, 53565.8710937500f, 53586.1562500000f, 53606.4453125000f, +53626.7343750000f, 53647.0234375000f, 53667.3164062500f, 53687.6132812500f, +53707.9101562500f, 53728.2070312500f, 53748.5078125000f, 53768.8125000000f, +53789.1171875000f, 53809.4218750000f, 53829.7304687500f, 53850.0390625000f, +53870.3515625000f, 53890.6679687500f, 53910.9804687500f, 53931.3007812500f, +53951.6210937500f, 53971.9414062500f, 53992.2656250000f, 54012.5898437500f, +54032.9179687500f, 54053.2460937500f, 54073.5781250000f, 54093.9140625000f, +54114.2460937500f, 54134.5859375000f, 54154.9218750000f, 54175.2656250000f, +54195.6054687500f, 54215.9531250000f, 54236.2968750000f, 54256.6484375000f, +54276.9960937500f, 54297.3476562500f, 54317.7031250000f, 54338.0585937500f, +54358.4179687500f, 54378.7773437500f, 54399.1406250000f, 54419.5039062500f, +54439.8671875000f, 54460.2343750000f, 54480.6054687500f, 54500.9765625000f, +54521.3515625000f, 54541.7265625000f, 54562.1015625000f, 54582.4804687500f, +54602.8632812500f, 54623.2460937500f, 54643.6289062500f, 54664.0156250000f, +54684.4062500000f, 54704.7968750000f, 54725.1875000000f, 54745.5820312500f, +54765.9765625000f, 54786.3750000000f, 54806.7734375000f, 54827.1757812500f, +54847.5820312500f, 54867.9843750000f, 54888.3945312500f, 54908.8046875000f, +54929.2148437500f, 54949.6289062500f, 54970.0429687500f, 54990.4609375000f, +55010.8789062500f, 55031.3007812500f, 55051.7226562500f, 55072.1445312500f, +55092.5742187500f, 55113.0000000000f, 55133.4296875000f, 55153.8632812500f, +55174.2968750000f, 55194.7343750000f, 55215.1718750000f, 55235.6093750000f, +55256.0507812500f, 55276.4960937500f, 55296.9414062500f, 55317.3867187500f, +55337.8359375000f, 55358.2890625000f, 55378.7421875000f, 55399.1953125000f, +55419.6523437500f, 55440.1093750000f, 55460.5703125000f, 55481.0351562500f, +55501.4960937500f, 55521.9648437500f, 55542.4335937500f, 55562.9023437500f, +55583.3750000000f, 55603.8476562500f, 55624.3242187500f, 55644.8007812500f, +55665.2812500000f, 55685.7617187500f, 55706.2421875000f, 55726.7304687500f, +55747.2148437500f, 55767.7031250000f, 55788.1953125000f, 55808.6875000000f, +55829.1796875000f, 55849.6796875000f, 55870.1757812500f, 55890.6757812500f, +55911.1796875000f, 55931.6796875000f, 55952.1875000000f, 55972.6953125000f, +55993.2031250000f, 56013.7148437500f, 56034.2265625000f, 56054.7421875000f, +56075.2617187500f, 56095.7773437500f, 56116.3007812500f, 56136.8242187500f, +56157.3476562500f, 56177.8750000000f, 56198.4023437500f, 56218.9296875000f, +56239.4648437500f, 56259.9960937500f, 56280.5312500000f, 56301.0703125000f, +56321.6093750000f, 56342.1523437500f, 56362.6953125000f, 56383.2382812500f, +56403.7851562500f, 56424.3359375000f, 56444.8867187500f, 56465.4375000000f, +56485.9921875000f, 56506.5468750000f, 56527.1054687500f, 56547.6679687500f, +56568.2265625000f, 56588.7929687500f, 56609.3593750000f, 56629.9257812500f, +56650.4960937500f, 56671.0664062500f, 56691.6406250000f, 56712.2148437500f, +56732.7890625000f, 56753.3671875000f, 56773.9492187500f, 56794.5312500000f, +56815.1171875000f, 56835.7031250000f, 56856.2890625000f, 56876.8789062500f, +56897.4726562500f, 56918.0664062500f, 56938.6601562500f, 56959.2578125000f, +56979.8593750000f, 57000.4570312500f, 57021.0625000000f, 57041.6679687500f, +57062.2734375000f, 57082.8828125000f, 57103.4921875000f, 57124.1054687500f, +57144.7187500000f, 57165.3320312500f, 57185.9531250000f, 57206.5703125000f, +57227.1914062500f, 57247.8164062500f, 57268.4414062500f, 57289.0664062500f, +57309.6953125000f, 57330.3281250000f, 57350.9609375000f, 57371.5937500000f, +57392.2304687500f, 57412.8710937500f, 57433.5078125000f, 57454.1523437500f, +57474.7968750000f, 57495.4414062500f, 57516.0898437500f, 57536.7382812500f, +57557.3906250000f, 57578.0429687500f, 57598.6953125000f, 57619.3515625000f, +57640.0117187500f, 57660.6718750000f, 57681.3359375000f, 57702.0000000000f, +57722.6640625000f, 57743.3320312500f, 57764.0039062500f, 57784.6718750000f, +57805.3476562500f, 57826.0234375000f, 57846.6992187500f, 57867.3789062500f, +57888.0585937500f, 57908.7421875000f, 57929.4257812500f, 57950.1132812500f, +57970.8007812500f, 57991.4921875000f, 58012.1835937500f, 58032.8750000000f, +58053.5703125000f, 58074.2695312500f, 58094.9687500000f, 58115.6679687500f, +58136.3710937500f, 58157.0781250000f, 58177.7851562500f, 58198.4921875000f, +58219.2031250000f, 58239.9140625000f, 58260.6289062500f, 58281.3437500000f, +58302.0625000000f, 58322.7812500000f, 58343.5039062500f, 58364.2265625000f, +58384.9492187500f, 58405.6796875000f, 58426.4062500000f, 58447.1367187500f, +58467.8710937500f, 58488.6054687500f, 58509.3398437500f, 58530.0781250000f, +58550.8164062500f, 58571.5585937500f, 58592.3007812500f, 58613.0468750000f, +58633.7929687500f, 58654.5429687500f, 58675.2929687500f, 58696.0468750000f, +58716.8007812500f, 58737.5585937500f, 58758.3164062500f, 58779.0742187500f, +58799.8359375000f, 58820.6015625000f, 58841.3671875000f, 58862.1328125000f, +58882.9023437500f, 58903.6718750000f, 58924.4453125000f, 58945.2187500000f, +58965.9960937500f, 58986.7734375000f, 59007.5546875000f, 59028.3359375000f, +59049.1210937500f, 59069.9062500000f, 59090.6953125000f, 59111.4843750000f, +59132.2734375000f, 59153.0664062500f, 59173.8632812500f, 59194.6562500000f, +59215.4570312500f, 59236.2578125000f, 59257.0585937500f, 59277.8632812500f, +59298.6679687500f, 59319.4765625000f, 59340.2851562500f, 59361.0976562500f, +59381.9101562500f, 59402.7226562500f, 59423.5390625000f, 59444.3593750000f, +59465.1796875000f, 59486.0000000000f, 59506.8242187500f, 59527.6484375000f, +59548.4765625000f, 59569.3085937500f, 59590.1367187500f, 59610.9726562500f, +59631.8046875000f, 59652.6445312500f, 59673.4804687500f, 59694.3203125000f, +59715.1640625000f, 59736.0078125000f, 59756.8515625000f, 59777.6992187500f, +59798.5507812500f, 59819.4023437500f, 59840.2539062500f, 59861.1093750000f, +59881.9648437500f, 59902.8242187500f, 59923.6835937500f, 59944.5468750000f, +59965.4101562500f, 59986.2773437500f, 60007.1445312500f, 60028.0117187500f, +60048.8828125000f, 60069.7578125000f, 60090.6328125000f, 60111.5078125000f, +60132.3867187500f, 60153.2656250000f, 60174.1484375000f, 60195.0312500000f, +60215.9179687500f, 60236.8046875000f, 60257.6953125000f, 60278.5859375000f, +60299.4804687500f, 60320.3750000000f, 60341.2695312500f, 60362.1679687500f, +60383.0703125000f, 60403.9687500000f, 60424.8750000000f, 60445.7812500000f, +60466.6875000000f, 60487.5976562500f, 60508.5078125000f, 60529.4218750000f, +60550.3359375000f, 60571.2500000000f, 60592.1679687500f, 60613.0898437500f, +60634.0117187500f, 60654.9335937500f, 60675.8593750000f, 60696.7890625000f, +60717.7148437500f, 60738.6484375000f, 60759.5781250000f, 60780.5156250000f, +60801.4492187500f, 60822.3867187500f, 60843.3281250000f, 60864.2695312500f, +60885.2148437500f, 60906.1601562500f, 60927.1054687500f, 60948.0546875000f, +60969.0039062500f, 60989.9570312500f, 61010.9101562500f, 61031.8671875000f, +61052.8242187500f, 61073.7851562500f, 61094.7460937500f, 61115.7109375000f, +61136.6757812500f, 61157.6406250000f, 61178.6093750000f, 61199.5820312500f, +61220.5546875000f, 61241.5273437500f, 61262.5039062500f, 61283.4804687500f, +61304.4609375000f, 61325.4414062500f, 61346.4257812500f, 61367.4101562500f, +61388.3945312500f, 61409.3828125000f, 61430.3750000000f, 61451.3671875000f, +61472.3593750000f, 61493.3554687500f, 61514.3515625000f, 61535.3515625000f, +61556.3515625000f, 61577.3554687500f, 61598.3593750000f, 61619.3671875000f, +61640.3750000000f, 61661.3828125000f, 61682.3945312500f, 61703.4101562500f, +61724.4257812500f, 61745.4414062500f, 61766.4609375000f, 61787.4804687500f, +61808.5039062500f, 61829.5273437500f, 61850.5546875000f, 61871.5820312500f, +61892.6132812500f, 61913.6445312500f, 61934.6757812500f, 61955.7109375000f, +61976.7460937500f, 61997.7851562500f, 62018.8281250000f, 62039.8671875000f, +62060.9140625000f, 62081.9570312500f, 62103.0039062500f, 62124.0546875000f, +62145.1054687500f, 62166.1562500000f, 62187.2109375000f, 62208.2695312500f, +62229.3281250000f, 62250.3867187500f, 62271.4492187500f, 62292.5117187500f, +62313.5781250000f, 62334.6445312500f, 62355.7148437500f, 62376.7851562500f, +62397.8554687500f, 62418.9296875000f, 62440.0078125000f, 62461.0820312500f, +62482.1640625000f, 62503.2460937500f, 62524.3281250000f, 62545.4140625000f, +62566.5000000000f, 62587.5859375000f, 62608.6757812500f, 62629.7695312500f, +62650.8632812500f, 62671.9570312500f, 62693.0546875000f, 62714.1562500000f, +62735.2539062500f, 62756.3593750000f, 62777.4609375000f, 62798.5703125000f, +62819.6757812500f, 62840.7851562500f, 62861.8984375000f, 62883.0117187500f, +62904.1250000000f, 62925.2421875000f, 62946.3593750000f, 62967.4804687500f, +62988.6015625000f, 63009.7265625000f, 63030.8515625000f, 63051.9804687500f, +63073.1093750000f, 63094.2382812500f, 63115.3710937500f, 63136.5078125000f, +63157.6445312500f, 63178.7812500000f, 63199.9218750000f, 63221.0625000000f, +63242.2070312500f, 63263.3515625000f, 63284.4960937500f, 63305.6445312500f, +63326.7968750000f, 63347.9492187500f, 63369.1015625000f, 63390.2578125000f, +63411.4140625000f, 63432.5742187500f, 63453.7343750000f, 63474.8984375000f, +63496.0625000000f, 63517.2304687500f, 63538.3984375000f, 63559.5664062500f, +63580.7382812500f, 63601.9101562500f, 63623.0859375000f, 63644.2656250000f, +63665.4414062500f, 63686.6210937500f, 63707.8046875000f, 63728.9882812500f, +63750.1757812500f, 63771.3632812500f, 63792.5507812500f, 63813.7421875000f, +63834.9335937500f, 63856.1289062500f, 63877.3242187500f, 63898.5234375000f, +63919.7226562500f, 63940.9257812500f, 63962.1289062500f, 63983.3320312500f, +64004.5390625000f, 64025.7460937500f, 64046.9570312500f, 64068.1679687500f, +64089.3828125000f, 64110.5976562500f, 64131.8164062500f, 64153.0351562500f, +64174.2539062500f, 64195.4765625000f, 64216.7031250000f, 64237.9296875000f, +64259.1562500000f, 64280.3867187500f, 64301.6171875000f, 64322.8515625000f, +64344.0859375000f, 64365.3203125000f, 64386.5585937500f, 64407.8007812500f, +64429.0429687500f, 64450.2851562500f, 64471.5312500000f, 64492.7773437500f, +64514.0273437500f, 64535.2773437500f, 64556.5312500000f, 64577.7851562500f, +64599.0390625000f, 64620.2968750000f, 64641.5546875000f, 64662.8164062500f, +64684.0781250000f, 64705.3437500000f, 64726.6093750000f, 64747.8789062500f, +64769.1484375000f, 64790.4218750000f, 64811.6953125000f, 64832.9687500000f, +64854.2460937500f, 64875.5234375000f, 64896.8046875000f, 64918.0859375000f, +64939.3710937500f, 64960.6562500000f, 64981.9414062500f, 65003.2304687500f, +65024.5234375000f, 65045.8164062500f, 65067.1093750000f, 65088.4062500000f, +65109.7031250000f, 65131.0000000000f, 65152.3046875000f, 65173.6054687500f, +65194.9101562500f, 65216.2187500000f, 65237.5234375000f, 65258.8359375000f, +65280.1484375000f, 65301.4609375000f, 65322.7734375000f, 65344.0937500000f, +65365.4101562500f, 65386.7304687500f, 65408.0546875000f, 65429.3750000000f, +65450.7031250000f, 65472.0312500000f, 65493.3593750000f, 65514.6875000000f, +65536.0234375000f, 65557.3593750000f, 65578.6953125000f, 65600.0312500000f, +65621.3671875000f, 65642.7109375000f, 65664.0546875000f, 65685.3984375000f, +65706.7421875000f, 65728.0937500000f, 65749.4453125000f, 65770.7968750000f, +65792.1484375000f, 65813.5000000000f, 65834.8593750000f, 65856.2187500000f, +65877.5781250000f, 65898.9375000000f, 65920.3046875000f, 65941.6718750000f, +65963.0390625000f, 65984.4062500000f, 66005.7734375000f, 66027.1484375000f, +66048.5234375000f, 66069.8984375000f, 66091.2734375000f, 66112.6562500000f, +66134.0312500000f, 66155.4140625000f, 66176.8046875000f, 66198.1875000000f, +66219.5781250000f, 66240.9687500000f, 66262.3593750000f, 66283.7500000000f, +66305.1484375000f, 66326.5390625000f, 66347.9375000000f, 66369.3359375000f, +66390.7421875000f, 66412.1484375000f, 66433.5468750000f, 66454.9531250000f, +66476.3671875000f, 66497.7734375000f, 66519.1875000000f, 66540.6015625000f, +66562.0156250000f, 66583.4375000000f, 66604.8515625000f, 66626.2734375000f, +66647.6953125000f, 66669.1171875000f, 66690.5468750000f, 66711.9765625000f, +66733.4062500000f, 66754.8359375000f, 66776.2656250000f, 66797.7031250000f, +66819.1406250000f, 66840.5781250000f, 66862.0156250000f, 66883.4531250000f, +66904.8984375000f, 66926.3437500000f, 66947.7890625000f, 66969.2343750000f, +66990.6875000000f, 67012.1406250000f, 67033.5937500000f, 67055.0468750000f, +67076.5078125000f, 67097.9609375000f, 67119.4218750000f, 67140.8828125000f, +67162.3515625000f, 67183.8125000000f, 67205.2812500000f, 67226.7500000000f, +67248.2187500000f, 67269.6953125000f, 67291.1640625000f, 67312.6406250000f, +67334.1171875000f, 67355.6015625000f, 67377.0781250000f, 67398.5625000000f, +67420.0468750000f, 67441.5312500000f, 67463.0234375000f, 67484.5078125000f, +67506.0000000000f, 67527.4921875000f, 67548.9843750000f, 67570.4843750000f, +67591.9843750000f, 67613.4843750000f, 67634.9843750000f, 67656.4843750000f, +67677.9921875000f, 67699.5000000000f, 67721.0078125000f, 67742.5156250000f, +67764.0234375000f, 67785.5390625000f, 67807.0546875000f, 67828.5703125000f, +67850.0859375000f, 67871.6093750000f, 67893.1328125000f, 67914.6562500000f, +67936.1796875000f, 67957.7031250000f, 67979.2343750000f, 68000.7656250000f, +68022.2968750000f, 68043.8281250000f, 68065.3671875000f, 68086.9062500000f, +68108.4453125000f, 68129.9843750000f, 68151.5234375000f, 68173.0703125000f, +68194.6171875000f, 68216.1640625000f, 68237.7109375000f, 68259.2578125000f, +68280.8125000000f, 68302.3671875000f, 68323.9218750000f, 68345.4843750000f, +68367.0390625000f, 68388.6015625000f, 68410.1640625000f, 68431.7265625000f, +68453.2968750000f, 68474.8593750000f, 68496.4296875000f, 68518.0000000000f, +68539.5781250000f, 68561.1484375000f, 68582.7265625000f, 68604.3046875000f, +68625.8828125000f, 68647.4687500000f, 68669.0468750000f, 68690.6328125000f, +68712.2187500000f, 68733.8046875000f, 68755.3984375000f, 68776.9921875000f, +68798.5859375000f, 68820.1796875000f, 68841.7734375000f, 68863.3750000000f, +68884.9687500000f, 68906.5703125000f, 68928.1796875000f, 68949.7812500000f, +68971.3906250000f, 68993.0000000000f, 69014.6093750000f, 69036.2187500000f, +69057.8359375000f, 69079.4453125000f, 69101.0625000000f, 69122.6796875000f, +69144.3046875000f, 69165.9218750000f, 69187.5468750000f, 69209.1718750000f, +69230.8046875000f, 69252.4296875000f, 69274.0625000000f, 69295.6953125000f, +69317.3281250000f, 69338.9609375000f, 69360.6015625000f, 69382.2343750000f, +69403.8750000000f, 69425.5234375000f, 69447.1640625000f, 69468.8125000000f, +69490.4609375000f, 69512.1093750000f, 69533.7578125000f, 69555.4062500000f, +69577.0625000000f, 69598.7187500000f, 69620.3750000000f, 69642.0390625000f, +69663.6953125000f, 69685.3593750000f, 69707.0234375000f, 69728.6875000000f, +69750.3593750000f, 69772.0234375000f, 69793.6953125000f, 69815.3671875000f, +69837.0390625000f, 69858.7187500000f, 69880.3984375000f, 69902.0781250000f, +69923.7578125000f, 69945.4375000000f, 69967.1250000000f, 69988.8125000000f, +70010.5000000000f, 70032.1875000000f, 70053.8750000000f, 70075.5703125000f, +70097.2656250000f, 70118.9609375000f, 70140.6562500000f, 70162.3593750000f, +70184.0625000000f, 70205.7578125000f, 70227.4687500000f, 70249.1718750000f, +70270.8828125000f, 70292.5859375000f, 70314.2968750000f, 70336.0156250000f, +70357.7265625000f, 70379.4453125000f, 70401.1640625000f, 70422.8828125000f, +70444.6015625000f, 70466.3281250000f, 70488.0468750000f, 70509.7734375000f, +70531.5078125000f, 70553.2343750000f, 70574.9687500000f, 70596.6953125000f, +70618.4296875000f, 70640.1718750000f, 70661.9062500000f, 70683.6484375000f, +70705.3906250000f, 70727.1328125000f, 70748.8750000000f, 70770.6250000000f, +70792.3671875000f, 70814.1171875000f, 70835.8671875000f, 70857.6250000000f, +70879.3750000000f, 70901.1328125000f, 70922.8906250000f, 70944.6484375000f, +70966.4140625000f, 70988.1796875000f, 71009.9375000000f, 71031.7109375000f, +71053.4765625000f, 71075.2421875000f, 71097.0156250000f, 71118.7890625000f, +71140.5625000000f, 71162.3437500000f, 71184.1171875000f, 71205.8984375000f, +71227.6796875000f, 71249.4609375000f, 71271.2500000000f, 71293.0312500000f, +71314.8203125000f, 71336.6093750000f, 71358.4062500000f, 71380.1953125000f, +71401.9921875000f, 71423.7890625000f, 71445.5859375000f, 71467.3828125000f, +71489.1875000000f, 71510.9921875000f, 71532.7968750000f, 71554.6015625000f, +71576.4062500000f, 71598.2187500000f, 71620.0312500000f, 71641.8437500000f, +71663.6562500000f, 71685.4687500000f, 71707.2890625000f, 71729.1093750000f, +71750.9296875000f, 71772.7500000000f, 71794.5781250000f, 71816.4062500000f, +71838.2343750000f, 71860.0625000000f, 71881.8906250000f, 71903.7265625000f, +71925.5625000000f, 71947.3984375000f, 71969.2343750000f, 71991.0703125000f, +72012.9140625000f, 72034.7578125000f, 72056.6015625000f, 72078.4453125000f, +72100.2968750000f, 72122.1484375000f, 72144.0000000000f, 72165.8515625000f, +72187.7031250000f, 72209.5625000000f, 72231.4140625000f, 72253.2734375000f, +72275.1406250000f, 72297.0000000000f, 72318.8671875000f, 72340.7265625000f, +72362.6015625000f, 72384.4687500000f, 72406.3359375000f, 72428.2109375000f, +72450.0859375000f, 72471.9609375000f, 72493.8359375000f, 72515.7187500000f, +72537.6015625000f, 72559.4843750000f, 72581.3671875000f, 72603.2500000000f, +72625.1406250000f, 72647.0234375000f, 72668.9140625000f, 72690.8125000000f, +72712.7031250000f, 72734.6015625000f, 72756.5000000000f, 72778.3984375000f, +72800.2968750000f, 72822.1953125000f, 72844.1015625000f, 72866.0078125000f, +72887.9140625000f, 72909.8203125000f, 72931.7343750000f, 72953.6484375000f, +72975.5625000000f, 72997.4765625000f, 73019.3906250000f, 73041.3125000000f, +73063.2343750000f, 73085.1562500000f, 73107.0781250000f, 73129.0000000000f, +73150.9296875000f, 73172.8593750000f, 73194.7890625000f, 73216.7187500000f, +73238.6562500000f, 73260.5859375000f, 73282.5234375000f, 73304.4609375000f, +73326.4062500000f, 73348.3437500000f, 73370.2890625000f, 73392.2343750000f, +73414.1796875000f, 73436.1328125000f, 73458.0781250000f, 73480.0312500000f, +73501.9843750000f, 73523.9375000000f, 73545.8984375000f, 73567.8515625000f, +73589.8125000000f, 73611.7734375000f, 73633.7343750000f, 73655.7031250000f, +73677.6718750000f, 73699.6328125000f, 73721.6093750000f, 73743.5781250000f, +73765.5468750000f, 73787.5234375000f, 73809.5000000000f, 73831.4765625000f, +73853.4609375000f, 73875.4375000000f, 73897.4218750000f, 73919.4062500000f, +73941.3906250000f, 73963.3750000000f, 73985.3671875000f, 74007.3593750000f, +74029.3515625000f, 74051.3437500000f, 74073.3437500000f, 74095.3359375000f, +74117.3359375000f, 74139.3359375000f, 74161.3437500000f, 74183.3437500000f, +74205.3515625000f, 74227.3593750000f, 74249.3671875000f, 74271.3750000000f, +74293.3906250000f, 74315.3984375000f, 74337.4140625000f, 74359.4375000000f, +74381.4531250000f, 74403.4687500000f, 74425.4921875000f, 74447.5156250000f, +74469.5390625000f, 74491.5703125000f, 74513.6015625000f, 74535.6250000000f, +74557.6562500000f, 74579.6953125000f, 74601.7265625000f, 74623.7656250000f, +74645.8046875000f, 74667.8437500000f, 74689.8828125000f, 74711.9296875000f, +74733.9687500000f, 74756.0156250000f, 74778.0625000000f, 74800.1171875000f, +74822.1640625000f, 74844.2187500000f, 74866.2734375000f, 74888.3281250000f, +74910.3828125000f, 74932.4453125000f, 74954.5078125000f, 74976.5703125000f, +74998.6328125000f, 75020.6953125000f, 75042.7656250000f, 75064.8359375000f, +75086.9062500000f, 75108.9765625000f, 75131.0546875000f, 75153.1250000000f, +75175.2031250000f, 75197.2812500000f, 75219.3593750000f, 75241.4453125000f, +75263.5312500000f, 75285.6171875000f, 75307.7031250000f, 75329.7890625000f, +75351.8828125000f, 75373.9687500000f, 75396.0625000000f, 75418.1562500000f, +75440.2578125000f, 75462.3515625000f, 75484.4531250000f, 75506.5546875000f, +75528.6562500000f, 75550.7656250000f, 75572.8671875000f, 75594.9765625000f, +75617.0859375000f, 75639.1953125000f, 75661.3125000000f, 75683.4218750000f, +75705.5390625000f, 75727.6562500000f, 75749.7734375000f, 75771.8984375000f, +75794.0156250000f, 75816.1406250000f, 75838.2656250000f, 75860.3984375000f, +75882.5234375000f, 75904.6562500000f, 75926.7890625000f, 75948.9218750000f, +75971.0546875000f, 75993.1953125000f, 76015.3281250000f, 76037.4687500000f, +76059.6171875000f, 76081.7578125000f, 76103.8984375000f, 76126.0468750000f, +76148.1953125000f, 76170.3437500000f, 76192.5000000000f, 76214.6484375000f, +76236.8046875000f, 76258.9609375000f, 76281.1171875000f, 76303.2812500000f, +76325.4375000000f, 76347.6015625000f, 76369.7656250000f, 76391.9296875000f, +76414.1015625000f, 76436.2734375000f, 76458.4375000000f, 76480.6093750000f, +76502.7890625000f, 76524.9609375000f, 76547.1406250000f, 76569.3203125000f, +76591.5000000000f, 76613.6796875000f, 76635.8671875000f, 76658.0468750000f, +76680.2343750000f, 76702.4218750000f, 76724.6171875000f, 76746.8046875000f, +76769.0000000000f, 76791.1953125000f, 76813.3906250000f, 76835.5859375000f, +76857.7890625000f, 76879.9921875000f, 76902.1953125000f, 76924.3984375000f, +76946.6015625000f, 76968.8125000000f, 76991.0156250000f, 77013.2265625000f, +77035.4453125000f, 77057.6562500000f, 77079.8750000000f, 77102.0859375000f, +77124.3046875000f, 77146.5312500000f, 77168.7500000000f, 77190.9765625000f, +77213.1953125000f, 77235.4296875000f, 77257.6562500000f, 77279.8828125000f, +77302.1171875000f, 77324.3515625000f, 77346.5859375000f, 77368.8203125000f, +77391.0546875000f, 77413.2968750000f, 77435.5390625000f, 77457.7812500000f, +77480.0234375000f, 77502.2734375000f, 77524.5234375000f, 77546.7656250000f, +77569.0234375000f, 77591.2734375000f, 77613.5234375000f, 77635.7812500000f, +77658.0390625000f, 77680.2968750000f, 77702.5546875000f, 77724.8203125000f, +77747.0859375000f, 77769.3515625000f, 77791.6171875000f, 77813.8828125000f, +77836.1562500000f, 77858.4218750000f, 77880.6953125000f, 77902.9765625000f, +77925.2500000000f, 77947.5312500000f, 77969.8046875000f, 77992.0859375000f, +78014.3671875000f, 78036.6562500000f, 78058.9375000000f, 78081.2265625000f, +78103.5156250000f, 78125.8046875000f, 78148.1015625000f, 78170.3906250000f, +78192.6875000000f, 78214.9843750000f, 78237.2812500000f, 78259.5859375000f, +78281.8828125000f, 78304.1875000000f, 78326.4921875000f, 78348.8046875000f, +78371.1093750000f, 78393.4218750000f, 78415.7265625000f, 78438.0468750000f, +78460.3593750000f, 78482.6718750000f, 78504.9921875000f, 78527.3125000000f, +78549.6328125000f, 78571.9531250000f, 78594.2812500000f, 78616.6015625000f, +78638.9296875000f, 78661.2578125000f, 78683.5859375000f, 78705.9218750000f, +78728.2578125000f, 78750.5859375000f, 78772.9296875000f, 78795.2656250000f, +78817.6015625000f, 78839.9453125000f, 78862.2890625000f, 78884.6328125000f, +78906.9765625000f, 78929.3281250000f, 78951.6796875000f, 78974.0234375000f, +78996.3828125000f, 79018.7343750000f, 79041.0859375000f, 79063.4453125000f, +79085.8046875000f, 79108.1640625000f, 79130.5312500000f, 79152.8906250000f, +79175.2578125000f, 79197.6250000000f, 79219.9921875000f, 79242.3593750000f, +79264.7343750000f, 79287.1015625000f, 79309.4765625000f, 79331.8593750000f, +79354.2343750000f, 79376.6093750000f, 79398.9921875000f, 79421.3750000000f, +79443.7578125000f, 79466.1484375000f, 79488.5312500000f, 79510.9218750000f, +79533.3125000000f, 79555.7031250000f, 79578.1015625000f, 79600.4921875000f, +79622.8906250000f, 79645.2890625000f, 79667.6875000000f, 79690.0859375000f, +79712.4921875000f, 79734.8984375000f, 79757.3046875000f, 79779.7109375000f, +79802.1171875000f, 79824.5312500000f, 79846.9453125000f, 79869.3593750000f, +79891.7734375000f, 79914.1875000000f, 79936.6093750000f, 79959.0312500000f, +79981.4531250000f, 80003.8750000000f, 80026.2968750000f, 80048.7265625000f, +80071.1562500000f, 80093.5859375000f, 80116.0156250000f, 80138.4453125000f, +80160.8828125000f, 80183.3203125000f, 80205.7578125000f, 80228.1953125000f, +80250.6328125000f, 80273.0781250000f, 80295.5234375000f, 80317.9687500000f, +80340.4140625000f, 80362.8593750000f, 80385.3125000000f, 80407.7656250000f, +80430.2187500000f, 80452.6718750000f, 80475.1250000000f, 80497.5859375000f, +80520.0468750000f, 80542.5078125000f, 80564.9687500000f, 80587.4296875000f, +80609.8984375000f, 80632.3671875000f, 80654.8359375000f, 80677.3046875000f, +80699.7734375000f, 80722.2500000000f, 80744.7265625000f, 80767.2031250000f, +80789.6796875000f, 80812.1640625000f, 80834.6406250000f, 80857.1250000000f, +80879.6093750000f, 80902.0937500000f, 80924.5859375000f, 80947.0703125000f, +80969.5625000000f, 80992.0546875000f, 81014.5468750000f, 81037.0468750000f, +81059.5390625000f, 81082.0390625000f, 81104.5390625000f, 81127.0390625000f, +81149.5468750000f, 81172.0468750000f, 81194.5546875000f, 81217.0625000000f, +81239.5703125000f, 81262.0859375000f, 81284.6015625000f, 81307.1093750000f, +81329.6250000000f, 81352.1484375000f, 81374.6640625000f, 81397.1875000000f, +81419.7031250000f, 81442.2265625000f, 81464.7578125000f, 81487.2812500000f, +81509.8125000000f, 81532.3359375000f, 81554.8671875000f, 81577.4062500000f, +81599.9375000000f, 81622.4765625000f, 81645.0078125000f, 81667.5468750000f, +81690.0937500000f, 81712.6328125000f, 81735.1796875000f, 81757.7187500000f, +81780.2656250000f, 81802.8203125000f, 81825.3671875000f, 81847.9218750000f, +81870.4687500000f, 81893.0234375000f, 81915.5859375000f, 81938.1406250000f, +81960.7031250000f, 81983.2578125000f, 82005.8203125000f, 82028.3906250000f, +82050.9531250000f, 82073.5234375000f, 82096.0859375000f, 82118.6562500000f, +82141.2265625000f, 82163.8046875000f, 82186.3750000000f, 82208.9531250000f, +82231.5312500000f, 82254.1093750000f, 82276.6953125000f, 82299.2734375000f, +82321.8593750000f, 82344.4453125000f, 82367.0312500000f, 82389.6250000000f, +82412.2109375000f, 82434.8046875000f, 82457.3984375000f, 82479.9921875000f, +82502.5859375000f, 82525.1875000000f, 82547.7890625000f, 82570.3906250000f, +82592.9921875000f, 82615.5937500000f, 82638.2031250000f, 82660.8046875000f, +82683.4140625000f, 82706.0234375000f, 82728.6406250000f, 82751.2500000000f, +82773.8671875000f, 82796.4843750000f, 82819.1015625000f, 82841.7187500000f, +82864.3437500000f, 82886.9687500000f, 82909.5859375000f, 82932.2187500000f, +82954.8437500000f, 82977.4687500000f, 83000.1015625000f, 83022.7343750000f, +83045.3671875000f, 83068.0000000000f, 83090.6406250000f, 83113.2812500000f, +83135.9140625000f, 83158.5546875000f, 83181.2031250000f, 83203.8437500000f, +83226.4921875000f, 83249.1406250000f, 83271.7890625000f, 83294.4375000000f, +83317.0937500000f, 83339.7421875000f, 83362.3984375000f, 83385.0546875000f, +83407.7109375000f, 83430.3750000000f, 83453.0312500000f, 83475.6953125000f, +83498.3593750000f, 83521.0312500000f, 83543.6953125000f, 83566.3671875000f, +83589.0312500000f, 83611.7109375000f, 83634.3828125000f, 83657.0546875000f, +83679.7343750000f, 83702.4140625000f, 83725.0937500000f, 83747.7734375000f, +83770.4531250000f, 83793.1406250000f, 83815.8281250000f, 83838.5156250000f, +83861.2031250000f, 83883.8906250000f, 83906.5859375000f, 83929.2734375000f, +83951.9687500000f, 83974.6718750000f, 83997.3671875000f, 84020.0703125000f, +84042.7656250000f, 84065.4687500000f, 84088.1718750000f, 84110.8828125000f, +84133.5859375000f, 84156.2968750000f, 84179.0078125000f, 84201.7187500000f, +84224.4296875000f, 84247.1484375000f, 84269.8671875000f, 84292.5859375000f, +84315.3046875000f, 84338.0234375000f, 84360.7500000000f, 84383.4687500000f, +84406.1953125000f, 84428.9218750000f, 84451.6484375000f, 84474.3828125000f, +84497.1171875000f, 84519.8437500000f, 84542.5859375000f, 84565.3203125000f, +84588.0546875000f, 84610.7968750000f, 84633.5390625000f, 84656.2812500000f, +84679.0234375000f, 84701.7734375000f, 84724.5156250000f, 84747.2656250000f, +84770.0156250000f, 84792.7656250000f, 84815.5234375000f, 84838.2734375000f, +84861.0312500000f, 84883.7890625000f, 84906.5468750000f, 84929.3125000000f, +84952.0703125000f, 84974.8359375000f, 84997.6015625000f, 85020.3671875000f, +85043.1406250000f, 85065.9062500000f, 85088.6796875000f, 85111.4531250000f, +85134.2265625000f, 85157.0000000000f, 85179.7812500000f, 85202.5625000000f, +85225.3359375000f, 85248.1250000000f, 85270.9062500000f, 85293.6875000000f, +85316.4765625000f, 85339.2656250000f, 85362.0546875000f, 85384.8437500000f, +85407.6406250000f, 85430.4375000000f, 85453.2265625000f, 85476.0234375000f, +85498.8281250000f, 85521.6250000000f, 85544.4296875000f, 85567.2343750000f, +85590.0390625000f, 85612.8437500000f, 85635.6484375000f, 85658.4609375000f, +85681.2734375000f, 85704.0859375000f, 85726.8984375000f, 85749.7109375000f, +85772.5312500000f, 85795.3515625000f, 85818.1718750000f, 85840.9921875000f, +85863.8125000000f, 85886.6406250000f, 85909.4687500000f, 85932.2968750000f, +85955.1250000000f, 85977.9531250000f, 86000.7890625000f, 86023.6171875000f, +86046.4531250000f, 86069.2890625000f, 86092.1328125000f, 86114.9687500000f, +86137.8125000000f, 86160.6562500000f, 86183.5000000000f, 86206.3437500000f, +86229.1953125000f, 86252.0390625000f, 86274.8906250000f, 86297.7421875000f, +86320.6015625000f, 86343.4531250000f, 86366.3125000000f, 86389.1718750000f, +86412.0312500000f, 86434.8906250000f, 86457.7500000000f, 86480.6171875000f, +86503.4843750000f, 86526.3515625000f, 86549.2187500000f, 86572.0859375000f, +86594.9609375000f, 86617.8281250000f, 86640.7031250000f, 86663.5859375000f, +86686.4609375000f, 86709.3359375000f, 86732.2187500000f, 86755.1015625000f, +86777.9843750000f, 86800.8750000000f, 86823.7578125000f, 86846.6484375000f, +86869.5390625000f, 86892.4296875000f, 86915.3203125000f, 86938.2109375000f, +86961.1093750000f, 86984.0078125000f, 87006.9062500000f, 87029.8046875000f, +87052.7109375000f, 87075.6093750000f, 87098.5156250000f, 87121.4218750000f, +87144.3281250000f, 87167.2421875000f, 87190.1484375000f, 87213.0625000000f, +87235.9765625000f, 87258.8906250000f, 87281.8125000000f, 87304.7265625000f, +87327.6484375000f, 87350.5703125000f, 87373.4921875000f, 87396.4140625000f, +87419.3437500000f, 87442.2734375000f, 87465.1953125000f, 87488.1328125000f, +87511.0625000000f, 87533.9921875000f, 87556.9296875000f, 87579.8671875000f, +87602.8046875000f, 87625.7421875000f, 87648.6875000000f, 87671.6250000000f, +87694.5703125000f, 87717.5156250000f, 87740.4609375000f, 87763.4140625000f, +87786.3593750000f, 87809.3125000000f, 87832.2656250000f, 87855.2187500000f, +87878.1796875000f, 87901.1328125000f, 87924.0937500000f, 87947.0546875000f, +87970.0156250000f, 87992.9765625000f, 88015.9453125000f, 88038.9062500000f, +88061.8750000000f, 88084.8437500000f, 88107.8203125000f, 88130.7890625000f, +88153.7656250000f, 88176.7421875000f, 88199.7187500000f, 88222.6953125000f, +88245.6718750000f, 88268.6562500000f, 88291.6406250000f, 88314.6250000000f, +88337.6093750000f, 88360.5937500000f, 88383.5859375000f, 88406.5781250000f, +88429.5703125000f, 88452.5625000000f, 88475.5546875000f, 88498.5546875000f, +88521.5468750000f, 88544.5468750000f, 88567.5468750000f, 88590.5546875000f, +88613.5546875000f, 88636.5625000000f, 88659.5703125000f, 88682.5781250000f, +88705.5859375000f, 88728.6015625000f, 88751.6093750000f, 88774.6250000000f, +88797.6406250000f, 88820.6562500000f, 88843.6796875000f, 88866.6953125000f, +88889.7187500000f, 88912.7421875000f, 88935.7656250000f, 88958.7890625000f, +88981.8203125000f, 89004.8515625000f, 89027.8828125000f, 89050.9140625000f, +89073.9453125000f, 89096.9843750000f, 89120.0156250000f, 89143.0546875000f, +89166.0937500000f, 89189.1328125000f, 89212.1796875000f, 89235.2265625000f, +89258.2656250000f, 89281.3125000000f, 89304.3671875000f, 89327.4140625000f, +89350.4687500000f, 89373.5156250000f, 89396.5703125000f, 89419.6328125000f, +89442.6875000000f, 89465.7421875000f, 89488.8046875000f, 89511.8671875000f, +89534.9296875000f, 89558.0000000000f, 89581.0625000000f, 89604.1328125000f, +89627.2031250000f, 89650.2734375000f, 89673.3437500000f, 89696.4140625000f, +89719.4921875000f, 89742.5703125000f, 89765.6484375000f, 89788.7265625000f, +89811.8046875000f, 89834.8906250000f, 89857.9765625000f, 89881.0625000000f, +89904.1484375000f, 89927.2343750000f, 89950.3281250000f, 89973.4140625000f, +89996.5078125000f, 90019.6015625000f, 90042.7031250000f, 90065.7968750000f, +90088.8984375000f, 90112.0000000000f, 90135.1015625000f, 90158.2031250000f, +90181.3046875000f, 90204.4140625000f, 90227.5234375000f, 90250.6328125000f, +90273.7421875000f, 90296.8515625000f, 90319.9687500000f, 90343.0859375000f, +90366.2031250000f, 90389.3203125000f, 90412.4375000000f, 90435.5625000000f, +90458.6796875000f, 90481.8046875000f, 90504.9296875000f, 90528.0625000000f, +90551.1875000000f, 90574.3203125000f, 90597.4531250000f, 90620.5859375000f, +90643.7187500000f, 90666.8515625000f, 90689.9921875000f, 90713.1328125000f, +90736.2656250000f, 90759.4140625000f, 90782.5546875000f, 90805.7031250000f, +90828.8437500000f, 90851.9921875000f, 90875.1406250000f, 90898.2968750000f, +90921.4453125000f, 90944.6015625000f, 90967.7578125000f, 90990.9140625000f, +91014.0703125000f, 91037.2265625000f, 91060.3906250000f, 91083.5546875000f, +91106.7187500000f, 91129.8828125000f, 91153.0468750000f, 91176.2187500000f, +91199.3828125000f, 91222.5546875000f, 91245.7343750000f, 91268.9062500000f, +91292.0781250000f, 91315.2578125000f, 91338.4375000000f, 91361.6171875000f, +91384.7968750000f, 91407.9843750000f, 91431.1640625000f, 91454.3515625000f, +91477.5390625000f, 91500.7265625000f, 91523.9218750000f, 91547.1093750000f, +91570.3046875000f, 91593.5000000000f, 91616.6953125000f, 91639.8906250000f, +91663.0937500000f, 91686.2890625000f, 91709.4921875000f, 91732.6953125000f, +91755.9062500000f, 91779.1093750000f, 91802.3203125000f, 91825.5312500000f, +91848.7421875000f, 91871.9531250000f, 91895.1640625000f, 91918.3828125000f, +91941.5937500000f, 91964.8125000000f, 91988.0390625000f, 92011.2578125000f, +92034.4765625000f, 92057.7031250000f, 92080.9296875000f, 92104.1562500000f, +92127.3828125000f, 92150.6171875000f, 92173.8437500000f, 92197.0781250000f, +92220.3125000000f, 92243.5468750000f, 92266.7890625000f, 92290.0234375000f, +92313.2656250000f, 92336.5078125000f, 92359.7500000000f, 92382.9921875000f, +92406.2421875000f, 92429.4921875000f, 92452.7421875000f, 92475.9921875000f, +92499.2421875000f, 92522.4921875000f, 92545.7500000000f, 92569.0078125000f, +92592.2656250000f, 92615.5234375000f, 92638.7812500000f, 92662.0468750000f, +92685.3125000000f, 92708.5781250000f, 92731.8437500000f, 92755.1093750000f, +92778.3750000000f, 92801.6484375000f, 92824.9218750000f, 92848.1953125000f, +92871.4687500000f, 92894.7500000000f, 92918.0234375000f, 92941.3046875000f, +92964.5859375000f, 92987.8671875000f, 93011.1562500000f, 93034.4375000000f, +93057.7265625000f, 93081.0156250000f, 93104.3046875000f, 93127.5937500000f, +93150.8906250000f, 93174.1796875000f, 93197.4765625000f, 93220.7734375000f, +93244.0781250000f, 93267.3750000000f, 93290.6796875000f, 93313.9765625000f, +93337.2812500000f, 93360.5859375000f, 93383.8984375000f, 93407.2031250000f, +93430.5156250000f, 93453.8281250000f, 93477.1406250000f, 93500.4531250000f, +93523.7734375000f, 93547.0859375000f, 93570.4062500000f, 93593.7265625000f, +93617.0468750000f, 93640.3750000000f, 93663.6953125000f, 93687.0234375000f, +93710.3515625000f, 93733.6796875000f, 93757.0156250000f, 93780.3437500000f, +93803.6796875000f, 93827.0156250000f, 93850.3515625000f, 93873.6875000000f, +93897.0234375000f, 93920.3671875000f, 93943.7109375000f, 93967.0546875000f, +93990.3984375000f, 94013.7421875000f, 94037.0937500000f, 94060.4453125000f, +94083.7968750000f, 94107.1484375000f, 94130.5000000000f, 94153.8515625000f, +94177.2109375000f, 94200.5703125000f, 94223.9296875000f, 94247.2890625000f, +94270.6562500000f, 94294.0156250000f, 94317.3828125000f, 94340.7500000000f, +94364.1171875000f, 94387.4843750000f, 94410.8593750000f, 94434.2343750000f, +94457.6015625000f, 94480.9843750000f, 94504.3593750000f, 94527.7343750000f, +94551.1171875000f, 94574.5000000000f, 94597.8828125000f, 94621.2656250000f, +94644.6484375000f, 94668.0390625000f, 94691.4218750000f, 94714.8125000000f, +94738.2109375000f, 94761.6015625000f, 94784.9921875000f, 94808.3906250000f, +94831.7890625000f, 94855.1875000000f, 94878.5859375000f, 94901.9843750000f, +94925.3906250000f, 94948.7968750000f, 94972.2031250000f, 94995.6093750000f, +95019.0156250000f, 95042.4296875000f, 95065.8359375000f, 95089.2500000000f, +95112.6640625000f, 95136.0859375000f, 95159.5000000000f, 95182.9218750000f, +95206.3359375000f, 95229.7578125000f, 95253.1796875000f, 95276.6093750000f, +95300.0312500000f, 95323.4609375000f, 95346.8906250000f, 95370.3203125000f, +95393.7500000000f, 95417.1875000000f, 95440.6171875000f, 95464.0546875000f, +95487.4921875000f, 95510.9296875000f, 95534.3750000000f, 95557.8125000000f, +95581.2578125000f, 95604.7031250000f, 95628.1484375000f, 95651.5937500000f, +95675.0468750000f, 95698.5000000000f, 95721.9453125000f, 95745.3984375000f, +95768.8593750000f, 95792.3125000000f, 95815.7734375000f, 95839.2265625000f, +95862.6875000000f, 95886.1562500000f, 95909.6171875000f, 95933.0781250000f, +95956.5468750000f, 95980.0156250000f, 96003.4843750000f, 96026.9531250000f, +96050.4296875000f, 96073.8984375000f, 96097.3750000000f, 96120.8515625000f, +96144.3281250000f, 96167.8046875000f, 96191.2890625000f, 96214.7734375000f, +96238.2578125000f, 96261.7421875000f, 96285.2265625000f, 96308.7109375000f, +96332.2031250000f, 96355.6953125000f, 96379.1875000000f, 96402.6796875000f, +96426.1718750000f, 96449.6718750000f, 96473.1640625000f, 96496.6640625000f, +96520.1640625000f, 96543.6718750000f, 96567.1718750000f, 96590.6796875000f, +96614.1875000000f, 96637.6953125000f, 96661.2031250000f, 96684.7109375000f, +96708.2265625000f, 96731.7343750000f, 96755.2500000000f, 96778.7656250000f, +96802.2890625000f, 96825.8046875000f, 96849.3281250000f, 96872.8515625000f, +96896.3750000000f, 96919.8984375000f, 96943.4218750000f, 96966.9531250000f, +96990.4843750000f, 97014.0156250000f, 97037.5468750000f, 97061.0781250000f, +97084.6093750000f, 97108.1484375000f, 97131.6875000000f, 97155.2265625000f, +97178.7656250000f, 97202.3125000000f, 97225.8515625000f, 97249.3984375000f, +97272.9453125000f, 97296.4921875000f, 97320.0390625000f, 97343.5937500000f, +97367.1406250000f, 97390.6953125000f, 97414.2500000000f, 97437.8046875000f, +97461.3671875000f, 97484.9218750000f, 97508.4843750000f, 97532.0468750000f, +97555.6093750000f, 97579.1718750000f, 97602.7421875000f, 97626.3125000000f, +97649.8750000000f, 97673.4531250000f, 97697.0234375000f, 97720.5937500000f, +97744.1718750000f, 97767.7421875000f, 97791.3203125000f, 97814.9062500000f, +97838.4843750000f, 97862.0625000000f, 97885.6484375000f, 97909.2343750000f, +97932.8203125000f, 97956.4062500000f, 97979.9921875000f, 98003.5859375000f, +98027.1796875000f, 98050.7734375000f, 98074.3671875000f, 98097.9609375000f, +98121.5625000000f, 98145.1562500000f, 98168.7578125000f, 98192.3593750000f, +98215.9609375000f, 98239.5703125000f, 98263.1718750000f, 98286.7812500000f, +98310.3906250000f, 98334.0000000000f, 98357.6171875000f, 98381.2265625000f, +98404.8437500000f, 98428.4609375000f, 98452.0781250000f, 98475.6953125000f, +98499.3125000000f, 98522.9375000000f, 98546.5546875000f, 98570.1796875000f, +98593.8125000000f, 98617.4375000000f, 98641.0625000000f, 98664.6953125000f, +98688.3281250000f, 98711.9609375000f, 98735.5937500000f, 98759.2265625000f, +98782.8671875000f, 98806.5078125000f, 98830.1484375000f, 98853.7890625000f, +98877.4296875000f, 98901.0703125000f, 98924.7187500000f, 98948.3671875000f, +98972.0156250000f, 98995.6640625000f, 99019.3203125000f, 99042.9687500000f, +99066.6250000000f, 99090.2812500000f, 99113.9375000000f, 99137.5937500000f, +99161.2578125000f, 99184.9140625000f, 99208.5781250000f, 99232.2421875000f, +99255.9062500000f, 99279.5781250000f, 99303.2421875000f, 99326.9140625000f, +99350.5859375000f, 99374.2578125000f, 99397.9296875000f, 99421.6093750000f, +99445.2812500000f, 99468.9609375000f, 99492.6406250000f, 99516.3203125000f, +99540.0000000000f, 99563.6875000000f, 99587.3750000000f, 99611.0625000000f, +99634.7500000000f, 99658.4375000000f, 99682.1250000000f, 99705.8203125000f, +99729.5156250000f, 99753.2109375000f, 99776.9062500000f, 99800.6015625000f, +99824.3046875000f, 99848.0000000000f, 99871.7031250000f, 99895.4062500000f, +99919.1093750000f, 99942.8203125000f, 99966.5234375000f, 99990.2343750000f, +100013.9453125000f, 100037.6562500000f, 100061.3750000000f, 100085.0859375000f, +100108.8046875000f, 100132.5234375000f, 100156.2421875000f, 100179.9609375000f, +100203.6796875000f, 100227.4062500000f, 100251.1328125000f, 100274.8515625000f, +100298.5859375000f, 100322.3125000000f, 100346.0390625000f, 100369.7734375000f, +100393.5078125000f, 100417.2421875000f, 100440.9765625000f, 100464.7109375000f, +100488.4531250000f, 100512.1953125000f, 100535.9296875000f, 100559.6796875000f, +100583.4218750000f, 100607.1640625000f, 100630.9140625000f, 100654.6640625000f, +100678.4140625000f, 100702.1640625000f, 100725.9140625000f, 100749.6718750000f, +100773.4218750000f, 100797.1796875000f, 100820.9375000000f, 100844.6953125000f, +100868.4609375000f, 100892.2187500000f, 100915.9843750000f, 100939.7500000000f, +100963.5156250000f, 100987.2890625000f, 101011.0546875000f, 101034.8281250000f, +101058.6015625000f, 101082.3750000000f, 101106.1484375000f, 101129.9218750000f, +101153.7031250000f, 101177.4843750000f, 101201.2578125000f, 101225.0468750000f, +101248.8281250000f, 101272.6093750000f, 101296.3984375000f, 101320.1875000000f, +101343.9765625000f, 101367.7656250000f, 101391.5546875000f, 101415.3515625000f, +101439.1406250000f, 101462.9375000000f, 101486.7343750000f, 101510.5390625000f, +101534.3359375000f, 101558.1406250000f, 101581.9375000000f, 101605.7421875000f, +101629.5468750000f, 101653.3593750000f, 101677.1640625000f, 101700.9765625000f, +101724.7890625000f, 101748.6015625000f, 101772.4140625000f, 101796.2265625000f, +101820.0468750000f, 101843.8671875000f, 101867.6875000000f, 101891.5078125000f, +101915.3281250000f, 101939.1484375000f, 101962.9765625000f, 101986.8046875000f, +102010.6328125000f, 102034.4609375000f, 102058.2890625000f, 102082.1250000000f, +102105.9609375000f, 102129.7890625000f, 102153.6250000000f, 102177.4687500000f, +102201.3046875000f, 102225.1484375000f, 102248.9843750000f, 102272.8281250000f, +102296.6718750000f, 102320.5234375000f, 102344.3671875000f, 102368.2187500000f, +102392.0703125000f, 102415.9218750000f, 102439.7734375000f, 102463.6250000000f, +102487.4843750000f, 102511.3437500000f, 102535.1953125000f, 102559.0625000000f, +102582.9218750000f, 102606.7812500000f, 102630.6484375000f, 102654.5156250000f, +102678.3828125000f, 102702.2500000000f, 102726.1171875000f, 102749.9921875000f, +102773.8593750000f, 102797.7343750000f, 102821.6093750000f, 102845.4843750000f, +102869.3671875000f, 102893.2421875000f, 102917.1250000000f, 102941.0078125000f, +102964.8906250000f, 102988.7734375000f, 103012.6640625000f, 103036.5468750000f, +103060.4375000000f, 103084.3281250000f, 103108.2187500000f, 103132.1171875000f, +103156.0078125000f, 103179.9062500000f, 103203.8046875000f, 103227.7031250000f, +103251.6015625000f, 103275.5000000000f, 103299.4062500000f, 103323.3125000000f, +103347.2187500000f, 103371.1250000000f, 103395.0312500000f, 103418.9375000000f, +103442.8515625000f, 103466.7656250000f, 103490.6796875000f, 103514.5937500000f, +103538.5078125000f, 103562.4296875000f, 103586.3515625000f, 103610.2656250000f, +103634.1875000000f, 103658.1171875000f, 103682.0390625000f, 103705.9687500000f, +103729.8906250000f, 103753.8203125000f, 103777.7500000000f, 103801.6875000000f, +103825.6171875000f, 103849.5546875000f, 103873.4921875000f, 103897.4296875000f, +103921.3671875000f, 103945.3046875000f, 103969.2500000000f, 103993.1875000000f, +104017.1328125000f, 104041.0781250000f, 104065.0312500000f, 104088.9765625000f, +104112.9296875000f, 104136.8750000000f, 104160.8281250000f, 104184.7812500000f, +104208.7421875000f, 104232.6953125000f, 104256.6562500000f, 104280.6171875000f, +104304.5703125000f, 104328.5390625000f, 104352.5000000000f, 104376.4687500000f, +104400.4296875000f, 104424.3984375000f, 104448.3671875000f, 104472.3359375000f, +104496.3125000000f, 104520.2812500000f, 104544.2578125000f, 104568.2343750000f, +104592.2109375000f, 104616.1875000000f, 104640.1718750000f, 104664.1484375000f, +104688.1328125000f, 104712.1171875000f, 104736.1015625000f, 104760.0937500000f, +104784.0781250000f, 104808.0703125000f, 104832.0625000000f, 104856.0546875000f, +104880.0468750000f, 104904.0390625000f, 104928.0390625000f, 104952.0390625000f, +104976.0390625000f, 105000.0390625000f, 105024.0390625000f, 105048.0390625000f, +105072.0468750000f, 105096.0546875000f, 105120.0625000000f, 105144.0703125000f, +105168.0781250000f, 105192.0937500000f, 105216.1015625000f, 105240.1171875000f, +105264.1328125000f, 105288.1484375000f, 105312.1718750000f, 105336.1875000000f, +105360.2109375000f, 105384.2343750000f, 105408.2578125000f, 105432.2812500000f, +105456.3125000000f, 105480.3359375000f, 105504.3671875000f, 105528.3984375000f, +105552.4296875000f, 105576.4609375000f, 105600.5000000000f, 105624.5390625000f, +105648.5703125000f, 105672.6093750000f, 105696.6562500000f, 105720.6953125000f, +105744.7343750000f, 105768.7812500000f, 105792.8281250000f, 105816.8750000000f, +105840.9218750000f, 105864.9765625000f, 105889.0234375000f, 105913.0781250000f, +105937.1328125000f, 105961.1875000000f, 105985.2421875000f, 106009.3046875000f, +106033.3593750000f, 106057.4218750000f, 106081.4843750000f, 106105.5468750000f, +106129.6171875000f, 106153.6796875000f, 106177.7500000000f, 106201.8203125000f, +106225.8906250000f, 106249.9609375000f, 106274.0312500000f, 106298.1093750000f, +106322.1796875000f, 106346.2578125000f, 106370.3359375000f, 106394.4218750000f, +106418.5000000000f, 106442.5859375000f, 106466.6640625000f, 106490.7500000000f, +106514.8359375000f, 106538.9296875000f, 106563.0156250000f, 106587.1093750000f, +106611.2031250000f, 106635.2968750000f, 106659.3906250000f, 106683.4843750000f, +106707.5859375000f, 106731.6796875000f, 106755.7812500000f, 106779.8828125000f, +106803.9843750000f, 106828.0937500000f, 106852.1953125000f, 106876.3046875000f, +106900.4140625000f, 106924.5234375000f, 106948.6328125000f, 106972.7500000000f, +106996.8593750000f, 107020.9765625000f, 107045.0937500000f, 107069.2109375000f, +107093.3281250000f, 107117.4531250000f, 107141.5703125000f, 107165.6953125000f, +107189.8203125000f, 107213.9453125000f, 107238.0781250000f, 107262.2031250000f, +107286.3359375000f, 107310.4687500000f, 107334.6015625000f, 107358.7343750000f, +107382.8671875000f, 107407.0078125000f, 107431.1484375000f, 107455.2812500000f, +107479.4296875000f, 107503.5703125000f, 107527.7109375000f, 107551.8593750000f, +107576.0078125000f, 107600.1562500000f, 107624.3046875000f, 107648.4531250000f, +107672.6015625000f, 107696.7578125000f, 107720.9140625000f, 107745.0703125000f, +107769.2265625000f, 107793.3828125000f, 107817.5468750000f, 107841.7031250000f, +107865.8671875000f, 107890.0312500000f, 107914.1953125000f, 107938.3671875000f, +107962.5312500000f, 107986.7031250000f, 108010.8750000000f, 108035.0468750000f, +108059.2187500000f, 108083.3984375000f, 108107.5703125000f, 108131.7500000000f, +108155.9296875000f, 108180.1093750000f, 108204.2890625000f, 108228.4765625000f, +108252.6562500000f, 108276.8437500000f, 108301.0312500000f, 108325.2187500000f, +108349.4062500000f, 108373.6015625000f, 108397.7968750000f, 108421.9843750000f, +108446.1796875000f, 108470.3828125000f, 108494.5781250000f, 108518.7734375000f, +108542.9765625000f, 108567.1796875000f, 108591.3828125000f, 108615.5859375000f, +108639.7968750000f, 108664.0000000000f, 108688.2109375000f, 108712.4218750000f, +108736.6328125000f, 108760.8437500000f, 108785.0546875000f, 108809.2734375000f, +108833.4921875000f, 108857.7109375000f, 108881.9296875000f, 108906.1484375000f, +108930.3671875000f, 108954.5937500000f, 108978.8203125000f, 109003.0468750000f, +109027.2734375000f, 109051.5000000000f, 109075.7343750000f, 109099.9609375000f, +109124.1953125000f, 109148.4296875000f, 109172.6640625000f, 109196.9062500000f, +109221.1406250000f, 109245.3828125000f, 109269.6250000000f, 109293.8671875000f, +109318.1093750000f, 109342.3515625000f, 109366.6015625000f, 109390.8515625000f, +109415.1015625000f, 109439.3515625000f, 109463.6015625000f, 109487.8515625000f, +109512.1093750000f, 109536.3671875000f, 109560.6171875000f, 109584.8828125000f, +109609.1406250000f, 109633.3984375000f, 109657.6640625000f, 109681.9296875000f, +109706.1953125000f, 109730.4609375000f, 109754.7265625000f, 109778.9921875000f, +109803.2656250000f, 109827.5390625000f, 109851.8125000000f, 109876.0859375000f, +109900.3593750000f, 109924.6406250000f, 109948.9218750000f, 109973.1953125000f, +109997.4765625000f, 110021.7656250000f, 110046.0468750000f, 110070.3281250000f, +110094.6171875000f, 110118.9062500000f, 110143.1953125000f, 110167.4843750000f, +110191.7812500000f, 110216.0703125000f, 110240.3671875000f, 110264.6640625000f, +110288.9609375000f, 110313.2578125000f, 110337.5625000000f, 110361.8593750000f, +110386.1640625000f, 110410.4687500000f, 110434.7734375000f, 110459.0781250000f, +110483.3906250000f, 110507.6953125000f, 110532.0078125000f, 110556.3203125000f, +110580.6328125000f, 110604.9453125000f, 110629.2656250000f, 110653.5781250000f, +110677.8984375000f, 110702.2187500000f, 110726.5390625000f, 110750.8671875000f, +110775.1875000000f, 110799.5156250000f, 110823.8437500000f, 110848.1718750000f, +110872.5000000000f, 110896.8281250000f, 110921.1640625000f, 110945.4921875000f, +110969.8281250000f, 110994.1640625000f, 111018.5078125000f, 111042.8437500000f, +111067.1796875000f, 111091.5234375000f, 111115.8671875000f, 111140.2109375000f, +111164.5546875000f, 111188.9062500000f, 111213.2500000000f, 111237.6015625000f, +111261.9531250000f, 111286.3046875000f, 111310.6562500000f, 111335.0156250000f, +111359.3671875000f, 111383.7265625000f, 111408.0859375000f, 111432.4453125000f, +111456.8046875000f, 111481.1718750000f, 111505.5312500000f, 111529.8984375000f, +111554.2656250000f, 111578.6328125000f, 111603.0000000000f, 111627.3750000000f, +111651.7500000000f, 111676.1171875000f, 111700.4921875000f, 111724.8671875000f, +111749.2500000000f, 111773.6250000000f, 111798.0078125000f, 111822.3906250000f, +111846.7734375000f, 111871.1562500000f, 111895.5390625000f, 111919.9296875000f, +111944.3125000000f, 111968.7031250000f, 111993.0937500000f, 112017.4843750000f, +112041.8828125000f, 112066.2734375000f, 112090.6718750000f, 112115.0703125000f, +112139.4687500000f, 112163.8671875000f, 112188.2734375000f, 112212.6718750000f, +112237.0781250000f, 112261.4843750000f, 112285.8906250000f, 112310.2968750000f, +112334.7031250000f, 112359.1171875000f, 112383.5312500000f, 112407.9453125000f, +112432.3593750000f, 112456.7734375000f, 112481.1875000000f, 112505.6093750000f, +112530.0312500000f, 112554.4453125000f, 112578.8750000000f, 112603.2968750000f, +112627.7187500000f, 112652.1484375000f, 112676.5781250000f, 112701.0078125000f, +112725.4375000000f, 112749.8671875000f, 112774.2968750000f, 112798.7343750000f, +112823.1718750000f, 112847.6093750000f, 112872.0468750000f, 112896.4843750000f, +112920.9296875000f, 112945.3671875000f, 112969.8125000000f, 112994.2578125000f, +113018.7031250000f, 113043.1484375000f, 113067.6015625000f, 113092.0546875000f, +113116.5000000000f, 113140.9531250000f, 113165.4140625000f, 113189.8671875000f, +113214.3203125000f, 113238.7812500000f, 113263.2421875000f, 113287.7031250000f, +113312.1640625000f, 113336.6250000000f, 113361.0937500000f, 113385.5625000000f, +113410.0234375000f, 113434.4921875000f, 113458.9687500000f, 113483.4375000000f, +113507.9062500000f, 113532.3828125000f, 113556.8593750000f, 113581.3359375000f, +113605.8125000000f, 113630.2968750000f, 113654.7734375000f, 113679.2578125000f, +113703.7421875000f, 113728.2265625000f, 113752.7109375000f, 113777.1953125000f, +113801.6875000000f, 113826.1796875000f, 113850.6640625000f, 113875.1562500000f, +113899.6562500000f, 113924.1484375000f, 113948.6484375000f, 113973.1406250000f, +113997.6406250000f, 114022.1406250000f, 114046.6484375000f, 114071.1484375000f, +114095.6484375000f, 114120.1562500000f, 114144.6640625000f, 114169.1718750000f, +114193.6796875000f, 114218.1953125000f, 114242.7031250000f, 114267.2187500000f, +114291.7343750000f, 114316.2500000000f, 114340.7656250000f, 114365.2890625000f, +114389.8046875000f, 114414.3281250000f, 114438.8515625000f, 114463.3750000000f, +114487.8984375000f, 114512.4296875000f, 114536.9531250000f, 114561.4843750000f, +114586.0156250000f, 114610.5468750000f, 114635.0859375000f, 114659.6171875000f, +114684.1562500000f, 114708.6875000000f, 114733.2265625000f, 114757.7656250000f, +114782.3125000000f, 114806.8515625000f, 114831.3984375000f, 114855.9375000000f, +114880.4843750000f, 114905.0390625000f, 114929.5859375000f, 114954.1328125000f, +114978.6875000000f, 115003.2421875000f, 115027.7968750000f, 115052.3515625000f, +115076.9062500000f, 115101.4609375000f, 115126.0234375000f, 115150.5859375000f, +115175.1484375000f, 115199.7109375000f, 115224.2734375000f, 115248.8437500000f, +115273.4062500000f, 115297.9765625000f, 115322.5468750000f, 115347.1171875000f, +115371.6953125000f, 115396.2656250000f, 115420.8437500000f, 115445.4140625000f, +115469.9921875000f, 115494.5781250000f, 115519.1562500000f, 115543.7343750000f, +115568.3203125000f, 115592.9062500000f, 115617.4921875000f, 115642.0781250000f, +115666.6640625000f, 115691.2578125000f, 115715.8437500000f, 115740.4375000000f, +115765.0312500000f, 115789.6250000000f, 115814.2265625000f, 115838.8203125000f, +115863.4218750000f, 115888.0234375000f, 115912.6250000000f, 115937.2265625000f, +115961.8281250000f, 115986.4296875000f, 116011.0390625000f, 116035.6484375000f, +116060.2578125000f, 116084.8671875000f, 116109.4765625000f, 116134.0937500000f, +116158.7109375000f, 116183.3203125000f, 116207.9375000000f, 116232.5546875000f, +116257.1796875000f, 116281.7968750000f, 116306.4218750000f, 116331.0468750000f, +116355.6718750000f, 116380.2968750000f, 116404.9218750000f, 116429.5546875000f, +116454.1796875000f, 116478.8125000000f, 116503.4453125000f, 116528.0781250000f, +116552.7187500000f, 116577.3515625000f, 116601.9921875000f, 116626.6328125000f, +116651.2734375000f, 116675.9140625000f, 116700.5546875000f, 116725.2031250000f, +116749.8437500000f, 116774.4921875000f, 116799.1406250000f, 116823.7890625000f, +116848.4375000000f, 116873.0937500000f, 116897.7500000000f, 116922.3984375000f, +116947.0546875000f, 116971.7187500000f, 116996.3750000000f, 117021.0312500000f, +117045.6953125000f, 117070.3593750000f, 117095.0234375000f, 117119.6875000000f, +117144.3515625000f, 117169.0234375000f, 117193.6875000000f, 117218.3593750000f, +117243.0312500000f, 117267.7031250000f, 117292.3828125000f, 117317.0546875000f, +117341.7343750000f, 117366.4140625000f, 117391.0937500000f, 117415.7734375000f, +117440.4531250000f, 117465.1328125000f, 117489.8203125000f, 117514.5078125000f, +117539.1953125000f, 117563.8828125000f, 117588.5703125000f, 117613.2656250000f, +117637.9531250000f, 117662.6484375000f, 117687.3437500000f, 117712.0390625000f, +117736.7421875000f, 117761.4375000000f, 117786.1406250000f, 117810.8359375000f, +117835.5390625000f, 117860.2500000000f, 117884.9531250000f, 117909.6562500000f, +117934.3671875000f, 117959.0781250000f, 117983.7890625000f, 118008.5000000000f, +118033.2109375000f, 118057.9296875000f, 118082.6406250000f, 118107.3593750000f, +118132.0781250000f, 118156.7968750000f, 118181.5156250000f, 118206.2421875000f, +118230.9609375000f, 118255.6875000000f, 118280.4140625000f, 118305.1406250000f, +118329.8671875000f, 118354.6015625000f, 118379.3281250000f, 118404.0625000000f, +118428.7968750000f, 118453.5312500000f, 118478.2734375000f, 118503.0078125000f, +118527.7500000000f, 118552.4843750000f, 118577.2265625000f, 118601.9687500000f, +118626.7187500000f, 118651.4609375000f, 118676.2109375000f, 118700.9531250000f, +118725.7031250000f, 118750.4531250000f, 118775.2109375000f, 118799.9609375000f, +118824.7187500000f, 118849.4687500000f, 118874.2265625000f, 118898.9843750000f, +118923.7500000000f, 118948.5078125000f, 118973.2734375000f, 118998.0312500000f, +119022.7968750000f, 119047.5625000000f, 119072.3281250000f, 119097.1015625000f, +119121.8671875000f, 119146.6406250000f, 119171.4140625000f, 119196.1875000000f, +119220.9609375000f, 119245.7421875000f, 119270.5156250000f, 119295.2968750000f, +119320.0781250000f, 119344.8593750000f, 119369.6406250000f, 119394.4218750000f, +119419.2109375000f, 119444.0000000000f, 119468.7812500000f, 119493.5703125000f, +119518.3671875000f, 119543.1562500000f, 119567.9531250000f, 119592.7421875000f, +119617.5390625000f, 119642.3359375000f, 119667.1328125000f, 119691.9375000000f, +119716.7343750000f, 119741.5390625000f, 119766.3437500000f, 119791.1484375000f, +119815.9531250000f, 119840.7578125000f, 119865.5703125000f, 119890.3750000000f, +119915.1875000000f, 119940.0000000000f, 119964.8125000000f, 119989.6328125000f, +120014.4453125000f, 120039.2656250000f, 120064.0781250000f, 120088.8984375000f, +120113.7265625000f, 120138.5468750000f, 120163.3671875000f, 120188.1953125000f, +120213.0234375000f, 120237.8515625000f, 120262.6796875000f, 120287.5078125000f, +120312.3437500000f, 120337.1718750000f, 120362.0078125000f, 120386.8437500000f, +120411.6796875000f, 120436.5156250000f, 120461.3593750000f, 120486.1953125000f, +120511.0390625000f, 120535.8828125000f, 120560.7265625000f, 120585.5703125000f, +120610.4218750000f, 120635.2656250000f, 120660.1171875000f, 120684.9687500000f, +120709.8203125000f, 120734.6718750000f, 120759.5312500000f, 120784.3828125000f, +120809.2421875000f, 120834.1015625000f, 120858.9609375000f, 120883.8203125000f, +120908.6875000000f, 120933.5468750000f, 120958.4140625000f, 120983.2812500000f, +121008.1484375000f, 121033.0156250000f, 121057.8828125000f, 121082.7578125000f, +121107.6250000000f, 121132.5000000000f, 121157.3750000000f, 121182.2500000000f, +121207.1328125000f, 121232.0078125000f, 121256.8906250000f, 121281.7734375000f, +121306.6562500000f, 121331.5390625000f, 121356.4218750000f, 121381.3125000000f, +121406.1953125000f, 121431.0859375000f, 121455.9765625000f, 121480.8671875000f, +121505.7656250000f, 121530.6562500000f, 121555.5546875000f, 121580.4531250000f, +121605.3515625000f, 121630.2500000000f, 121655.1484375000f, 121680.0468750000f, +121704.9531250000f, 121729.8593750000f, 121754.7656250000f, 121779.6718750000f, +121804.5781250000f, 121829.4843750000f, 121854.3984375000f, 121879.3125000000f, +121904.2265625000f, 121929.1406250000f, 121954.0546875000f, 121978.9687500000f, +122003.8906250000f, 122028.8125000000f, 122053.7343750000f, 122078.6562500000f, +122103.5781250000f, 122128.5000000000f, 122153.4296875000f, 122178.3515625000f, +122203.2812500000f, 122228.2109375000f, 122253.1484375000f, 122278.0781250000f, +122303.0078125000f, 122327.9453125000f, 122352.8828125000f, 122377.8203125000f, +122402.7578125000f, 122427.6953125000f, 122452.6406250000f, 122477.5859375000f, +122502.5234375000f, 122527.4687500000f, 122552.4218750000f, 122577.3671875000f, +122602.3125000000f, 122627.2656250000f, 122652.2187500000f, 122677.1718750000f, +122702.1250000000f, 122727.0781250000f, 122752.0390625000f, 122776.9921875000f, +122801.9531250000f, 122826.9140625000f, 122851.8750000000f, 122876.8359375000f, +122901.8046875000f, 122926.7656250000f, 122951.7343750000f, 122976.7031250000f, +123001.6718750000f, 123026.6406250000f, 123051.6093750000f, 123076.5859375000f, +123101.5625000000f, 123126.5390625000f, 123151.5156250000f, 123176.4921875000f, +123201.4687500000f, 123226.4531250000f, 123251.4296875000f, 123276.4140625000f, +123301.3984375000f, 123326.3828125000f, 123351.3750000000f, 123376.3593750000f, +123401.3515625000f, 123426.3437500000f, 123451.3359375000f, 123476.3281250000f, +123501.3203125000f, 123526.3203125000f, 123551.3125000000f, 123576.3125000000f, +123601.3125000000f, 123626.3125000000f, 123651.3125000000f, 123676.3203125000f, +123701.3203125000f, 123726.3281250000f, 123751.3359375000f, 123776.3437500000f, +123801.3515625000f, 123826.3671875000f, 123851.3750000000f, 123876.3906250000f, +123901.4062500000f, 123926.4218750000f, 123951.4375000000f, 123976.4609375000f, +124001.4765625000f, 124026.5000000000f, 124051.5234375000f, 124076.5468750000f, +124101.5703125000f, 124126.5937500000f, 124151.6250000000f, 124176.6562500000f, +124201.6796875000f, 124226.7109375000f, 124251.7500000000f, 124276.7812500000f, +124301.8125000000f, 124326.8515625000f, 124351.8906250000f, 124376.9296875000f, +124401.9687500000f, 124427.0078125000f, 124452.0546875000f, 124477.0937500000f, +124502.1406250000f, 124527.1875000000f, 124552.2343750000f, 124577.2812500000f, +124602.3359375000f, 124627.3828125000f, 124652.4375000000f, 124677.4921875000f, +124702.5468750000f, 124727.6015625000f, 124752.6640625000f, 124777.7187500000f, +124802.7812500000f, 124827.8437500000f, 124852.9062500000f, 124877.9687500000f, +124903.0312500000f, 124928.1015625000f, 124953.1640625000f, 124978.2343750000f, +125003.3046875000f, 125028.3750000000f, 125053.4531250000f, 125078.5234375000f, +125103.6015625000f, 125128.6796875000f, 125153.7578125000f, 125178.8359375000f, +125203.9140625000f, 125228.9921875000f, 125254.0781250000f, 125279.1640625000f, +125304.2500000000f, 125329.3359375000f, 125354.4218750000f, 125379.5078125000f, +125404.6015625000f, 125429.6953125000f, 125454.7890625000f, 125479.8828125000f, +125504.9765625000f, 125530.0703125000f, 125555.1718750000f, 125580.2656250000f, +125605.3671875000f, 125630.4687500000f, 125655.5703125000f, 125680.6796875000f, +125705.7812500000f, 125730.8906250000f, 125756.0000000000f, 125781.1093750000f, +125806.2187500000f, 125831.3281250000f, 125856.4375000000f, 125881.5546875000f, +125906.6718750000f, 125931.7890625000f, 125956.9062500000f, 125982.0234375000f, +126007.1484375000f, 126032.2656250000f, 126057.3906250000f, 126082.5156250000f, +126107.6406250000f, 126132.7656250000f, 126157.8906250000f, 126183.0234375000f, +126208.1562500000f, 126233.2812500000f, 126258.4140625000f, 126283.5546875000f, +126308.6875000000f, 126333.8203125000f, 126358.9609375000f, 126384.1015625000f, +126409.2421875000f, 126434.3828125000f, 126459.5234375000f, 126484.6718750000f, +126509.8125000000f, 126534.9609375000f, 126560.1093750000f, 126585.2578125000f, +126610.4140625000f, 126635.5625000000f, 126660.7109375000f, 126685.8671875000f, +126711.0234375000f, 126736.1796875000f, 126761.3359375000f, 126786.5000000000f, +126811.6562500000f, 126836.8203125000f, 126861.9843750000f, 126887.1484375000f, +126912.3125000000f, 126937.4765625000f, 126962.6484375000f, 126987.8203125000f, +127012.9843750000f, 127038.1562500000f, 127063.3281250000f, 127088.5078125000f, +127113.6796875000f, 127138.8593750000f, 127164.0390625000f, 127189.2109375000f, +127214.3984375000f, 127239.5781250000f, 127264.7578125000f, 127289.9453125000f, +127315.1328125000f, 127340.3125000000f, 127365.5000000000f, 127390.6953125000f, +127415.8828125000f, 127441.0781250000f, 127466.2656250000f, 127491.4609375000f, +127516.6562500000f, 127541.8515625000f, 127567.0546875000f, 127592.2500000000f, +127617.4531250000f, 127642.6484375000f, 127667.8515625000f, 127693.0625000000f, +127718.2656250000f, 127743.4687500000f, 127768.6796875000f, 127793.8906250000f, +127819.0937500000f, 127844.3125000000f, 127869.5234375000f, 127894.7343750000f, +127919.9531250000f, 127945.1640625000f, 127970.3828125000f, 127995.6015625000f, +128020.8203125000f, 128046.0468750000f, 128071.2656250000f, 128096.4921875000f, +128121.7187500000f, 128146.9453125000f, 128172.1718750000f, 128197.3984375000f, +128222.6328125000f, 128247.8593750000f, 128273.0937500000f, 128298.3281250000f, +128323.5625000000f, 128348.7968750000f, 128374.0390625000f, 128399.2734375000f, +128424.5156250000f, 128449.7578125000f, 128475.0000000000f, 128500.2421875000f, +128525.4921875000f, 128550.7343750000f, 128575.9843750000f, 128601.2343750000f, +128626.4843750000f, 128651.7343750000f, 128676.9843750000f, 128702.2421875000f, +128727.4921875000f, 128752.7500000000f, 128778.0078125000f, 128803.2656250000f, +128828.5234375000f, 128853.7890625000f, 128879.0468750000f, 128904.3125000000f, +128929.5781250000f, 128954.8437500000f, 128980.1093750000f, 129005.3828125000f, +129030.6484375000f, 129055.9218750000f, 129081.1953125000f, 129106.4687500000f, +129131.7421875000f, 129157.0156250000f, 129182.2968750000f, 129207.5703125000f, +129232.8515625000f, 129258.1328125000f, 129283.4140625000f, 129308.6953125000f, +129333.9843750000f, 129359.2656250000f, 129384.5546875000f, 129409.8437500000f, +129435.1328125000f, 129460.4218750000f, 129485.7187500000f, 129511.0078125000f, +129536.3046875000f, 129561.6015625000f, 129586.8984375000f, 129612.1953125000f, +129637.4921875000f, 129662.7968750000f, 129688.0937500000f, 129713.3984375000f, +129738.7031250000f, 129764.0078125000f, 129789.3203125000f, 129814.6250000000f, +129839.9375000000f, 129865.2421875000f, 129890.5546875000f, 129915.8671875000f, +129941.1875000000f, 129966.5000000000f, 129991.8125000000f, 130017.1328125000f, +130042.4531250000f, 130067.7734375000f, 130093.0937500000f, 130118.4218750000f, +130143.7421875000f, 130169.0703125000f, 130194.3906250000f, 130219.7187500000f, +130245.0546875000f, 130270.3828125000f, 130295.7109375000f, 130321.0468750000f, +130346.3828125000f, 130371.7187500000f, 130397.0546875000f, 130422.3906250000f, +130447.7265625000f, 130473.0703125000f, 130498.4062500000f, 130523.7500000000f, +130549.0937500000f, 130574.4375000000f, 130599.7890625000f, 130625.1328125000f, +130650.4843750000f, 130675.8359375000f, 130701.1875000000f, 130726.5390625000f, +130751.8906250000f, 130777.2421875000f, 130802.6015625000f, 130827.9609375000f, +130853.3203125000f, 130878.6796875000f, 130904.0390625000f, 130929.3984375000f, +130954.7656250000f, 130980.1250000000f, 131005.4921875000f, 131030.8593750000f, +131056.2265625000f, 131081.5937500000f, 131106.9687500000f, 131132.3437500000f, +131157.7187500000f, 131183.0937500000f, 131208.4687500000f, 131233.8437500000f, +131259.2187500000f, 131284.5937500000f, 131309.9843750000f, 131335.3593750000f, +131360.7500000000f, 131386.1250000000f, 131411.5156250000f, 131436.9062500000f, +131462.2968750000f, 131487.6875000000f, 131513.0781250000f, 131538.4687500000f, +131563.8593750000f, 131589.2500000000f, 131614.6406250000f, 131640.0468750000f, +131665.4375000000f, 131690.8437500000f, 131716.2343750000f, 131741.6406250000f, +131767.0468750000f, 131792.4531250000f, 131817.8593750000f, 131843.2656250000f, +131868.6718750000f, 131894.0781250000f, 131919.4843750000f, 131944.8906250000f, +131970.3125000000f, 131995.7187500000f, 132021.1406250000f, 132046.5468750000f, +132071.9687500000f, 132097.3906250000f, 132122.8125000000f, 132148.2343750000f, +132173.6562500000f, 132199.0781250000f, 132224.5000000000f, 132249.9218750000f, +132275.3593750000f, 132300.7812500000f, 132326.2187500000f, 132351.6406250000f, +132377.0781250000f, 132402.5156250000f, 132427.9375000000f, 132453.3750000000f, +132478.8125000000f, 132504.2500000000f, 132529.6875000000f, 132555.1406250000f, +132580.5781250000f, 132606.0156250000f, 132631.4687500000f, 132656.9062500000f, +132682.3593750000f, 132707.7968750000f, 132733.2500000000f, 132758.7031250000f, +132784.1562500000f, 132809.6093750000f, 132835.0625000000f, 132860.5156250000f, +132885.9687500000f, 132911.4375000000f, 132936.8906250000f, 132962.3437500000f, +132987.8125000000f, 133013.2656250000f, 133038.7343750000f, 133064.2031250000f, +133089.6718750000f, 133115.1406250000f, 133140.6093750000f, 133166.0781250000f, +133191.5468750000f, 133217.0156250000f, 133242.4843750000f, 133267.9687500000f, +133293.4375000000f, 133318.9218750000f, 133344.3906250000f, 133369.8750000000f, +133395.3593750000f, 133420.8437500000f, 133446.3281250000f, 133471.8125000000f, +133497.2968750000f, 133522.7812500000f, 133548.2656250000f, 133573.7500000000f, +133599.2500000000f, 133624.7343750000f, 133650.2343750000f, 133675.7343750000f, +133701.2187500000f, 133726.7187500000f, 133752.2187500000f, 133777.7187500000f, +133803.2187500000f, 133828.7187500000f, 133854.2187500000f, 133879.7187500000f, +133905.2343750000f, 133930.7343750000f, 133956.2500000000f, 133981.7500000000f, +134007.2656250000f, 134032.7812500000f, 134058.2812500000f, 134083.7968750000f, +134109.3125000000f, 134134.8281250000f, 134160.3437500000f, 134185.8593750000f, +134211.3906250000f, 134236.9062500000f, 134262.4218750000f, 134287.9531250000f, +134313.4843750000f, 134339.0000000000f, 134364.5312500000f, 134390.0625000000f, +134415.5937500000f, 134441.1250000000f, 134466.6562500000f, 134492.1875000000f, +134517.7187500000f, 134543.2500000000f, 134568.7968750000f, 134594.3281250000f, +134619.8593750000f, 134645.4062500000f, 134670.9531250000f, 134696.4843750000f, +134722.0312500000f, 134747.5781250000f, 134773.1250000000f, 134798.6718750000f, +134824.2187500000f, 134849.7656250000f, 134875.3281250000f, 134900.8750000000f, +134926.4375000000f, 134951.9843750000f, 134977.5468750000f, 135003.0937500000f, +135028.6562500000f, 135054.2187500000f, 135079.7812500000f, 135105.3437500000f, +135130.9062500000f, 135156.4687500000f, 135182.0312500000f, 135207.5937500000f, +135233.1718750000f, 135258.7343750000f, 135284.3125000000f, 135309.8750000000f, +135335.4531250000f, 135361.0312500000f, 135386.6093750000f, 135412.1718750000f, +135437.7500000000f, 135463.3437500000f, 135488.9218750000f, 135514.5000000000f, +135540.0781250000f, 135565.6718750000f, 135591.2500000000f, 135616.8437500000f, +135642.4218750000f, 135668.0156250000f, 135693.6093750000f, 135719.1875000000f, +135744.7812500000f, 135770.3750000000f, 135795.9687500000f, 135821.5625000000f, +135847.1718750000f, 135872.7656250000f, 135898.3593750000f, 135923.9687500000f, +135949.5625000000f, 135975.1718750000f, 136000.7812500000f, 136026.3750000000f, +136051.9843750000f, 136077.5937500000f, 136103.2031250000f, 136128.8125000000f, +136154.4218750000f, 136180.0468750000f, 136205.6562500000f, 136231.2656250000f, +136256.8906250000f, 136282.5000000000f, 136308.1250000000f, 136333.7343750000f, +136359.3593750000f, 136384.9843750000f, 136410.6093750000f, 136436.2343750000f, +136461.8593750000f, 136487.4843750000f, 136513.1093750000f, 136538.7500000000f, +136564.3750000000f, 136590.0000000000f, 136615.6406250000f, 136641.2812500000f, +136666.9062500000f, 136692.5468750000f, 136718.1875000000f, 136743.8281250000f, +136769.4687500000f, 136795.1093750000f, 136820.7500000000f, 136846.3906250000f, +136872.0468750000f, 136897.6875000000f, 136923.3281250000f, 136948.9843750000f, +136974.6250000000f, 137000.2812500000f, 137025.9375000000f, 137051.5937500000f, +137077.2500000000f, 137102.9062500000f, 137128.5625000000f, 137154.2187500000f, +137179.8750000000f, 137205.5312500000f, 137231.2031250000f, 137256.8593750000f, +137282.5312500000f, 137308.1875000000f, 137333.8593750000f, 137359.5312500000f, +137385.2031250000f, 137410.8750000000f, 137436.5468750000f, 137462.2187500000f, +137487.8906250000f, 137513.5625000000f, 137539.2343750000f, 137564.9218750000f, +137590.5937500000f, 137616.2812500000f, 137641.9531250000f, 137667.6406250000f, +137693.3281250000f, 137719.0000000000f, 137744.6875000000f, 137770.3750000000f, +137796.0625000000f, 137821.7656250000f, 137847.4531250000f, 137873.1406250000f, +137898.8281250000f, 137924.5312500000f, 137950.2187500000f, 137975.9218750000f, +138001.6250000000f, 138027.3125000000f, 138053.0156250000f, 138078.7187500000f, +138104.4218750000f, 138130.1250000000f, 138155.8281250000f, 138181.5468750000f, +138207.2500000000f, 138232.9531250000f, 138258.6718750000f, 138284.3750000000f, +138310.0937500000f, 138335.7968750000f, 138361.5156250000f, 138387.2343750000f, +138412.9531250000f, 138438.6718750000f, 138464.3906250000f, 138490.1093750000f, +138515.8281250000f, 138541.5468750000f, 138567.2812500000f, 138593.0000000000f, +138618.7343750000f, 138644.4531250000f, 138670.1875000000f, 138695.9218750000f, +138721.6562500000f, 138747.3750000000f, 138773.1093750000f, 138798.8437500000f, +138824.5937500000f, 138850.3281250000f, 138876.0625000000f, 138901.7968750000f, +138927.5468750000f, 138953.2812500000f, 138979.0312500000f, 139004.7812500000f, +139030.5156250000f, 139056.2656250000f, 139082.0156250000f, 139107.7656250000f, +139133.5156250000f, 139159.2656250000f, 139185.0156250000f, 139210.7812500000f, +139236.5312500000f, 139262.2812500000f, 139288.0468750000f, 139313.7968750000f, +139339.5625000000f, 139365.3281250000f, 139391.0937500000f, 139416.8437500000f, +139442.6093750000f, 139468.3750000000f, 139494.1562500000f, 139519.9218750000f, +139545.6875000000f, 139571.4531250000f, 139597.2343750000f, 139623.0000000000f, +139648.7812500000f, 139674.5468750000f, 139700.3281250000f, 139726.1093750000f, +139751.8906250000f, 139777.6718750000f, 139803.4531250000f, 139829.2343750000f, +139855.0156250000f, 139880.7968750000f, 139906.5937500000f, 139932.3750000000f, +139958.1562500000f, 139983.9531250000f, 140009.7500000000f, 140035.5312500000f, +140061.3281250000f, 140087.1250000000f, 140112.9218750000f, 140138.7187500000f, +140164.5156250000f, 140190.3125000000f, 140216.1093750000f, 140241.9218750000f, +140267.7187500000f, 140293.5156250000f, 140319.3281250000f, 140345.1406250000f, +140370.9375000000f, 140396.7500000000f, 140422.5625000000f, 140448.3750000000f, +140474.1875000000f, 140500.0000000000f, 140525.8125000000f, 140551.6250000000f, +140577.4531250000f, 140603.2656250000f, 140629.0781250000f, 140654.9062500000f, +140680.7187500000f, 140706.5468750000f, 140732.3750000000f, 140758.2031250000f, +140784.0312500000f, 140809.8593750000f, 140835.6875000000f, 140861.5156250000f, +140887.3437500000f, 140913.1718750000f, 140939.0156250000f, 140964.8437500000f, +140990.6875000000f, 141016.5156250000f, 141042.3593750000f, 141068.2031250000f, +141094.0312500000f, 141119.8750000000f, 141145.7187500000f, 141171.5625000000f, +141197.4062500000f, 141223.2656250000f, 141249.1093750000f, 141274.9531250000f, +141300.8125000000f, 141326.6562500000f, 141352.5156250000f, 141378.3593750000f, +141404.2187500000f, 141430.0781250000f, 141455.9375000000f, 141481.7968750000f, +141507.6562500000f, 141533.5156250000f, 141559.3750000000f, 141585.2343750000f, +141611.0937500000f, 141636.9687500000f, 141662.8281250000f, 141688.7031250000f, +141714.5781250000f, 141740.4375000000f, 141766.3125000000f, 141792.1875000000f, +141818.0625000000f, 141843.9375000000f, 141869.8125000000f, 141895.6875000000f, +141921.5625000000f, 141947.4531250000f, 141973.3281250000f, 141999.2031250000f, +142025.0937500000f, 142050.9843750000f, 142076.8593750000f, 142102.7500000000f, +142128.6406250000f, 142154.5312500000f, 142180.4218750000f, 142206.3125000000f, +142232.2031250000f, 142258.0937500000f, 142283.9843750000f, 142309.8906250000f, +142335.7812500000f, 142361.6875000000f, 142387.5781250000f, 142413.4843750000f, +142439.3906250000f, 142465.2968750000f, 142491.1875000000f, 142517.0937500000f, +142543.0000000000f, 142568.9218750000f, 142594.8281250000f, 142620.7343750000f, +142646.6406250000f, 142672.5625000000f, 142698.4687500000f, 142724.3906250000f, +142750.3125000000f, 142776.2187500000f, 142802.1406250000f, 142828.0625000000f, +142853.9843750000f, 142879.9062500000f, 142905.8281250000f, 142931.7500000000f, +142957.6718750000f, 142983.6093750000f, 143009.5312500000f, 143035.4687500000f, +143061.3906250000f, 143087.3281250000f, 143113.2500000000f, 143139.1875000000f, +143165.1250000000f, 143191.0625000000f, 143217.0000000000f, 143242.9375000000f, +143268.8750000000f, 143294.8125000000f, 143320.7656250000f, 143346.7031250000f, +143372.6562500000f, 143398.5937500000f, 143424.5468750000f, 143450.4843750000f, +143476.4375000000f, 143502.3906250000f, 143528.3437500000f, 143554.2968750000f, +143580.2500000000f, 143606.2031250000f, 143632.1562500000f, 143658.1093750000f, +143684.0781250000f, 143710.0312500000f, 143736.0000000000f, 143761.9531250000f, +143787.9218750000f, 143813.8906250000f, 143839.8437500000f, 143865.8125000000f, +143891.7812500000f, 143917.7500000000f, 143943.7187500000f, 143969.7031250000f, +143995.6718750000f, 144021.6406250000f, 144047.6250000000f, 144073.5937500000f, +144099.5781250000f, 144125.5468750000f, 144151.5312500000f, 144177.5156250000f, +144203.5000000000f, 144229.4687500000f, 144255.4531250000f, 144281.4531250000f, +144307.4375000000f, 144333.4218750000f, 144359.4062500000f, 144385.4062500000f, +144411.3906250000f, 144437.3906250000f, 144463.3750000000f, 144489.3750000000f, +144515.3750000000f, 144541.3593750000f, 144567.3593750000f, 144593.3593750000f, +144619.3593750000f, 144645.3593750000f, 144671.3750000000f, 144697.3750000000f, +144723.3750000000f, 144749.3906250000f, 144775.3906250000f, 144801.4062500000f, +144827.4062500000f, 144853.4218750000f, 144879.4375000000f, 144905.4531250000f, +144931.4687500000f, 144957.4843750000f, 144983.5000000000f, 145009.5156250000f, +145035.5312500000f, 145061.5625000000f, 145087.5781250000f, 145113.5937500000f, +145139.6250000000f, 145165.6562500000f, 145191.6718750000f, 145217.7031250000f, +145243.7343750000f, 145269.7656250000f, 145295.7968750000f, 145321.8281250000f, +145347.8593750000f, 145373.8906250000f, 145399.9218750000f, 145425.9687500000f, +145452.0000000000f, 145478.0468750000f, 145504.0781250000f, 145530.1250000000f, +145556.1718750000f, 145582.2187500000f, 145608.2500000000f, 145634.2968750000f, +145660.3437500000f, 145686.4062500000f, 145712.4531250000f, 145738.5000000000f, +145764.5468750000f, 145790.6093750000f, 145816.6562500000f, 145842.7187500000f, +145868.7656250000f, 145894.8281250000f, 145920.8906250000f, 145946.9531250000f, +145973.0156250000f, 145999.0781250000f, 146025.1406250000f, 146051.2031250000f, +146077.2656250000f, 146103.3281250000f, 146129.4062500000f, 146155.4687500000f, +146181.5468750000f, 146207.6093750000f, 146233.6875000000f, 146259.7656250000f, +146285.8437500000f, 146311.9218750000f, 146338.0000000000f, 146364.0781250000f, +146390.1562500000f, 146416.2343750000f, 146442.3125000000f, 146468.3906250000f, +146494.4843750000f, 146520.5625000000f, 146546.6562500000f, 146572.7500000000f, +146598.8281250000f, 146624.9218750000f, 146651.0156250000f, 146677.1093750000f, +146703.2031250000f, 146729.2968750000f, 146755.3906250000f, 146781.4843750000f, +146807.5937500000f, 146833.6875000000f, 146859.7968750000f, 146885.8906250000f, +146912.0000000000f, 146938.0937500000f, 146964.2031250000f, 146990.3125000000f, +147016.4218750000f, 147042.5312500000f, 147068.6406250000f, 147094.7500000000f, +147120.8593750000f, 147146.9687500000f, 147173.0937500000f, 147199.2031250000f, +147225.3281250000f, 147251.4375000000f, 147277.5625000000f, 147303.6875000000f, +147329.7968750000f, 147355.9218750000f, 147382.0468750000f, 147408.1718750000f, +147434.2968750000f, 147460.4218750000f, 147486.5625000000f, 147512.6875000000f, +147538.8125000000f, 147564.9531250000f, 147591.0781250000f, 147617.2187500000f, +147643.3593750000f, 147669.4843750000f, 147695.6250000000f, 147721.7656250000f, +147747.9062500000f, 147774.0468750000f, 147800.1875000000f, 147826.3281250000f, +147852.4843750000f, 147878.6250000000f, 147904.7812500000f, 147930.9218750000f, +147957.0781250000f, 147983.2187500000f, 148009.3750000000f, 148035.5312500000f, +148061.6875000000f, 148087.8437500000f, 148114.0000000000f, 148140.1562500000f, +148166.3125000000f, 148192.4687500000f, 148218.6250000000f, 148244.7968750000f, +148270.9531250000f, 148297.1250000000f, 148323.2812500000f, 148349.4531250000f, +148375.6250000000f, 148401.7968750000f, 148427.9531250000f, 148454.1250000000f, +148480.2968750000f, 148506.4843750000f, 148532.6562500000f, 148558.8281250000f, +148585.0000000000f, 148611.1875000000f, 148637.3593750000f, 148663.5468750000f, +148689.7187500000f, 148715.9062500000f, 148742.0937500000f, 148768.2812500000f, +148794.4687500000f, 148820.6562500000f, 148846.8437500000f, 148873.0312500000f, +148899.2187500000f, 148925.4218750000f, 148951.6093750000f, 148977.7968750000f, +149004.0000000000f, 149030.1875000000f, 149056.3906250000f, 149082.5937500000f, +149108.7968750000f, 149135.0000000000f, 149161.2031250000f, 149187.4062500000f, +149213.6093750000f, 149239.8125000000f, 149266.0156250000f, 149292.2187500000f, +149318.4375000000f, 149344.6406250000f, 149370.8593750000f, 149397.0625000000f, +149423.2812500000f, 149449.5000000000f, 149475.7187500000f, 149501.9375000000f, +149528.1562500000f, 149554.3750000000f, 149580.5937500000f, 149606.8125000000f, +149633.0312500000f, 149659.2656250000f, 149685.4843750000f, 149711.7187500000f, +149737.9375000000f, 149764.1718750000f, 149790.4062500000f, 149816.6250000000f, +149842.8593750000f, 149869.0937500000f, 149895.3281250000f, 149921.5625000000f, +149947.8125000000f, 149974.0468750000f, 150000.2812500000f, 150026.5312500000f, +150052.7656250000f, 150079.0156250000f, 150105.2500000000f, 150131.5000000000f, +150157.7500000000f, 150183.9843750000f, 150210.2343750000f, 150236.4843750000f, +150262.7343750000f, 150288.9843750000f, 150315.2500000000f, 150341.5000000000f, +150367.7500000000f, 150394.0156250000f, 150420.2656250000f, 150446.5312500000f, +150472.7812500000f, 150499.0468750000f, 150525.3125000000f, 150551.5781250000f, +150577.8437500000f, 150604.1093750000f, 150630.3750000000f, 150656.6406250000f, +150682.9062500000f, 150709.1718750000f, 150735.4531250000f, 150761.7187500000f, +150788.0000000000f, 150814.2656250000f, 150840.5468750000f, 150866.8281250000f, +150893.1093750000f, 150919.3750000000f, 150945.6562500000f, 150971.9375000000f, +150998.2343750000f, 151024.5156250000f, 151050.7968750000f, 151077.0781250000f, +151103.3750000000f, 151129.6562500000f, 151155.9531250000f, 151182.2343750000f, +151208.5312500000f, 151234.8281250000f, 151261.1250000000f, 151287.4062500000f, +151313.7031250000f, 151340.0000000000f, 151366.3125000000f, 151392.6093750000f, +151418.9062500000f, 151445.2031250000f, 151471.5156250000f, 151497.8125000000f, +151524.1250000000f, 151550.4375000000f, 151576.7343750000f, 151603.0468750000f, +151629.3593750000f, 151655.6718750000f, 151681.9843750000f, 151708.2968750000f, +151734.6093750000f, 151760.9218750000f, 151787.2500000000f, 151813.5625000000f, +151839.8750000000f, 151866.2031250000f, 151892.5156250000f, 151918.8437500000f, +151945.1718750000f, 151971.5000000000f, 151997.8281250000f, 152024.1562500000f, +152050.4843750000f, 152076.8125000000f, 152103.1406250000f, 152129.4687500000f, +152155.7968750000f, 152182.1406250000f, 152208.4687500000f, 152234.8125000000f, +152261.1406250000f, 152287.4843750000f, 152313.8281250000f, 152340.1718750000f, +152366.5156250000f, 152392.8593750000f, 152419.2031250000f, 152445.5468750000f, +152471.8906250000f, 152498.2343750000f, 152524.5781250000f, 152550.9375000000f, +152577.2812500000f, 152603.6406250000f, 152630.0000000000f, 152656.3437500000f, +152682.7031250000f, 152709.0625000000f, 152735.4218750000f, 152761.7812500000f, +152788.1406250000f, 152814.5000000000f, 152840.8593750000f, 152867.2187500000f, +152893.5937500000f, 152919.9531250000f, 152946.3281250000f, 152972.6875000000f, +152999.0625000000f, 153025.4375000000f, 153051.8125000000f, 153078.1718750000f, +153104.5468750000f, 153130.9218750000f, 153157.2968750000f, 153183.6875000000f, +153210.0625000000f, 153236.4375000000f, 153262.8125000000f, 153289.2031250000f, +153315.5781250000f, 153341.9687500000f, 153368.3593750000f, 153394.7343750000f, +153421.1250000000f, 153447.5156250000f, 153473.9062500000f, 153500.2968750000f, +153526.6875000000f, 153553.0781250000f, 153579.4843750000f, 153605.8750000000f, +153632.2656250000f, 153658.6718750000f, 153685.0625000000f, 153711.4687500000f, +153737.8750000000f, 153764.2656250000f, 153790.6718750000f, 153817.0781250000f, +153843.4843750000f, 153869.8906250000f, 153896.2968750000f, 153922.7031250000f, +153949.1250000000f, 153975.5312500000f, 154001.9375000000f, 154028.3593750000f, +154054.7656250000f, 154081.1875000000f, 154107.6093750000f, 154134.0312500000f, +154160.4375000000f, 154186.8593750000f, 154213.2812500000f, 154239.7031250000f, +154266.1250000000f, 154292.5625000000f, 154318.9843750000f, 154345.4062500000f, +154371.8437500000f, 154398.2656250000f, 154424.7031250000f, 154451.1250000000f, +154477.5625000000f, 154504.0000000000f, 154530.4375000000f, 154556.8750000000f, +154583.3125000000f, 154609.7500000000f, 154636.1875000000f, 154662.6250000000f, +154689.0625000000f, 154715.5156250000f, 154741.9531250000f, 154768.4062500000f, +154794.8437500000f, 154821.2968750000f, 154847.7500000000f, 154874.1875000000f, +154900.6406250000f, 154927.0937500000f, 154953.5468750000f, 154980.0000000000f, +155006.4687500000f, 155032.9218750000f, 155059.3750000000f, 155085.8281250000f, +155112.2968750000f, 155138.7500000000f, 155165.2187500000f, 155191.6875000000f, +155218.1406250000f, 155244.6093750000f, 155271.0781250000f, 155297.5468750000f, +155324.0156250000f, 155350.4843750000f, 155376.9531250000f, 155403.4375000000f, +155429.9062500000f, 155456.3750000000f, 155482.8593750000f, 155509.3281250000f, +155535.8125000000f, 155562.2812500000f, 155588.7656250000f, 155615.2500000000f, +155641.7343750000f, 155668.2187500000f, 155694.7031250000f, 155721.1875000000f, +155747.6718750000f, 155774.1562500000f, 155800.6562500000f, 155827.1406250000f, +155853.6406250000f, 155880.1250000000f, 155906.6250000000f, 155933.1093750000f, +155959.6093750000f, 155986.1093750000f, 156012.6093750000f, 156039.1093750000f, +156065.6093750000f, 156092.1093750000f, 156118.6093750000f, 156145.1093750000f, +156171.6250000000f, 156198.1250000000f, 156224.6406250000f, 156251.1406250000f, +156277.6562500000f, 156304.1718750000f, 156330.6718750000f, 156357.1875000000f, +156383.7031250000f, 156410.2187500000f, 156436.7343750000f, 156463.2500000000f, +156489.7656250000f, 156516.2968750000f, 156542.8125000000f, 156569.3281250000f, +156595.8593750000f, 156622.3750000000f, 156648.9062500000f, 156675.4375000000f, +156701.9687500000f, 156728.4843750000f, 156755.0156250000f, 156781.5468750000f, +156808.0781250000f, 156834.6093750000f, 156861.1562500000f, 156887.6875000000f, +156914.2187500000f, 156940.7656250000f, 156967.2968750000f, 156993.8437500000f, +157020.3750000000f, 157046.9218750000f, 157073.4687500000f, 157100.0156250000f, +157126.5625000000f, 157153.1093750000f, 157179.6562500000f, 157206.2031250000f, +157232.7500000000f, 157259.2968750000f, 157285.8593750000f, 157312.4062500000f, +157338.9531250000f, 157365.5156250000f, 157392.0781250000f, 157418.6250000000f, +157445.1875000000f, 157471.7500000000f, 157498.3125000000f, 157524.8750000000f, +157551.4375000000f, 157578.0000000000f, 157604.5625000000f, 157631.1406250000f, +157657.7031250000f, 157684.2656250000f, 157710.8437500000f, 157737.4062500000f, +157763.9843750000f, 157790.5625000000f, 157817.1406250000f, 157843.7031250000f, +157870.2812500000f, 157896.8593750000f, 157923.4375000000f, 157950.0312500000f, +157976.6093750000f, 158003.1875000000f, 158029.7656250000f, 158056.3593750000f, +158082.9375000000f, 158109.5312500000f, 158136.1250000000f, 158162.7031250000f, +158189.2968750000f, 158215.8906250000f, 158242.4843750000f, 158269.0781250000f, +158295.6718750000f, 158322.2656250000f, 158348.8593750000f, 158375.4531250000f, +158402.0625000000f, 158428.6562500000f, 158455.2656250000f, 158481.8593750000f, +158508.4687500000f, 158535.0781250000f, 158561.6718750000f, 158588.2812500000f, +158614.8906250000f, 158641.5000000000f, 158668.1093750000f, 158694.7187500000f, +158721.3437500000f, 158747.9531250000f, 158774.5625000000f, 158801.1875000000f, +158827.7968750000f, 158854.4218750000f, 158881.0312500000f, 158907.6562500000f, +158934.2812500000f, 158960.9062500000f, 158987.5312500000f, 159014.1562500000f, +159040.7812500000f, 159067.4062500000f, 159094.0312500000f, 159120.6562500000f, +159147.2968750000f, 159173.9218750000f, 159200.5625000000f, 159227.1875000000f, +159253.8281250000f, 159280.4687500000f, 159307.0937500000f, 159333.7343750000f, +159360.3750000000f, 159387.0156250000f, 159413.6562500000f, 159440.2968750000f, +159466.9531250000f, 159493.5937500000f, 159520.2343750000f, 159546.8906250000f, +159573.5312500000f, 159600.1875000000f, 159626.8281250000f, 159653.4843750000f, +159680.1406250000f, 159706.7968750000f, 159733.4531250000f, 159760.1093750000f, +159786.7656250000f, 159813.4218750000f, 159840.0781250000f, 159866.7343750000f, +159893.4062500000f, 159920.0625000000f, 159946.7187500000f, 159973.3906250000f, +160000.0625000000f, 160026.7187500000f, 160053.3906250000f, 160080.0625000000f, +160106.7343750000f, 160133.4062500000f, 160160.0781250000f, 160186.7500000000f, +160213.4218750000f, 160240.1093750000f, 160266.7812500000f, 160293.4531250000f, +160320.1406250000f, 160346.8125000000f, 160373.5000000000f, 160400.1875000000f, +160426.8593750000f, 160453.5468750000f, 160480.2343750000f, 160506.9218750000f, +160533.6093750000f, 160560.2968750000f, 160587.0000000000f, 160613.6875000000f, +160640.3750000000f, 160667.0781250000f, 160693.7656250000f, 160720.4687500000f, +160747.1562500000f, 160773.8593750000f, 160800.5625000000f, 160827.2500000000f, +160853.9531250000f, 160880.6562500000f, 160907.3593750000f, 160934.0781250000f, +160960.7812500000f, 160987.4843750000f, 161014.1875000000f, 161040.9062500000f, +161067.6093750000f, 161094.3281250000f, 161121.0312500000f, 161147.7500000000f, +161174.4687500000f, 161201.1875000000f, 161227.8906250000f, 161254.6093750000f, +161281.3281250000f, 161308.0625000000f, 161334.7812500000f, 161361.5000000000f, +161388.2187500000f, 161414.9531250000f, 161441.6718750000f, 161468.4062500000f, +161495.1250000000f, 161521.8593750000f, 161548.5937500000f, 161575.3281250000f, +161602.0468750000f, 161628.7812500000f, 161655.5156250000f, 161682.2656250000f, +161709.0000000000f, 161735.7343750000f, 161762.4687500000f, 161789.2187500000f, +161815.9531250000f, 161842.7031250000f, 161869.4375000000f, 161896.1875000000f, +161922.9375000000f, 161949.6718750000f, 161976.4218750000f, 162003.1718750000f, +162029.9218750000f, 162056.6718750000f, 162083.4375000000f, 162110.1875000000f, +162136.9375000000f, 162163.6875000000f, 162190.4531250000f, 162217.2031250000f, +162243.9687500000f, 162270.7343750000f, 162297.4843750000f, 162324.2500000000f, +162351.0156250000f, 162377.7812500000f, 162404.5468750000f, 162431.3125000000f, +162458.0781250000f, 162484.8437500000f, 162511.6250000000f, 162538.3906250000f, +162565.1718750000f, 162591.9375000000f, 162618.7187500000f, 162645.4843750000f, +162672.2656250000f, 162699.0468750000f, 162725.8281250000f, 162752.6093750000f, +162779.3906250000f, 162806.1718750000f, 162832.9531250000f, 162859.7343750000f, +162886.5156250000f, 162913.3125000000f, 162940.0937500000f, 162966.8750000000f, +162993.6718750000f, 163020.4687500000f, 163047.2500000000f, 163074.0468750000f, +163100.8437500000f, 163127.6406250000f, 163154.4375000000f, 163181.2343750000f, +163208.0312500000f, 163234.8281250000f, 163261.6250000000f, 163288.4375000000f, +163315.2343750000f, 163342.0468750000f, 163368.8437500000f, 163395.6562500000f, +163422.4687500000f, 163449.2656250000f, 163476.0781250000f, 163502.8906250000f, +163529.7031250000f, 163556.5156250000f, 163583.3281250000f, 163610.1406250000f, +163636.9687500000f, 163663.7812500000f, 163690.5937500000f, 163717.4218750000f, +163744.2343750000f, 163771.0625000000f, 163797.8906250000f, 163824.7031250000f, +163851.5312500000f, 163878.3593750000f, 163905.1875000000f, 163932.0156250000f, +163958.8437500000f, 163985.6718750000f, 164012.5000000000f, 164039.3437500000f, +164066.1718750000f, 164093.0156250000f, 164119.8437500000f, 164146.6875000000f, +164173.5156250000f, 164200.3593750000f, 164227.2031250000f, 164254.0468750000f, +164280.8906250000f, 164307.7343750000f, 164334.5781250000f, 164361.4218750000f, +164388.2656250000f, 164415.1093750000f, 164441.9687500000f, 164468.8125000000f, +164495.6718750000f, 164522.5156250000f, 164549.3750000000f, 164576.2343750000f, +164603.0781250000f, 164629.9375000000f, 164656.7968750000f, 164683.6562500000f, +164710.5156250000f, 164737.3750000000f, 164764.2343750000f, 164791.1093750000f, +164817.9687500000f, 164844.8281250000f, 164871.7031250000f, 164898.5781250000f, +164925.4375000000f, 164952.3125000000f, 164979.1875000000f, 165006.0468750000f, +165032.9218750000f, 165059.7968750000f, 165086.6718750000f, 165113.5468750000f, +165140.4375000000f, 165167.3125000000f, 165194.1875000000f, 165221.0781250000f, +165247.9531250000f, 165274.8437500000f, 165301.7187500000f, 165328.6093750000f, +165355.5000000000f, 165382.3750000000f, 165409.2656250000f, 165436.1562500000f, +165463.0468750000f, 165489.9375000000f, 165516.8281250000f +}; + +/* pre-calculated cos() in steps of PI/72, PI/18 and PI/24 for MDCT calcs. + * The Octave formula to generate each table is given in the comment above it */ +/* 0.5 ./ cos ((1:12) .* pi/24) */ +static const gfloat cos24_table[] = { + 5.043144802900764167574720886477734893560409545898437500000000e-01f, + 5.176380902050414789528076653368771076202392578125000000000000e-01f, + 5.411961001461970122150546558259520679712295532226562500000000e-01f, + 5.773502691896257310588680411456152796745300292968750000000000e-01f, + 6.302362070051322762154200063378084450960159301757812500000000e-01f, + 7.071067811865474617150084668537601828575134277343750000000000e-01f, + 8.213398158522907666068135768000502139329910278320312500000000e-01f, + 9.999999999999997779553950749686919152736663818359375000000000e-01f, + 1.306562964876376353728915091778617352247238159179687500000000e+00f, + 1.931851652578136846472034449107013642787933349609375000000000e+00f, + 3.830648787770190910606515899416990578174591064453125000000000e+00f, + 8.165889364191922000000000000000000000000000000000000000000000e+15f +}; + +/* cos ((0:8) .* pi/18) */ +static const gfloat cos18_table[] = { + 1.000000000000000000000000000000000000000000000000000000000000e+00f, + 9.848077530122080203156542665965389460325241088867187500000000e-01f, + 9.396926207859084279050421173451468348503112792968750000000000e-01f, + 8.660254037844387076106045242340769618749618530273437500000000e-01f, + 7.660444431189780134516809084743726998567581176757812500000000e-01f, + 6.427876096865393629187224178167525678873062133789062500000000e-01f, + 5.000000000000001110223024625156540423631668090820312500000000e-01f, + 3.420201433256688239303855425532674416899681091308593750000000e-01f, + 1.736481776669304144533612088707741349935531616210937500000000e-01f +}; + +/* 0.5 ./ cos ((1:35) .* pi/72)) */ +static const gfloat icos72_table[] = { + 5.004763425816599609063928255636710673570632934570312500000000e-01f, + 5.019099187716736798492433990759309381246566772460937500000000e-01f, + 5.043144802900764167574720886477734893560409545898437500000000e-01f, + 5.077133059428725614381505693017970770597457885742187500000000e-01f, + 5.121397571572545714957414020318537950515747070312500000000000e-01f, + 5.176380902050414789528076653368771076202392578125000000000000e-01f, + 5.242645625704053236049162478593643754720687866210937500000000e-01f, + 5.320888862379560269033618169487453997135162353515625000000000e-01f, + 5.411961001461970122150546558259520679712295532226562500000000e-01f, + 5.516889594812458552652856269560288637876510620117187500000000e-01f, + 5.636909734331712051869089918909594416618347167968750000000000e-01f, + 5.773502691896257310588680411456152796745300292968750000000000e-01f, + 5.928445237170802961657045671017840504646301269531250000000000e-01f, + 6.103872943807280293526673631276935338973999023437500000000000e-01f, + 6.302362070051321651931175438221544027328491210937500000000000e-01f, + 6.527036446661392821155800447741057723760604858398437500000000e-01f, + 6.781708524546284921896699415810871869325637817382812500000000e-01f, + 7.071067811865474617150084668537601828575134277343750000000000e-01f, + 7.400936164611303658134033867099788039922714233398437500000000e-01f, + 7.778619134302061643992942663317080587148666381835937500000000e-01f, + 8.213398158522907666068135768000502139329910278320312500000000e-01f, + 8.717233978105488612087015098950359970331192016601562500000000e-01f, + 9.305794983517888807611484480730723589658737182617187500000000e-01f, + 9.999999999999997779553950749686919152736663818359375000000000e-01f, + 1.082840285100100219395358180918265134096145629882812500000000e+00f, + 1.183100791576249255498964885191526263952255249023437500000000e+00f, + 1.306562964876376353728915091778617352247238159179687500000000e+00f, + 1.461902200081543146126250576344318687915802001953125000000000e+00f, + 1.662754761711521034328598034335300326347351074218750000000000e+00f, + 1.931851652578135070115195048856548964977264404296875000000000e+00f, + 2.310113157672649020213384574162773787975311279296875000000000e+00f, + 2.879385241571815523542454684502445161342620849609375000000000e+00f, + 3.830648787770197127855453800293616950511932373046875000000000e+00f, + 5.736856622834929808618653623852878808975219726562500000000000e+00f, + 1.146279281302667207853573927422985434532165527343750000000000e+01f +}; + +static const gfloat mdct_swin[4][36] = { + { + 0.0436193869f, 0.1305261850f, 0.2164396197f, 0.3007057905f, 0.3826834261f, 0.4617486000f, + 0.5372996330f, 0.6087614298f, 0.6755902171f, 0.7372773290f, 0.7933533192f, 0.8433914185f, + 0.8870108128f, 0.9238795042f, 0.9537169337f, 0.9762960076f, 0.9914448857f, 0.9990482330f, + 0.9990482330f, 0.9914448857f, 0.9762960076f, 0.9537169337f, 0.9238795042f, 0.8870108128f, + 0.8433914185f, 0.7933533192f, 0.7372773290f, 0.6755902171f, 0.6087614298f, 0.5372996330f, + 0.4617486000f, 0.3826834261f, 0.3007057905f, 0.2164396197f, 0.1305261850f, 0.0436193869f, + }, { + 0.0436193869f, 0.1305261850f, 0.2164396197f, 0.3007057905f, 0.3826834261f, 0.4617486000f, + 0.5372996330f, 0.6087614298f, 0.6755902171f, 0.7372773290f, 0.7933533192f, 0.8433914185f, + 0.8870108128f, 0.9238795042f, 0.9537169337f, 0.9762960076f, 0.9914448857f, 0.9990482330f, + 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, + 0.9914448857f, 0.9238795042f, 0.7933533192f, 0.6087614298f, 0.3826834261f, 0.1305261850f, + 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, + }, { + 0.1305261850f, 0.3826834261f, 0.6087614298f, 0.7933533192f, 0.9238795042f, 0.9914448857f, + 0.9914448857f, 0.9238795042f, 0.7933533192f, 0.6087614298f, 0.3826834261f, 0.1305261850f, + 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, + 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, + 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, + 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, + }, { + 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, 0.0000000000f, + 0.1305261850f, 0.3826834261f, 0.6087614298f, 0.7933533192f, 0.9238795042f, 0.9914448857f, + 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, 1.0000000000f, + 0.9990482330f, 0.9914448857f, 0.9762960076f, 0.9537169337f, 0.9238795042f, 0.8870108128f, + 0.8433914185f, 0.7933533192f, 0.7372773290f, 0.6755902171f, 0.6087614298f, 0.5372996330f, + 0.4617486000f, 0.3826834261f, 0.3007057905f, 0.2164396197f, 0.1305261850f, 0.0436193869f, + } +}; + +/* pre-calculated table for (1.0 / (2.0 * cos ((2*i+1) * (M_PI / (64))))) + * for i 0:31 */ +/* 0.5 ./ cos (((2 .* 0:30)+1) .* pi/64) */ +/* NOTE: The table is already offset by pi/64 at index 0, by the +1 term + ie, index x yields 0.5 / cos ((2*x+1) * pi/64) */ +static const gfloat synth_cos64_table[] = { + 5.0060299823519627260e-01f, 5.0241928618815567820e-01f, + 5.0547095989754364798e-01f, 5.0979557910415917998e-01f, + 5.1544730992262455249e-01f, 5.2249861493968885462e-01f, + 5.3104259108978413284e-01f, 5.4119610014619701222e-01f, + 5.5310389603444454210e-01f, 5.6694403481635768927e-01f, + 5.8293496820613388554e-01f, 6.0134488693504528634e-01f, + 6.2250412303566482475e-01f, 6.4682178335999007679e-01f, + 6.7480834145500567800e-01f, 7.0710678118654746172e-01f, + 7.4453627100229857749e-01f, 7.8815462345125020249e-01f, + 8.3934964541552681272e-01f, 8.9997622313641556513e-01f, + 9.7256823786196078263e-01f, 1.0606776859903470633e+00f, + 1.1694399334328846596e+00f, 1.3065629648763763537e+00f, + 1.4841646163141661852e+00f, 1.7224470982383341955e+00f, + 2.0577810099534108446e+00f, 2.5629154477415054814e+00f, + 3.4076084184687189804e+00f, 5.1011486186891552563e+00f, + 1.0190008123548032870e+01f +}; + +static __CACHE_LINE_DECL_ALIGN(const gfloat dewindow[512]) = { + 0.000000000f, -0.000015259f, -0.000015259f, -0.000015259f, + -0.000015259f, -0.000015259f, -0.000015259f, -0.000030518f, + -0.000030518f, -0.000030518f, -0.000030518f, -0.000045776f, + -0.000045776f, -0.000061035f, -0.000061035f, -0.000076294f, + -0.000076294f, -0.000091553f, -0.000106812f, -0.000106812f, + -0.000122070f, -0.000137329f, -0.000152588f, -0.000167847f, + -0.000198364f, -0.000213623f, -0.000244141f, -0.000259399f, + -0.000289917f, -0.000320435f, -0.000366211f, -0.000396729f, + -0.000442505f, -0.000473022f, -0.000534058f, -0.000579834f, + -0.000625610f, -0.000686646f, -0.000747681f, -0.000808716f, + -0.000885010f, -0.000961304f, -0.001037598f, -0.001113892f, + -0.001205444f, -0.001296997f, -0.001388550f, -0.001480103f, + -0.001586914f, -0.001693726f, -0.001785278f, -0.001907349f, + -0.002014160f, -0.002120972f, -0.002243042f, -0.002349854f, + -0.002456665f, -0.002578735f, -0.002685547f, -0.002792358f, + -0.002899170f, -0.002990723f, -0.003082275f, -0.003173828f, + 0.003250122f, 0.003326416f, 0.003387451f, 0.003433228f, + 0.003463745f, 0.003479004f, 0.003479004f, 0.003463745f, + 0.003417969f, 0.003372192f, 0.003280640f, 0.003173828f, + 0.003051758f, 0.002883911f, 0.002700806f, 0.002487183f, + 0.002227783f, 0.001937866f, 0.001617432f, 0.001266479f, + 0.000869751f, 0.000442505f, -0.000030518f, -0.000549316f, + -0.001098633f, -0.001693726f, -0.002334595f, -0.003005981f, + -0.003723145f, -0.004486084f, -0.005294800f, -0.006118774f, + -0.007003784f, -0.007919312f, -0.008865356f, -0.009841919f, + -0.010848999f, -0.011886597f, -0.012939453f, -0.014022827f, + -0.015121460f, -0.016235352f, -0.017349243f, -0.018463135f, + -0.019577026f, -0.020690918f, -0.021789551f, -0.022857666f, + -0.023910522f, -0.024932861f, -0.025909424f, -0.026840210f, + -0.027725220f, -0.028533936f, -0.029281616f, -0.029937744f, + -0.030532837f, -0.031005859f, -0.031387329f, -0.031661987f, + -0.031814575f, -0.031845093f, -0.031738281f, -0.031478882f, + 0.031082153f, 0.030517578f, 0.029785156f, 0.028884888f, + 0.027801514f, 0.026535034f, 0.025085449f, 0.023422241f, + 0.021575928f, 0.019531250f, 0.017257690f, 0.014801025f, + 0.012115479f, 0.009231567f, 0.006134033f, 0.002822876f, + -0.000686646f, -0.004394531f, -0.008316040f, -0.012420654f, + -0.016708374f, -0.021179199f, -0.025817871f, -0.030609131f, + -0.035552979f, -0.040634155f, -0.045837402f, -0.051132202f, + -0.056533813f, -0.061996460f, -0.067520142f, -0.073059082f, + -0.078628540f, -0.084182739f, -0.089706421f, -0.095169067f, + -0.100540161f, -0.105819702f, -0.110946655f, -0.115921021f, + -0.120697021f, -0.125259399f, -0.129562378f, -0.133590698f, + -0.137298584f, -0.140670776f, -0.143676758f, -0.146255493f, + -0.148422241f, -0.150115967f, -0.151306152f, -0.151962280f, + -0.152069092f, -0.151596069f, -0.150497437f, -0.148773193f, + -0.146362305f, -0.143264771f, -0.139450073f, -0.134887695f, + -0.129577637f, -0.123474121f, -0.116577148f, -0.108856201f, + 0.100311279f, 0.090927124f, 0.080688477f, 0.069595337f, + 0.057617187f, 0.044784546f, 0.031082153f, 0.016510010f, + 0.001068115f, -0.015228271f, -0.032379150f, -0.050354004f, + -0.069168091f, -0.088775635f, -0.109161377f, -0.130310059f, + -0.152206421f, -0.174789429f, -0.198059082f, -0.221984863f, + -0.246505737f, -0.271591187f, -0.297210693f, -0.323318481f, + -0.349868774f, -0.376800537f, -0.404083252f, -0.431655884f, + -0.459472656f, -0.487472534f, -0.515609741f, -0.543823242f, + -0.572036743f, -0.600219727f, -0.628295898f, -0.656219482f, + -0.683914185f, -0.711318970f, -0.738372803f, -0.765029907f, + -0.791213989f, -0.816864014f, -0.841949463f, -0.866363525f, + -0.890090942f, -0.913055420f, -0.935195923f, -0.956481934f, + -0.976852417f, -0.996246338f, -1.014617920f, -1.031936646f, + -1.048156738f, -1.063217163f, -1.077117920f, -1.089782715f, + -1.101211548f, -1.111373901f, -1.120223999f, -1.127746582f, + -1.133926392f, -1.138763428f, -1.142211914f, -1.144287109f, + 1.144989014f, 1.144287109f, 1.142211914f, 1.138763428f, + 1.133926392f, 1.127746582f, 1.120223999f, 1.111373901f, + 1.101211548f, 1.089782715f, 1.077117920f, 1.063217163f, + 1.048156738f, 1.031936646f, 1.014617920f, 0.996246338f, + 0.976852417f, 0.956481934f, 0.935195923f, 0.913055420f, + 0.890090942f, 0.866363525f, 0.841949463f, 0.816864014f, + 0.791213989f, 0.765029907f, 0.738372803f, 0.711318970f, + 0.683914185f, 0.656219482f, 0.628295898f, 0.600219727f, + 0.572036743f, 0.543823242f, 0.515609741f, 0.487472534f, + 0.459472656f, 0.431655884f, 0.404083252f, 0.376800537f, + 0.349868774f, 0.323318481f, 0.297210693f, 0.271591187f, + 0.246505737f, 0.221984863f, 0.198059082f, 0.174789429f, + 0.152206421f, 0.130310059f, 0.109161377f, 0.088775635f, + 0.069168091f, 0.050354004f, 0.032379150f, 0.015228271f, + -0.001068115f, -0.016510010f, -0.031082153f, -0.044784546f, + -0.057617187f, -0.069595337f, -0.080688477f, -0.090927124f, + 0.100311279f, 0.108856201f, 0.116577148f, 0.123474121f, + 0.129577637f, 0.134887695f, 0.139450073f, 0.143264771f, + 0.146362305f, 0.148773193f, 0.150497437f, 0.151596069f, + 0.152069092f, 0.151962280f, 0.151306152f, 0.150115967f, + 0.148422241f, 0.146255493f, 0.143676758f, 0.140670776f, + 0.137298584f, 0.133590698f, 0.129562378f, 0.125259399f, + 0.120697021f, 0.115921021f, 0.110946655f, 0.105819702f, + 0.100540161f, 0.095169067f, 0.089706421f, 0.084182739f, + 0.078628540f, 0.073059082f, 0.067520142f, 0.061996460f, + 0.056533813f, 0.051132202f, 0.045837402f, 0.040634155f, + 0.035552979f, 0.030609131f, 0.025817871f, 0.021179199f, + 0.016708374f, 0.012420654f, 0.008316040f, 0.004394531f, + 0.000686646f, -0.002822876f, -0.006134033f, -0.009231567f, + -0.012115479f, -0.014801025f, -0.017257690f, -0.019531250f, + -0.021575928f, -0.023422241f, -0.025085449f, -0.026535034f, + -0.027801514f, -0.028884888f, -0.029785156f, -0.030517578f, + 0.031082153f, 0.031478882f, 0.031738281f, 0.031845093f, + 0.031814575f, 0.031661987f, 0.031387329f, 0.031005859f, + 0.030532837f, 0.029937744f, 0.029281616f, 0.028533936f, + 0.027725220f, 0.026840210f, 0.025909424f, 0.024932861f, + 0.023910522f, 0.022857666f, 0.021789551f, 0.020690918f, + 0.019577026f, 0.018463135f, 0.017349243f, 0.016235352f, + 0.015121460f, 0.014022827f, 0.012939453f, 0.011886597f, + 0.010848999f, 0.009841919f, 0.008865356f, 0.007919312f, + 0.007003784f, 0.006118774f, 0.005294800f, 0.004486084f, + 0.003723145f, 0.003005981f, 0.002334595f, 0.001693726f, + 0.001098633f, 0.000549316f, 0.000030518f, -0.000442505f, + -0.000869751f, -0.001266479f, -0.001617432f, -0.001937866f, + -0.002227783f, -0.002487183f, -0.002700806f, -0.002883911f, + -0.003051758f, -0.003173828f, -0.003280640f, -0.003372192f, + -0.003417969f, -0.003463745f, -0.003479004f, -0.003479004f, + -0.003463745f, -0.003433228f, -0.003387451f, -0.003326416f, + 0.003250122f, 0.003173828f, 0.003082275f, 0.002990723f, + 0.002899170f, 0.002792358f, 0.002685547f, 0.002578735f, + 0.002456665f, 0.002349854f, 0.002243042f, 0.002120972f, + 0.002014160f, 0.001907349f, 0.001785278f, 0.001693726f, + 0.001586914f, 0.001480103f, 0.001388550f, 0.001296997f, + 0.001205444f, 0.001113892f, 0.001037598f, 0.000961304f, + 0.000885010f, 0.000808716f, 0.000747681f, 0.000686646f, + 0.000625610f, 0.000579834f, 0.000534058f, 0.000473022f, + 0.000442505f, 0.000396729f, 0.000366211f, 0.000320435f, + 0.000289917f, 0.000259399f, 0.000244141f, 0.000213623f, + 0.000198364f, 0.000167847f, 0.000152588f, 0.000137329f, + 0.000122070f, 0.000106812f, 0.000106812f, 0.000091553f, + 0.000076294f, 0.000076294f, 0.000061035f, 0.000061035f, + 0.000045776f, 0.000045776f, 0.000030518f, 0.000030518f, + 0.000030518f, 0.000030518f, 0.000015259f, 0.000015259f, + 0.000015259f, 0.000015259f, 0.000015259f, 0.000015259f +}; + +/*********************************************************************** + * Use the header information to select the subband allocation table + **********************************************************************/ +static void +II_pick_table (frame_params * fr_ps) +{ + int table, ver, lay, bsp, br_per_ch, sfrq; + + ver = fr_ps->header.version; + lay = fr_ps->header.layer - 1; + bsp = fr_ps->header.bitrate_idx; + + /* decision rules refer to per-channel bitrates (kbits/sec/chan) */ + if (ver == MPEG_VERSION_1) { + br_per_ch = bitrates_v1[lay][bsp] / fr_ps->stereo; + + sfrq = s_rates[ver][fr_ps->header.srate_idx]; + + /* MPEG-1 */ + if ((sfrq == 48000 && br_per_ch >= 56) || + (br_per_ch >= 56 && br_per_ch <= 80)) + table = 0; + else if (sfrq != 48000 && br_per_ch >= 96) + table = 1; + else if (sfrq != 32000 && br_per_ch <= 48) + table = 2; + else + table = 3; + + } else { + /* br_per_ch = bitrates_v2[lay][bsp] / fr_ps->stereo; */ + + /* MPEG-2 LSF */ + table = 4; + } + + fr_ps->sblimit = ba_tables[table].sub_bands; + fr_ps->alloc = &ba_tables[table].alloc; +} + +static int +js_bound (gint lay, gint m_ext) +{ + /* layer + mode_ext -> jsbound */ + static const int jsb_table[3][4] = { + {4, 8, 12, 16}, {4, 8, 12, 16}, {0, 4, 8, 16} + }; + + if (lay < 1 || lay > 3 || m_ext < 0 || m_ext > 3) { + GST_WARNING ("js_bound bad layer/modext (%d/%d)\n", lay, m_ext); + return 0; + } + return (jsb_table[lay - 1][m_ext]); +} + +static void +hdr_to_frps (frame_params * fr_ps) +{ + fr_header *hdr = &fr_ps->header; + + fr_ps->actual_mode = hdr->mode; + fr_ps->stereo = (hdr->mode == MPG_MD_MONO) ? 1 : 2; + fr_ps->sblimit = SBLIMIT; + + if (hdr->mode == MPG_MD_JOINT_STEREO) + fr_ps->jsbound = js_bound (hdr->layer, hdr->mode_ext); + else + fr_ps->jsbound = fr_ps->sblimit; +} + +/***************************************************************************** +* +* CRC error protection package +* +*****************************************************************************/ +static void +update_CRC (const guint data, const guint length, guint * crc) +{ + unsigned int masking, carry; + + masking = 1 << length; + + while ((masking >>= 1)) { + carry = *crc & 0x8000; + *crc <<= 1; + if (!carry ^ !(data & masking)) + *crc ^= CRC16_POLYNOMIAL; + } + *crc &= 0xffff; +} + +static void +I_CRC_calc (const frame_params * fr_ps, guint bit_alloc[2][SBLIMIT], + guint * crc) +{ + gint i, k; + const fr_header *hdr = &fr_ps->header; + const gint stereo = fr_ps->stereo; + const gint jsbound = fr_ps->jsbound; + + *crc = 0xffff; /* changed from '0' 92-08-11 shn */ + update_CRC (hdr->bitrate_idx, 4, crc); + update_CRC (hdr->srate_idx, 2, crc); + update_CRC (hdr->padding, 1, crc); + update_CRC (hdr->extension, 1, crc); + update_CRC (hdr->mode, 2, crc); + update_CRC (hdr->mode_ext, 2, crc); + update_CRC (hdr->copyright, 1, crc); + update_CRC (hdr->original, 1, crc); + update_CRC (hdr->emphasis, 2, crc); + + for (i = 0; i < SBLIMIT; i++) + for (k = 0; k < ((i < jsbound) ? stereo : 1); k++) + update_CRC (bit_alloc[k][i], 4, crc); +} + +static void +II_CRC_calc (const frame_params * fr_ps, guint bit_alloc[2][SBLIMIT], + guint scfsi[2][SBLIMIT], guint * crc) +{ + gint i, k; + const fr_header *hdr = &fr_ps->header; + const gint stereo = fr_ps->stereo; + const gint sblimit = fr_ps->sblimit; + const gint jsbound = fr_ps->jsbound; + const al_table *alloc = fr_ps->alloc; + + *crc = 0xffff; /* changed from '0' 92-08-11 shn */ + + update_CRC (hdr->bitrate_idx, 4, crc); + update_CRC (hdr->srate_idx, 2, crc); + update_CRC (hdr->padding, 1, crc); + update_CRC (hdr->extension, 1, crc); + update_CRC (hdr->mode, 2, crc); + update_CRC (hdr->mode_ext, 2, crc); + update_CRC (hdr->copyright, 1, crc); + update_CRC (hdr->original, 1, crc); + update_CRC (hdr->emphasis, 2, crc); + + for (i = 0; i < sblimit; i++) + for (k = 0; k < ((i < jsbound) ? stereo : 1); k++) + update_CRC (bit_alloc[k][i], (*alloc)[i][0].bits, crc); + + for (i = 0; i < sblimit; i++) + for (k = 0; k < stereo; k++) + if (bit_alloc[k][i]) + update_CRC (scfsi[k][i], 2, crc); +} + +/* + * 2 Bitstream buffer implementations. 1 reading from a provided + * data pointer, the other from a fixed size ring buffer. + */ + +/* Create and initialise a new bitstream reader */ +Bit_stream_struc * +bs_new () +{ + Bit_stream_struc *bs; + + bs = (Bit_stream_struc *) calloc(1, sizeof(Bit_stream_struc)); + g_return_val_if_fail (bs != NULL, NULL); + + bs->master.cur_bit = 8; + bs->master.size = 0; + bs->master.cur_used = 0; + bs->read.cur_bit = 8; + bs->read.size = 0; + bs->read.cur_used = 0; + return bs; +} + +/* Release a bitstream reader */ +void +bs_free (Bit_stream_struc * bs) +{ + g_return_if_fail (bs != NULL); + + free (bs); +} + +/* Set data as the stream for processing */ +gboolean +bs_set_data (Bit_stream_struc * bs, const guint8 * data, gsize size) +{ + g_return_val_if_fail (bs != NULL, FALSE); + g_return_val_if_fail (data != NULL, FALSE); + g_return_val_if_fail (size != 0, FALSE); + + bs->master.data = data; + bs->master.cur_byte = (guint8 *) data; + bs->master.size = size; + bs->master.bitpos = 0; + bs->master.cur_used = 0; + bs_reset (bs); + return TRUE; +} + +/* Advance N bits on the indicated BSreader */ +static inline void +bs_eat (Bit_stream_struc * bs, BSReader * read, guint32 Nbits) +{ + while (Nbits > 0) { + gint k; + + /* Check for the data limit */ + if (read->cur_used >= read->size) { + return; + } + + if (Nbits < 8 || read->cur_bit != 8) { + /* Take as many bits as we can from the current byte */ + k = MIN (Nbits, read->cur_bit); + + /* Adjust our tracking vars */ + read->cur_bit -= k; + Nbits -= k; + read->bitpos += k; + + /* Move to the next byte if we consumed the current one */ + if (read->cur_bit == 0) { + read->cur_bit = 8; + read->cur_used++; + read->cur_byte++; + } + } else { + /* Take as many bytes as we can from current buffer */ + k = MIN (Nbits / 8, (guint32)(read->size - read->cur_used)); + + read->cur_used += k; + read->cur_byte += k; + + /* convert to bits */ + k *= 8; + read->bitpos += k; + Nbits -= k; + } + } +} + +/* Advance the master position by Nbits */ +void +bs_consume (Bit_stream_struc * bs, guint32 Nbits) +{ +#if 0 + static gint n = 0; + GST_DEBUG ("%d Consumed %d bits to end at %" G_GUINT64_FORMAT, + n++, Nbits, bs_pos (bs) + Nbits); +#endif + bs_eat (bs, &bs->master, Nbits); +} + +/* Advance the read position by Nbits */ +void +bs_skipbits (Bit_stream_struc * bs, guint32 Nbits) +{ + bs_eat (bs, &bs->read, Nbits); +} + +/* Advances the read position to the first bit of next frame or + * last byte in the buffer when the sync code is not found */ +gboolean +bs_seek_sync (Bit_stream_struc * bs) +{ + gboolean res = FALSE; + guint8 last_byte; + guint8 *start_pos; + + /* Align to the start of the next byte */ + if (bs->read.cur_bit != BS_BYTE_SIZE) { + bs->read.bitpos += (BS_BYTE_SIZE - bs->read.cur_bit); + bs->read.cur_bit = BS_BYTE_SIZE; + bs->read.cur_used++; + bs->read.cur_byte++; + } + + /* Ensure there's still some data to read */ + if (G_UNLIKELY (bs->read.cur_used >= bs->read.size)) { + return FALSE; + } + + start_pos = bs->read.cur_byte; + while (bs->read.cur_used < bs->read.size - 1) { + last_byte = bs->read.cur_byte[0]; + bs->read.cur_byte++; + bs->read.cur_used++; + + if (last_byte == 0xff && bs->read.cur_byte[0] >= 0xe0) { + /* Found a sync word */ + res = TRUE; + break; + } + } + /* Update the tracked position in the reader */ + bs->read.bitpos += BS_BYTE_SIZE * (bs->read.cur_byte - start_pos); + + if (res) { + /* Move past the first 3 bits of 2nd sync byte */ + bs->read.cur_bit = 5; + bs->read.bitpos += 3; + } + + return res; +} + +/* Extract N bytes from the bitstream into the out array. */ +void +bs_getbytes (Bit_stream_struc * bs, guint8 * out, guint32 N) +{ + gint j = N; + gint to_take; + + while (j > 0) { + /* Move to the next byte if we consumed any bits of the current one */ + if (bs->read.cur_bit != 8) { + bs->read.cur_bit = 8; + bs->read.cur_used++; + bs->read.cur_byte++; + } + + /* Check for the data limit */ + if (bs->read.cur_used >= bs->read.size) { + GST_WARNING ("Attempted to read beyond buffer"); + return; + } + + /* Take as many bytes as we can from the current buffer */ + to_take = MIN (j, (gint) (bs->read.size - bs->read.cur_used)); + memcpy (out, bs->read.cur_byte, to_take); + + out += to_take; + bs->read.cur_byte += to_take; + bs->read.cur_used += to_take; + j -= to_take; + bs->read.bitpos += (to_take * 8); + } +} + +static void +h_setbuf (huffdec_bitbuf * bb, guint8 * buf, guint size) +{ + bb->avail = size; + bb->buf_byte_idx = 0; + bb->buf_bit_idx = 8; + bb->buf = buf; +#if ENABLE_OPT_BS + if (buf) { + /* First load of the accumulator, assumes that size >= 4 */ + bb->buf_bit_idx = 32; + bb->remaining = bb->avail - 4; + + /* we need reverse the byte order */ + bb->accumulator = (guint) buf[3]; + bb->accumulator |= (guint) (buf[2]) << 8; + bb->accumulator |= (guint) (buf[1]) << 16; + bb->accumulator |= (guint) (buf[0]) << 24; + + bb->buf_byte_idx += 4; + } else { + bb->remaining = 0; + bb->accumulator = 0; + } +#endif +} + +static void +h_reset (huffdec_bitbuf * bb) +{ + h_setbuf (bb, NULL, 0); +} + +#if ENABLE_OPT_BS +static void +h_rewindNbits (huffdec_bitbuf * bb, guint N) +{ + guint bits = 0; + guint bytes = 0; + if (N <= (BS_ACUM_SIZE - bb->buf_bit_idx)) + bb->buf_bit_idx += N; + else { + N -= (BS_ACUM_SIZE - bb->buf_bit_idx); + bb->buf_bit_idx = 0; + bits = 8 - (N % 8); + bytes = (N + 8 + BS_ACUM_SIZE) >> 3; + if (bb->buf_byte_idx >= bytes) + bb->buf_byte_idx -= bytes; + else + bb->buf_byte_idx = 0; + bb->remaining += bytes; + h_getbits (bb, bits); + } +} +#else +void +static h_rewindNbits (huffdec_bitbuf * bb, guint N) +{ + guint32 byte_off; + + byte_off = (bb->buf_bit_idx + N) / 8; + + g_return_if_fail (bb->buf_byte_idx >= byte_off); + + bb->buf_bit_idx += N; + + if (bb->buf_bit_idx >= 8) { + bb->buf_bit_idx -= 8 * byte_off; + bb->buf_byte_idx -= byte_off; + } +} +#endif + +/* Constant declarations */ +static const gfloat multiple[64] = { + 2.00000000000000f, 1.58740105196820f, 1.25992104989487f, + 1.00000000000000f, 0.79370052598410f, 0.62996052494744f, 0.50000000000000f, + 0.39685026299205f, 0.31498026247372f, 0.25000000000000f, 0.19842513149602f, + 0.15749013123686f, 0.12500000000000f, 0.09921256574801f, 0.07874506561843f, + 0.06250000000000f, 0.04960628287401f, 0.03937253280921f, 0.03125000000000f, + 0.02480314143700f, 0.01968626640461f, 0.01562500000000f, 0.01240157071850f, + 0.00984313320230f, 0.00781250000000f, 0.00620078535925f, 0.00492156660115f, + 0.00390625000000f, 0.00310039267963f, 0.00246078330058f, 0.00195312500000f, + 0.00155019633981f, 0.00123039165029f, 0.00097656250000f, 0.00077509816991f, + 0.00061519582514f, 0.00048828125000f, 0.00038754908495f, 0.00030759791257f, + 0.00024414062500f, 0.00019377454248f, 0.00015379895629f, 0.00012207031250f, + 0.00009688727124f, 0.00007689947814f, 0.00006103515625f, 0.00004844363562f, + 0.00003844973907f, 0.00003051757813f, 0.00002422181781f, 0.00001922486954f, + 0.00001525878906f, 0.00001211090890f, 0.00000961243477f, 0.00000762939453f, + 0.00000605545445f, 0.00000480621738f, 0.00000381469727f, 0.00000302772723f, + 0.00000240310869f, 0.00000190734863f, 0.00000151386361f, 0.00000120155435f, + 1e-20f +}; + +/************************************************************* + * + * This module parses the starting 21 bits of the header + * + * **********************************************************/ + +static gboolean +read_main_header (Bit_stream_struc * bs, fr_header * hdr) +{ + if (bs_bits_avail (bs) < HEADER_LNGTH) { + return FALSE; + } + + /* Read 2 bits as version, since we're doing the MPEG2.5 thing + * of an 11 bit sync word and 2 bit version */ + hdr->version = bs_getbits (bs, 2); + hdr->layer = 4 - bs_getbits (bs, 2); + + /* error_protection TRUE indicates there is a CRC */ + hdr->error_protection = !bs_get1bit (bs); + hdr->bitrate_idx = bs_getbits (bs, 4); + hdr->srate_idx = bs_getbits (bs, 2); + hdr->padding = bs_get1bit (bs); + hdr->extension = bs_get1bit (bs); + hdr->mode = bs_getbits (bs, 2); + hdr->mode_ext = bs_getbits (bs, 2); + + hdr->copyright = bs_get1bit (bs); + hdr->original = bs_get1bit (bs); + hdr->emphasis = bs_getbits (bs, 2); + + return TRUE; +} + +/*************************************************************** + * + * This module contains the core of the decoder ie all the + * computational routines. (Layer I and II only) + * Functions are common to both layer unless + * otherwise specified. + * + ***************************************************************/ + +/************ Layer I, Layer II & Layer III ******************/ +static gboolean +read_header (mp3tl * tl, fr_header * hdr) +{ + Bit_stream_struc *bs = tl->bs; + + if (!read_main_header (bs, hdr)) + return FALSE; + + switch (hdr->layer) { + case 1: + hdr->bits_per_slot = 32; + hdr->frame_samples = 384; + break; + case 2: + hdr->bits_per_slot = 8; + hdr->frame_samples = 1152; + break; + case 3: + hdr->bits_per_slot = 8; + switch (hdr->version) { + case MPEG_VERSION_1: + hdr->frame_samples = 1152; + break; + case MPEG_VERSION_2: + case MPEG_VERSION_2_5: + hdr->frame_samples = 576; + break; + default: + return FALSE; + } + break; + default: + /* Layer must be 1, 2 or 3 */ + return FALSE; + } + + /* Sample rate index cannot be 0x03 (reserved value) */ + /* Bitrate index cannot be 0x0f (forbidden) */ + if (hdr->srate_idx == 0x03 || hdr->bitrate_idx == 0x0f) { + return FALSE; + } + + hdr->channels = (hdr->mode == MPG_MD_MONO) ? 1 : 2; + hdr->sample_rate = s_rates[hdr->version][hdr->srate_idx]; + hdr->bitrate = 0; + /*Free format as bitrate index is 0 */ + if (hdr->bitrate_idx == 0) { + /*Calculate Only for the first free format frame since the stream + * is of constant bitrate */ + if (tl->free_first) { + Bit_stream_struc org_bs; + fr_header hdr1; + guint N; + /*copy the orignal bitsream structure */ + memcpy (&org_bs, bs, sizeof (Bit_stream_struc)); + + /*Seek to next mp3 sync word and loop till there is data in the bitstream buffer */ + while (bs_seek_sync (bs)) { + if (!read_main_header (bs, &hdr1)) + return FALSE; + + /*Checks if the original and forwarded frames header details are same + *if yes then calculate free format bitrate else seek to next frame*/ + if (hdr->version == hdr1.version && + hdr->layer == hdr1.layer && + hdr->error_protection == hdr1.error_protection && + hdr->bitrate_idx == hdr1.bitrate_idx && + hdr->srate_idx == hdr1.srate_idx) { + /*Calculates distance between 2 valid frames */ + N = (guint)(bs->read.cur_used - org_bs.read.cur_used); + /*Copies back the original bitsream to main bs structure */ + memcpy (bs, &org_bs, sizeof (Bit_stream_struc)); + + /*Free format bitrate in kbps that will be used for future reference */ + tl->free_bitrate = + (hdr->sample_rate * (N - hdr->padding + 1) * 8 / hdr->frame_samples) / 1000; + hdr->bitrate = tl->free_bitrate * 1000; + tl->free_first = FALSE; + break; + } + } + } else + /*for all frames copy the same free format bitrate as the stream is cbr */ + hdr->bitrate = tl->free_bitrate * 1000; + } else if (hdr->version == MPEG_VERSION_1) + hdr->bitrate = bitrates_v1[hdr->layer - 1][hdr->bitrate_idx] * 1000; + else + hdr->bitrate = bitrates_v2[hdr->layer - 1][hdr->bitrate_idx] * 1000; + + if (hdr->sample_rate == 0 || hdr->bitrate == 0) { + return FALSE; + } + + /* Magic formula for calculating the size of a frame based on + * the duration of the frame and the bitrate */ + hdr->frame_slots = (hdr->frame_samples / hdr->bits_per_slot) + * hdr->bitrate / hdr->sample_rate + hdr->padding; + + /* Number of bits we need for decode is frame_slots * slot_size */ + hdr->frame_bits = hdr->frame_slots * hdr->bits_per_slot; + if (hdr->frame_bits <= 32) { + return FALSE; /* Invalid header */ + } + + return TRUE; +} + +#define MPEG1_STEREO_SI_SLOTS 32 +#define MPEG1_MONO_SI_SLOTS 17 +#define MPEG2_LSF_STEREO_SI_SLOTS 17 +#define MPEG2_LSF_MONO_SI_SLOTS 9 + +#define SAMPLE_RATES 3 +#define BIT_RATES 15 + +/* For layer 3 only - the number of slots for main data of + * current frame. In Layer 3, 1 slot = 1 byte */ +static gboolean +set_hdr_data_slots (fr_header * hdr) +{ + int nSlots; + + if (hdr->layer != 3) { + hdr->side_info_slots = 0; + hdr->main_slots = 0; + return TRUE; + } + + nSlots = hdr->frame_slots - hdr->padding; + +#if 0 + if (hdr->version == MPEG_VERSION_1) { + static const gint MPEG1_slot_table[SAMPLE_RATES][BIT_RATES] = { + {0, 104, 130, 156, 182, 208, 261, 313, 365, 417, 522, 626, 731, 835, 1044}, + {0, 96, 120, 144, 168, 192, 240, 288, 336, 384, 480, 576, 672, 768, 960}, + {0, 144, 180, 216, 252, 288, 360, 432, 504, 576, 720, 864, 1008, 1152, 1440} + }; + g_print ("Calced %d main slots, table says %d\n", nSlots, + MPEG1_slot_table[hdr->srate_idx][hdr->bitrate_idx]); + } else { + static const gint MPEG2_LSF_slot_table[SAMPLE_RATES][BIT_RATES] = { + {0, 26, 52, 78, 104, 130, 156, 182, 208, 261, 313, 365, 417, 470, 522}, + {0, 24, 48, 72, 96, 120, 144, 168, 192, 240, 288, 336, 384, 432, 480}, + {0, 36, 72, 108, 144, 180, 216, 252, 288, 360, 432, 504, 576, 648, 720} + }; + g_print ("Calced %d main slots, table says %d\n", nSlots, + MPEG2_LSF_slot_table[hdr->srate_idx][hdr->bitrate_idx]); + } +#endif + + if (hdr->version == MPEG_VERSION_1) { + if (hdr->channels == 1) + hdr->side_info_slots = MPEG1_MONO_SI_SLOTS; + else + hdr->side_info_slots = MPEG1_STEREO_SI_SLOTS; + } else { + if (hdr->channels == 1) + hdr->side_info_slots = MPEG2_LSF_MONO_SI_SLOTS; + else + hdr->side_info_slots = MPEG2_LSF_STEREO_SI_SLOTS; + } + nSlots -= hdr->side_info_slots; + + if (hdr->padding) + nSlots++; + + nSlots -= 4; + if (hdr->error_protection) + nSlots -= 2; + + if (nSlots < 0) + return FALSE; + + hdr->main_slots = nSlots; + + return TRUE; +} + +/******************************************************************* + * + * The bit allocation information is decoded. Layer I + * has 4 bit per subband whereas Layer II is Ws and bit rate + * dependent. + * + ********************************************************************/ + +/**************************** Layer II *************/ +static void +II_decode_bitalloc (Bit_stream_struc * bs, guint32 bit_alloc[2][SBLIMIT], + frame_params * fr_ps) +{ + int sb, ch; + int stereo = fr_ps->stereo; + int sblimit = fr_ps->sblimit; + int jsbound = fr_ps->jsbound; + const al_table *alloc = fr_ps->alloc; + + for (sb = 0; sb < jsbound; sb++) + for (ch = 0; ch < stereo; ch++) { + bit_alloc[ch][sb] = (char) bs_getbits (bs, (*alloc)[sb][0].bits); + } + + for (sb = jsbound; sb < sblimit; sb++) { + /* expand to 2 channels */ + bit_alloc[0][sb] = bit_alloc[1][sb] = bs_getbits (bs, (*alloc)[sb][0].bits); + } + + /* Zero the rest of the array */ + for (sb = sblimit; sb < SBLIMIT; sb++) + for (ch = 0; ch < stereo; ch++) + bit_alloc[ch][sb] = 0; +} + +/**************************** Layer I *************/ + +static void +I_decode_bitalloc (Bit_stream_struc * bs, guint32 bit_alloc[2][SBLIMIT], + frame_params * fr_ps) +{ + int i, j; + int stereo = fr_ps->stereo; + +// int sblimit = fr_ps->sblimit; + int jsbound = fr_ps->jsbound; + + for (i = 0; i < jsbound; i++) + for (j = 0; j < stereo; j++) { + bit_alloc[j][i] = bs_getbits (bs, 4); + } + + for (i = jsbound; i < SBLIMIT; i++) { + guint32 b = bs_getbits (bs, 4); + + for (j = 0; j < stereo; j++) + bit_alloc[j][i] = b; + } +} + +/***************************************************************** + * + * The following two functions implement the layer I and II + * format of scale factor extraction. Layer I involves reading + * 6 bit per subband as scale factor. Layer II requires reading + * first the scfsi which in turn indicate the number of scale factors + * transmitted. + * Layer I : I_decode_scale + * Layer II : II_decode_scale + * + ****************************************************************/ + +/************************** Layer I stuff ************************/ +static void +I_decode_scale (Bit_stream_struc * bs, guint32 bit_alloc[2][SBLIMIT], + guint32 scale_index[2][3][SBLIMIT], frame_params * fr_ps) +{ + int i, j; + int stereo = fr_ps->stereo; + +// int sblimit = fr_ps->sblimit; + + for (i = 0; i < SBLIMIT; i++) + for (j = 0; j < stereo; j++) { + if (!bit_alloc[j][i]) + scale_index[j][0][i] = SCALE_RANGE - 1; + else { + /* 6 bit per scale factor */ + scale_index[j][0][i] = bs_getbits (bs, 6); + } + } +} + +/*************************** Layer II stuff ***************************/ + +static void +II_decode_scale (Bit_stream_struc * bs, + guint scfsi[2][SBLIMIT], + guint bit_alloc[2][SBLIMIT], + guint scale_index[2][3][SBLIMIT], frame_params * fr_ps) +{ + int sb, ch; + int stereo = fr_ps->stereo; + int sblimit = fr_ps->sblimit; + + for (sb = 0; sb < sblimit; sb++) + for (ch = 0; ch < stereo; ch++) /* 2 bit scfsi */ + if (bit_alloc[ch][sb]) { + scfsi[ch][sb] = bs_getbits (bs, 2); + } + + for (sb = sblimit; sb < SBLIMIT; sb++) + for (ch = 0; ch < stereo; ch++) + scfsi[ch][sb] = 0; + + for (sb = 0; sb < sblimit; sb++) { + for (ch = 0; ch < stereo; ch++) { + if (bit_alloc[ch][sb]) { + switch (scfsi[ch][sb]) { + /* all three scale factors transmitted */ + case 0: + scale_index[ch][0][sb] = bs_getbits (bs, 6); + scale_index[ch][1][sb] = bs_getbits (bs, 6); + scale_index[ch][2][sb] = bs_getbits (bs, 6); + break; + /* scale factor 1 & 3 transmitted */ + case 1: + scale_index[ch][0][sb] = + scale_index[ch][1][sb] = bs_getbits (bs, 6); + scale_index[ch][2][sb] = bs_getbits (bs, 6); + break; + /* scale factor 1 & 2 transmitted */ + case 3: + scale_index[ch][0][sb] = bs_getbits (bs, 6); + scale_index[ch][1][sb] = + scale_index[ch][2][sb] = bs_getbits (bs, 6); + break; + /* only one scale factor transmitted */ + case 2: + scale_index[ch][0][sb] = + scale_index[ch][1][sb] = + scale_index[ch][2][sb] = bs_getbits (bs, 6); + break; + default: + break; + } + } else { + scale_index[ch][0][sb] = + scale_index[ch][1][sb] = scale_index[ch][2][sb] = SCALE_RANGE - 1; + } + } + } + for (sb = sblimit; sb < SBLIMIT; sb++) { + for (ch = 0; ch < stereo; ch++) { + scale_index[ch][0][sb] = + scale_index[ch][1][sb] = scale_index[ch][2][sb] = SCALE_RANGE - 1; + } + } +} + +/************************************************************** + * + * The following two routines take care of reading the + * compressed sample from the bit stream for both layer 1 and + * layer 2. For layer 1, read the number of bits as indicated + * by the bit_alloc information. For layer 2, if grouping is + * indicated for a particular subband, then the sample size has + * to be read from the bits_group and the merged samples has + * to be decompose into the three distinct samples. Otherwise, + * it is the same for as layer one. + * + **************************************************************/ + +/******************************* Layer I stuff ******************/ + +static void +I_buffer_sample (Bit_stream_struc * bs, + guint sample[2][3][SBLIMIT], + guint bit_alloc[2][SBLIMIT], frame_params * fr_ps) +{ + int i, j, k; + int stereo = fr_ps->stereo; + +// int sblimit = fr_ps->sblimit; + int jsbound = fr_ps->jsbound; + unsigned int s; + + for (i = 0; i < jsbound; i++) { + for (j = 0; j < stereo; j++) { + k = bit_alloc[j][i]; + if (k == 0) + sample[j][0][i] = 0; + else + sample[j][0][i] = bs_getbits (bs, k + 1); + } + } + for (i = jsbound; i < SBLIMIT; i++) { + k = bit_alloc[0][i]; + if (k == 0) + s = 0; + else + s = bs_getbits (bs, k + 1); + + for (j = 0; j < stereo; j++) + sample[j][0][i] = s; + } +} + +/*************************** Layer II stuff ************************/ + +static void +II_buffer_sample (Bit_stream_struc * bs, guint sample[2][3][SBLIMIT], + guint bit_alloc[2][SBLIMIT], frame_params * fr_ps) +{ + int sb, ch, k; + int stereo = fr_ps->stereo; + int sblimit = fr_ps->sblimit; + int jsbound = fr_ps->jsbound; + const al_table *alloc = fr_ps->alloc; + + for (sb = 0; sb < sblimit; sb++) { + for (ch = 0; ch < ((sb < jsbound) ? stereo : 1); ch++) { + guint allocation = bit_alloc[ch][sb]; + if (allocation) { + /* check for grouping in subband */ + if (alloc[0][sb][allocation].group == 3) { + k = alloc[0][sb][allocation].bits; + sample[ch][0][sb] = bs_getbits (bs, k); + sample[ch][1][sb] = bs_getbits (bs, k); + sample[ch][2][sb] = bs_getbits (bs, k); + } else { /* bit_alloc = 3, 5, 9 */ + unsigned int nlevels, c = 0; + + nlevels = alloc[0][sb][allocation].steps; + k = alloc[0][sb][allocation].bits; + c = bs_getbits (bs, k); + for (k = 0; k < 3; k++) { + sample[ch][k][sb] = c % nlevels; + c /= nlevels; + } + } + } else { /* for no sample transmitted */ + sample[ch][0][sb] = 0; + sample[ch][1][sb] = 0; + sample[ch][2][sb] = 0; + } + if (stereo == 2 && sb >= jsbound) { /* joint stereo : copy L to R */ + sample[1][0][sb] = sample[0][0][sb]; + sample[1][1][sb] = sample[0][1][sb]; + sample[1][2][sb] = sample[0][2][sb]; + } + } + } + for (sb = sblimit; sb < SBLIMIT; sb++) + for (ch = 0; ch < stereo; ch++) { + sample[ch][0][sb] = 0; + sample[ch][1][sb] = 0; + sample[ch][2][sb] = 0; + } +} + +/************************************************************** + * + * Restore the compressed sample to a factional number. + * first complement the MSB of the sample + * for layer I : + * Use s = (s' + 2^(-nb+1) ) * 2^nb / (2^nb-1) + * for Layer II : + * Use the formula s = s' * c + d + * + **************************************************************/ +static const gfloat c_table[17] = { + 1.33333333333f, 1.60000000000f, 1.14285714286f, + 1.77777777777f, 1.06666666666f, 1.03225806452f, + 1.01587301587f, 1.00787401575f, 1.00392156863f, + 1.00195694716f, 1.00097751711f, 1.00048851979f, + 1.00024420024f, 1.00012208522f, 1.00006103888f, + 1.00003051851f, 1.00001525902f +}; + +static const gfloat d_table[17] = { + 0.500000000f, 0.500000000f, 0.250000000f, 0.500000000f, + 0.125000000f, 0.062500000f, 0.031250000f, 0.015625000f, + 0.007812500f, 0.003906250f, 0.001953125f, 0.0009765625f, + 0.00048828125f, 0.00024414063f, 0.00012207031f, + 0.00006103516f, 0.00003051758f +}; + +/************************** Layer II stuff ************************/ + +static void +II_dequant_and_scale_sample (guint sample[2][3][SBLIMIT], + guint bit_alloc[2][SBLIMIT], float fraction[2][3][SBLIMIT], + guint scale_index[2][3][SBLIMIT], int scale_block, frame_params * fr_ps) +{ + int sb, gr, ch, x; + int stereo = fr_ps->stereo; + int sblimit = fr_ps->sblimit; + const al_table *alloc = fr_ps->alloc; + + for (sb = 0; sb < sblimit; sb++) { + for (ch = 0; ch < stereo; ch++) { + guint allocation = bit_alloc[ch][sb]; + + if (allocation != 0) { + gfloat scale_val, val; + gfloat c_quant, d_quant; + + c_quant = c_table[alloc[0][sb][allocation].quant]; + d_quant = d_table[alloc[0][sb][allocation].quant]; + scale_val = multiple[scale_index[ch][scale_block][sb]]; + + for (gr = 0; gr < 3; gr++) { + /* locate MSB in the sample */ + x = 0; + while ((1UL << x) < (*alloc)[sb][allocation].steps) + x++; + + /* MSB inversion */ + if (((sample[ch][gr][sb] >> (x - 1)) & 1) == 1) + val = 0.0f; + else + val = -1.0f; + + /* Form a 2's complement sample */ + val += (gfloat) ((double) (sample[ch][gr][sb] & ((1 << (x - 1)) - 1)) + / (double) (1L << (x - 1))); + + /* Dequantize the sample */ + val += d_quant; + val *= c_quant; + + /* And scale */ + val *= scale_val; + fraction[ch][gr][sb] = val; + } + } else { + fraction[ch][0][sb] = 0.0f; + fraction[ch][1][sb] = 0.0f; + fraction[ch][2][sb] = 0.0f; + } + } + } + + for (sb = sblimit; sb < SBLIMIT; sb++) + for (ch = 0; ch < stereo; ch++) { + fraction[ch][0][sb] = 0.0f; + fraction[ch][1][sb] = 0.0f; + fraction[ch][2][sb] = 0.0f; + } +} + +/***************************** Layer I stuff ***********************/ + +static void +I_dequant_and_scale_sample (guint sample[2][3][SBLIMIT], + float fraction[2][3][SBLIMIT], + guint bit_alloc[2][SBLIMIT], + guint scale_index[2][3][SBLIMIT], frame_params * fr_ps) +{ + int sb, ch; + guint nb; + int stereo = fr_ps->stereo; + + for (sb = 0; sb < SBLIMIT; sb++) { + for (ch = 0; ch < stereo; ch++) { + guint allocation = bit_alloc[ch][sb]; + + if (allocation != 0) { + double val; + + nb = allocation + 1; + + if (((sample[ch][0][sb] >> allocation) & 1) != 0) + val = 0.0; + else + val = -1.0; + + val += (double) (sample[ch][0][sb] & ((1 << allocation) - 1)) / + (double) (1L << (nb - 1)); + + val = + (double) (val + 1.0 / (1L << allocation)) * + (double) (1L << nb) / ((1L << nb) - 1); + + val *= (double) multiple[scale_index[ch][0][sb]]; + + fraction[ch][0][sb] = (gfloat) val; + } else + fraction[ch][0][sb] = 0.0f; + } + } +} + +/***************************************************************** + * + * The following are the subband synthesis routines. They apply + * to both layer I and layer II stereo or mono. The user has to + * decide what parameters are to be passed to the routines. + * + ***************************************************************/ + +/* Write output samples into the outBuf, incrementing psamples for each + * sample, wrapping at bufSize */ +static inline void +out_fifo (short pcm_sample[2][SSLIMIT][SBLIMIT], int num, + frame_params * fr_ps, gint16 * outBuf, guint32 * psamples, guint32 bufSize) +{ + int i, j, k, l; + int stereo = fr_ps->stereo; + k = *psamples; + if (stereo == 2) { + for (i = 0; i < num; i++) { + for (j = 0; j < SBLIMIT; j++) { + outBuf[k] = pcm_sample[0][i][j]; + outBuf[k+1] = pcm_sample[1][i][j]; + k += 2; + k %= bufSize; + } + } + } else if (stereo == 1) { + for (i = 0; i < num; i++) { + for (j = 0; j < SBLIMIT; j++) { + outBuf[k] = pcm_sample[0][i][j]; + k++; + k %= bufSize; + } + } + } else { + for (i = 0; i < num; i++) { + for (j = 0; j < SBLIMIT; j++) { + for (l = 0; l < stereo; l++) { + outBuf[k] = pcm_sample[l][i][j]; + k++; + k %= bufSize; + } + } + } + } + *psamples = k; +} + +/************************************************************* + * + * Pass the subband sample through the synthesis window + * + **************************************************************/ + +/* create in synthesis filter */ +static void +init_syn_filter (frame_params * fr_ps) +{ + int i, k; + gfloat (*filter)[32]; + + filter = fr_ps->filter; + + for (i = 0; i < 64; i++) + for (k = 0; k < 32; k++) { + if ((filter[i][k] = 1e9f * cosf ((float)((PI64 * i + PI4) * (2 * k + 1)))) >= 0.0f) + modff (filter[i][k] + 0.5f, &filter[i][k]); + else + modff (filter[i][k] - 0.5f, &filter[i][k]); + filter[i][k] *= 1e-9f; + } + + for (i = 0; i < 2; i++) + fr_ps->bufOffset[i] = 64; +} + +/*************************************************************** + * + * Window the restored sample + * + ***************************************************************/ + +#define INV_SQRT_2 (7.071067811865474617150084668537e-01f) + +#if defined(USE_ARM_NEON) +static const __CACHE_LINE_DECL_ALIGN(float dct8_k[8]) = { + INV_SQRT_2, 0.0f, 5.4119610014619701222e-01f, 1.3065629648763763537e+00f, + 5.0979557910415917998e-01f, 6.0134488693504528634e-01f, + 8.9997622313641556513e-01f, 2.5629154477415054814e+00f +}; + +STATIC_INLINE void +MPG_DCT_8 (gfloat in[8], gfloat out[8]) +{ + __asm__ volatile ( + "vld1.64 {q0-q1}, [%[dct8_k],:128] \n\t" /* read dct8_k */ + "vld1.64 {q2-q3}, [%[in],:128] \n\t" /* read in */ + "vrev64.f32 q3, q3 \n\t" + "vswp d6, d7 \n\t" + "vadd.f32 q4, q2, q3 \n\t" /* ei0, ei2, ei3, ei1 */ + "vadd.f32 s28, s16, s19 \n\t" /* t0 = ei0 + ei1 */ + "vadd.f32 s29, s17, s18 \n\t" /* t1 = ei2 + ei3 */ + "vsub.f32 s30, s16, s19 \n\t" /* t2 = ei0 - ei1 */ + "vsub.f32 s31, s17, s18 \n\t" /* t3 = ei2 - ei3 */ + "vmul.f32 d15, d15, d1 \n\t" + "vsub.f32 q5, q2, q3 \n\t" /* oi0', oi1', oi2', oi3' */ + "vsub.f32 s27, s30, s31 \n\t" /* t4' = t2 - t3 */ + "vadd.f32 s24, s28, s29 \n\t" /* out0 = t0 + t1 */ + "vmul.f32 q5, q5, q1 \n\t" /* oi0, oi1, oi2, oi3 */ + "vmul.f32 s27, s27, s0 \n\t" /* out6 = t4 */ + "vadd.f32 s25, s30, s31 \n\t" /* out2 = t2 + t3 */ + "vadd.f32 s25, s25, s27 \n\t" /* out2 = t2 + t3 + t4 */ + "vsub.f32 s26, s28, s29 \n\t" /* out4' = t0 - t1 */ + "vrev64.f32 d11, d11 \n\t" + "vmul.f32 s26, s26, s0 \n\t" /* out4 = (t0 -t1) * INV_SQRT_2 */ + "vadd.f32 d4, d10, d11 \n\t" /* t0,t1 = oi0 + oi3, oi1 + oi2 */ + "vsub.f32 d5, d10, d11 \n\t" /* t2',t3' = oi0 - oi3, oi1 - oi3 */ + "vmul.f32 d5, d5, d1 \n\t" /* t2, t3 */ + "vadd.f32 s12, s10, s11 \n\t" /* t4 = t2 + t3 */ + "vsub.f32 s13, s10, s11 \n\t" /* t5' = t2 - t3 */ + "vmul.f32 s31, s13, s0 \n\t" /* out7 = oo3 = t5 */ + "vadd.f32 s14, s8, s9 \n\t" /* oo0 = t0 + t1 */ + "vadd.f32 s15, s12, s31 \n\t" /* oo1 = t4 + t5 */ + "vsub.f32 s16, s8, s9 \n\t" /* oo2' = t0 - t1 */ + "vmul.f32 s16, s16, s0 \n\t" /* oo2 */ + "vadd.f32 s28, s14, s15 \n\t" /* out1 = oo0 + oo1 */ + "vadd.f32 s29, s15, s16 \n\t" /* out3 = oo1 + oo2 */ + "vadd.f32 s30, s16, s31 \n\t" /* out5 = oo2 + oo3 */ + "vst2.32 {q6, q7}, [%[out],:128] \n\t" + : [in] "+&r" (in), + [out] "+&r" (out) + : [dct8_k] "r" (dct8_k) + : "memory", "cc", + "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7" + ); +} +#else +STATIC_INLINE void +MPG_DCT_8 (gfloat in[8], gfloat out[8]) +{ + gfloat even_in[4]; + gfloat odd_in[4], odd_out[4]; + gfloat tmp[6]; + + /* Even indices */ + even_in[0] = in[0] + in[7]; + even_in[1] = in[3] + in[4]; + even_in[2] = in[1] + in[6]; + even_in[3] = in[2] + in[5]; + + tmp[0] = even_in[0] + even_in[1]; + tmp[1] = even_in[2] + even_in[3]; + tmp[2] = (even_in[0] - even_in[1]) * synth_cos64_table[7]; + tmp[3] = (even_in[2] - even_in[3]) * synth_cos64_table[23]; + tmp[4] = (gfloat) ((tmp[2] - tmp[3]) * INV_SQRT_2); + + out[0] = tmp[0] + tmp[1]; + out[2] = tmp[2] + tmp[3] + tmp[4]; + out[4] = (gfloat) ((tmp[0] - tmp[1]) * INV_SQRT_2); + out[6] = tmp[4]; + + /* Odd indices */ + odd_in[0] = (in[0] - in[7]) * synth_cos64_table[3]; + odd_in[1] = (in[1] - in[6]) * synth_cos64_table[11]; + odd_in[2] = (in[2] - in[5]) * synth_cos64_table[19]; + odd_in[3] = (in[3] - in[4]) * synth_cos64_table[27]; + + tmp[0] = odd_in[0] + odd_in[3]; + tmp[1] = odd_in[1] + odd_in[2]; + tmp[2] = (odd_in[0] - odd_in[3]) * synth_cos64_table[7]; + tmp[3] = (odd_in[1] - odd_in[2]) * synth_cos64_table[23]; + tmp[4] = tmp[2] + tmp[3]; + tmp[5] = (gfloat) ((tmp[2] - tmp[3]) * INV_SQRT_2); + + odd_out[0] = tmp[0] + tmp[1]; + odd_out[1] = tmp[4] + tmp[5]; + odd_out[2] = (gfloat) ((tmp[0] - tmp[1]) * INV_SQRT_2); + odd_out[3] = tmp[5]; + + out[1] = odd_out[0] + odd_out[1]; + out[3] = odd_out[1] + odd_out[2]; + out[5] = odd_out[2] + odd_out[3]; + out[7] = odd_out[3]; +} +#endif + +#if defined(USE_ARM_NEON) + +static const __CACHE_LINE_DECL_ALIGN(float dct16_k[8]) = { + 5.0241928618815567820e-01f, 5.2249861493968885462e-01f, + 5.6694403481635768927e-01f, 6.4682178335999007679e-01f, + 6.4682178335999007679e-01f, 1.0606776859903470633e+00f, + 1.7224470982383341955e+00f, 5.1011486186891552563e+00f +}; + +STATIC_INLINE void +MPG_DCT_16 (gfloat in[16], gfloat out[16]) +{ + __CACHE_LINE_DECL_ALIGN(gfloat even_in[8]); + __CACHE_LINE_DECL_ALIGN(gfloat even_out[8]); + __CACHE_LINE_DECL_ALIGN(gfloat odd_in[8]); + __CACHE_LINE_DECL_ALIGN(gfloat odd_out[8]); + + __asm__ volatile ( + "vld1.64 {q0-q1}, [%[dct16_k],:128] \n\t" /* read dct16_k */ + "vld1.64 {q2-q3}, [%[in],:128]! \n\t" /* read in */ + "vld1.64 {q4-q5}, [%[in],:128] \n\t" /* read in */ + "vrev64.f32 q4, q4 \n\t" + "vrev64.f32 q5, q5 \n\t" + "vswp d8, d9 \n\t" + "vswp d10, d11 \n\t" + "vadd.f32 q6, q2, q4 \n\t" + "vadd.f32 q7, q3, q5 \n\t" + "vst1.64 {q6-q7}, [%[even_in],:128] \n\t" + "vsub.f32 q6, q2, q4 \n\t" + "vsub.f32 q7, q3, q5 \n\t" + "vmul.f32 q6, q6, q0 \n\t" + "vmul.f32 q7, q7, q1 \n\t" + "vst1.64 {q6-q7}, [%[odd_in],:128] \n\t" + : [in] "+&r" (in) + : [even_in] "r" (even_in), [odd_in] "r" (odd_in), [dct16_k] "r" (dct8_k) + : "memory", "cc", + "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7" + ); + + MPG_DCT_8 (even_in, even_out); + MPG_DCT_8 (odd_in, odd_out); + + __asm__ volatile ( + "vld1.64 {q0-q1}, [%[even_out],:128] \n\t" + "vld1.64 {q2-q3}, [%[odd_out],:128] \n\t" + "vswp q1, q2 \n\t" + "vadd.f32 s4, s4, s5 \n\t" + "vadd.f32 s5, s5, s6 \n\t" + "vadd.f32 s6, s6, s7 \n\t" + "vadd.f32 s7, s7, s12 \n\t" + "vst2.32 {q0-q1}, [%[out],:128]! \n\t" + "vadd.f32 s12, s12, s13 \n\t" + "vadd.f32 s13, s13, s14 \n\t" + "vadd.f32 s14, s14, s15 \n\t" + "vst2.32 {q2-q3}, [%[out],:128]! \n\t" + : [out] "+&r" (out) + : [even_out] "r" (even_out), [odd_out] "r" (odd_out) + : "memory", "cc", + "q0", "q1", "q2", "q3" + ); +} +#else +STATIC_INLINE void +MPG_DCT_16 (gfloat in[16], gfloat out[16]) +{ + __CACHE_LINE_DECL_ALIGN(gfloat even_in[8]); + __CACHE_LINE_DECL_ALIGN(gfloat even_out[8]); + __CACHE_LINE_DECL_ALIGN(gfloat odd_in[8]); + __CACHE_LINE_DECL_ALIGN(gfloat odd_out[8]); + gfloat a, b; + + a = in[0]; b = in[15]; + even_in[0] = a + b; + odd_in[0] = (a - b) * synth_cos64_table[1]; + a = in[1]; b = in[14]; + even_in[1] = a + b; + odd_in[1] = (a - b) * synth_cos64_table[5]; + a = in[2]; b = in[13]; + even_in[2] = a + b; + odd_in[2] = (a - b) * synth_cos64_table[9]; + a = in[3]; b = in[12]; + even_in[3] = a + b; + odd_in[3] = (a - b) * synth_cos64_table[13]; + a = in[4]; b = in[11]; + even_in[4] = a + b; + odd_in[4] = (a - b) * synth_cos64_table[17]; + a = in[5]; b = in[10]; + even_in[5] = a + b; + odd_in[5] = (a - b) * synth_cos64_table[21]; + a = in[6]; b = in[9]; + even_in[6] = a + b; + odd_in[6] = (a - b) * synth_cos64_table[25]; + a = in[7]; b = in[8]; + even_in[7] = a + b; + odd_in[7] = (a - b) * synth_cos64_table[29]; + + MPG_DCT_8 (even_in, even_out); + MPG_DCT_8 (odd_in, odd_out); + + out[0] = even_out[0]; + out[1] = odd_out[0] + odd_out[1]; + out[2] = even_out[1]; + out[3] = odd_out[1] + odd_out[2]; + out[4] = even_out[2]; + out[5] = odd_out[2] + odd_out[3]; + out[6] = even_out[3]; + out[7] = odd_out[3] + odd_out[4]; + out[8] = even_out[4]; + out[9] = odd_out[4] + odd_out[5]; + out[10] = even_out[5]; + out[11] = odd_out[5] + odd_out[6]; + out[12] = even_out[6]; + out[13] = odd_out[6] + odd_out[7]; + out[14] = even_out[7]; + out[15] = odd_out[7]; +} +#endif + +STATIC_INLINE void +MPG_DCT_32 (gfloat in[32], gfloat out[32]) +{ + gint i; + __CACHE_LINE_DECL_ALIGN(gfloat even_in[16]); + __CACHE_LINE_DECL_ALIGN(gfloat even_out[16]); + __CACHE_LINE_DECL_ALIGN(gfloat odd_in[16]); + __CACHE_LINE_DECL_ALIGN(gfloat odd_out[16]); + + for (i = 0; i < 16; i++) { + even_in[i] = in[i] + in[31 - i]; + odd_in[i] = (in[i] - in[31 - i]) * synth_cos64_table[2 * i]; + } + + MPG_DCT_16 (even_in, even_out); + MPG_DCT_16 (odd_in, odd_out); + + for (i = 0; i < 15; i++) { + out[2 * i] = even_out[i]; + out[2 * i + 1] = odd_out[i] + odd_out[i + 1]; + } + out[30] = even_out[15]; + out[31] = odd_out[15]; +} + +#if defined(USE_ARM_NEON) + +#define WIN_MAC \ + " vld1.32 {q0-q1}, [r3,:128], r5 \n\t" /* read win */ \ + " vld1.32 {q2-q3}, [r4,:128], r5 \n\t" /* read uvec */ \ + " pld [r3] \n\t" \ + " pld [r4] \n\t" \ + " vmla.f32 q4, q0, q2 \n\t" /* acc += uvec * win */ \ + " vmla.f32 q5, q1, q3 \n\t" + +STATIC_INLINE void +mp3_dewindow_output (gfloat *uvec, short *samples, gfloat* window) +{ + __asm__ volatile ( + "pld [%[win]] \n\t" + "pld [%[uvec]] \n\t" + "mov r5, #32*4 \n\t" /* step = 32 floats */ + "mov ip, #4 \n\t" /* ip = 4 */ + "0: \n\t" + " add r3, %[win], r5 \n\t" /* pw = win */ + " add r4, %[uvec], r5 \n\t" /* puvec = uvec */ + " vld1.32 {q0-q1}, [%[win],:128]! \n\t" /* read win */ + " vld1.32 {q2-q3}, [%[uvec],:128]! \n\t" /* read uvec */ + " pld [r3] \n\t" + " pld [r4] \n\t" + " vmul.f32 q4, q0, q2 \n\t" /* acc = uvec * win */ + " vmul.f32 q5, q1, q3 \n\t" + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + WIN_MAC + " vcvt.s32.f32 q4, q4, #31 \n\t" + " vcvt.s32.f32 q5, q5, #31 \n\t" + " vshrn.s32 d0, q4, #16 \n\t" + " vshrn.s32 d1, q5, #16 \n\t" + " vst1.64 {d0-d1}, [%[samp],:128]! \n\t" + " pld [%[win]] \n\t" + " pld [%[uvec]] \n\t" + " subs ip, ip, #1 \n\t" + " bne 0b \n\t" + + : [win] "+&r" (window), + [samp] "+&r" (samples), + [uvec] "+&r" (uvec) + : + : "memory", "cc", "ip", "r3", "r4", "r5", + "q0", "q1", "q2", "q3", "q4", "q5" + ); +} + +#else +STATIC_INLINE void +mp3_dewindow_output (gfloat *u_vec, short *samples, gfloat* window) +{ + gint i; + gfloat *u_vec0; + + /* dewindowing */ + for (i = 0; i < HAN_SIZE; i++) + u_vec[i] *= dewindow[i]; + + /* Now calculate 32 samples */ + for (i = 0; i < 32; i++) { + gfloat sum; + u_vec0 = u_vec + i; + sum = u_vec0[1 << 5]; + sum += u_vec0[2 << 5]; + sum += u_vec0[3 << 5]; + sum += u_vec0[4 << 5]; + sum += u_vec0[5 << 5]; + sum += u_vec0[6 << 5]; + sum += u_vec0[7 << 5]; + sum += u_vec0[8 << 5]; + sum += u_vec0[9 << 5]; + sum += u_vec0[10 << 5]; + sum += u_vec0[11 << 5]; + sum += u_vec0[12 << 5]; + sum += u_vec0[13 << 5]; + sum += u_vec0[14 << 5]; + sum += u_vec0[15 << 5]; + u_vec0[0] += sum; + } + + for (i = 0; i < 32; i++) { + gfloat sample = u_vec[i]; + if (sample > 0) { + sample = sample * SCALE + 0.5f; + if (sample < (SCALE - 1)) { + samples[i] = (short) (sample); + } else { + samples[i] = (short) (SCALE - 1); + } + } else { + sample = sample * SCALE - 0.5f; + if (sample > -SCALE) { + samples[i] = (short) (sample); + } else { + samples[i] = (short) (-SCALE); + } + } + } +} +#endif + +#if defined(USE_ARM_NEON) +STATIC_INLINE void +build_uvec (gfloat *u_vec, gfloat *cur_synbuf, gint k) +{ + __asm__ volatile ( + "mov ip, #8 \n\t" /* i = 8 */ + "mov r5, #512 \n\t" + "sub r5, r5, #1 \n\t" /* r5 = 511 */ + "veor d0, d0 \n\t" + "0: \n\t" + " add r4, %[k], #16 \n\t" + " add r4, %[cur_synbuf], r4, lsl #2 \n\t" + " pld [r4] \n\t" + " mov r3, %[u_vec] \n\t" + " vstr s0, [r3, #16*4] \n\t" + " vld1.64 {q1-q2}, [r4,:128]! \n\t" + " vld1.64 {q3-q4}, [r4,:128]! \n\t" + " vst1.64 {q1-q2}, [r3,:128]! \n\t" + " vst1.64 {q3-q4}, [r3,:128]! \n\t" + " add r3, r3, #4 \n\t" + " vneg.f32 q1, q1 \n\t" + " vneg.f32 q2, q2 \n\t" + " vneg.f32 q3, q3 \n\t" + " vneg.f32 q4, q4 \n\t" + " vrev64.f32 q1, q1 \n\t" + " vrev64.f32 q2, q2 \n\t" + " vrev64.f32 q3, q3 \n\t" + " vrev64.f32 q4, q4 \n\t" + " vswp d2, d3 \n\t" + " vswp d6, d7 \n\t" + " vswp d8, d9 \n\t" + " vswp d4, d5 \n\t" + " vst1.64 {q4}, [r3]! \n\t" + " vst1.64 {q3}, [r3]! \n\t" + " vst1.64 {q2}, [r3]! \n\t" + " vst1.64 {q1}, [r3]! \n\t" + " add %[k], %[k], #32 \n\t" /* k += 32 */ + " and %[k], %[k], r5 \n\t" /* k &= 511 */ + " add r4, %[cur_synbuf], %[k], lsl #2\n\t" + " pld [r4] \n\t" + " add r3, %[u_vec], #48*4 \n\t" + " vld1.64 {q1-q2}, [r4,:128]! \n\t" + " vld1.64 {q3-q4}, [r4,:128]! \n\t" + " vldr.32 s2, [r4] \n\t" + " vneg.f32 q1, q1 \n\t" + " vneg.f32 q2, q2 \n\t" + " vneg.f32 q3, q3 \n\t" + " vneg.f32 q4, q4 \n\t" + " vst1.64 {q1-q2}, [r3,:128]! \n\t" + " vst1.64 {q3-q4}, [r3,:128]! \n\t" + " vneg.f32 s2, s2 \n\t" + " add r3, %[u_vec], #32*4 \n\t" + " vrev64.f32 q1, q1 \n\t" + " vrev64.f32 q3, q3 \n\t" + " vrev64.f32 q4, q4 \n\t" + " vrev64.f32 q2, q2 \n\t" + " vswp d2, d3 \n\t" + " vswp d6, d7 \n\t" + " vswp d8, d9 \n\t" + " vswp d4, d5 \n\t" + " vstmia r3!, {s2} \n\t" + " vstmia r3!, {q4} \n\t" + " vstmia r3!, {q3} \n\t" + " vstmia r3!, {q2} \n\t" + " vstmia r3!, {q1} \n\t" + " subs ip, ip, #1 \n\t" /* i-- */ + " add %[u_vec], %[u_vec], #64*4 \n\t" + " add %[k], %[k], #32 \n\t" /* k += 32 */ + " and %[k], %[k], r5 \n\t" /* k &= 511 */ + " bne 0b \n\t" + : [u_vec] "+&r" (u_vec), [k] "+&r" (k) + : [cur_synbuf] "r" (cur_synbuf) + : "memory", "cc", "r3", "r4", "r5", "ip", + "q0", "q1", "q2", "q3", "q4" + ); +} +#else +STATIC_INLINE void +build_uvec (gfloat *u_vec, gfloat *cur_synbuf, gint k) +{ + gint i, j; + + for (j = 0; j < 8; j++) { + for (i = 0; i < 16; i++) { + /* Copy first 32 elements */ + u_vec [i] = cur_synbuf [k + i + 16]; + u_vec [i + 17] = -cur_synbuf [k + 31 - i]; + } + + /* k wraps at the synthesis buffer boundary */ + k = (k + 32) & 511; + + for (i = 0; i < 16; i++) { + /* Copy next 32 elements */ + u_vec [i + 32] = -cur_synbuf [k + 16 - i]; + u_vec [i + 48] = -cur_synbuf [k + i]; + } + u_vec [16] = 0; + + /* k wraps at the synthesis buffer boundary */ + k = (k + 32) & 511; + u_vec += 64; + } +} +#endif + +/* Synthesis matrixing variant which uses a 32 point DCT */ +static void +mp3_SubBandSynthesis (mp3tl * tl ATTR_UNUSED, frame_params * fr_ps, + float *polyPhaseIn, gint channel, short *samples) +{ + gint k; + gfloat *cur_synbuf = fr_ps->synbuf[channel]; + __CACHE_LINE_DECL_ALIGN(gfloat u_vec[HAN_SIZE]); + + /* Shift down 32 samples in the fifo, which should always leave room */ + k = fr_ps->bufOffset[channel]; + k = (k - 32) & 511; + fr_ps->bufOffset[channel] = k; + + /* DCT part */ + MPG_DCT_32 (polyPhaseIn, cur_synbuf + k); + + /* Build the U vector */ + build_uvec (u_vec, cur_synbuf, k); + + /* Dewindow and output samples */ + mp3_dewindow_output (u_vec, samples, (gfloat*) dewindow); +} + +/************************* Layer III routines **********************/ + +static gboolean +III_get_side_info (guint8 * data, III_side_info_t * si, frame_params * fr_ps) +{ + int ch, gr, i; + int stereo = fr_ps->stereo; + huffdec_bitbuf bb; + + h_setbuf (&bb, data, fr_ps->header.side_info_slots); + + if (fr_ps->header.version == MPEG_VERSION_1) { + si->main_data_begin = h_getbits (&bb, 9); + if (stereo == 1) + si->private_bits = h_getbits (&bb, 5); + else + si->private_bits = h_getbits (&bb, 3); + + for (ch = 0; ch < stereo; ch++) { + guint8 scfsi = (guint8) h_getbits (&bb, 4); + si->scfsi[0][ch] = scfsi & 0x08; + si->scfsi[1][ch] = scfsi & 0x04; + si->scfsi[2][ch] = scfsi & 0x02; + si->scfsi[3][ch] = scfsi & 0x01; + } + + for (gr = 0; gr < 2; gr++) { + for (ch = 0; ch < stereo; ch++) { + gr_info_t *gi = &(si->gr[gr][ch]); + + gi->part2_3_length = h_getbits (&bb, 12); + gi->big_values = h_getbits (&bb, 9); + /* Add 116 to avoid doing it in the III_dequantize loop */ + gi->global_gain = h_getbits (&bb, 8) + 116; + gi->scalefac_compress = h_getbits (&bb, 4); + gi->window_switching_flag = h_get1bit (&bb); + if (gi->window_switching_flag) { + gi->block_type = h_getbits (&bb, 2); + gi->mixed_block_flag = h_get1bit (&bb); + gi->table_select[0] = h_getbits (&bb, 5); + gi->table_select[1] = h_getbits (&bb, 5); + for (i = 0; i < 3; i++) + gi->subblock_gain[i] = h_getbits (&bb, 3); + + if (gi->block_type == 0) { + GST_WARNING ("Side info bad: block_type == 0 in split block."); + return FALSE; + } else if (gi->block_type == 2 && gi->mixed_block_flag == 0) { + gi->region0_count = 8; /* MI 9; */ + gi->region1_count = 12; + } else { + gi->region0_count = 7; /* MI 8; */ + gi->region1_count = 13; + } + } else { + for (i = 0; i < 3; i++) + gi->table_select[i] = h_getbits (&bb, 5); + gi->region0_count = h_getbits (&bb, 4); + gi->region1_count = h_getbits (&bb, 3); + gi->block_type = 0; + } + gi->preflag = h_get1bit (&bb); + /* Add 1 & multiply by 2 to avoid doing it in the III_dequantize loop */ + gi->scalefac_scale = 2 * (h_get1bit (&bb) + 1); + gi->count1table_select = h_get1bit (&bb); + } + } + } else { /* Layer 3 LSF */ + + si->main_data_begin = h_getbits (&bb, 8); + if (stereo == 1) + si->private_bits = h_getbits (&bb, 1); + else + si->private_bits = h_getbits (&bb, 2); + + for (gr = 0; gr < 1; gr++) { + for (ch = 0; ch < stereo; ch++) { + gr_info_t *gi = &(si->gr[gr][ch]); + + gi->part2_3_length = h_getbits (&bb, 12); + gi->big_values = h_getbits (&bb, 9); + /* Add 116 to avoid doing it in the III_dequantize loop */ + gi->global_gain = h_getbits (&bb, 8) + 116; + gi->scalefac_compress = h_getbits (&bb, 9); + gi->window_switching_flag = h_get1bit (&bb); + if (gi->window_switching_flag) { + gi->block_type = h_getbits (&bb, 2); + gi->mixed_block_flag = h_get1bit (&bb); + gi->table_select[0] = h_getbits (&bb, 5); + gi->table_select[1] = h_getbits (&bb, 5); + for (i = 0; i < 3; i++) + gi->subblock_gain[i] = h_getbits (&bb, 3); + + /* Set region_count parameters since they are + * implicit in this case. */ + if (gi->block_type == 0) { + GST_WARNING ("Side info bad: block_type == 0 in split block.\n"); + return FALSE; + } else if (gi->block_type == 2 && gi->mixed_block_flag == 0) { + gi->region0_count = 8; /* MI 9; */ + gi->region1_count = 12; + } else { + gi->region0_count = 7; /* MI 8; */ + gi->region1_count = 13; + } + } else { + for (i = 0; i < 3; i++) + gi->table_select[i] = h_getbits (&bb, 5); + gi->region0_count = h_getbits (&bb, 4); + gi->region1_count = h_getbits (&bb, 3); + gi->block_type = 0; + } + + gi->preflag = 0; + /* Add 1 & multiply by 2 to avoid doing it in the III_dequantize loop */ + gi->scalefac_scale = 2 * (h_get1bit (&bb) + 1); + gi->count1table_select = h_get1bit (&bb); + } + } + } + + return TRUE; +} + +static const gint slen_table[2][16] = { + {0, 0, 0, 0, 3, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4}, + {0, 1, 2, 3, 0, 1, 2, 3, 1, 2, 3, 1, 2, 3, 2, 3} +}; + +struct +{ + gint l[23]; + gint s[15]; +} static const sfBandIndex[] = { + /* MPEG-1 */ + { + /* 44.1 khz */ + { 0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 52, 62, 74, 90, 110, 134, 162, 196, + 238, 288, 342, 418, 576}, + { 0, 4, 8, 12, 16, 22, 30, 40, 52, 66, 84, 106, 136, 192, 192 } + }, { + /* 48khz */ + { 0, 4, 8, 12, 16, 20, 24, 30, 36, 42, 50, 60, 72, 88, 106, 128, 156, 190, + 230, 276, 330, 384, 576}, + { 0, 4, 8, 12, 16, 22, 28, 38, 50, 64, 80, 100, 126, 192, 192 } + }, { + /* 32khz */ + { 0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 54, 66, 82, 102, 126, 156, 194, 240, + 296, 364, 448, 550, 576 }, + { 0, 4, 8, 12, 16, 22, 30, 42, 58, 78, 104, 138, 180, 192, 192 } + }, + /* MPEG-2 */ + { + /* 22.05 khz */ + { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, + 284, 336, 396, 464, 522, 576 }, + { 0, 4, 8, 12, 18, 24, 32, 42, 56, 74, 100, 132, 174, 192, 192 } + }, { + /* 24khz */ + { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 114, 136, 162, 194, 232, + 278, 330, 394, 464, 540, 576 }, + { 0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 136, 180, 192, 192 } + }, { + /* 16 khz */ + { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, + 284, 336, 396, 464, 522, 576 }, + { 0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192, 192 } + }, + /* MPEG-2.5 */ + { + /* 11025 */ + { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, + 284, 336, 396, 464, 522, 576 }, + { 0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192, 192 } + }, { + /* 12khz */ + { 0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, + 284, 336, 396, 464, 522, 576 }, + { 0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192, 192 } + }, { + /* 8khz */ + { 0, 12, 24, 36, 48, 60, 72, 88, 108, 132, 160, 192, 232, 280, 336, 400, + 476, 566, 568, 570, 572, 574, 576 }, + { 0, 8, 16, 24, 36, 52, 72, 96, 124, 160, 162, 164, 166, 192, 192 } + } +}; + +/* Offset into the sfBand table for each MPEG version */ +static const guint sfb_offset[] = { 6, 0 /* invalid */ , 3, 0 }; + +static void +III_get_scale_factors (III_scalefac_t * scalefac, III_side_info_t * si, + int gr, int ch, mp3tl * tl) +{ + int sfb, window; + gr_info_t *gr_info = &(si->gr[gr][ch]); + huffdec_bitbuf *bb = &tl->c_impl.bb; + gint slen0, slen1; + + slen0 = slen_table[0][gr_info->scalefac_compress]; + slen1 = slen_table[1][gr_info->scalefac_compress]; + if (gr_info->window_switching_flag && (gr_info->block_type == 2)) { + if (gr_info->mixed_block_flag) { /* MIXED *//* NEW - ag 11/25 */ + for (sfb = 0; sfb < 8; sfb++) + (*scalefac)[ch].l[sfb] = h_getbits (bb, slen0); + + for (sfb = 3; sfb < 6; sfb++) + for (window = 0; window < 3; window++) + (*scalefac)[ch].s[window][sfb] = h_getbits (bb, slen0); + + for ( /* sfb = 6 */ ; sfb < 12; sfb++) + for (window = 0; window < 3; window++) + (*scalefac)[ch].s[window][sfb] = h_getbits (bb, slen1); + + for (sfb = 12, window = 0; window < 3; window++) + (*scalefac)[ch].s[window][sfb] = 0; + } else { + /* SHORT block */ + for (sfb = 0; sfb < 6; sfb++) + for (window = 0; window < 3; window++) + (*scalefac)[ch].s[window][sfb] = h_getbits (bb, slen0); + for ( /* sfb = 6 */ ; sfb < 12; sfb++) + for (window = 0; window < 3; window++) + (*scalefac)[ch].s[window][sfb] = h_getbits (bb, slen1); + + for (window = 0; window < 3; window++) + (*scalefac)[ch].s[window][12] = 0; + } + } else { + gint i; + const gint l_sfbtable[5] = { 0, 6, 11, 16, 21 }; + /* LONG types 0,1,3 */ + if (gr == 0) { + for (sfb = 0; sfb < 11; sfb++) { + (*scalefac)[ch].l[sfb] = h_getbits (bb, slen0); + } + for (sfb = 11; sfb < 21; sfb++) { + (*scalefac)[ch].l[sfb] = h_getbits (bb, slen1); + } + } else { + for (i = 0; i < 2; i++) { + if (si->scfsi[i][ch] == 0) { + for (sfb = l_sfbtable[i]; sfb < l_sfbtable[i + 1]; sfb++) { + (*scalefac)[ch].l[sfb] = h_getbits (bb, slen0); + } + } + } + for ( /* i = 2 */ ; i < 4; i++) { + if (si->scfsi[i][ch] == 0) { + for (sfb = l_sfbtable[i]; sfb < l_sfbtable[i + 1]; sfb++) { + (*scalefac)[ch].l[sfb] = h_getbits (bb, slen1); + } + } + } + } + (*scalefac)[ch].l[21] = 0; + } +} + +/*** new MPEG2 stuff ***/ + +static const guint nr_of_sfb_block[6][3][4] = { + {{6, 5, 5, 5}, {9, 9, 9, 9}, {6, 9, 9, 9}}, + {{6, 5, 7, 3}, {9, 9, 12, 6}, {6, 9, 12, 6}}, + {{11, 10, 0, 0}, {18, 18, 0, 0}, {15, 18, 0, 0}}, + {{7, 7, 7, 0}, {12, 12, 12, 0}, {6, 15, 12, 0}}, + {{6, 6, 6, 3}, {12, 9, 9, 6}, {6, 12, 9, 6}}, + {{8, 8, 5, 0}, {15, 12, 9, 0}, {6, 18, 9, 0}} +}; + +static void +III_get_LSF_scale_data (guint * scalefac_buffer, III_side_info_t * si, + gint gr, gint ch, mp3tl * tl) +{ + short i, j, k; + short blocktypenumber; + short blocknumber = -1; + + gr_info_t *gr_info = &(si->gr[gr][ch]); + guint scalefac_comp, int_scalefac_comp, new_slen[4]; + + huffdec_bitbuf *bb = &tl->c_impl.bb; + fr_header *hdr = &tl->fr_ps.header; + + scalefac_comp = gr_info->scalefac_compress; + + blocktypenumber = 0; + if ((gr_info->block_type == 2) && (gr_info->mixed_block_flag == 0)) + blocktypenumber = 1; + + if ((gr_info->block_type == 2) && (gr_info->mixed_block_flag == 1)) + blocktypenumber = 2; + + if (!(((hdr->mode_ext == 1) || (hdr->mode_ext == 3)) && (ch == 1))) { + if (scalefac_comp < 400) { + new_slen[0] = (scalefac_comp >> 4) / 5; + new_slen[1] = (scalefac_comp >> 4) % 5; + new_slen[2] = (scalefac_comp % 16) >> 2; + new_slen[3] = (scalefac_comp % 4); + gr_info->preflag = 0; + blocknumber = 0; + } else if (scalefac_comp < 500) { + new_slen[0] = ((scalefac_comp - 400) >> 2) / 5; + new_slen[1] = ((scalefac_comp - 400) >> 2) % 5; + new_slen[2] = (scalefac_comp - 400) % 4; + new_slen[3] = 0; + gr_info->preflag = 0; + blocknumber = 1; + } else if (scalefac_comp < 512) { + new_slen[0] = (scalefac_comp - 500) / 3; + new_slen[1] = (scalefac_comp - 500) % 3; + new_slen[2] = 0; + new_slen[3] = 0; + gr_info->preflag = 1; + blocknumber = 2; + } + } + + if ((((hdr->mode_ext == 1) || (hdr->mode_ext == 3)) && (ch == 1))) { + /* intensity_scale = scalefac_comp %2; */ + int_scalefac_comp = scalefac_comp >> 1; + + if (int_scalefac_comp < 180) { + new_slen[0] = int_scalefac_comp / 36; + new_slen[1] = (int_scalefac_comp % 36) / 6; + new_slen[2] = (int_scalefac_comp % 36) % 6; + new_slen[3] = 0; + gr_info->preflag = 0; + blocknumber = 3; + } else if (int_scalefac_comp < 244) { + new_slen[0] = ((int_scalefac_comp - 180) % 64) >> 4; + new_slen[1] = ((int_scalefac_comp - 180) % 16) >> 2; + new_slen[2] = (int_scalefac_comp - 180) % 4; + new_slen[3] = 0; + gr_info->preflag = 0; + blocknumber = 4; + } else if (int_scalefac_comp < 255) { + new_slen[0] = (int_scalefac_comp - 244) / 3; + new_slen[1] = (int_scalefac_comp - 244) % 3; + new_slen[2] = 0; + new_slen[3] = 0; + gr_info->preflag = 0; + blocknumber = 5; + } + } + + if (blocknumber < 0) { + GST_WARNING ("Invalid block number"); + return; + } + + k = 0; + for (i = 0; i < 4; i++) { + guint slen = new_slen[i]; + if (slen == 0) { + for (j = nr_of_sfb_block[blocknumber][blocktypenumber][i]; j > 0; j--) { + scalefac_buffer[k] = 0; + k++; + } + } else { + for (j = nr_of_sfb_block[blocknumber][blocktypenumber][i]; j > 0; j--) { + scalefac_buffer[k] = h_getbits (bb, slen); + k++; + } + } + } + for (; k < 45; k++) + scalefac_buffer[k] = 0; +} + +static void +III_get_LSF_scale_factors (III_scalefac_t * scalefac, III_side_info_t * si, + int gr, int ch, mp3tl * tl) +{ + int sfb, k = 0, window; + gr_info_t *gr_info = &(si->gr[gr][ch]); + guint *scalefac_buffer; + + scalefac_buffer = tl->c_impl.scalefac_buffer; + III_get_LSF_scale_data (scalefac_buffer, si, gr, ch, tl); + + if (gr_info->window_switching_flag && (gr_info->block_type == 2)) { + if (gr_info->mixed_block_flag) { /* MIXED *//* NEW - ag 11/25 */ + for (sfb = 0; sfb < 8; sfb++) { + (*scalefac)[ch].l[sfb] = scalefac_buffer[k]; + k++; + } + for (sfb = 3; sfb < 12; sfb++) + for (window = 0; window < 3; window++) { + (*scalefac)[ch].s[window][sfb] = scalefac_buffer[k]; + k++; + } + for (sfb = 12, window = 0; window < 3; window++) + (*scalefac)[ch].s[window][sfb] = 0; + } else { /* SHORT */ + for (sfb = 0; sfb < 12; sfb++) + for (window = 0; window < 3; window++) { + (*scalefac)[ch].s[window][sfb] = scalefac_buffer[k]; + k++; + } + for (sfb = 12, window = 0; window < 3; window++) + (*scalefac)[ch].s[window][sfb] = 0; + } + } else { /* LONG types 0,1,3 */ + for (sfb = 0; sfb < 21; sfb++) { + (*scalefac)[ch].l[sfb] = scalefac_buffer[k]; + k++; + } + (*scalefac)[ch].l[21] = 0; + } +} + +#define HUFFBITS guint32 +#define HTSIZE 34 +#define MXOFF 250 + +/* do the huffman-decoding */ +/* note! for counta,countb -the 4 bit value is returned in y, discard x */ +static inline gboolean +huffman_decoder (huffdec_bitbuf * bb, gint tnum, int *x, int *y, int *v, int *w) +{ + HUFFBITS level; + guint point = 0; + gboolean error = TRUE; + const struct huffcodetab *h; + + g_return_val_if_fail (tnum >= 0 && tnum <= HTSIZE, FALSE); + + /* Grab a ptr to the huffman table to use */ + h = huff_tables + tnum; + + level = (guint32) (1) << (sizeof (HUFFBITS) * 8 - 1); + + /* table 0 needs no bits */ + if (h->treelen == 0) { + *x = *y = *v = *w = 0; + return TRUE; + } + + /* Lookup in Huffman table. */ + do { + if (h->val[point][0] == 0) { /*end of tree */ + *x = h->val[point][1] >> 4; + *y = h->val[point][1] & 0xf; + + error = FALSE; + break; + } + if (h_get1bit (bb)) { + while (h->val[point][1] >= MXOFF) + point += h->val[point][1]; + point += h->val[point][1]; + } else { + while (h->val[point][0] >= MXOFF) + point += h->val[point][0]; + point += h->val[point][0]; + } + level >>= 1; + } while (level || (point < h->treelen)); + + /* Check for error. */ + if (error) { + /* set x and y to a medium value as a simple concealment */ + GST_WARNING ("Illegal Huffman code in data."); + *x = (h->xlen - 1) << 1; + *y = (h->ylen - 1) << 1; + } + + /* Process sign encodings for quadruples tables. */ + if (h->quad_table) { + *v = (*y >> 3) & 1; + *w = (*y >> 2) & 1; + *x = (*y >> 1) & 1; + *y = *y & 1; + + if (*v && (h_get1bit (bb) == 1)) + *v = -*v; + if (*w && (h_get1bit (bb) == 1)) + *w = -*w; + if (*x && (h_get1bit (bb) == 1)) + *x = -*x; + if (*y && (h_get1bit (bb) == 1)) + *y = -*y; + } + /* Process sign and escape encodings for dual tables. */ + else { + /* x and y are reversed in the test bitstream. + Reverse x and y here to make test bitstream work. */ + + if (h->linbits && ((h->xlen - 1) == *x)) + *x += h_getbits (bb, h->linbits); + if (*x && (h_get1bit (bb) == 1)) + *x = -*x; + + if (h->linbits && ((h->ylen - 1) == *y)) + *y += h_getbits (bb, h->linbits); + if (*y && (h_get1bit (bb) == 1)) + *y = -*y; + } + + return !error; +} + +static gboolean +III_huffman_decode (gint is[SBLIMIT][SSLIMIT], III_side_info_t * si, + gint ch, gint gr, gint part2_start, mp3tl * tl) +{ + guint i; + int x, y; + int v = 0, w = 0; + gint h; /* Index of the huffman table to use */ + guint region1Start; + guint region2Start; + int sfreq; + guint grBits; + gr_info_t *gi = &(si->gr[gr][ch]); + huffdec_bitbuf *bb = &tl->c_impl.bb; + frame_params *fr_ps = &tl->fr_ps; + + /* Calculate index. */ + sfreq = sfb_offset[fr_ps->header.version] + fr_ps->header.srate_idx; + + /* Find region boundary for short block case. */ + if ((gi->window_switching_flag) && (gi->block_type == 2)) { + /* Region2. */ + if (fr_ps->header.version == MPEG_VERSION_2_5 + && fr_ps->header.srate_idx == 2) { + region1Start = 72; + } else { + region1Start = 36; /* sfb[9/3]*3=36 */ + } + region2Start = 576; /* No Region2 for short block case. */ + } else { /* Find region boundary for long block case. */ + region1Start = sfBandIndex[sfreq].l[gi->region0_count + 1]; /* MI */ + region2Start = sfBandIndex[sfreq].l[gi->region0_count + gi->region1_count + 2]; /* MI */ + } + + /* Read bigvalues area. */ + /* i < SSLIMIT * SBLIMIT => gi->big_values < SSLIMIT * SBLIMIT/2 */ + for (i = 0; i < gi->big_values * 2; i += 2) { + if (i < region1Start) + h = gi->table_select[0]; + else if (i < region2Start) + h = gi->table_select[1]; + else + h = gi->table_select[2]; + + if (!huffman_decoder (bb, h, &x, &y, &v, &w)) + return FALSE; + is[i / SSLIMIT][i % SSLIMIT] = x; + is[(i + 1) / SSLIMIT][(i + 1) % SSLIMIT] = y; + } + + /* Read count1 area. */ + h = gi->count1table_select + 32; + grBits = part2_start + gi->part2_3_length; + + while ((h_sstell (bb) < grBits) && (i + 3) < (SBLIMIT * SSLIMIT)) { + if (!huffman_decoder (bb, h, &x, &y, &v, &w)) + return FALSE; + + is[i / SSLIMIT][i % SSLIMIT] = v; + is[(i + 1) / SSLIMIT][(i + 1) % SSLIMIT] = w; + is[(i + 2) / SSLIMIT][(i + 2) % SSLIMIT] = x; + is[(i + 3) / SSLIMIT][(i + 3) % SSLIMIT] = y; + i += 4; + } + + if (h_sstell (bb) > grBits) { + /* Didn't end exactly at the grBits boundary. Rewind one entry. */ + if (i >= 4) + i -= 4; + h_rewindNbits (bb, h_sstell (bb) - grBits); + } + + /* Dismiss any stuffing Bits */ + if (h_sstell (bb) < grBits) + h_flushbits (bb, grBits - h_sstell (bb)); + + g_assert (i <= SSLIMIT * SBLIMIT); + + /* Zero out rest. */ + for (; i < SSLIMIT * SBLIMIT; i++) + is[i / SSLIMIT][i % SSLIMIT] = 0; + + return TRUE; +} + +static const gint pretab[22] = + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3, 2, 0 }; + +static void +III_dequantize_sample (gint is[SBLIMIT][SSLIMIT], + gfloat xr[SBLIMIT][SSLIMIT], + III_scalefac_t * scalefac, + gr_info_t * gr_info, gint ch, gint gr, frame_params * fr_ps) +{ + int ss, sb, cb = 0, sfreq; + +// int stereo = fr_ps->stereo; + int next_cb_boundary; + int cb_begin = 0; + int cb_width = 0; + gint tmp; + gint16 pow_factor; + gboolean is_short_blk; + + /* Calculate index. */ + sfreq = sfb_offset[fr_ps->header.version] + fr_ps->header.srate_idx; + + /* choose correct scalefactor band per block type, initalize boundary */ + if (gr_info->window_switching_flag && (gr_info->block_type == 2)) { + if (gr_info->mixed_block_flag) { + next_cb_boundary = sfBandIndex[sfreq].l[1]; /* LONG blocks: 0,1,3 */ + } else { + next_cb_boundary = sfBandIndex[sfreq].s[1] * 3; /* pure SHORT block */ + cb_width = sfBandIndex[sfreq].s[1]; + cb_begin = 0; + } + } else { + next_cb_boundary = sfBandIndex[sfreq].l[1]; /* LONG blocks: 0,1,3 */ + } + + /* apply formula per block type */ + for (sb = 0; sb < SBLIMIT; sb++) { + gint sb_off = sb * 18; + is_short_blk = gr_info->window_switching_flag && + (((gr_info->block_type == 2) && (gr_info->mixed_block_flag == 0)) || + ((gr_info->block_type == 2) && gr_info->mixed_block_flag && (sb >= 2))); + + for (ss = 0; ss < SSLIMIT; ss++) { + if (sb_off + ss == next_cb_boundary) { /* Adjust critical band boundary */ + if (gr_info->window_switching_flag && (gr_info->block_type == 2)) { + if (gr_info->mixed_block_flag) { + if ((sb_off + ss) == sfBandIndex[sfreq].l[8]) { + next_cb_boundary = sfBandIndex[sfreq].s[4] * 3; + cb = 3; + cb_width = sfBandIndex[sfreq].s[cb + 1] - + sfBandIndex[sfreq].s[cb]; + cb_begin = sfBandIndex[sfreq].s[cb] * 3; + } else if ((sb_off + ss) < sfBandIndex[sfreq].l[8]) + next_cb_boundary = sfBandIndex[sfreq].l[(++cb) + 1]; + else { + next_cb_boundary = sfBandIndex[sfreq].s[(++cb) + 1] * 3; + cb_width = sfBandIndex[sfreq].s[cb + 1] - + sfBandIndex[sfreq].s[cb]; + cb_begin = sfBandIndex[sfreq].s[cb] * 3; + } + } else { + next_cb_boundary = sfBandIndex[sfreq].s[(++cb) + 1] * 3; + cb_width = sfBandIndex[sfreq].s[cb + 1] - sfBandIndex[sfreq].s[cb]; + cb_begin = sfBandIndex[sfreq].s[cb] * 3; + } + } else /* long blocks */ + next_cb_boundary = sfBandIndex[sfreq].l[(++cb) + 1]; + } + + /* Compute overall (global) scaling. */ + pow_factor = gr_info->global_gain; + + /* Do long/short dependent scaling operations. */ + if (is_short_blk) { + pow_factor -= + 8 * gr_info->subblock_gain[((sb_off + ss) - cb_begin) / cb_width]; + pow_factor -= gr_info->scalefac_scale * + (*scalefac)[ch].s[(sb_off + ss - cb_begin) / cb_width][cb]; + } else { + /* LONG block types 0,1,3 & 1st 2 subbands of switched blocks */ + pow_factor -= gr_info->scalefac_scale * + ((*scalefac)[ch].l[cb] + gr_info->preflag * pretab[cb]); + } + +#if 1 + /* g_assert (pow_factor >= 0 && pow_factor < + (sizeof (pow_2_table) / sizeof (pow_2_table[0]))); */ + xr[sb][ss] = pow_2_table[pow_factor]; +#else + /* Old method using powf */ + pow_factor -= 326; + if (pow_factor >= (-140)) + xr[sb][ss] = powf (2.0, 0.25 * (pow_factor)); + else + xr[sb][ss] = 0; +#endif + + /* Scale quantized value. */ + tmp = is[sb][ss]; + if (tmp >= 0) { + xr[sb][ss] *= pow_43_table[tmp]; + } else { + xr[sb][ss] *= -1.0f * pow_43_table[-tmp]; + } + } + } +} + +static void +III_reorder (gfloat xr[SBLIMIT][SSLIMIT], gfloat ro[SBLIMIT][SSLIMIT], + gr_info_t * gr_info, frame_params * fr_ps) +{ + int sfreq; + int sfb, sfb_start, sfb_lines; + int sb, ss, window, freq, src_line, des_line; + + /* Calculate index. */ + sfreq = sfb_offset[fr_ps->header.version] + fr_ps->header.srate_idx; + + if (gr_info->window_switching_flag && (gr_info->block_type == 2)) { + for (sb = 0; sb < SBLIMIT; sb++) + for (ss = 0; ss < SSLIMIT; ss++) + ro[sb][ss] = 0; + + if (gr_info->mixed_block_flag) { + /* NO REORDER FOR LOW 2 SUBBANDS */ + for (sb = 0; sb < 2; sb++) + for (ss = 0; ss < SSLIMIT; ss++) { + ro[sb][ss] = xr[sb][ss]; + } + /* REORDERING FOR REST SWITCHED SHORT */ + for (sfb = 3, sfb_start = sfBandIndex[sfreq].s[3], + sfb_lines = sfBandIndex[sfreq].s[4] - sfb_start; + sfb < 13; sfb++, sfb_start = sfBandIndex[sfreq].s[sfb], + (sfb_lines = sfBandIndex[sfreq].s[sfb + 1] - sfb_start)) + for (window = 0; window < 3; window++) + for (freq = 0; freq < sfb_lines; freq++) { + src_line = sfb_start * 3 + window * sfb_lines + freq; + des_line = (sfb_start * 3) + window + (freq * 3); + ro[des_line / SSLIMIT][des_line % SSLIMIT] = + xr[src_line / SSLIMIT][src_line % SSLIMIT]; + } + } else { /* pure short */ + for (sfb = 0, sfb_start = 0, sfb_lines = sfBandIndex[sfreq].s[1]; + sfb < 13; sfb++, sfb_start = sfBandIndex[sfreq].s[sfb], + (sfb_lines = sfBandIndex[sfreq].s[sfb + 1] - sfb_start)) + for (window = 0; window < 3; window++) + for (freq = 0; freq < sfb_lines; freq++) { + src_line = sfb_start * 3 + window * sfb_lines + freq; + des_line = (sfb_start * 3) + window + (freq * 3); + ro[des_line / SSLIMIT][des_line % SSLIMIT] = + xr[src_line / SSLIMIT][src_line % SSLIMIT]; + } + } + } else { /*long blocks */ + for (sb = 0; sb < SBLIMIT; sb++) + for (ss = 0; ss < SSLIMIT; ss++) + ro[sb][ss] = xr[sb][ss]; + } +} + +static void +III_i_stereo_k_values (gint is_pos, gfloat io, gint i, gfloat k[2][576]) +{ + if (is_pos == 0) { + k[0][i] = 1; + k[1][i] = 1; + } else if ((is_pos % 2) == 1) { + k[0][i] = powf (io, ((is_pos + 1) / 2.0f)); + k[1][i] = 1; + } else { + k[0][i] = 1; + k[1][i] = powf (io, (is_pos / 2.0f)); + } +} + +static void +III_stereo (gfloat xr[2][SBLIMIT][SSLIMIT], gfloat lr[2][SBLIMIT][SSLIMIT], + III_scalefac_t * scalefac, gr_info_t * gr_info, frame_params * fr_ps) +{ + int sfreq; + int stereo = fr_ps->stereo; + int ms_stereo = (fr_ps->header.mode == MPG_MD_JOINT_STEREO) && + (fr_ps->header.mode_ext & 0x2); + int i_stereo = (fr_ps->header.mode == MPG_MD_JOINT_STEREO) && + (fr_ps->header.mode_ext & 0x1); + int sfb; + int i, j, sb, ss; + short is_pos[SBLIMIT * SSLIMIT]; + gfloat is_ratio[SBLIMIT * SSLIMIT]; + gfloat io; + gfloat k[2][SBLIMIT * SSLIMIT]; + + int lsf = (fr_ps->header.version != MPEG_VERSION_1); + + if ((gr_info->scalefac_compress % 2) == 1) { + io = 0.707106781188f; + } else { + io = 0.840896415256f; + } + + /* Calculate index. */ + sfreq = sfb_offset[fr_ps->header.version] + fr_ps->header.srate_idx; + + /* intialization */ + for (i = 0; i < SBLIMIT * SSLIMIT; i++) + is_pos[i] = 7; + + if ((stereo == 2) && i_stereo) { + if (gr_info->window_switching_flag && (gr_info->block_type == 2)) { + if (gr_info->mixed_block_flag) { + int max_sfb = 0; + + for (j = 0; j < 3; j++) { + int sfbcnt; + + sfbcnt = 2; + for (sfb = 12; sfb >= 3; sfb--) { + int lines; + + lines = sfBandIndex[sfreq].s[sfb + 1] - sfBandIndex[sfreq].s[sfb]; + i = 3 * sfBandIndex[sfreq].s[sfb] + (j + 1) * lines - 1; + while (lines > 0) { + if (xr[1][i / SSLIMIT][i % SSLIMIT] != 0.0f) { + sfbcnt = sfb; + sfb = -10; + lines = -10; + } + lines--; + i--; + } + } + sfb = sfbcnt + 1; + + if (sfb > max_sfb) + max_sfb = sfb; + + while (sfb < 12) { + sb = sfBandIndex[sfreq].s[sfb + 1] - sfBandIndex[sfreq].s[sfb]; + i = 3 * sfBandIndex[sfreq].s[sfb] + j * sb; + for (; sb > 0; sb--) { + is_pos[i] = (*scalefac)[1].s[j][sfb]; + if (is_pos[i] != 7) { + if (lsf) { + III_i_stereo_k_values (is_pos[i], io, i, k); + } else { + is_ratio[i] = tanf (is_pos[i] * (float)(PI / 12)); + } + } + i++; + } + sfb++; + } + + sb = sfBandIndex[sfreq].s[12] - sfBandIndex[sfreq].s[11]; + sfb = 3 * sfBandIndex[sfreq].s[11] + j * sb; + sb = sfBandIndex[sfreq].s[13] - sfBandIndex[sfreq].s[12]; + + i = 3 * sfBandIndex[sfreq].s[11] + j * sb; + for (; sb > 0; sb--) { + is_pos[i] = is_pos[sfb]; + is_ratio[i] = is_ratio[sfb]; + k[0][i] = k[0][sfb]; + k[1][i] = k[1][sfb]; + i++; + } + } + if (max_sfb <= 3) { + i = 2; + ss = 17; + sb = -1; + while (i >= 0) { + if (xr[1][i][ss] != 0.0f) { + sb = i * 18 + ss; + i = -1; + } else { + ss--; + if (ss < 0) { + i--; + ss = 17; + } + } + } + i = 0; + while (sfBandIndex[sfreq].l[i] <= sb) + i++; + sfb = i; + i = sfBandIndex[sfreq].l[i]; + for (; sfb < 8; sfb++) { + sb = sfBandIndex[sfreq].l[sfb + 1] - sfBandIndex[sfreq].l[sfb]; + for (; sb > 0; sb--) { + is_pos[i] = (*scalefac)[1].l[sfb]; + if (is_pos[i] != 7) { + if (lsf) { + III_i_stereo_k_values (is_pos[i], io, i, k); + } else { + is_ratio[i] = tanf (is_pos[i] * (float)(PI / 12)); + } + } + i++; + } + } + } + } else { + for (j = 0; j < 3; j++) { + int sfbcnt; + + sfbcnt = -1; + for (sfb = 12; sfb >= 0; sfb--) { + int lines; + + lines = sfBandIndex[sfreq].s[sfb + 1] - sfBandIndex[sfreq].s[sfb]; + i = 3 * sfBandIndex[sfreq].s[sfb] + (j + 1) * lines - 1; + while (lines > 0) { + if (xr[1][i / SSLIMIT][i % SSLIMIT] != 0.0f) { + sfbcnt = sfb; + sfb = -10; + lines = -10; + } + lines--; + i--; + } + } + sfb = sfbcnt + 1; + while (sfb < 12) { + sb = sfBandIndex[sfreq].s[sfb + 1] - sfBandIndex[sfreq].s[sfb]; + i = 3 * sfBandIndex[sfreq].s[sfb] + j * sb; + for (; sb > 0; sb--) { + is_pos[i] = (*scalefac)[1].s[j][sfb]; + if (is_pos[i] != 7) { + if (lsf) { + III_i_stereo_k_values (is_pos[i], io, i, k); + } else { + is_ratio[i] = tanf (is_pos[i] * (float)(PI / 12)); + } + } + i++; + } + sfb++; + } + + sb = sfBandIndex[sfreq].s[12] - sfBandIndex[sfreq].s[11]; + sfb = 3 * sfBandIndex[sfreq].s[11] + j * sb; + sb = sfBandIndex[sfreq].s[13] - sfBandIndex[sfreq].s[12]; + + i = 3 * sfBandIndex[sfreq].s[11] + j * sb; + for (; sb > 0; sb--) { + is_pos[i] = is_pos[sfb]; + is_ratio[i] = is_ratio[sfb]; + k[0][i] = k[0][sfb]; + k[1][i] = k[1][sfb]; + i++; + } + } + } + } else { + i = 31; + ss = 17; + sb = 0; + while (i >= 0) { + if (xr[1][i][ss] != 0.0f) { + sb = i * 18 + ss; + i = -1; + } else { + ss--; + if (ss < 0) { + i--; + ss = 17; + } + } + } + i = 0; + while (sfBandIndex[sfreq].l[i] <= sb) + i++; + sfb = i; + i = sfBandIndex[sfreq].l[i]; + for (; sfb < 21; sfb++) { + sb = sfBandIndex[sfreq].l[sfb + 1] - sfBandIndex[sfreq].l[sfb]; + for (; sb > 0; sb--) { + is_pos[i] = (*scalefac)[1].l[sfb]; + if (is_pos[i] != 7) { + if (lsf) { + III_i_stereo_k_values (is_pos[i], io, i, k); + } else { + is_ratio[i] = tanf (is_pos[i] * (float)(PI / 12)); + } + } + i++; + } + } + sfb = sfBandIndex[sfreq].l[20]; + if (i > sfBandIndex[sfreq].l[21]) + sb = 576 - i; + else + sb = 576 - sfBandIndex[sfreq].l[21]; + + for (; sb > 0; sb--) { + is_pos[i] = is_pos[sfb]; + is_ratio[i] = is_ratio[sfb]; + k[0][i] = k[0][sfb]; + k[1][i] = k[1][sfb]; + i++; + } + } + } +#if 0 + for (ch = 0; ch < 2; ch++) + for (sb = 0; sb < SBLIMIT; sb++) + for (ss = 0; ss < SSLIMIT; ss++) + lr[ch][sb][ss] = 0; +#else + memset (lr, 0, sizeof (gfloat) * 2 * SBLIMIT * SSLIMIT); +#endif + + if (stereo == 2) + for (sb = 0; sb < SBLIMIT; sb++) + for (ss = 0; ss < SSLIMIT; ss++) { + i = (sb * 18) + ss; + if (is_pos[i] == 7) { + if (ms_stereo) { + lr[0][sb][ss] = (xr[0][sb][ss] + xr[1][sb][ss]) * 0.707106781188f; + lr[1][sb][ss] = (xr[0][sb][ss] - xr[1][sb][ss]) * 0.707106781188f; + } else { + lr[0][sb][ss] = xr[0][sb][ss]; + lr[1][sb][ss] = xr[1][sb][ss]; + } + } else if (i_stereo) { + if (lsf) { + lr[0][sb][ss] = xr[0][sb][ss] * k[0][i]; + lr[1][sb][ss] = xr[0][sb][ss] * k[1][i]; + } else { + lr[0][sb][ss] = xr[0][sb][ss] * (is_ratio[i] / (1 + is_ratio[i])); + lr[1][sb][ss] = xr[0][sb][ss] * (1 / (1 + is_ratio[i])); + } + } else { + GST_WARNING ("Error in stereo processing"); + } + } else /* mono , bypass xr[0][][] to lr[0][][] */ + for (sb = 0; sb < SBLIMIT; sb++) + for (ss = 0; ss < SSLIMIT; ss++) + lr[0][sb][ss] = xr[0][sb][ss]; + +} + +static const gfloat cs_table[8] = { + 0.85749292571f, 0.88174199732f, 0.94962864910f, 0.98331459249f, + 0.99551781607f, 0.99916055818f, 0.99989919524f, 0.99999315507f +}; + +static const gfloat ca_table[8] = { + -0.51449575543f, -0.47173196857f, -0.31337745420f, -0.18191319961f, + -0.09457419253f, -0.04096558289f, -0.01419856857f, -0.00369997467f +}; + +static void +III_antialias (gfloat xr[SBLIMIT][SSLIMIT], + gfloat hybridIn[SBLIMIT][SSLIMIT], gr_info_t * gr_info) +{ + gfloat bu, bd; /* upper and lower butterfly inputs */ + int ss, sb, sblim; + + /* clear all inputs */ + for (sb = 0; sb < SBLIMIT; sb++) + for (ss = 0; ss < SSLIMIT; ss++) + hybridIn[sb][ss] = xr[sb][ss]; + + if (gr_info->window_switching_flag && (gr_info->block_type == 2) && + !gr_info->mixed_block_flag) + return; + + if (gr_info->window_switching_flag && gr_info->mixed_block_flag && + (gr_info->block_type == 2)) + sblim = 1; + else + sblim = SBLIMIT - 1; + + /* 31 alias-reduction operations between each pair of sub-bands */ + /* with 8 butterflies between each pair */ + + for (sb = 0; sb < sblim; sb++) + for (ss = 0; ss < 8; ss++) { + bu = xr[sb][17 - ss]; + bd = xr[sb + 1][ss]; + hybridIn[sb][17 - ss] = (bu * cs_table[ss]) - (bd * ca_table[ss]); + hybridIn[sb + 1][ss] = (bd * cs_table[ss]) + (bu * ca_table[ss]); + } +} + +static inline void imdct_9pt (gfloat invec[9], gfloat outvec[9]); + +#define ICOS24(i) (cos24_table[(i)]) +#define COS18(i) (cos18_table[(i)]) +#define ICOS36_A(i) (icos72_table[4*(i)+1]) +#define ICOS72_A(i) (icos72_table[2*(i)]) + +/* Short (12 point) version of the IMDCT performed + as 2 x 3-point IMDCT */ +static inline void +inv_mdct_s (gfloat invec[6], gfloat outvec[12]) +{ + int i; + gfloat H[6], h[6], even_idct[3], odd_idct[3], *tmp; + gfloat t0, t1, t2; + /* sqrt (3) / 2.0 */ + const gfloat sqrt32 = 0.8660254037844385965883020617184229195117950439453125f; + + /* Preprocess the input to the two 3-point IDCT's */ + tmp = invec; + for (i = 1; i < 6; i++) { + H[i] = tmp[0] + tmp[1]; + tmp++; + } + + /* 3-point IMDCT */ + t0 = H[4] / 2.0f + invec[0]; + t1 = H[2] * sqrt32; + even_idct[0] = t0 + t1; + even_idct[1] = invec[0] - H[4]; + even_idct[2] = t0 - t1; + /* END 3-point IMDCT */ + + /* 3-point IMDCT */ + t2 = H[3] + H[5]; + + t0 = (t2) / 2.0f + H[1]; + t1 = (H[1] + H[3]) * sqrt32; + odd_idct[0] = t0 + t1; + odd_idct[1] = H[1] - t2; + odd_idct[2] = t0 - t1; + /* END 3-point IMDCT */ + + /* Post-Twiddle */ + odd_idct[0] *= ICOS24 (1); + odd_idct[1] *= ICOS24 (5); + odd_idct[2] *= ICOS24 (9); + + h[0] = (even_idct[0] + odd_idct[0]) * ICOS24 (0); + h[1] = (even_idct[1] + odd_idct[1]) * ICOS24 (2); + h[2] = (even_idct[2] + odd_idct[2]) * ICOS24 (4); + + h[3] = (even_idct[2] - odd_idct[2]) * ICOS24 (6); + h[4] = (even_idct[1] - odd_idct[1]) * ICOS24 (8); + h[5] = (even_idct[0] - odd_idct[0]) * ICOS24 (10); + + /* Rearrange the 6 values from the IDCT to the output vector */ + outvec[0] = h[3]; + outvec[1] = h[4]; + outvec[2] = h[5]; + outvec[3] = -h[5]; + outvec[4] = -h[4]; + outvec[5] = -h[3]; + outvec[6] = -h[2]; + outvec[7] = -h[1]; + outvec[8] = -h[0]; + outvec[9] = -h[0]; + outvec[10] = -h[1]; + outvec[11] = -h[2]; +} + +static inline void +inv_mdct_l (gfloat invec[18], gfloat outvec[36]) +{ + int i; + gfloat H[17], h[18], even[9], odd[9], even_idct[9], odd_idct[9], *tmp; + + for (i = 0; i < 17; i++) + H[i] = invec[i] + invec[i + 1]; + + even[0] = invec[0]; + odd[0] = H[0]; + tmp = H; + for (i = 1; i < 9; i++) { + even[i] = tmp[1]; + odd[i] = tmp[0] + tmp[2]; + tmp += 2; + } + + imdct_9pt (even, even_idct); + imdct_9pt (odd, odd_idct); + + for (i = 0; i < 9; i++) { + odd_idct[i] *= ICOS36_A (i); + h[i] = (even_idct[i] + odd_idct[i]) * ICOS72_A (i); + } + for ( /* i = 9 */ ; i < 18; i++) { + h[i] = (even_idct[17 - i] - odd_idct[17 - i]) * ICOS72_A (i); + } + + /* Rearrange the 18 values from the IDCT to the output vector */ + outvec[0] = h[9]; + outvec[1] = h[10]; + outvec[2] = h[11]; + outvec[3] = h[12]; + outvec[4] = h[13]; + outvec[5] = h[14]; + outvec[6] = h[15]; + outvec[7] = h[16]; + outvec[8] = h[17]; + + outvec[9] = -h[17]; + outvec[10] = -h[16]; + outvec[11] = -h[15]; + outvec[12] = -h[14]; + outvec[13] = -h[13]; + outvec[14] = -h[12]; + outvec[15] = -h[11]; + outvec[16] = -h[10]; + outvec[17] = -h[9]; + + outvec[35] = outvec[18] = -h[8]; + outvec[34] = outvec[19] = -h[7]; + outvec[33] = outvec[20] = -h[6]; + outvec[32] = outvec[21] = -h[5]; + outvec[31] = outvec[22] = -h[4]; + outvec[30] = outvec[23] = -h[3]; + outvec[29] = outvec[24] = -h[2]; + outvec[28] = outvec[25] = -h[1]; + outvec[27] = outvec[26] = -h[0]; +} + +static inline void +imdct_9pt (gfloat invec[9], gfloat outvec[9]) +{ + int i; + gfloat even_idct[5], odd_idct[4]; + gfloat t0, t1, t2; + + /* BEGIN 5 Point IMDCT */ + t0 = invec[6] / 2.0f + invec[0]; + t1 = invec[0] - invec[6]; + t2 = invec[2] - invec[4] - invec[8]; + + even_idct[0] = t0 + invec[2] * COS18 (2) + + invec[4] * COS18 (4) + invec[8] * COS18 (8); + + even_idct[1] = t2 / 2.0f + t1; + even_idct[2] = t0 - invec[2] * COS18 (8) + - invec[4] * COS18 (2) + invec[8] * COS18 (4); + + even_idct[3] = t0 - invec[2] * COS18 (4) + + invec[4] * COS18 (8) - invec[8] * COS18 (2); + + even_idct[4] = t1 - t2; + /* END 5 Point IMDCT */ + + /* BEGIN 4 Point IMDCT */ + { + gfloat odd1, odd2; + odd1 = invec[1] + invec[3]; + odd2 = invec[3] + invec[5]; + t0 = (invec[5] + invec[7]) * 0.5f + invec[1]; + + odd_idct[0] = t0 + odd1 * COS18 (2) + odd2 * COS18 (4); + odd_idct[1] = (invec[1] - invec[5]) * 1.5f - invec[7]; + odd_idct[2] = t0 - odd1 * COS18 (8) - odd2 * COS18 (2); + odd_idct[3] = t0 - odd1 * COS18 (4) + odd2 * COS18 (8); + } + /* END 4 Point IMDCT */ + + /* Adjust for non power of 2 IDCT */ + odd_idct[0] += invec[7] * COS18 (8); + odd_idct[1] -= invec[7] * COS18 (6); + odd_idct[2] += invec[7] * COS18 (4); + odd_idct[3] -= invec[7] * COS18 (2); + + /* Post-Twiddle */ + odd_idct[0] *= 0.5f / COS18 (1); + odd_idct[1] *= 0.5f / COS18 (3); + odd_idct[2] *= 0.5f / COS18 (5); + odd_idct[3] *= 0.5f / COS18 (7); + + for (i = 0; i < 4; i++) { + outvec[i] = even_idct[i] + odd_idct[i]; + } + outvec[4] = even_idct[4]; + /* Mirror into the other half of the vector */ + for (i = 5; i < 9; i++) { + outvec[i] = even_idct[8 - i] - odd_idct[8 - i]; + } +} + +static void +inv_mdct (gfloat in[18], gfloat out[36], gint block_type) +{ + int i, j; + + if (block_type == 2) { + gfloat tmp[12], tin[18], *tmpptr; + for (i = 0; i < 36; i++) { + out[i] = 0.0f; + } + + /* The short blocks input vector has to be re-arranged */ + tmpptr = tin; + for (i = 0; i < 3; i++) { + gfloat *v = &(in[i]); /* Input vector */ + for (j = 0; j < 6; j++) { + tmpptr[j] = *v; + v += 3; + } + tmpptr += 6; + } + + for (i = 0; i < 18; i += 6) { + tmpptr = &(out[i + 6]); + + inv_mdct_s (&(tin[i]), tmp); + + /* The three short blocks must be windowed and overlapped added + * with each other */ + for (j = 0; j < 12; j++) { + tmpptr[j] += tmp[j] * mdct_swin[2][j]; + } + } /* end for (i... */ + } else { /* block_type != 2 */ + inv_mdct_l (in, out); + + /* Window the imdct result */ + for (i = 0; i < 36; i++) + out[i] = out[i] * mdct_swin[block_type][i]; + } +} +static void +init_hybrid (mp3cimpl_info * c_impl) +{ + int i, j, k; + + for (i = 0; i < 2; i++) + for (j = 0; j < SBLIMIT; j++) + for (k = 0; k < SSLIMIT; k++) + c_impl->prevblck[i][j][k] = 0.0f; +} + +/* III_hybrid + * Parameters: + * double fsIn[SSLIMIT] - freq samples per subband in + * double tsOut[SSLIMIT] - time samples per subband out + * int sb, ch + * gr_info_t *gr_info + * frame_params *fr_ps + */ +static void +III_hybrid (gfloat fsIn[SSLIMIT], gfloat tsOut[SSLIMIT], int sb, int ch, + gr_info_t * gr_info, mp3tl * tl) +{ + gfloat rawout[36]; + int i; + i = (gr_info->window_switching_flag && gr_info->mixed_block_flag && + (sb < 2)) ? 0 : gr_info->block_type; + + inv_mdct (fsIn, rawout, i); + + /* overlap addition */ + for (i = 0; i < SSLIMIT; i++) { + tsOut[i] = rawout[i] + tl->c_impl.prevblck[ch][sb][i]; + tl->c_impl.prevblck[ch][sb][i] = rawout[i + 18]; + } +} + +/* Invert the odd frequencies for odd subbands in preparation for polyphase + * filtering */ +static void +III_frequency_inversion (gfloat hybridOut[SBLIMIT][SSLIMIT], + mp3tl * tl ATTR_UNUSED) +{ + guint ss, sb; + + for (ss = 1; ss < 18; ss += 2) { + for (sb = 1; sb < SBLIMIT; sb += 2) { + hybridOut[sb][ss] = -hybridOut[sb][ss]; + } + } +} + +static void +III_subband_synthesis (mp3tl * tl, frame_params * fr_ps, + gfloat hybridOut[SBLIMIT][SSLIMIT], gint channel, + short samples[SSLIMIT][SBLIMIT]) +{ + gint ss, sb; + gfloat polyPhaseIn[SBLIMIT]; /* PolyPhase Input. */ + + for (ss = 0; ss < 18; ss++) { + /* Each of the 32 subbands has 18 samples. On each iteration, we take + * one sample from each subband, (32 samples), and use a 32 point DCT + * to perform matrixing, and copy the result into the synthesis + * buffer fifo. */ + for (sb = 0; sb < SBLIMIT; sb++) { + polyPhaseIn[sb] = hybridOut[sb][ss]; + } + + mp3_SubBandSynthesis (tl, fr_ps, polyPhaseIn, channel, + &(tl->pcm_sample[channel][ss][0])); + } +} + +static Mp3TlRetcode +c_decode_mp3 (mp3tl * tl) +{ + III_scalefac_t III_scalefac; + III_side_info_t III_side_info; + huffdec_bitbuf *bb; + guint gr, ch, sb; + gint diff; + fr_header *hdr; + guint8 side_info[32]; /* At most 32 bytes side info for MPEG-1 stereo */ + gboolean MainDataOK; + + hdr = &tl->fr_ps.header; + bb = &tl->c_impl.bb; + + /* Check enough side_info data available */ + if (bs_bits_avail (tl->bs) < hdr->side_info_slots * 8) + return MP3TL_ERR_NEED_DATA; + + bs_getbytes (tl->bs, side_info, hdr->side_info_slots); + if (!III_get_side_info (side_info, &III_side_info, &tl->fr_ps)) { + GST_DEBUG ("Bad side info"); + return MP3TL_ERR_BAD_FRAME; + } + + /* Check enough main_data available */ + if (bs_bits_avail (tl->bs) < hdr->main_slots * 8) + return MP3TL_ERR_NEED_DATA; + + /* Verify that sufficient main_data was extracted from */ + /* the previous sync interval */ + diff = tl->c_impl.main_data_end - III_side_info.main_data_begin; + MainDataOK = (diff >= 0); + if (!MainDataOK) { + GST_DEBUG ("MainDataEnd: %d MainDataBegin: %d delta: %d", + tl->c_impl.main_data_end, III_side_info.main_data_begin, diff); + } + + /* Copy the remaining main data in the bit reservoir to the start of the + * huffman bit buffer, and then append the incoming bytes */ + if (MainDataOK) { + if (diff > 0) { + memmove (tl->c_impl.hb_buf, tl->c_impl.hb_buf + diff, + III_side_info.main_data_begin); + tl->c_impl.main_data_end = III_side_info.main_data_begin; + } + } + /* And append the incoming bytes to the reservoir */ + bs_getbytes (tl->bs, tl->c_impl.hb_buf + tl->c_impl.main_data_end, + hdr->main_slots); + tl->c_impl.main_data_end += hdr->main_slots; + + if (!MainDataOK) { + GST_DEBUG ("Bad frame - not enough main data bits"); + return MP3TL_ERR_BAD_FRAME; + } + + /* And setup the huffman bitstream reader for this data */ + h_setbuf (bb, tl->c_impl.hb_buf, tl->c_impl.main_data_end); + + /* Clear the scale factors to avoid problems with badly coded files + * that try to reuse scalefactors from the first granule when they didn't + * supply them. */ + memset (III_scalefac, 0, sizeof (III_scalefac_t)); + + for (gr = 0; gr < tl->n_granules; gr++) { + gfloat lr[2][SBLIMIT][SSLIMIT], ro[2][SBLIMIT][SSLIMIT]; + + for (ch = 0; ch < hdr->channels; ch++) { + gint is[SBLIMIT][SSLIMIT]; /* Quantized samples. */ + int part2_start; + + part2_start = h_sstell (bb); + if (hdr->version == MPEG_VERSION_1) { + III_get_scale_factors (&III_scalefac, &III_side_info, gr, ch, tl); + } else { + III_get_LSF_scale_factors (&III_scalefac, &III_side_info, gr, ch, tl); + } + + if (III_side_info.gr[gr][ch].big_values > ((SBLIMIT * SSLIMIT) / 2)) { + GST_DEBUG ("Bad side info decoding frame: big_values"); + return MP3TL_ERR_BAD_FRAME; + } + + if (!III_huffman_decode (is, &III_side_info, ch, gr, part2_start, tl)) { + GST_DEBUG ("Failed to decode huffman info"); + return MP3TL_ERR_BAD_FRAME; + } + +#ifdef HUFFMAN_DEBUG + { + gint i, j; + fprintf (stderr, "\nFrame %" G_GUINT64_FORMAT ", granule %d, channel %d\n", + tl->frame_num, gr, ch); + for (i = 0 ; i < 32; i++) { + fprintf (stderr, "SB %02d: ", i); + for (j = 0; j < 18; j++) { + fprintf (stderr, "%4d ", is[i][j]); + } + fprintf (stderr, "\n"); + } + } +#endif + + III_dequantize_sample (is, ro[ch], &III_scalefac, + &(III_side_info.gr[gr][ch]), ch, gr, &tl->fr_ps); + +#ifdef DEQUANT_DEBUG + { + gint i, j; + fprintf (stderr, "\nFrame %" G_GUINT64_FORMAT ", granule %d, channel %d\n", + tl->frame_num, gr, ch); + for (i = 0 ; i < 32; i++) { + fprintf (stderr, "SB %02d: ", i); + for (j = 0; j < 18; j++) { + fprintf (stderr, "%+f ", ro[ch][i][j]); + } + fprintf (stderr, "\n"); + } + } +#endif + } + + III_stereo (ro, lr, &III_scalefac, &(III_side_info.gr[gr][0]), &tl->fr_ps); + + for (ch = 0; ch < hdr->channels; ch++) { + gfloat re[SBLIMIT][SSLIMIT]; + gfloat hybridIn[SBLIMIT][SSLIMIT]; /* Hybrid filter input */ + gfloat hybridOut[SBLIMIT][SSLIMIT]; /* Hybrid filter out */ + gr_info_t *gi = &(III_side_info.gr[gr][ch]); + + III_reorder (lr[ch], re, gi, &tl->fr_ps); + + /* Antialias butterflies. */ + III_antialias (re, hybridIn, gi); +#if 0 + int i; + g_print ("HybridIn\n"); + for (sb = 0; sb < SBLIMIT; sb++) { + g_print ("SB %02d: ", sb); + for (i = 0; i < SSLIMIT; i++) { + g_print ("%06f ", hybridIn[sb][i]); + } + g_print ("\n"); + } +#endif + + for (sb = 0; sb < SBLIMIT; sb++) { + /* Hybrid synthesis. */ + III_hybrid (hybridIn[sb], hybridOut[sb], sb, ch, gi, tl); + } + + /* Frequency inversion for polyphase. Invert odd numbered indices */ + III_frequency_inversion (hybridOut, tl); + +#if 0 + g_print ("HybridOut\n"); + for (sb = 0; sb < SBLIMIT; sb++) { + g_print ("SB %02d: ", sb); + for (i = 0; i < SSLIMIT; i++) { + g_print ("%06f ", hybridOut[sb][i]); + } + g_print ("\n"); + } +#endif + + /* Polyphase synthesis */ + III_subband_synthesis (tl, &tl->fr_ps, hybridOut, ch, tl->pcm_sample[ch]); +#if 0 + if (ch == 0) { + g_print ("synth\n"); + + for (i = 0; i < SSLIMIT; i++) { + g_print ("SS %02d: ", i); + for (sb = 0; sb < SBLIMIT; sb++) { + g_print ("%04d ", tl->pcm_sample[ch][sb][i]); + } + g_print ("\n"); + } + } +#endif + + } + /* Output PCM sample points for one granule. */ + out_fifo (tl->pcm_sample, 18, &tl->fr_ps, tl->sample_buf, + &tl->sample_w, SAMPLE_BUF_SIZE); + } + + return MP3TL_ERR_OK; +} + +static gboolean +mp3_c_init (mp3tl * tl) +{ + init_hybrid (&tl->c_impl); + return TRUE; +} + +static void +mp3_c_flush (mp3tl * tl) +{ + init_hybrid (&tl->c_impl); + h_reset (&tl->c_impl.bb); + memset (tl->c_impl.hb_buf, 0, HDBB_BUFSIZE); + tl->c_impl.main_data_end = 0; +} + +/* Minimum size in bytes of an MP3 frame */ +#define MIN_FRAME_SIZE 24 + +mp3tl * +mp3tl_new (Bit_stream_struc * bs, Mp3TlMode mode) +{ + mp3tl *tl; + void *alloc_memory; + + g_return_val_if_fail (bs != NULL, NULL); + g_return_val_if_fail (mode == MP3TL_MODE_16BIT, NULL); + + alloc_memory = calloc(1, (sizeof (mp3tl) + __CACHE_LINE_BYTES)); + + tl = (mp3tl *) __CACHE_LINE_ALIGN(alloc_memory); + g_return_val_if_fail (tl != NULL, NULL); + + tl->alloc_memory = alloc_memory; + tl->bs = bs; + tl->need_sync = TRUE; + tl->need_header = TRUE; + tl->at_eos = FALSE; + tl->lost_sync = TRUE; + + tl->sample_size = 16; + tl->sample_buf = NULL; + tl->sample_w = 0; + tl->stream_layer = 0; + tl->error_count = 0; + + tl->fr_ps.alloc = NULL; + init_syn_filter (&tl->fr_ps); + + tl->free_first = TRUE; + + if (!mp3_c_init (tl)) { + free (tl); + return NULL; + } + + return tl; +} + +void +mp3tl_free (mp3tl * tl) +{ + g_return_if_fail (tl != NULL); + + free (tl->alloc_memory); +}; + +void +mp3tl_set_eos (mp3tl * tl, gboolean is_eos) +{ + tl->at_eos = is_eos; +} + +Mp3TlRetcode +mp3tl_sync (mp3tl * tl) +{ + g_return_val_if_fail (tl != NULL, MP3TL_ERR_PARAM); + + if (tl->need_sync) { + guint64 sync_start ATTR_UNUSED; + + /* Find a sync word, with valid header */ + bs_reset (tl->bs); + + /* Need at least sync word + header bits */ + if (bs_bits_avail (tl->bs) < SYNC_WORD_LNGTH + HEADER_LNGTH) + return MP3TL_ERR_NO_SYNC; + + sync_start = bs_pos (tl->bs); + GST_LOG ("Starting sync search at %" G_GUINT64_FORMAT " (byte %" + G_GUINT64_FORMAT ")", sync_start, sync_start / 8); + + do { + gboolean sync; + guint64 offset; + guint64 frame_start; + fr_header *hdr = &tl->fr_ps.header; + gboolean valid = TRUE; + guint64 total_offset ATTR_UNUSED; + + sync = bs_seek_sync (tl->bs); + offset = bs_read_pos (tl->bs) - bs_pos (tl->bs); + total_offset = bs_read_pos (tl->bs) - sync_start; + + if (!sync) { + /* Leave the last byte in the stream, as it might be the first byte + * of our sync word later */ + if (offset > 8) + bs_consume (tl->bs, (guint32) (offset - 8)); + + tl->lost_sync = TRUE; + GST_LOG ("Not enough data in buffer for a sync sequence"); + return MP3TL_ERR_NO_SYNC; + } + g_assert (offset >= SYNC_WORD_LNGTH); + + /* Check if we skipped any data to find the sync word */ + if (offset != SYNC_WORD_LNGTH) { + GST_DEBUG ("Skipped %" G_GUINT64_FORMAT " bits to find sync", offset); + tl->lost_sync = TRUE; + } + + /* Remember the start of frame */ + frame_start = bs_read_pos (tl->bs) - SYNC_WORD_LNGTH; + + /* Look ahead and check the header details */ + if (bs_bits_avail (tl->bs) < HEADER_LNGTH) { + /* Consume bytes to the start of sync word and go get more data */ + bs_consume (tl->bs, (guint32) (offset - SYNC_WORD_LNGTH)); + tl->lost_sync = TRUE; + + GST_LOG ("Not enough data in buffer to read header"); + return MP3TL_ERR_NO_SYNC; + } + + /* Read header bits */ + GST_LOG ("Reading header at %" G_GUINT64_FORMAT " (byte %" + G_GUINT64_FORMAT ")", bs_read_pos (tl->bs), bs_read_pos (tl->bs) / 8); + if (!read_header (tl, hdr)) { + valid = FALSE; + GST_LOG ("Bad header"); + } else { + /* Fill out the derived header details */ + hdr->sample_size = tl->sample_size; + if (!set_hdr_data_slots (hdr)) { + GST_LOG ("Bad header (slots)"); + valid = FALSE; + } + + /* Data is not allowed to suddenly change to a different layer */ + if (tl->stream_layer != 0 && hdr->layer != tl->stream_layer) { + GST_LOG ("Bad header (layer changed)"); + valid = FALSE; + } + } + + /* FIXME: Could check the CRC to confirm a sync point */ + + /* If we skipped any to find the sync, and we have enough data, + * jump ahead to where we expect the next frame to be and confirm + * that there is a sync word there */ + if (valid && tl->lost_sync) { + gint64 remain; + + remain = hdr->frame_bits - (bs_read_pos (tl->bs) - frame_start); + if (hdr->frame_bits < (8 * MIN_FRAME_SIZE)) { + GST_LOG ("Header indicates a frame too small to be correct"); + valid = FALSE; + } else if (bs_bits_avail (tl->bs) >= hdr->frame_bits) { + guint32 sync_word; + fr_header next_hdr; + + GST_DEBUG ("Peeking ahead %u bits to check sync (%" + G_GINT64_FORMAT ", %" G_GUINT64_FORMAT ", %" + G_GUINT64_FORMAT ")", hdr->frame_bits, remain, + (guint64) bs_read_pos (tl->bs), (guint64) frame_start); + + /* Skip 'remain' bits */ + bs_skipbits (tl->bs, (guint32) (remain - 1)); + + /* Read a sync word and check */ + sync_word = bs_getbits_aligned (tl->bs, SYNC_WORD_LNGTH); + + if (sync_word != SYNC_WORD) { + valid = FALSE; + GST_LOG ("No next sync word %u bits later @ %" G_GUINT64_FORMAT + ". Got 0x%03x", hdr->frame_bits, + bs_read_pos (tl->bs) - SYNC_WORD_LNGTH, sync_word); + } else if (!read_header (tl, &next_hdr)) { + GST_LOG ("Invalid header at next indicated frame"); + valid = FALSE; + } else { + /* Check that the samplerate and layer for the next header is + * the same */ + if ((hdr->layer != next_hdr.layer) || + (hdr->sample_rate != next_hdr.sample_rate) || + (hdr->copyright != next_hdr.copyright) || + (hdr->original != next_hdr.original) || + (hdr->emphasis != next_hdr.emphasis)) { + valid = FALSE; + GST_LOG ("Invalid header at next indicated frame"); + } + } + + if (valid) + GST_LOG ("Good - found a valid frame %u bits later.", + hdr->frame_bits); + } else if (!tl->at_eos) { + GST_LOG ("Not enough data in buffer to test next header"); + + /* Not at the end of stream, so wait for more data to validate the + * frame with */ + /* Consume bytes to the start of sync word and go get more data */ + bs_consume (tl->bs, (guint32) (offset - SYNC_WORD_LNGTH)); + return MP3TL_ERR_NO_SYNC; + } + } + + if (!valid) { + /* Move past the first byte of the sync word and keep looking */ + bs_consume (tl->bs, (guint32) (offset - SYNC_WORD_LNGTH + 8)); + } else { + /* Consume everything up to the start of sync word */ + if (offset > SYNC_WORD_LNGTH) + bs_consume (tl->bs, (guint32) (offset - SYNC_WORD_LNGTH)); + + tl->need_sync = FALSE; + GST_DEBUG ("OK after %" G_GUINT64_FORMAT " offset", + total_offset - SYNC_WORD_LNGTH); + } + } while (tl->need_sync); + + if (bs_pos (tl->bs) != sync_start) + GST_DEBUG ("Skipped %" G_GUINT64_FORMAT " bits, found sync", + bs_pos (tl->bs) - sync_start); + } + + return MP3TL_ERR_OK; +} + +Mp3TlRetcode +mp3tl_decode_header (mp3tl * tl, const fr_header ** ret_hdr) +{ + fr_header *hdr; + Mp3TlRetcode ret; + + g_return_val_if_fail (tl != NULL, MP3TL_ERR_PARAM); + hdr = &tl->fr_ps.header; + if (G_LIKELY (ret_hdr != NULL)) + *ret_hdr = hdr; + + if (!tl->need_header) + return MP3TL_ERR_OK; + + if ((ret = mp3tl_sync (tl)) != MP3TL_ERR_OK) + return ret; + + /* Restart the read ptr and move past the sync word */ + bs_reset (tl->bs); + bs_getbits (tl->bs, SYNC_WORD_LNGTH); + + /* If there are less than header bits available, something went + * wrong in the sync */ + g_assert (bs_bits_avail (tl->bs) >= HEADER_LNGTH); + + GST_DEBUG ("Frame is %d bytes (%d bits)", + hdr->frame_bits / 8, hdr->frame_bits); + + /* Consume the header and sync word */ + bs_consume (tl->bs, SYNC_WORD_LNGTH + HEADER_LNGTH); + + tl->need_header = FALSE; + return MP3TL_ERR_OK; +} + +Mp3TlRetcode +mp3tl_gather_frame (mp3tl * tl, guint64 * _offset, gint * _length) +{ + guint64 sync_start ATTR_UNUSED; + gboolean sync; + guint64 offset; + guint64 frame_start; + fr_header *hdr; + gboolean valid = TRUE; + + /* Find a sync word, with valid header */ + bs_reset (tl->bs); + + /* Need at least sync word + header bits */ + if (bs_bits_avail (tl->bs) < SYNC_WORD_LNGTH + HEADER_LNGTH) + return MP3TL_ERR_NO_SYNC; + + sync_start = bs_pos (tl->bs); + GST_LOG ("Starting sync search at %" G_GUINT64_FORMAT " (byte %" + G_GUINT64_FORMAT ")", sync_start, sync_start / 8); + + hdr = &tl->fr_ps.header; + sync = bs_seek_sync (tl->bs); + offset = bs_read_pos (tl->bs) - bs_pos (tl->bs); + + if (!sync) { + GST_LOG ("Not enough data for a sync sequence"); + return MP3TL_ERR_NO_SYNC; + } + + /* Check if we skipped any data to find the sync word */ + if (offset != SYNC_WORD_LNGTH) { + GST_DEBUG ("Skipped %" G_GUINT64_FORMAT " bits to find sync", offset); + } + + /* Remember the start of frame */ + frame_start = bs_read_pos (tl->bs) - SYNC_WORD_LNGTH; + + /* Look ahead and check the header details */ + if (bs_bits_avail (tl->bs) < HEADER_LNGTH) { + GST_LOG ("Not enough data to read header"); + return MP3TL_ERR_NO_SYNC; + } + + /* Read header bits */ + GST_LOG ("Reading header at %" G_GUINT64_FORMAT " (byte %" + G_GUINT64_FORMAT ")", bs_read_pos (tl->bs), bs_read_pos (tl->bs) / 8); + if (!read_header (tl, hdr)) { + valid = FALSE; + GST_LOG ("Bad header"); + } else { + /* Fill out the derived header details */ + hdr->sample_size = tl->sample_size; + if (!set_hdr_data_slots (hdr)) { + GST_LOG ("Bad header (slots)"); + valid = FALSE; + } + + /* Data is not allowed to suddenly change to a different layer */ + if (tl->stream_layer != 0 && hdr->layer != tl->stream_layer) { + GST_LOG ("Bad header (layer changed)"); + valid = FALSE; + } + } + + /* If we skipped any to find the sync, and we have enough data, + * jump ahead to where we expect the next frame to be and confirm + * that there is a sync word there */ + if (valid) { + gint64 remain; + + remain = hdr->frame_bits - (bs_read_pos (tl->bs) - frame_start); + if (hdr->frame_bits < (8 * MIN_FRAME_SIZE)) { + GST_LOG ("Header indicates a frame too small to be correct"); + } else if (bs_bits_avail (tl->bs) >= hdr->frame_bits) { + guint32 sync_word; + fr_header next_hdr; + + GST_DEBUG ("Peeking ahead %u bits to check sync (%" + G_GINT64_FORMAT ", %" G_GUINT64_FORMAT ", %" + G_GUINT64_FORMAT ")", hdr->frame_bits, remain, + (guint64) bs_read_pos (tl->bs), (guint64) frame_start); + + /* Skip 'remain' bits */ + bs_skipbits (tl->bs, (guint32) (remain - 1)); + + /* Read a sync word and check */ + sync_word = bs_getbits_aligned (tl->bs, SYNC_WORD_LNGTH); + + if (sync_word != SYNC_WORD) { + valid = FALSE; + GST_LOG ("No next sync word %u bits later @ %" G_GUINT64_FORMAT + ". Got 0x%03x", hdr->frame_bits, + bs_read_pos (tl->bs) - SYNC_WORD_LNGTH, sync_word); + } else if (!read_header (tl, &next_hdr)) { + GST_LOG ("Invalid header at next indicated frame"); + valid = FALSE; + } else { + /* Check that the samplerate and layer for the next header is + * the same */ + if ((hdr->layer != next_hdr.layer) || + (hdr->sample_rate != next_hdr.sample_rate) || + (hdr->copyright != next_hdr.copyright) || + (hdr->original != next_hdr.original) || + (hdr->emphasis != next_hdr.emphasis)) { + valid = FALSE; + GST_LOG ("Invalid header at next indicated frame"); + } + } + + if (valid) { + GST_LOG ("Good - found a valid frame %u bits later.", + hdr->frame_bits); + } + } else if (!tl->at_eos) { + GST_LOG ("Not enough data in buffer to test next header"); + /* Not at the end of stream, so wait for more data to validate the + * frame with */ + return MP3TL_ERR_NO_SYNC; + } + *_offset = frame_start >> 3; + *_length = hdr->frame_bits >> 3; + tl->lost_sync = FALSE; + } else { + *_offset = (frame_start + SYNC_WORD_LNGTH) >> 3; + return MP3TL_ERR_NO_SYNC; + } + + return MP3TL_ERR_OK; +} + +/********************************************************************* + * Decode the current frame into the samples buffer + *********************************************************************/ +Mp3TlRetcode +mp3tl_decode_frame (mp3tl * tl, guint8 * samples, guint bufsize) +{ + fr_header *hdr; + int i, j; + int error_protection; + guint new_crc; + Mp3TlRetcode ret; + gint64 frame_start_pos; + + g_return_val_if_fail (tl != NULL, MP3TL_ERR_PARAM); + g_return_val_if_fail (samples != NULL, MP3TL_ERR_PARAM); + + hdr = &tl->fr_ps.header; + + if ((ret = mp3tl_decode_header (tl, NULL)) != MP3TL_ERR_OK) + return ret; + + /* Check that the buffer is big enough to hold the decoded samples */ + if (bufsize < hdr->frame_samples * (hdr->sample_size / 8) * hdr->channels) + return MP3TL_ERR_PARAM; + + bs_reset (tl->bs); + + GST_LOG ("Starting decode of frame size %u bits, with %u bits in buffer", + hdr->frame_bits, (guint) bs_bits_avail (tl->bs)); + + /* Got enough bits for the decode? (exclude the header) */ + if (bs_bits_avail (tl->bs) < + hdr->frame_bits - (SYNC_WORD_LNGTH + HEADER_LNGTH)) + return MP3TL_ERR_NEED_DATA; + + hdr_to_frps (&tl->fr_ps); + + if (hdr->version == MPEG_VERSION_1) + tl->n_granules = 2; + else + tl->n_granules = 1; + + tl->stream_layer = hdr->layer; + + error_protection = hdr->error_protection; + + /* We're about to start reading bits out of the stream, + * after which we'll need a new sync and header + */ + tl->need_sync = TRUE; + tl->need_header = TRUE; + + /* Set up the output buffer */ + tl->sample_w = 0; + tl->sample_buf = (gint16 *) samples; + + /* Remember the start of the frame */ + frame_start_pos = bs_read_pos (tl->bs) - (SYNC_WORD_LNGTH + HEADER_LNGTH); + + /* Retrieve the CRC from the stream */ + if (error_protection) + tl->old_crc = bs_getbits (tl->bs, 16); + + switch (hdr->layer) { + case 1:{ + guint bit_alloc[2][SBLIMIT], scale_index[2][3][SBLIMIT]; + guint ch; + + I_decode_bitalloc (tl->bs, bit_alloc, &tl->fr_ps); + I_decode_scale (tl->bs, bit_alloc, scale_index, &tl->fr_ps); + + /* Compute and check the CRC */ + if (error_protection) { + I_CRC_calc (&tl->fr_ps, bit_alloc, &new_crc); + if (new_crc != tl->old_crc) { + tl->error_count++; + GST_DEBUG ("CRC mismatch - Bad frame"); + return MP3TL_ERR_BAD_FRAME; + } + } + + for (i = 0; i < SCALE_BLOCK; i++) { + I_buffer_sample (tl->bs, tl->sample, bit_alloc, &tl->fr_ps); + I_dequant_and_scale_sample (tl->sample, tl->fraction, bit_alloc, + scale_index, &tl->fr_ps); + + for (ch = 0; ch < hdr->channels; ch++) { + mp3_SubBandSynthesis (tl, &tl->fr_ps, &(tl->fraction[ch][0][0]), ch, + &((tl->pcm_sample)[ch][0][0])); + } + out_fifo (tl->pcm_sample, 1, &tl->fr_ps, tl->sample_buf, &tl->sample_w, + SAMPLE_BUF_SIZE); + } + break; + } + + case 2:{ + guint bit_alloc[2][SBLIMIT], scfsi[2][SBLIMIT], + scale_index[2][3][SBLIMIT]; + guint ch; + + /* Choose bit allocations table */ + II_pick_table (&tl->fr_ps); + + /* Read band bit allocations from the data and scale */ + II_decode_bitalloc (tl->bs, bit_alloc, &tl->fr_ps); + II_decode_scale (tl->bs, scfsi, bit_alloc, scale_index, &tl->fr_ps); + + if (error_protection) { + II_CRC_calc (&tl->fr_ps, bit_alloc, scfsi, &new_crc); + if (new_crc != tl->old_crc) { + tl->error_count++; + GST_DEBUG ("CRC mismatch - Bad frame"); + return MP3TL_ERR_BAD_FRAME; + } + } + + for (i = 0; i < SCALE_BLOCK; i++) { + II_buffer_sample (tl->bs, (tl->sample), bit_alloc, &tl->fr_ps); + II_dequant_and_scale_sample (tl->sample, bit_alloc, tl->fraction, + scale_index, i >> 2, &tl->fr_ps); + + for (j = 0; j < 3; j++) + for (ch = 0; ch < hdr->channels; ch++) { + mp3_SubBandSynthesis (tl, &tl->fr_ps, &((tl->fraction)[ch][j][0]), + ch, &((tl->pcm_sample)[ch][j][0])); + } + out_fifo (tl->pcm_sample, 3, &tl->fr_ps, tl->sample_buf, &tl->sample_w, + SAMPLE_BUF_SIZE); + } + break; + } + case 3: + ret = c_decode_mp3 (tl); + if (ret == MP3TL_ERR_BAD_FRAME) { + /* Consume the data from our bitreader */ + bs_consume (tl->bs, hdr->frame_bits - (SYNC_WORD_LNGTH + HEADER_LNGTH)); + return ret; + } + + if (ret != MP3TL_ERR_OK) + return ret; + break; + default: + GST_WARNING ("Unknown layer %d, invalid bitstream.", hdr->layer); + return MP3TL_ERR_STREAM; + } + + /* skip ancillary data HP 22-nov-95 */ + if (hdr->bitrate_idx != 0) { /* if not free-format */ + /* Ancillary bits are any left in the frame that didn't get used */ + gint64 anc_len = hdr->frame_slots * hdr->bits_per_slot; + + anc_len -= bs_read_pos (tl->bs) - frame_start_pos; + + if (anc_len > 0) { + GST_DEBUG ("Skipping %" G_GINT64_FORMAT " ancillary bits", anc_len); + do { + bs_getbits (tl->bs, (guint32) MIN (anc_len, MAX_LENGTH)); + anc_len -= MAX_LENGTH; + } while (anc_len > 0); + } + } + + tl->frame_num++; + tl->bits_used += hdr->frame_bits; + + /* Consume the data */ + bs_consume (tl->bs, hdr->frame_bits - (SYNC_WORD_LNGTH + HEADER_LNGTH)); + + GST_DEBUG ("Used %u bits = %u slots plus %u", hdr->frame_bits, + hdr->frame_slots, hdr->frame_bits % hdr->bits_per_slot); + + GST_DEBUG ("Avg slots/frame so far = %.3f; b/smp = %.2f; br = %.3f kbps", + (float) tl->bits_used / (tl->frame_num * hdr->bits_per_slot), + (float) tl->bits_used / (tl->frame_num * hdr->frame_samples), + (float) (1.0/1000 * tl->bits_used) / (tl->frame_num * hdr->frame_samples) * + s_rates[hdr->version][hdr->srate_idx]); + + /* Correctly decoded a frame, so assume we're synchronised */ + tl->lost_sync = FALSE; + + return MP3TL_ERR_OK; +} + +void +mp3tl_flush (mp3tl * tl) +{ + GST_LOG ("Flush"); + /* Clear out the bytestreams */ + bs_flush (tl->bs); + + tl->need_header = TRUE; + tl->need_sync = TRUE; + tl->lost_sync = TRUE; + + init_syn_filter (&tl->fr_ps); + + tl->sample_buf = NULL; + tl->sample_w = 0; + memset (tl->pcm_sample, 0, sizeof (tl->pcm_sample)); + + mp3_c_flush (tl); +} + +Mp3TlRetcode +mp3tl_skip_frame (mp3tl * tl) +{ + fr_header *hdr; + Mp3TlRetcode ret; + + g_return_val_if_fail (tl != NULL, MP3TL_ERR_PARAM); + + hdr = &tl->fr_ps.header; + + if ((ret = mp3tl_decode_header (tl, NULL)) != MP3TL_ERR_OK) + return ret; + + bs_reset (tl->bs); + + /* Got enough bits to consume? (exclude the header) */ + if (bs_bits_avail (tl->bs) < + hdr->frame_bits - (SYNC_WORD_LNGTH + HEADER_LNGTH)) + return MP3TL_ERR_NEED_DATA; + + hdr_to_frps (&tl->fr_ps); + + if (hdr->version == MPEG_VERSION_1) + tl->n_granules = 2; + else + tl->n_granules = 1; + + tl->stream_layer = hdr->layer; + + /* We're about to start reading bits out of the stream, + * after which we'll need a new sync and header + */ + tl->need_sync = TRUE; + tl->need_header = TRUE; + + tl->frame_num++; + tl->bits_used += hdr->frame_bits; + + /* Consume the data */ + bs_consume (tl->bs, hdr->frame_bits - (SYNC_WORD_LNGTH + HEADER_LNGTH)); + + GST_DEBUG ("Skipped %u bits = %u slots plus %u", hdr->frame_bits, + hdr->frame_slots, hdr->frame_bits % hdr->bits_per_slot); + + GST_DEBUG ("Avg slots/frame so far = %.3f; b/smp = %.2f; br = %.3f kbps", + (float) tl->bits_used / (tl->frame_num * hdr->bits_per_slot), + (float) tl->bits_used / (tl->frame_num * hdr->frame_samples), + (float) (1.0/1000 * tl->bits_used) / (tl->frame_num * hdr->frame_samples) * + s_rates[hdr->version][hdr->srate_idx]); + + return MP3TL_ERR_OK; +} + +const char * +mp3tl_get_err_reason (mp3tl * tl) +{ + return tl->reason; +} + +#define ID3_HDR_MIN 10 + +Mp3TlRetcode +mp3tl_skip_id3(mp3tl * tl) +{ + guint8 buf[ID3_HDR_MIN]; + + bs_reset(tl->bs); + + if (bs_bits_avail(tl->bs) < 8 * ID3_HDR_MIN) { + GST_DEBUG("Not enough data to read ID3 header"); + return MP3TL_ERR_NEED_DATA; + } + + bs_getbytes(tl->bs, buf, ID3_HDR_MIN); + + /* skip ID3 tag, if present */ + if (!memcmp(buf, "ID3", 3)) { + guint32 bytes_needed = (buf[6] << (7*3)) | (buf[7] << (7*2)) | (buf[8] << 7) | buf[9]; + + if (bs_bits_avail(tl->bs) < 8 * bytes_needed) { + GST_DEBUG("Not enough data to read ID3 tag (need %d)", bytes_needed); + return MP3TL_ERR_NEED_DATA; + } + + bs_consume(tl->bs, 8 * (ID3_HDR_MIN + bytes_needed)); + GST_DEBUG("ID3 tag found, skipping %d bytes", (ID3_HDR_MIN + bytes_needed)); + } + + bs_reset(tl->bs); + return MP3TL_ERR_OK; +} + +#define XING_HDR_MIN 8 + +Mp3TlRetcode +mp3tl_skip_xing(mp3tl * tl, const fr_header * hdr) +{ + const guint32 xing_id = 0x58696e67; /* 'Xing' in hex */ + const guint32 info_id = 0x496e666f; /* 'Info' in hex - found in LAME CBR files */ + guint32 xing_offset; + guint32 read_id; + + if (hdr->version == MPEG_VERSION_1) { /* MPEG-1 file */ + if (hdr->channels == 1) + xing_offset = 0x11; + else + xing_offset = 0x20; + } else { /* MPEG-2 header */ + if (hdr->channels == 1) + xing_offset = 0x09; + else + xing_offset = 0x11; + } + + bs_reset(tl->bs); + + if (bs_bits_avail(tl->bs) < 8 * (xing_offset + XING_HDR_MIN)) { + GST_DEBUG("Not enough data to read Xing header"); + return MP3TL_ERR_NEED_DATA; + } + + /* Read 4 bytes from the frame at the specified location */ + bs_skipbits(tl->bs, 8 * xing_offset); + read_id = bs_getbits(tl->bs, 32); + + /* skip Xing header, if present */ + if (read_id == xing_id || read_id == info_id) { + + bs_consume(tl->bs, hdr->frame_bits); + GST_DEBUG("Xing header found, skipping %d bytes", hdr->frame_bits / 8); + return MP3TL_ERR_STREAM; + + } else { + GST_DEBUG("No Xing header found"); + } + + bs_reset(tl->bs); + return MP3TL_ERR_OK; +} + +} // namespace flump3dec diff --git a/libraries/audio/src/flump3dec.h b/libraries/audio/src/flump3dec.h new file mode 100644 index 0000000000..721e092b0a --- /dev/null +++ b/libraries/audio/src/flump3dec.h @@ -0,0 +1,428 @@ +/* + * FLUENDO S.A. + * Copyright (C) <2005 - 2011> + * + * This Source Code is licensed under MIT license and the explanations attached + * in MIT License Statements. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is furnished to do + * so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * MIT license Statements for Fluendo's mp3 plug-in Source Code + * ------------------------------------------------------------ + * + * Fluendo's mp3 software Source Code (the "Source Code") is licensed under the + * MIT license provisions. + * + * The MIT license is an open source license that permits the User to operate and + * use in many forms the Source Code, which would be governed under its + * regulations. + * + * The purpose of this note is to clarify the intellectual property rights granted + * over the Source Code by Fluendo, as well as other legal issues that concern + * your use of it. + * + * MIT license contents and provisions + * ----------------------------------- + * + * The MIT license allows you to do the following things with the Source Code: + * + * - Copy and use the Source Code alone or jointly with other code for any + * purposes. + * Copy of the Source Code is not limited and is royalty-free. + * + * - Merge the Source Code with other code for developing new applications with no + * limits. + * + * - Modifying the Source Code for developing the plug-in or for implementing the + * plug-in in other applications for any purposes. The MIT License does not + * require you to share these modifications with anyone. + * + * - Publish, distribute, sublicense and sell copies of the Source Code to third + * parties. + * + * - Permit anyone to whom the Source Code is licensed to enjoy the rights above + * subject to the MIT license provisions. + * + * By licensing this Source Code under the MIT License, Fluendo is offering to the + * community the rights set out above without restriction and without any + * obligation for the User of the Source Code to release his/her modifications + * back to the community. Anyone operating with the Source Code released from + * Fluendo must grant the same MIT license rights to the community, except for any + * modifications operated on the Source Code which can be granted under a + * different license (even a proprietary license). + * + * All these rights granted to the User for the Source Code hold a limitation + * which is to include MIT permission notice and the following copyright notice: + * "Copyright 2005 Fluendo, S.L. This Source Code is licensed under MIT license + * and the explanations attached in MIT License Statements". These notices shall + * be included in all copies of the Source Code or in substantial parts of the + * Source Code which may be released separately or with modifications. + * + * Patents over the plug-in and/or Source Code + * ------------------------------------------- + * + * The binaries that can be created by compiling this Source Code released by + * Fluendo might be covered by patents in various parts of the world. Fluendo + * does not own or claim to own any patents on the techniques used in the code. + * (Such patents are owned or claimed to be owned by Thompson Licensing, S.A. and + * some other entities as the case may be). + * + * Fluendo has got the relevant licenses to cover its own activities with the + * Source Code but it is not authorized to sublicense nor to grant the rights + * which it has acquired over the patents. In this sense, you can work and deal + * freely with the Source Code under MIT provisions set out above, bearing in mind + * that some activities might not be allowed under applicable patent regulations + * and that Fluendo is not granting any rights in relation to such patents. + * + * The patent license granted to Fluendo only covers Fluendo's own Software and + * Source Code activities. In any case, this software license does not allow you + * to redistribute or copy complete, ready to use mp3 software decoder binaries + * made from the Source Code as made available by Fluendo. You can of course + * distribute binaries you make yourself under any terms allowed by the MIT + * license and whatever necessary rights you have or have acquired according to + * applicable patent regulations. + * + * As Fluendo can not assure that any of the activities you undertake do not + * infringe any patents or other industrial or intellectual property rights, + * Fluendo hereby disclaims any liability for any patent infringement that may be + * claimed to you or to any other person from any legitimate right’s owner, as + * stated in MIT license. So it is your responsibility to get information and to + * acquire the necessary patent licenses to undertake your activities legally. + */ + +// +// Modifications and bug fixes copyright 2018 High Fidelity, Inc. +// Now passes ISO/IEC 11172-4 "full accuracy" compliance testing. +// + +#ifndef __FLUMP3DEC_H__ +#define __FLUMP3DEC_H__ + +#include +#include +#include + +#if 0 +#include +#define G_GINT64_FORMAT "lld" +#define G_GUINT64_FORMAT "llu" + +#define GST_LOG(f, ...) do { printf(f "\n", __VA_ARGS__); } while (0) +#define GST_DEBUG(f, ...) do { printf(f "\n", __VA_ARGS__); } while (0) +#define GST_WARNING(f, ...) do { printf(f "\n", __VA_ARGS__); } while (0) +#else +#define GST_LOG(f, ...) do {} while (0) +#define GST_DEBUG(f, ...) do {} while (0) +#define GST_WARNING(f, ...) do {} while (0) +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#define g_assert(cond) assert(cond) +#define g_return_if_fail(cond) { if (!(cond)) return; } +#define g_return_val_if_fail(cond, val) { if (!(cond)) return (val); } + +namespace flump3dec { + +typedef char gchar; +typedef unsigned char guchar; +typedef int gint; +typedef unsigned int guint; +typedef float gfloat; +typedef double gdouble; +typedef int gboolean; +typedef size_t gsize; + +typedef int8_t gint8; +typedef uint8_t guint8; +typedef int16_t gint16; +typedef uint16_t guint16; +typedef int32_t gint32; +typedef uint32_t guint32; +typedef int64_t gint64; +typedef uint64_t guint64; + +/* Accumulator optimization on bitstream management */ +#define ENABLE_OPT_BS 1 + +/* Bit stream reader definitions */ +#define MAX_LENGTH 32 /* Maximum length of word written or + read from bit stream */ +#define BS_BYTE_SIZE 8 + +#if ENABLE_OPT_BS +#define BS_ACUM_SIZE 32 +#else +#define BS_ACUM_SIZE 8 +#endif + +typedef struct BSReader +{ + guint64 bitpos; /* Number of bits read so far */ + + gsize size; /* Number of bytes in the buffer list */ + const guint8 *data; /* Current data buffer */ + guint8 *cur_byte; /* ptr to the current byte */ + guint8 cur_bit; /* the next bit to be used in the current byte, + * numbered from 8 down to 1 */ + gsize cur_used; /* Number of bytes _completely_ consumed out of + * the 'cur buffer' */ +} BSReader; + +typedef struct Bit_stream_struc +{ + BSReader master; /* Master tracking position, advanced + * by bs_consume() */ + BSReader read; /* Current read position, set back to the + * master by bs_reset() */ +} Bit_stream_struc; + +/* Create and initialise a new bitstream reader */ +Bit_stream_struc *bs_new (); + +/* Release a bitstream reader */ +void bs_free (Bit_stream_struc * bs); + +/* Reset the current read position to the master position */ +static inline void +bs_reset (Bit_stream_struc * bs) +{ + memcpy (&bs->read, &bs->master, sizeof (BSReader)); +} + +/* Reset master and read states */ +static inline void +bs_flush (Bit_stream_struc * bs) +{ + g_return_if_fail (bs != NULL); + + bs->master.cur_bit = 8; + bs->master.size = 0; + bs->master.cur_used = 0; + bs->master.cur_byte = NULL; + bs->master.data = NULL; + bs->master.bitpos = 0; + + bs_reset (bs); +} + +/* Set data as the stream for processing */ +gboolean bs_set_data (Bit_stream_struc * bs, const guint8 * data, gsize size); + +/* Advance the master position by Nbits */ +void bs_consume (Bit_stream_struc * bs, guint32 Nbits); + +/* Number of bits available for reading */ +static inline gsize bs_bits_avail (Bit_stream_struc * bs) +{ + return ((bs->read.size - bs->read.cur_used) * 8 + (bs->read.cur_bit - 8)); +} + +/* Extract N bytes from the bitstream into the out array. */ +void bs_getbytes (Bit_stream_struc * bs, guint8 * out, guint32 N); + +/* Advance the read pointer by N bits */ +void bs_skipbits (Bit_stream_struc * bs, guint32 N); + +/* give number of consumed bytes */ +static inline gsize bs_get_consumed (Bit_stream_struc * bs) +{ + return bs->master.cur_used; +} + +/* Current bitstream position in bits */ +static inline guint64 +bs_pos (Bit_stream_struc * bs) +{ + return bs->master.bitpos; +} + +/* Current read bitstream position in bits */ +static inline guint64 +bs_read_pos (Bit_stream_struc * bs) +{ + return bs->read.bitpos; +} + +/* Advances the read position to the first bit of next frame or + * last byte in the buffer when the sync code is not found */ +gboolean bs_seek_sync (Bit_stream_struc * bs); + +/* Read N bits from the stream */ +/* bs - bit stream structure */ +/* N - number of bits to read from the bit stream */ +/* v - output value */ +static inline guint32 +bs_getbits (Bit_stream_struc * bs, guint32 N) +{ + guint32 val = 0; + gint j = N; + + g_assert (N <= MAX_LENGTH); + + while (j > 0) { + gint tmp; + gint k; + gint mask; + + /* Move to the next byte if we consumed the current one */ + if (bs->read.cur_bit == 0) { + bs->read.cur_bit = 8; + bs->read.cur_used++; + bs->read.cur_byte++; + } + + /* Protect against data limit */ + if ((bs->read.cur_used >= bs->read.size)) { + GST_WARNING ("Attempted to read beyond data"); + /* Return the bits we got so far */ + return val; + } + /* Take as many bits as we can from the current byte */ + k = MIN (j, bs->read.cur_bit); + + /* We want the k bits from the current byte, starting from + * the cur_bit. Mask out the top 'already used' bits, then shift + * the bits we want down to the bottom */ + mask = (1 << bs->read.cur_bit) - 1; + tmp = bs->read.cur_byte[0] & mask; + + /* Trim off the bits we're leaving for next time */ + tmp = tmp >> (bs->read.cur_bit - k); + + /* Adjust our tracking vars */ + bs->read.cur_bit -= k; + j -= k; + bs->read.bitpos += k; + + /* Put these bits in the right spot in the output */ + val |= tmp << j; + } + + return val; +} + +/* Read 1 bit from the stream */ +static inline guint32 +bs_get1bit (Bit_stream_struc * bs) +{ + return bs_getbits (bs, 1); +} + +/* read the next byte aligned N bits from the bit stream */ +static inline guint32 +bs_getbits_aligned (Bit_stream_struc * bs, guint32 N) +{ + guint32 align; + + align = bs->read.cur_bit; + if (align != 8 && align != 0) + bs_getbits (bs, align); + + return bs_getbits (bs, N); +} + +/* MPEG Header Definitions - ID Bit Values */ +#define MPEG_VERSION_1 0x03 +#define MPEG_VERSION_2 0x02 +#define MPEG_VERSION_2_5 0x00 + +/* Header Information Structure */ +typedef struct +{ + /* Stuff read straight from the MPEG header */ + guint version; + guint layer; + gboolean error_protection; + + gint bitrate_idx; /* Index into the bitrate tables */ + guint srate_idx; /* Index into the sample rate table */ + + gboolean padding; + gboolean extension; + guint mode; + guint mode_ext; + gboolean copyright; + gboolean original; + guint emphasis; + + /* Derived attributes */ + guint bitrate; /* Bitrate of the frame, kbps */ + guint sample_rate; /* sample rate in Hz */ + guint sample_size; /* in bits */ + guint frame_samples; /* Number of samples per channels in this + frame */ + guint channels; /* Number of channels in the frame */ + + guint bits_per_slot; /* Number of bits per slot */ + guint frame_slots; /* Total number of data slots in this frame */ + guint main_slots; /* Slots of main data in this frame */ + guint frame_bits; /* Number of bits in the frame, including header + and sync word */ + guint side_info_slots; /* Number of slots of side info in the frame */ +} fr_header; + +typedef struct mp3tl mp3tl; +typedef enum +{ + MP3TL_ERR_OK = 0, /* Successful return code */ + MP3TL_ERR_NO_SYNC, /* There was no sync word in the data buffer */ + MP3TL_ERR_NEED_DATA, /* Not enough data in the buffer for the requested op */ + MP3TL_ERR_BAD_FRAME, /* The frame data was corrupt and skipped */ + MP3TL_ERR_STREAM, /* Encountered invalid data in the stream */ + MP3TL_ERR_UNSUPPORTED_STREAM, /* Encountered valid but unplayable data in + * the stream */ + MP3TL_ERR_PARAM, /* Invalid parameter was passed in */ + MP3TL_ERR_UNKNOWN /* Unspecified internal decoder error (bug) */ +} Mp3TlRetcode; + +typedef enum +{ + MP3TL_MODE_16BIT = 0 /* Decoder mode to use */ +} Mp3TlMode; + +mp3tl *mp3tl_new (Bit_stream_struc * bs, Mp3TlMode mode); + +void mp3tl_free (mp3tl * tl); + +void mp3tl_set_eos (mp3tl * tl, gboolean more_data); +Mp3TlRetcode mp3tl_sync (mp3tl * tl); +Mp3TlRetcode mp3tl_gather_frame (mp3tl * tl, guint64 * _offset, gint * _length); +Mp3TlRetcode mp3tl_decode_header (mp3tl * tl, const fr_header ** ret_hdr); +Mp3TlRetcode mp3tl_skip_frame (mp3tl * tl); +Mp3TlRetcode mp3tl_decode_frame (mp3tl * tl, guint8 * samples, guint bufsize); +const char *mp3tl_get_err_reason (mp3tl * tl); +void mp3tl_flush (mp3tl * tl); + +Mp3TlRetcode mp3tl_skip_id3 (mp3tl * tl); +Mp3TlRetcode mp3tl_skip_xing (mp3tl * tl, const fr_header * hdr); + +} // namespace flump3dec + +#endif //__FLUMP3DEC_H__ diff --git a/libraries/auto-updater/src/AutoUpdater.cpp b/libraries/auto-updater/src/AutoUpdater.cpp index 6749cd9e10..300a22983a 100644 --- a/libraries/auto-updater/src/AutoUpdater.cpp +++ b/libraries/auto-updater/src/AutoUpdater.cpp @@ -11,13 +11,16 @@ #include "AutoUpdater.h" -#include - -#include -#include #include -AutoUpdater::AutoUpdater() { +#include +#include +#include +#include + +AutoUpdater::AutoUpdater() : + _currentVersion(BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? BuildInfo::VERSION : BuildInfo::BUILD_NUMBER) +{ #if defined Q_OS_WIN32 _operatingSystem = "windows"; #elif defined Q_OS_MAC @@ -33,9 +36,22 @@ void AutoUpdater::checkForUpdate() { this->getLatestVersionData(); } +const QUrl BUILDS_XML_URL("https://highfidelity.com/builds.xml"); +const QUrl MASTER_BUILDS_XML_URL("https://highfidelity.com/dev-builds.xml"); + void AutoUpdater::getLatestVersionData() { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest latestVersionRequest(BUILDS_XML_URL); + + QUrl buildsURL; + + if (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable) { + buildsURL = BUILDS_XML_URL; + } else if (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Master) { + buildsURL = MASTER_BUILDS_XML_URL; + } + + QNetworkRequest latestVersionRequest(buildsURL); + latestVersionRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); latestVersionRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(latestVersionRequest); @@ -52,12 +68,22 @@ void AutoUpdater::parseLatestVersionData() { QString clientOnly; }; - int version { 0 }; + QString version; QString downloadUrl; QString releaseTime; QString releaseNotes; QString commitSha; QString pullRequestNumber; + + QString versionKey; + + // stable builds look at the stable_version node (semantic version) + // master builds look at the version node (build number) + if (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable) { + versionKey = "stable_version"; + } else if (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Master) { + versionKey = "version"; + } while (xml.readNextStartElement()) { if (xml.name() == "projects") { @@ -77,8 +103,8 @@ void AutoUpdater::parseLatestVersionData() { QHash campaignInstallers; while (xml.readNextStartElement()) { - if (xml.name() == "version") { - version = xml.readElementText().toInt(); + if (xml.name() == versionKey) { + version = xml.readElementText(); } else if (xml.name() == "url") { downloadUrl = xml.readElementText(); } else if (xml.name() == "installers") { @@ -159,31 +185,31 @@ void AutoUpdater::parseLatestVersionData() { } void AutoUpdater::checkVersionAndNotify() { - if (BuildInfo::BUILD_TYPE != BuildInfo::BuildType::Stable || _builds.empty()) { - // No version checking is required in nightly/PR/dev builds or when no build - // data was found for the platform + if (_builds.empty()) { + // no build data was found for this platform return; } - int latestVersionAvailable = _builds.lastKey(); - if (QCoreApplication::applicationVersion().toInt() < latestVersionAvailable) { + + qDebug() << "Checking if update version" << _builds.lastKey().versionString + << "is newer than current version" << _currentVersion.versionString; + + if (_builds.lastKey() > _currentVersion) { emit newVersionIsAvailable(); } } -void AutoUpdater::performAutoUpdate(int version) { - // NOTE: This is not yet auto updating - however this is a checkpoint towards that end - // Next PR will handle the automatic download, upgrading and application restart - const QMap& chosenVersion = _builds.value(version); +void AutoUpdater::openLatestUpdateURL() { + const QMap& chosenVersion = _builds.last(); const QUrl& downloadUrl = chosenVersion.value("downloadUrl"); QDesktopServices::openUrl(downloadUrl); QCoreApplication::quit(); } -void AutoUpdater::downloadUpdateVersion(int version) { +void AutoUpdater::downloadUpdateVersion(const QString& version) { emit newVersionIsDownloaded(); } -void AutoUpdater::appendBuildData(int versionNumber, +void AutoUpdater::appendBuildData(const QString& versionNumber, const QString& downloadURL, const QString& releaseTime, const QString& releaseNotes, @@ -194,6 +220,6 @@ void AutoUpdater::appendBuildData(int versionNumber, thisBuildDetails.insert("releaseTime", releaseTime); thisBuildDetails.insert("releaseNotes", releaseNotes); thisBuildDetails.insert("pullRequestNumber", pullRequestNumber); - _builds.insert(versionNumber, thisBuildDetails); + _builds.insert(ApplicationVersion(versionNumber), thisBuildDetails); } diff --git a/libraries/auto-updater/src/AutoUpdater.h b/libraries/auto-updater/src/AutoUpdater.h index f56d7993e9..c788ac31d1 100644 --- a/libraries/auto-updater/src/AutoUpdater.h +++ b/libraries/auto-updater/src/AutoUpdater.h @@ -26,10 +26,9 @@ #include #include +#include #include -const QUrl BUILDS_XML_URL("https://highfidelity.com/builds.xml"); - class AutoUpdater : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY @@ -43,25 +42,29 @@ public: }; void checkForUpdate(); - const QMap>& getBuildData() { return _builds; } - void performAutoUpdate(int version); + const QMap>& getBuildData() { return _builds; } + void openLatestUpdateURL(); void setInstallerType(InstallerType type) { _installerType = type; } void setInstallerCampaign(QString campaign) { _installerCampaign = campaign; } + const ApplicationVersion& getCurrentVersion() const { return _currentVersion; } + signals: void latestVersionDataParsed(); void newVersionIsAvailable(); void newVersionIsDownloaded(); private: - QMap> _builds; + QMap> _builds; QString _operatingSystem; InstallerType _installerType { InstallerType::FULL }; QString _installerCampaign { "" }; + + ApplicationVersion _currentVersion; void getLatestVersionData(); - void downloadUpdateVersion(int version); - void appendBuildData(int versionNumber, + void downloadUpdateVersion(const QString& version); + void appendBuildData(const QString& versionNumber, const QString& downloadURL, const QString& releaseTime, const QString& releaseNotes, diff --git a/libraries/avatars-renderer/CMakeLists.txt b/libraries/avatars-renderer/CMakeLists.txt index 6f4f819793..a70c8294d5 100644 --- a/libraries/avatars-renderer/CMakeLists.txt +++ b/libraries/avatars-renderer/CMakeLists.txt @@ -14,6 +14,7 @@ include_hifi_library_headers(audio) include_hifi_library_headers(entities) include_hifi_library_headers(octree) include_hifi_library_headers(task) +include_hifi_library_headers(workload) include_hifi_library_headers(graphics-scripting) # for ScriptableModel.h target_bullet() diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 5ce7637a3a..69356cdfaa 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -49,6 +49,9 @@ const float DISPLAYNAME_FADE_FACTOR = pow(0.01f, 1.0f / DISPLAYNAME_FADE_TIME); const float DISPLAYNAME_ALPHA = 1.0f; const float DISPLAYNAME_BACKGROUND_ALPHA = 0.4f; const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f); +const float Avatar::MYAVATAR_LOADING_PRIORITY = (float)M_PI; // Entity priority is computed as atan2(maxDim, distance) which is <= PI / 2 +const float Avatar::OTHERAVATAR_LOADING_PRIORITY = MYAVATAR_LOADING_PRIORITY - EPSILON; +const float Avatar::ATTACHMENT_LOADING_PRIORITY = OTHERAVATAR_LOADING_PRIORITY - EPSILON; namespace render { template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar) { @@ -209,6 +212,8 @@ void Avatar::setTargetScale(float targetScale) { _targetScale = newValue; _scaleChanged = usecTimestampNow(); _isAnimatingScale = true; + + emit targetScaleChanged(targetScale); } } @@ -304,7 +309,6 @@ void Avatar::updateAvatarEntities() { // NOTE: if this avatar entity is not attached to us, strip its entity script completely... auto attachedScript = properties.getScript(); if (!isMyAvatar() && !attachedScript.isEmpty()) { - qCDebug(avatars_renderer) << "removing entity script from avatar attached entity:" << entityID << "old script:" << attachedScript; QString noScript; properties.setScript(noScript); } @@ -551,10 +555,17 @@ void Avatar::measureMotionDerivatives(float deltaTime) { // angular glm::quat orientation = getWorldOrientation(); - glm::quat delta = glm::inverse(_lastOrientation) * orientation; - glm::vec3 angularVelocity = glm::axis(delta) * glm::angle(delta) * invDeltaTime; - setWorldAngularVelocity(angularVelocity); - _lastOrientation = getWorldOrientation(); + float changeDot = glm::abs(glm::dot(orientation, _lastOrientation)); + float CHANGE_DOT_THRESHOLD = 0.9999f; + if (changeDot < CHANGE_DOT_THRESHOLD) { + float angle = 2.0f * acosf(changeDot); + glm::quat delta = glm::inverse(_lastOrientation) * orientation; + glm::vec3 angularVelocity = (angle * invDeltaTime) * glm::axis(delta); + setWorldAngularVelocity(angularVelocity); + _lastOrientation = orientation; + } else { + setWorldAngularVelocity(glm::vec3(0.0f)); + } } enum TextRendererType { @@ -1329,6 +1340,9 @@ void Avatar::scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const { } void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { + if (!isMyAvatar()) { + createOrb(); + } AvatarData::setSkeletonModelURL(skeletonModelURL); if (QThread::currentThread() == thread()) { _skeletonModel->setURL(_skeletonModelURL); @@ -1418,6 +1432,7 @@ void Avatar::setAttachmentData(const QVector& attachmentData) { if (_attachmentModels[i]->getURL() != attachmentData[i].modelURL) { _attachmentModelsTexturesLoaded[i] = false; } + _attachmentModels[i]->setLoadingPriority(ATTACHMENT_LOADING_PRIORITY); _attachmentModels[i]->setURL(attachmentData[i].modelURL); } } @@ -1545,15 +1560,15 @@ void Avatar::updateDisplayNameAlpha(bool showDisplayName) { void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) { float uniformScale = getModelScale(); - float radius = uniformScale * _skeletonModel->getBoundingCapsuleRadius(); - float height = uniformScale * _skeletonModel->getBoundingCapsuleHeight(); + float radius = glm::max(MIN_AVATAR_RADIUS, uniformScale * _skeletonModel->getBoundingCapsuleRadius()); + float height = glm::max(MIN_AVATAR_HEIGHT, uniformScale * _skeletonModel->getBoundingCapsuleHeight()); shapeInfo.setCapsuleY(radius, 0.5f * height); - glm::vec3 offset = uniformScale * _skeletonModel->getBoundingCapsuleOffset(); shapeInfo.setOffset(offset); } void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) { + // FIXME: this doesn't take into account Avatar rotation ShapeInfo shapeInfo; computeShapeInfo(shapeInfo); glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 10c1d9ead2..157f7b2ec6 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -23,7 +23,6 @@ #include #include - #include "Head.h" #include "SkeletonModel.h" #include "Rig.h" @@ -41,7 +40,6 @@ static const float SCALING_RATIO = .05f; extern const float CHAT_MESSAGE_SCALE; extern const float CHAT_MESSAGE_HEIGHT; - enum ScreenTintLayer { SCREEN_TINT_BEFORE_LANDSCAPE = 0, SCREEN_TINT_BEFORE_AVATARS, @@ -69,7 +67,7 @@ public: static void setShowNamesAboveHeads(bool show); explicit Avatar(QThread* thread); - ~Avatar(); + virtual ~Avatar(); virtual void instantiableAvatar() = 0; @@ -109,6 +107,7 @@ public: float getLODDistance() const; virtual bool isMyAvatar() const override { return false; } + virtual void createOrb() { } virtual QVector getJointRotations() const override; using AvatarData::getJointRotation; @@ -167,8 +166,8 @@ public: virtual int parseDataFromBuffer(const QByteArray& buffer) override; - static void renderJointConnectingCone( gpu::Batch& batch, glm::vec3 position1, glm::vec3 position2, - float radius1, float radius2, const glm::vec4& color); + static void renderJointConnectingCone(gpu::Batch& batch, glm::vec3 position1, glm::vec3 position2, + float radius1, float radius2, const glm::vec4& color); virtual void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) { } @@ -235,7 +234,7 @@ public: /// Scales a world space position vector relative to the avatar position and scale /// \param vector position to be scaled. Will store the result - void scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const; + void scaleVectorRelativeToPosition(glm::vec3& positionToScale) const; void slamPosition(const glm::vec3& position); virtual void updateAttitude(const glm::quat& orientation) override; @@ -254,7 +253,6 @@ public: void setPositionViaScript(const glm::vec3& position) override; void setOrientationViaScript(const glm::quat& orientation) override; - /**jsdoc * @function MyAvatar.getParentID * @returns {Uuid} @@ -283,7 +281,6 @@ public: // This calls through to the SpatiallyNestable versions, but is here to expose these to JavaScript. Q_INVOKABLE virtual void setParentJointIndex(quint16 parentJointIndex) override; - /**jsdoc * Returns an array of joints, where each joint is an object containing name, index, and parentIndex fields. * @function MyAvatar.getSkeleton @@ -349,7 +346,6 @@ public: // not all subclasses of AvatarData have access to this data. virtual bool canMeasureEyeHeight() const override { return true; } - virtual float getModelScale() const { return _modelScale; } virtual void setModelScale(float scale) { _modelScale = scale; } virtual glm::vec3 scaleForChildren() const override { return glm::vec3(getModelScale()); } @@ -365,6 +361,9 @@ public: virtual scriptable::ScriptableModelBase getScriptableModel() override; +signals: + void targetScaleChanged(float targetScale); + public slots: // FIXME - these should be migrated to use Pose data instead @@ -382,7 +381,7 @@ public slots: /**jsdoc * Get the rotation of the left palm in world coordinates. * @function MyAvatar.getLeftPalmRotation - * @returns {Vec3} The rotation of the left palm in world coordinates. + * @returns {Quat} The rotation of the left palm in world coordinates. * @example Report the rotation of your avatar's left palm. * print(JSON.stringify(MyAvatar.getLeftPalmRotation())); */ @@ -399,7 +398,7 @@ public slots: /**jsdoc * Get the rotation of the right palm in world coordinates. * @function MyAvatar.getRightPalmRotation - * @returns {Vec3} The rotation of the right palm in world coordinates. + * @returns {Quat} The rotation of the right palm in world coordinates. * @example Report the rotation of your avatar's right palm. * print(JSON.stringify(MyAvatar.getRightPalmRotation())); */ @@ -540,6 +539,10 @@ protected: AABox _renderBound; bool _isMeshVisible{ true }; bool _needMeshVisibleSwitch{ true }; + + static const float MYAVATAR_LOADING_PRIORITY; + static const float OTHERAVATAR_LOADING_PRIORITY; + static const float ATTACHMENT_LOADING_PRIORITY; }; #endif // hifi_Avatar_h diff --git a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp index 256b3bf8a6..bdee6d9147 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Head.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Head.cpp @@ -20,6 +20,7 @@ #include #include #include +#include "Logging.h" #include "Avatar.h" @@ -58,25 +59,30 @@ void Head::simulate(float deltaTime) { _longTermAverageLoudness = glm::mix(_longTermAverageLoudness, _averageLoudness, glm::min(deltaTime / AUDIO_LONG_TERM_AVERAGING_SECS, 1.0f)); } - if (!_isFaceTrackerConnected) { - if (!_isEyeTrackerConnected) { - // Update eye saccades - const float AVERAGE_MICROSACCADE_INTERVAL = 1.0f; - const float AVERAGE_SACCADE_INTERVAL = 6.0f; - const float MICROSACCADE_MAGNITUDE = 0.002f; - const float SACCADE_MAGNITUDE = 0.04f; - const float NOMINAL_FRAME_RATE = 60.0f; + if (!_isEyeTrackerConnected) { + // Update eye saccades + const float AVERAGE_MICROSACCADE_INTERVAL = 1.0f; + const float AVERAGE_SACCADE_INTERVAL = 6.0f; + const float MICROSACCADE_MAGNITUDE = 0.002f; + const float SACCADE_MAGNITUDE = 0.04f; + const float NOMINAL_FRAME_RATE = 60.0f; - if (randFloat() < deltaTime / AVERAGE_MICROSACCADE_INTERVAL) { - _saccadeTarget = MICROSACCADE_MAGNITUDE * randVector(); - } else if (randFloat() < deltaTime / AVERAGE_SACCADE_INTERVAL) { - _saccadeTarget = SACCADE_MAGNITUDE * randVector(); - } - _saccade += (_saccadeTarget - _saccade) * pow(0.5f, NOMINAL_FRAME_RATE * deltaTime); - } else { - _saccade = glm::vec3(); + if (randFloat() < deltaTime / AVERAGE_MICROSACCADE_INTERVAL) { + _saccadeTarget = MICROSACCADE_MAGNITUDE * randVector(); + } else if (randFloat() < deltaTime / AVERAGE_SACCADE_INTERVAL) { + _saccadeTarget = SACCADE_MAGNITUDE * randVector(); } + _saccade += (_saccadeTarget - _saccade) * pow(0.5f, NOMINAL_FRAME_RATE * deltaTime); + } else { + _saccade = glm::vec3(); + } + const float BLINK_SPEED = 10.0f; + const float BLINK_SPEED_VARIABILITY = 1.0f; + const float BLINK_START_VARIABILITY = 0.25f; + const float FULLY_OPEN = 0.0f; + const float FULLY_CLOSED = 1.0f; + if (getHasProceduralBlinkFaceMovement()) { // Detect transition from talking to not; force blink after that and a delay bool forceBlink = false; const float TALKING_LOUDNESS = 100.0f; @@ -88,29 +94,12 @@ void Head::simulate(float deltaTime) { forceBlink = true; } - // Update audio attack data for facial animation (eyebrows and mouth) - float audioAttackAveragingRate = (10.0f - deltaTime * NORMAL_HZ) / 10.0f; // --> 0.9 at 60 Hz - _audioAttack = audioAttackAveragingRate * _audioAttack + - (1.0f - audioAttackAveragingRate) * fabs((audioLoudness - _longTermAverageLoudness) - _lastLoudness); - _lastLoudness = (audioLoudness - _longTermAverageLoudness); - - const float BROW_LIFT_THRESHOLD = 100.0f; - if (_audioAttack > BROW_LIFT_THRESHOLD) { - _browAudioLift += sqrtf(_audioAttack) * 0.01f; - } - _browAudioLift = glm::clamp(_browAudioLift *= 0.7f, 0.0f, 1.0f); - - const float BLINK_SPEED = 10.0f; - const float BLINK_SPEED_VARIABILITY = 1.0f; - const float BLINK_START_VARIABILITY = 0.25f; - const float FULLY_OPEN = 0.0f; - const float FULLY_CLOSED = 1.0f; if (_leftEyeBlinkVelocity == 0.0f && _rightEyeBlinkVelocity == 0.0f) { // no blinking when brows are raised; blink less with increasing loudness const float BASE_BLINK_RATE = 15.0f / 60.0f; const float ROOT_LOUDNESS_TO_BLINK_INTERVAL = 0.25f; if (forceBlink || (_browAudioLift < EPSILON && shouldDo(glm::max(1.0f, sqrt(fabs(_averageLoudness - _longTermAverageLoudness)) * - ROOT_LOUDNESS_TO_BLINK_INTERVAL) / BASE_BLINK_RATE, deltaTime))) { + ROOT_LOUDNESS_TO_BLINK_INTERVAL) / BASE_BLINK_RATE, deltaTime))) { _leftEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY; _rightEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY; if (randFloat() < 0.5f) { @@ -136,22 +125,45 @@ void Head::simulate(float deltaTime) { _rightEyeBlinkVelocity = 0.0f; } } + } else { + _rightEyeBlink = FULLY_OPEN; + _leftEyeBlink = FULLY_OPEN; + } // use data to update fake Faceshift blendshape coefficients + if (getHasAudioEnabledFaceMovement()) { + // Update audio attack data for facial animation (eyebrows and mouth) + float audioAttackAveragingRate = (10.0f - deltaTime * NORMAL_HZ) / 10.0f; // --> 0.9 at 60 Hz + _audioAttack = audioAttackAveragingRate * _audioAttack + + (1.0f - audioAttackAveragingRate) * fabs((audioLoudness - _longTermAverageLoudness) - _lastLoudness); + _lastLoudness = (audioLoudness - _longTermAverageLoudness); + const float BROW_LIFT_THRESHOLD = 100.0f; + if (_audioAttack > BROW_LIFT_THRESHOLD) { + _browAudioLift += sqrtf(_audioAttack) * 0.01f; + } + _browAudioLift = glm::clamp(_browAudioLift *= 0.7f, 0.0f, 1.0f); calculateMouthShapes(deltaTime); - FaceTracker::updateFakeCoefficients(_leftEyeBlink, - _rightEyeBlink, - _browAudioLift, - _audioJawOpen, - _mouth2, - _mouth3, - _mouth4, - _transientBlendshapeCoefficients); - - applyEyelidOffset(getOrientation()); } else { - _saccade = glm::vec3(); + _audioJawOpen = 0.0f; + _browAudioLift = 0.0f; + _mouth2 = 0.0f; + _mouth3 = 0.0f; + _mouth4 = 0.0f; + _mouthTime = 0.0f; + } + + FaceTracker::updateFakeCoefficients(_leftEyeBlink, + _rightEyeBlink, + _browAudioLift, + _audioJawOpen, + _mouth2, + _mouth3, + _mouth4, + _transientBlendshapeCoefficients); + + if (getHasProceduralEyeFaceMovement()) { + applyEyelidOffset(getOrientation()); } _leftEyePosition = _rightEyePosition = getPosition(); @@ -208,30 +220,44 @@ void Head::calculateMouthShapes(float deltaTime) { void Head::applyEyelidOffset(glm::quat headOrientation) { // Adjusts the eyelid blendshape coefficients so that the eyelid follows the iris as the head pitches. - - if (disableEyelidAdjustment) { + bool isBlinking = (_rightEyeBlinkVelocity != 0.0f && _rightEyeBlinkVelocity != 0.0f); + if (disableEyelidAdjustment || isBlinking) { return; } - glm::quat eyeRotation = rotationBetween(headOrientation * IDENTITY_FORWARD, getLookAtPosition() - _eyePosition); - eyeRotation = eyeRotation * glm::angleAxis(safeEulerAngles(headOrientation).y, IDENTITY_UP); // Rotation w.r.t. head - float eyePitch = safeEulerAngles(eyeRotation).x; + const float EYE_PITCH_TO_COEFFICIENT = 3.5f; // Empirically determined + const float MAX_EYELID_OFFSET = 1.5f; + const float BLINK_DOWN_MULTIPLIER = 0.25f; + const float OPEN_DOWN_MULTIPLIER = 0.3f; + const float BROW_UP_MULTIPLIER = 0.5f; - const float EYE_PITCH_TO_COEFFICIENT = 1.6f; // Empirically determined - const float MAX_EYELID_OFFSET = 0.8f; // So that don't fully close eyes when looking way down - float eyelidOffset = glm::clamp(-eyePitch * EYE_PITCH_TO_COEFFICIENT, -1.0f, MAX_EYELID_OFFSET); + glm::vec3 lookAt = glm::normalize(getLookAtPosition() - _eyePosition); + glm::vec3 headUp = headOrientation * Vectors::UNIT_Y; + float eyePitch = (PI / 2.0f) - acos(glm::dot(lookAt, headUp)); + float eyelidOffset = glm::clamp(abs(eyePitch * EYE_PITCH_TO_COEFFICIENT), 0.0f, MAX_EYELID_OFFSET); - for (int i = 0; i < 2; i++) { - const int LEFT_EYE = 8; - float eyeCoefficient = _transientBlendshapeCoefficients[i] - _transientBlendshapeCoefficients[LEFT_EYE + i]; - eyeCoefficient = glm::clamp(eyelidOffset + eyeCoefficient * (1.0f - eyelidOffset), -1.0f, 1.0f); - if (eyeCoefficient > 0.0f) { - _transientBlendshapeCoefficients[i] = eyeCoefficient; - _transientBlendshapeCoefficients[LEFT_EYE + i] = 0.0f; + float blinkUpCoefficient = -eyelidOffset; + float blinkDownCoefficient = BLINK_DOWN_MULTIPLIER * eyelidOffset; + + float openUpCoefficient = eyelidOffset; + float openDownCoefficient = OPEN_DOWN_MULTIPLIER * eyelidOffset; + + float browsUpCoefficient = BROW_UP_MULTIPLIER * eyelidOffset; + float browsDownCoefficient = 0.0f; - } else { - _transientBlendshapeCoefficients[i] = 0.0f; - _transientBlendshapeCoefficients[LEFT_EYE + i] = -eyeCoefficient; + bool isLookingUp = (eyePitch > 0); + + if (isLookingUp) { + for (int i = 0; i < 2; i++) { + _transientBlendshapeCoefficients[EYE_BLINK_INDICES[i]] = blinkUpCoefficient; + _transientBlendshapeCoefficients[EYE_OPEN_INDICES[i]] = openUpCoefficient; + _transientBlendshapeCoefficients[BROWS_U_INDICES[i]] = browsUpCoefficient; + } + } else { + for (int i = 0; i < 2; i++) { + _transientBlendshapeCoefficients[EYE_BLINK_INDICES[i]] = blinkDownCoefficient; + _transientBlendshapeCoefficients[EYE_OPEN_INDICES[i]] = openDownCoefficient; + _transientBlendshapeCoefficients[BROWS_U_INDICES[i]] = browsDownCoefficient; } } } diff --git a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp deleted file mode 100644 index 4382216575..0000000000 --- a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp +++ /dev/null @@ -1,18 +0,0 @@ -// -// Created by Bradley Austin Davis on 2017/04/27 -// Copyright 2013-2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "OtherAvatar.h" - -OtherAvatar::OtherAvatar(QThread* thread) : Avatar(thread) { - // give the pointer to our head to inherited _headData variable from AvatarData - _headData = new Head(this); - _skeletonModel = std::make_shared(this, nullptr); - connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); - connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); - connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset); -} diff --git a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h deleted file mode 100644 index df09d7fd99..0000000000 --- a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// Created by Bradley Austin Davis on 2017/04/27 -// Copyright 2013-2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_OtherAvatar_h -#define hifi_OtherAvatar_h - -#include "Avatar.h" - -class OtherAvatar : public Avatar { -public: - explicit OtherAvatar(QThread* thread); - virtual void instantiableAvatar() override {}; -}; - -#endif // hifi_OtherAvatar_h diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 48ef1fb881..abdac838b6 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -300,14 +300,15 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent tranlationChangedSince(lastSentTime) || parentInfoChangedSince(lastSentTime)); - hasFaceTrackerInfo = !dropFaceTracking && hasFaceTracker() && (sendAll || faceTrackerInfoChangedSince(lastSentTime)); + hasFaceTrackerInfo = !dropFaceTracking && (hasFaceTracker() || getHasScriptedBlendshapes()) && + (sendAll || faceTrackerInfoChangedSince(lastSentTime)); hasJointData = sendAll || !sendMinimum; hasJointDefaultPoseFlags = hasJointData; } const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + - (hasFaceTrackerInfo ? AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getNumSummedBlendshapeCoefficients()) : 0) + + (hasFaceTrackerInfo ? AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) : 0) + (hasJointData ? AvatarDataPacket::maxJointDataSize(_jointData.size()) : 0) + (hasJointDefaultPoseFlags ? AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()) : 0); @@ -442,7 +443,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent auto startSection = destinationBuffer; auto data = reinterpret_cast(destinationBuffer); - uint8_t flags { 0 }; + uint16_t flags { 0 }; setSemiNibbleAt(flags, KEY_STATE_START_BIT, _keyState); @@ -450,20 +451,33 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent bool isFingerPointing = _handState & IS_FINGER_POINTING_FLAG; setSemiNibbleAt(flags, HAND_STATE_START_BIT, _handState & ~IS_FINGER_POINTING_FLAG); if (isFingerPointing) { - setAtBit(flags, HAND_STATE_FINGER_POINTING_BIT); + setAtBit16(flags, HAND_STATE_FINGER_POINTING_BIT); } // face tracker state if (_headData->_isFaceTrackerConnected) { - setAtBit(flags, IS_FACE_TRACKER_CONNECTED); + setAtBit16(flags, IS_FACE_TRACKER_CONNECTED); } // eye tracker state if (_headData->_isEyeTrackerConnected) { - setAtBit(flags, IS_EYE_TRACKER_CONNECTED); + setAtBit16(flags, IS_EYE_TRACKER_CONNECTED); } // referential state if (!parentID.isNull()) { - setAtBit(flags, HAS_REFERENTIAL); + setAtBit16(flags, HAS_REFERENTIAL); } + // audio face movement + if (_headData->getHasAudioEnabledFaceMovement()) { + setAtBit16(flags, AUDIO_ENABLED_FACE_MOVEMENT); + } + // procedural eye face movement + if (_headData->getHasProceduralEyeFaceMovement()) { + setAtBit16(flags, PROCEDURAL_EYE_FACE_MOVEMENT); + } + // procedural blink face movement + if (_headData->getHasProceduralBlinkFaceMovement()) { + setAtBit16(flags, PROCEDURAL_BLINK_FACE_MOVEMENT); + } + data->flags = flags; destinationBuffer += sizeof(AvatarDataPacket::AdditionalFlags); @@ -506,8 +520,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (hasFaceTrackerInfo) { auto startSection = destinationBuffer; auto faceTrackerInfo = reinterpret_cast(destinationBuffer); - const auto& blendshapeCoefficients = _headData->getSummedBlendshapeCoefficients(); - + const auto& blendshapeCoefficients = _headData->getBlendshapeCoefficients(); + // note: we don't use the blink and average loudness, we just use the numBlendShapes and + // compute the procedural info on the client side. faceTrackerInfo->leftEyeBlink = _headData->_leftEyeBlink; faceTrackerInfo->rightEyeBlink = _headData->_rightEyeBlink; faceTrackerInfo->averageLoudness = _headData->_averageLoudness; @@ -972,7 +987,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { PACKET_READ_CHECK(AdditionalFlags, sizeof(AvatarDataPacket::AdditionalFlags)); auto data = reinterpret_cast(sourceBuffer); - uint8_t bitItems = data->flags; + uint16_t bitItems = data->flags; // key state, stored as a semi-nibble in the bitItems auto newKeyState = (KeyState)getSemiNibbleAt(bitItems, KEY_STATE_START_BIT); @@ -980,26 +995,38 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { // hand state, stored as a semi-nibble plus a bit in the bitItems // we store the hand state as well as other items in a shared bitset. The hand state is an octal, but is split // into two sections to maintain backward compatibility. The bits are ordered as such (0-7 left to right). - // +---+-----+-----+--+ - // |x,x|H0,H1|x,x,x|H2| - // +---+-----+-----+--+ + // AA 6/1/18 added three more flags bits 8,9, and 10 for procedural audio, blink, and eye saccade enabled + // +---+-----+-----+--+--+--+--+-----+ + // |x,x|H0,H1|x,x,x|H2|Au|Bl|Ey|xxxxx| + // +---+-----+-----+--+--+--+--+-----+ // Hand state - H0,H1,H2 is found in the 3rd, 4th, and 8th bits auto newHandState = getSemiNibbleAt(bitItems, HAND_STATE_START_BIT) - + (oneAtBit(bitItems, HAND_STATE_FINGER_POINTING_BIT) ? IS_FINGER_POINTING_FLAG : 0); + + (oneAtBit16(bitItems, HAND_STATE_FINGER_POINTING_BIT) ? IS_FINGER_POINTING_FLAG : 0); - auto newFaceTrackerConnected = oneAtBit(bitItems, IS_FACE_TRACKER_CONNECTED); - auto newEyeTrackerConnected = oneAtBit(bitItems, IS_EYE_TRACKER_CONNECTED); + auto newFaceTrackerConnected = oneAtBit16(bitItems, IS_FACE_TRACKER_CONNECTED); + auto newEyeTrackerConnected = oneAtBit16(bitItems, IS_EYE_TRACKER_CONNECTED); + auto newHasAudioEnabledFaceMovement = oneAtBit16(bitItems, AUDIO_ENABLED_FACE_MOVEMENT); + auto newHasProceduralEyeFaceMovement = oneAtBit16(bitItems, PROCEDURAL_EYE_FACE_MOVEMENT); + auto newHasProceduralBlinkFaceMovement = oneAtBit16(bitItems, PROCEDURAL_BLINK_FACE_MOVEMENT); + + bool keyStateChanged = (_keyState != newKeyState); bool handStateChanged = (_handState != newHandState); bool faceStateChanged = (_headData->_isFaceTrackerConnected != newFaceTrackerConnected); bool eyeStateChanged = (_headData->_isEyeTrackerConnected != newEyeTrackerConnected); - bool somethingChanged = keyStateChanged || handStateChanged || faceStateChanged || eyeStateChanged; + bool audioEnableFaceMovementChanged = (_headData->getHasAudioEnabledFaceMovement() != newHasAudioEnabledFaceMovement); + bool proceduralEyeFaceMovementChanged = (_headData->getHasProceduralEyeFaceMovement() != newHasProceduralEyeFaceMovement); + bool proceduralBlinkFaceMovementChanged = (_headData->getHasProceduralBlinkFaceMovement() != newHasProceduralBlinkFaceMovement); + bool somethingChanged = keyStateChanged || handStateChanged || faceStateChanged || eyeStateChanged || audioEnableFaceMovementChanged || proceduralEyeFaceMovementChanged || proceduralBlinkFaceMovementChanged; _keyState = newKeyState; _handState = newHandState; _headData->_isFaceTrackerConnected = newFaceTrackerConnected; _headData->_isEyeTrackerConnected = newEyeTrackerConnected; + _headData->setHasAudioEnabledFaceMovement(newHasAudioEnabledFaceMovement); + _headData->setHasProceduralEyeFaceMovement(newHasProceduralEyeFaceMovement); + _headData->setHasProceduralBlinkFaceMovement(newHasProceduralBlinkFaceMovement); sourceBuffer += sizeof(AvatarDataPacket::AdditionalFlags); @@ -1060,23 +1087,21 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { PACKET_READ_CHECK(FaceTrackerInfo, sizeof(AvatarDataPacket::FaceTrackerInfo)); auto faceTrackerInfo = reinterpret_cast(sourceBuffer); - sourceBuffer += sizeof(AvatarDataPacket::FaceTrackerInfo); - - _headData->_leftEyeBlink = faceTrackerInfo->leftEyeBlink; - _headData->_rightEyeBlink = faceTrackerInfo->rightEyeBlink; - _headData->_averageLoudness = faceTrackerInfo->averageLoudness; - _headData->_browAudioLift = faceTrackerInfo->browAudioLift; - int numCoefficients = faceTrackerInfo->numBlendshapeCoefficients; const int coefficientsSize = sizeof(float) * numCoefficients; + sourceBuffer += sizeof(AvatarDataPacket::FaceTrackerInfo); + PACKET_READ_CHECK(FaceTrackerCoefficients, coefficientsSize); _headData->_blendshapeCoefficients.resize(numCoefficients); // make sure there's room for the copy! - _headData->_transientBlendshapeCoefficients.resize(numCoefficients); + //only copy the blendshapes to headData, not the procedural face info memcpy(_headData->_blendshapeCoefficients.data(), sourceBuffer, coefficientsSize); sourceBuffer += coefficientsSize; + int numBytesRead = sourceBuffer - startSection; _faceTrackerRate.increment(numBytesRead); _faceTrackerUpdateRate.increment(); + } else { + _headData->_blendshapeCoefficients.fill(0, _headData->_blendshapeCoefficients.size()); } if (hasJointData) { @@ -2375,8 +2400,8 @@ QVariant AttachmentData::toVariant() const { QVariantMap result; result["modelUrl"] = modelURL; result["jointName"] = jointName; - result["translation"] = glmToQMap(translation); - result["rotation"] = glmToQMap(glm::degrees(safeEulerAngles(rotation))); + result["translation"] = vec3ToQMap(translation); + result["rotation"] = vec3ToQMap(glm::degrees(safeEulerAngles(rotation))); result["scale"] = scale; result["soft"] = isSoft; return result; @@ -2530,15 +2555,18 @@ glm::mat4 AvatarData::getControllerRightHandMatrix() const { return _controllerRightHandMatrixCache.get(); } - QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& value) { QScriptValue obj = engine->newObject(); obj.setProperty("intersects", value.intersects); QScriptValue avatarIDValue = quuidToScriptValue(engine, value.avatarID); obj.setProperty("avatarID", avatarIDValue); obj.setProperty("distance", value.distance); + obj.setProperty("face", boxFaceToString(value.face)); + QScriptValue intersection = vec3toScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); + QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal); + obj.setProperty("surfaceNormal", surfaceNormal); obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo)); return obj; } @@ -2548,10 +2576,16 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra QScriptValue avatarIDValue = object.property("avatarID"); quuidFromScriptValue(avatarIDValue, value.avatarID); value.distance = object.property("distance").toVariant().toFloat(); + value.face = boxFaceFromString(object.property("face").toVariant().toString()); + QScriptValue intersection = object.property("intersection"); if (intersection.isValid()) { vec3FromScriptValue(intersection, value.intersection); } + QScriptValue surfaceNormal = object.property("surfaceNormal"); + if (surfaceNormal.isValid()) { + vec3FromScriptValue(surfaceNormal, value.surfaceNormal); + } value.extraInfo = object.property("extraInfo").toVariant().toMap(); } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 4946ce45b9..0f850aaf24 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -79,20 +79,30 @@ const quint32 AVATAR_MOTION_SCRIPTABLE_BITS = // Bitset of state flags - we store the key state, hand state, Faceshift, eye tracking, and existence of // referential data in this bit set. The hand state is an octal, but is split into two sections to maintain // backward compatibility. The bits are ordered as such (0-7 left to right). -// +-----+-----+-+-+-+--+ -// |K0,K1|H0,H1|F|E|R|H2| -// +-----+-----+-+-+-+--+ +// AA 6/1/18 added three more flags bits 8,9, and 10 for procedural audio, blink, and eye saccade enabled +// +// +-----+-----+-+-+-+--+--+--+--+-----+ +// |K0,K1|H0,H1|F|E|R|H2|Au|Bl|Ey|xxxxx| +// +-----+-----+-+-+-+--+--+--+--+-----+ +// // Key state - K0,K1 is found in the 1st and 2nd bits // Hand state - H0,H1,H2 is found in the 3rd, 4th, and 8th bits // Face tracker - F is found in the 5th bit // Eye tracker - E is found in the 6th bit // Referential Data - R is found in the 7th bit +// Procedural audio to mouth movement is enabled 8th bit +// Procedural Blink is enabled 9th bit +// Procedural Eyelid is enabled 10th bit + const int KEY_STATE_START_BIT = 0; // 1st and 2nd bits const int HAND_STATE_START_BIT = 2; // 3rd and 4th bits const int IS_FACE_TRACKER_CONNECTED = 4; // 5th bit const int IS_EYE_TRACKER_CONNECTED = 5; // 6th bit (was CHAT_CIRCLING) const int HAS_REFERENTIAL = 6; // 7th bit const int HAND_STATE_FINGER_POINTING_BIT = 7; // 8th bit +const int AUDIO_ENABLED_FACE_MOVEMENT = 8; // 9th bit +const int PROCEDURAL_EYE_FACE_MOVEMENT = 9; // 10th bit +const int PROCEDURAL_BLINK_FACE_MOVEMENT = 10; // 11th bit const char HAND_STATE_NULL = 0; @@ -200,9 +210,9 @@ namespace AvatarDataPacket { static_assert(sizeof(SensorToWorldMatrix) == SENSOR_TO_WORLD_SIZE, "AvatarDataPacket::SensorToWorldMatrix size doesn't match."); PACKED_BEGIN struct AdditionalFlags { - uint8_t flags; // additional flags: hand state, key state, eye tracking + uint16_t flags; // additional flags: hand state, key state, eye tracking } PACKED_END; - const size_t ADDITIONAL_FLAGS_SIZE = 1; + const size_t ADDITIONAL_FLAGS_SIZE = 2; static_assert(sizeof(AdditionalFlags) == ADDITIONAL_FLAGS_SIZE, "AvatarDataPacket::AdditionalFlags size doesn't match."); // only present if HAS_REFERENTIAL flag is set in AvatarInfo.flags @@ -391,7 +401,6 @@ class AvatarData : public QObject, public SpatiallyNestable { Q_PROPERTY(float sensorToWorldScale READ getSensorToWorldScale) public: - virtual QString getName() const override { return QString("Avatar:") + _displayName; } static const QString FRAME_NAME; @@ -501,6 +510,11 @@ public: float getDomainLimitedScale() const; + virtual bool getHasScriptedBlendshapes() const { return false; } + virtual bool getHasProceduralBlinkFaceMovement() const { return true; } + virtual bool getHasProceduralEyeFaceMovement() const { return true; } + virtual bool getHasAudioEnabledFaceMovement() const { return false; } + /**jsdoc * Returns the minimum scale allowed for this avatar in the current domain. * This value can change as the user changes avatars or when changing domains. @@ -1510,19 +1524,30 @@ void registerAvatarTypes(QScriptEngine* engine); class RayToAvatarIntersectionResult { public: -RayToAvatarIntersectionResult() : intersects(false), avatarID(), distance(0) {} - bool intersects; + bool intersects { false }; QUuid avatarID; - float distance; + float distance { 0.0f }; + BoxFace face; glm::vec3 intersection; + glm::vec3 surfaceNormal; QVariantMap extraInfo; }; - Q_DECLARE_METATYPE(RayToAvatarIntersectionResult) - QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& results); void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, RayToAvatarIntersectionResult& results); +class ParabolaToAvatarIntersectionResult { +public: + bool intersects { false }; + QUuid avatarID; + float distance { 0.0f }; + float parabolicDistance { 0.0f }; + BoxFace face; + glm::vec3 intersection; + glm::vec3 surfaceNormal; + QVariantMap extraInfo; +}; + Q_DECLARE_METATYPE(AvatarEntityMap) QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value); diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 974ae92432..174e81bb31 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -89,11 +89,15 @@ AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWe return avatar; } -AvatarSharedPointer AvatarHashMap::newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { +AvatarSharedPointer AvatarHashMap::newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer, + bool& isNew) { QWriteLocker locker(&_hashLock); auto avatar = _avatarHash.value(sessionUUID); if (!avatar) { avatar = addAvatar(sessionUUID, mixerWeakPointer); + isNew = true; + } else { + isNew = false; } return avatar; } @@ -125,8 +129,13 @@ AvatarSharedPointer AvatarHashMap::parseAvatarData(QSharedPointer(); + bool isNewAvatar; if (sessionUUID != _lastOwnerSessionUUID && (!nodeList->isIgnoringNode(sessionUUID) || nodeList->getRequestsDomainListData())) { - auto avatar = newOrExistingAvatar(sessionUUID, sendingNode); + auto avatar = newOrExistingAvatar(sessionUUID, sendingNode, isNewAvatar); + if (isNewAvatar) { + QWriteLocker locker(&_hashLock); + _pendingAvatars.insert(sessionUUID, { std::chrono::steady_clock::now(), 0, avatar }); + } // have the matching (or new) avatar parse the data from the packet int bytesRead = avatar->parseDataFromBuffer(byteArray); @@ -157,6 +166,7 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer { QReadLocker locker(&_hashLock); + _pendingAvatars.remove(identityUUID); auto me = _avatarHash.find(EMPTY); if ((me != _avatarHash.end()) && (identityUUID == me.value()->getSessionUUID())) { // We add MyAvatar to _avatarHash with an empty UUID. Code relies on this. In order to correctly handle an @@ -168,7 +178,8 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer if (!nodeList->isIgnoringNode(identityUUID) || nodeList->getRequestsDomainListData()) { // mesh URL for a UUID, find avatar in our list - auto avatar = newOrExistingAvatar(identityUUID, sendingNode); + bool isNewAvatar; + auto avatar = newOrExistingAvatar(identityUUID, sendingNode, isNewAvatar); bool identityChanged = false; bool displayNameChanged = false; bool skeletonModelUrlChanged = false; @@ -189,6 +200,7 @@ void AvatarHashMap::processKillAvatar(QSharedPointer message, S void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) { QWriteLocker locker(&_hashLock); + _pendingAvatars.remove(sessionUUID); auto removedAvatar = _avatarHash.take(sessionUUID); if (removedAvatar) { diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index ef6f7845eb..fd2cd76fbf 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -19,6 +19,7 @@ #include #include +#include #include @@ -145,13 +146,21 @@ protected: virtual AvatarSharedPointer parseAvatarData(QSharedPointer message, SharedNodePointer sendingNode); virtual AvatarSharedPointer newSharedAvatar(); virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer); - AvatarSharedPointer newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer); + AvatarSharedPointer newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer, + bool& isNew); virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID) const; // uses a QReadLocker on the hashLock virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason); virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason); AvatarHash _avatarHash; + struct PendingAvatar { + std::chrono::steady_clock::time_point creationTime; + int transmits; + AvatarSharedPointer avatar; + }; + using AvatarPendingHash = QHash; + AvatarPendingHash _pendingAvatars; mutable QReadWriteLock _hashLock; private: diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index 4119d7a459..19f5efcd16 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -17,7 +17,6 @@ #include #include -#include #include #include @@ -33,7 +32,7 @@ HeadData::HeadData(AvatarData* owningAvatar) : _summedBlendshapeCoefficients(QVector(0, 0.0f)), _owningAvatar(owningAvatar) { - + computeBlendshapesLookupMap(); } glm::quat HeadData::getRawOrientation() const { @@ -71,16 +70,10 @@ void HeadData::setOrientation(const glm::quat& orientation) { setHeadOrientation(orientation); } -//Lazily construct a lookup map from the blendshapes -static const QMap& getBlendshapesLookupMap() { - static std::once_flag once; - static QMap blendshapeLookupMap; - std::call_once(once, [&] { - for (int i = 0; i < NUM_FACESHIFT_BLENDSHAPES; i++) { - blendshapeLookupMap[FACESHIFT_BLENDSHAPES[i]] = i; - } - }); - return blendshapeLookupMap; +void HeadData::computeBlendshapesLookupMap(){ + for (int i = 0; i < NUM_FACESHIFT_BLENDSHAPES; i++) { + _blendshapeLookupMap[FACESHIFT_BLENDSHAPES[i]] = i; + } } int HeadData::getNumSummedBlendshapeCoefficients() const { @@ -108,11 +101,10 @@ const QVector& HeadData::getSummedBlendshapeCoefficients() { } void HeadData::setBlendshape(QString name, float val) { - const auto& blendshapeLookupMap = getBlendshapesLookupMap(); //Check to see if the named blendshape exists, and then set its value if it does - auto it = blendshapeLookupMap.find(name); - if (it != blendshapeLookupMap.end()) { + auto it = _blendshapeLookupMap.find(name); + if (it != _blendshapeLookupMap.end()) { if (_blendshapeCoefficients.size() <= it.value()) { _blendshapeCoefficients.resize(it.value() + 1); } @@ -123,6 +115,18 @@ void HeadData::setBlendshape(QString name, float val) { } } +int HeadData::getBlendshapeIndex(const QString& name) { + auto it = _blendshapeLookupMap.find(name); + int index = it != _blendshapeLookupMap.end() ? it.value() : -1; + return index; +} + +void HeadData::getBlendshapeIndices(const std::vector& blendShapeNames, std::vector& indexes) { + for (auto& name : blendShapeNames) { + indexes.push_back(getBlendshapeIndex(name)); + } +} + static const QString JSON_AVATAR_HEAD_ROTATION = QStringLiteral("rotation"); static const QString JSON_AVATAR_HEAD_BLENDSHAPE_COEFFICIENTS = QStringLiteral("blendShapes"); static const QString JSON_AVATAR_HEAD_LEAN_FORWARD = QStringLiteral("leanForward"); @@ -131,10 +135,9 @@ static const QString JSON_AVATAR_HEAD_LOOKAT = QStringLiteral("lookAt"); QJsonObject HeadData::toJson() const { QJsonObject headJson; - const auto& blendshapeLookupMap = getBlendshapesLookupMap(); QJsonObject blendshapesJson; - for (auto name : blendshapeLookupMap.keys()) { - auto index = blendshapeLookupMap[name]; + for (auto name : _blendshapeLookupMap.keys()) { + auto index = _blendshapeLookupMap[name]; float value = 0.0f; if (index < _blendshapeCoefficients.size()) { value += _blendshapeCoefficients[index]; diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index bcc2cacde5..6d211da2cd 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -20,6 +20,7 @@ #include #include +#include // degrees const float MIN_HEAD_YAW = -180.0f; @@ -55,6 +56,8 @@ public: void setOrientation(const glm::quat& orientation); void setBlendshape(QString name, float val); + int getBlendshapeIndex(const QString& name); + void getBlendshapeIndices(const std::vector& blendShapeNames, std::vector& indexes); const QVector& getBlendshapeCoefficients() const { return _blendshapeCoefficients; } const QVector& getSummedBlendshapeCoefficients(); int getNumSummedBlendshapeCoefficients() const; @@ -69,6 +72,24 @@ public: } bool lookAtPositionChangedSince(quint64 time) { return _lookAtPositionChanged >= time; } + bool getHasProceduralEyeFaceMovement() const { return _hasProceduralEyeFaceMovement; } + + void setHasProceduralEyeFaceMovement(const bool hasProceduralEyeFaceMovement) { + _hasProceduralEyeFaceMovement = hasProceduralEyeFaceMovement; + } + + bool getHasProceduralBlinkFaceMovement() const { return _hasProceduralBlinkFaceMovement; } + + void setHasProceduralBlinkFaceMovement(const bool hasProceduralBlinkFaceMovement) { + _hasProceduralBlinkFaceMovement = hasProceduralBlinkFaceMovement; + } + + bool getHasAudioEnabledFaceMovement() const { return _hasAudioEnabledFaceMovement; } + + void setHasAudioEnabledFaceMovement(const bool hasAudioEnabledFaceMovement) { + _hasAudioEnabledFaceMovement = hasAudioEnabledFaceMovement; + } + friend class AvatarData; QJsonObject toJson() const; @@ -83,6 +104,9 @@ protected: glm::vec3 _lookAtPosition; quint64 _lookAtPositionChanged { 0 }; + bool _hasAudioEnabledFaceMovement { true }; + bool _hasProceduralBlinkFaceMovement { true }; + bool _hasProceduralEyeFaceMovement { true }; bool _isFaceTrackerConnected { false }; bool _isEyeTrackerConnected { false }; float _leftEyeBlink { 0.0f }; @@ -93,6 +117,7 @@ protected: QVector _blendshapeCoefficients; QVector _transientBlendshapeCoefficients; QVector _summedBlendshapeCoefficients; + QMap _blendshapeLookupMap; AvatarData* _owningAvatar; private: @@ -101,6 +126,7 @@ private: HeadData& operator= (const HeadData&); void setHeadOrientation(const glm::quat& orientation); + void computeBlendshapesLookupMap(); }; #endif // hifi_HeadData_h diff --git a/libraries/baking/src/TextureBaker.cpp b/libraries/baking/src/TextureBaker.cpp index 2b50f6be97..ecfe724441 100644 --- a/libraries/baking/src/TextureBaker.cpp +++ b/libraries/baking/src/TextureBaker.cpp @@ -22,12 +22,16 @@ #include #include +#include + #include "ModelBakingLoggingCategory.h" const QString BAKED_TEXTURE_KTX_EXT = ".ktx"; const QString BAKED_TEXTURE_BCN_SUFFIX = "_bcn.ktx"; const QString BAKED_META_TEXTURE_SUFFIX = ".texmeta.json"; +bool TextureBaker::_compressionEnabled = true; + TextureBaker::TextureBaker(const QUrl& textureURL, image::TextureUsage::Type textureType, const QDir& outputDirectory, const QString& metaTexturePathPrefix, const QString& baseFilename, const QByteArray& textureContent) : @@ -124,42 +128,45 @@ void TextureBaker::processTexture() { TextureMeta meta; + auto originalCopyFilePath = _outputDirectory.absoluteFilePath(_textureURL.fileName()); { - auto filePath = _outputDirectory.absoluteFilePath(_textureURL.fileName()); - QFile file { filePath }; + QFile file { originalCopyFilePath }; if (!file.open(QIODevice::WriteOnly) || file.write(_originalTexture) == -1) { handleError("Could not write original texture for " + _textureURL.toString()); return; } - _outputFiles.push_back(filePath); + // IMPORTANT: _originalTexture is empty past this point + _originalTexture.clear(); + _outputFiles.push_back(originalCopyFilePath); meta.original = _metaTexturePathPrefix +_textureURL.fileName(); } - // IMPORTANT: _originalTexture is empty past this point - auto processedTexture = image::processImage(std::move(_originalTexture), _textureURL.toString().toStdString(), - ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, _abortProcessing); - processedTexture->setSourceHash(hash); - - if (shouldStop()) { + auto buffer = std::static_pointer_cast(std::make_shared(originalCopyFilePath)); + if (!buffer->open(QIODevice::ReadOnly)) { + handleError("Could not open original file at " + originalCopyFilePath); return; } - if (!processedTexture) { - handleError("Could not process texture " + _textureURL.toString()); - return; - } + // Compressed KTX + if (_compressionEnabled) { + auto processedTexture = image::processImage(buffer, _textureURL.toString().toStdString(), + ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, true, _abortProcessing); + if (!processedTexture) { + handleError("Could not process texture " + _textureURL.toString()); + return; + } + processedTexture->setSourceHash(hash); - - auto memKTX = gpu::Texture::serialize(*processedTexture); + if (shouldStop()) { + return; + } - if (!memKTX) { - handleError("Could not serialize " + _textureURL.toString() + " to KTX"); - return; - } + auto memKTX = gpu::Texture::serialize(*processedTexture); + if (!memKTX) { + handleError("Could not serialize " + _textureURL.toString() + " to KTX"); + return; + } - - // attempt to write the baked texture to the destination file path - if (memKTX->_header.isCompressed()) { const char* name = khronos::gl::texture::toString(memKTX->_header.getGLInternaFormat()); if (name == nullptr) { handleError("Could not determine internal format for compressed KTX: " + _textureURL.toString()); @@ -178,21 +185,45 @@ void TextureBaker::processTexture() { } _outputFiles.push_back(filePath); meta.availableTextureTypes[memKTX->_header.getGLInternaFormat()] = _metaTexturePathPrefix + fileName; - } else { + } + + // Uncompressed KTX + if (_textureType == image::TextureUsage::Type::CUBE_TEXTURE) { + buffer->reset(); + auto processedTexture = image::processImage(std::move(buffer), _textureURL.toString().toStdString(), + ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, false, _abortProcessing); + if (!processedTexture) { + handleError("Could not process texture " + _textureURL.toString()); + return; + } + processedTexture->setSourceHash(hash); + + if (shouldStop()) { + return; + } + + auto memKTX = gpu::Texture::serialize(*processedTexture); + if (!memKTX) { + handleError("Could not serialize " + _textureURL.toString() + " to KTX"); + return; + } + const char* data = reinterpret_cast(memKTX->_storage->data()); const size_t length = memKTX->_storage->size(); auto fileName = _baseFilename + ".ktx"; auto filePath = _outputDirectory.absoluteFilePath(fileName); - QFile ktxTextureFile { filePath }; - if (!ktxTextureFile.open(QIODevice::WriteOnly) || ktxTextureFile.write(data, length) == -1) { - handleError("Could not write ktx texture for " + _textureURL.toString()); + QFile bakedTextureFile { filePath }; + if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) { + handleError("Could not write baked texture for " + _textureURL.toString()); return; } _outputFiles.push_back(filePath); + meta.uncompressed = _metaTexturePathPrefix + fileName; + } else { + buffer.reset(); } - { auto data = meta.serialize(); _metaTextureFileName = _outputDirectory.absoluteFilePath(_baseFilename + BAKED_META_TEXTURE_SUFFIX); diff --git a/libraries/baking/src/TextureBaker.h b/libraries/baking/src/TextureBaker.h index 54839c001a..c8c4fb73b8 100644 --- a/libraries/baking/src/TextureBaker.h +++ b/libraries/baking/src/TextureBaker.h @@ -41,6 +41,8 @@ public: virtual void setWasAborted(bool wasAborted) override; + static void setCompressionEnabled(bool enabled) { _compressionEnabled = enabled; } + public slots: virtual void bake() override; virtual void abort() override; @@ -65,6 +67,8 @@ private: QString _metaTexturePathPrefix; std::atomic _abortProcessing { false }; + + static bool _compressionEnabled; }; #endif // hifi_TextureBaker_h diff --git a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp index 09b9b7f8f9..d8b8cbd54a 100644 --- a/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/Basic2DWindowOpenGLDisplayPlugin.cpp @@ -151,11 +151,9 @@ void Basic2DWindowOpenGLDisplayPlugin::compositeExtra() { batch.setModelTransform(stickTransform); batch.draw(gpu::TRIANGLE_STRIP, 4); - if (!virtualPadManager.getLeftVirtualPad()->isBeingTouched()) { - batch.setResourceTexture(0, _virtualPadJumpBtnTexture); - batch.setModelTransform(jumpTransform); - batch.draw(gpu::TRIANGLE_STRIP, 4); - } + batch.setResourceTexture(0, _virtualPadJumpBtnTexture); + batch.setModelTransform(jumpTransform); + batch.draw(gpu::TRIANGLE_STRIP, 4); }); } #endif diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index b78f00fa0e..a0d5cb0920 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -29,6 +29,8 @@ #include #include +#include "GeometryUtil.h" + // Used to animate the magnification windows //static const quint64 TOOLTIP_DELAY = 500 * MSECS_TO_USECS; @@ -275,7 +277,7 @@ bool CompositorHelper::getReticleOverDesktop() const { // as being over the desktop. if (isHMD()) { QMutexLocker locker(&_reticleLock); - glm::vec2 maxOverlayPosition = glm::vec2(_currentDisplayPlugin->getRecommendedUiSize()) * _currentDisplayPlugin->getRenderResolutionScale(); + glm::vec2 maxOverlayPosition = glm::vec2(_currentDisplayPlugin->getRecommendedUiSize()); static const glm::vec2 minOverlayPosition; if (glm::any(glm::lessThan(_reticlePositionInHMD, minOverlayPosition)) || glm::any(glm::greaterThan(_reticlePositionInHMD, maxOverlayPosition))) { @@ -317,7 +319,7 @@ void CompositorHelper::sendFakeMouseEvent() { void CompositorHelper::setReticlePosition(const glm::vec2& position, bool sendFakeEvent) { if (isHMD()) { - glm::vec2 maxOverlayPosition = glm::vec2(_currentDisplayPlugin->getRecommendedUiSize()) * _currentDisplayPlugin->getRenderResolutionScale(); + glm::vec2 maxOverlayPosition = glm::vec2(_currentDisplayPlugin->getRecommendedUiSize()); // FIXME don't allow negative mouseExtra glm::vec2 mouseExtra = (MOUSE_EXTENTS_PIXELS - maxOverlayPosition) / 2.0f; glm::vec2 minMouse = vec2(0) - mouseExtra; @@ -357,9 +359,9 @@ bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, c glm::vec3 localDirection = glm::normalize(transformVectorFast(worldToUi, direction)); const float UI_RADIUS = 1.0f; - float instersectionDistance; - if (raySphereIntersect(localDirection, localPosition, UI_RADIUS, &instersectionDistance)) { - result = transformPoint(uiToWorld, localPosition + localDirection * instersectionDistance); + float intersectionDistance; + if (raySphereIntersect(localDirection, localPosition, UI_RADIUS, &intersectionDistance)) { + result = transformPoint(uiToWorld, localPosition + localDirection * intersectionDistance); #ifdef WANT_DEBUG DebugDraw::getInstance().drawRay(position, result, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f)); #endif @@ -372,6 +374,23 @@ bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, c return false; } +bool CompositorHelper::calculateParabolaUICollisionPoint(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, glm::vec3& result, float& parabolicDistance) const { + glm::mat4 uiToWorld = getUiTransform(); + glm::mat4 worldToUi = glm::inverse(uiToWorld); + glm::vec3 localOrigin = transformPoint(worldToUi, origin); + glm::vec3 localVelocity = glm::normalize(transformVectorFast(worldToUi, velocity)); + glm::vec3 localAcceleration = glm::normalize(transformVectorFast(worldToUi, acceleration)); + + const float UI_RADIUS = 1.0f; + float intersectionDistance; + if (findParabolaSphereIntersection(localOrigin, localVelocity, localAcceleration, glm::vec3(0.0f), UI_RADIUS, intersectionDistance)) { + result = origin + velocity * intersectionDistance + 0.5f * acceleration * intersectionDistance * intersectionDistance; + parabolicDistance = intersectionDistance; + return true; + } + return false; +} + glm::vec2 CompositorHelper::sphericalToOverlay(const glm::vec2& sphericalPos) const { glm::vec2 result = sphericalPos; result.x *= -1.0f; diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index fb712c26fa..e25d30109f 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -52,6 +52,7 @@ public: void setRenderingWidget(QWidget* widget) { _renderingWidget = widget; } bool calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const; + bool calculateParabolaUICollisionPoint(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, glm::vec3& result, float& parabolicDistance) const; bool isHMD() const; bool fakeEventActive() const { return _fakeMouseEvent; } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 513f955e9e..9200843cf8 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -83,6 +83,7 @@ public: connect(qApp, &QCoreApplication::aboutToQuit, [this] { shutdown(); }); + setObjectName("Present"); } ~PresentThread() { @@ -887,7 +888,7 @@ OpenGLDisplayPlugin::~OpenGLDisplayPlugin() { } void OpenGLDisplayPlugin::updateCompositeFramebuffer() { - auto renderSize = glm::uvec2(glm::vec2(getRecommendedRenderSize()) * getRenderResolutionScale()); + auto renderSize = glm::uvec2(getRecommendedRenderSize()); if (!_compositeFramebuffer || _compositeFramebuffer->getSize() != renderSize) { _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y)); } diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index 4a75994e12..ccebeaf9cc 100644 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -48,6 +48,13 @@ void HTTPManager::incomingConnection(qintptr socketDescriptor) { } bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) { + // Reject paths with embedded NULs + if (url.path().contains(QChar(0x00))) { + connection->respond(HTTPConnection::StatusCode400, "Embedded NULs not allowed in requests"); + qCWarning(embeddedwebserver) << "Received a request with embedded NULs"; + return true; + } + if (!skipSubHandler && requestHandledByRequestHandler(connection, url)) { // this request was handled by our request handler object // so we don't need to attempt to do so in the document root @@ -57,17 +64,27 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, if (!_documentRoot.isEmpty()) { // check to see if there is a file to serve from the document root for this path QString subPath = url.path(); - + // remove any slash at the beginning of the path if (subPath.startsWith('/')) { subPath.remove(0, 1); } - + + QString absoluteDocumentRoot { QFileInfo(_documentRoot).absolutePath() }; QString filePath; - - if (QFileInfo(_documentRoot + subPath).isFile()) { - filePath = _documentRoot + subPath; - } else if (subPath.size() > 0 && !subPath.endsWith('/')) { + QFileInfo pathFileInfo { _documentRoot + subPath }; + QString absoluteFilePath { pathFileInfo.absoluteFilePath() }; + + // The absolute path for this file isn't under the document root + if (absoluteFilePath.indexOf(absoluteDocumentRoot) != 0) { + qCWarning(embeddedwebserver) << absoluteFilePath << "is outside the document root"; + connection->respond(HTTPConnection::StatusCode400, "Requested path outside document root"); + return true; + } + + if (pathFileInfo.isFile()) { + filePath = absoluteFilePath; + } else if (subPath.size() > 0 && !subPath.endsWith('/') && pathFileInfo.isDir()) { // this could be a directory with a trailing slash // send a redirect to the path with a slash so we can QString redirectLocation = '/' + subPath + '/'; @@ -80,6 +97,7 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, redirectHeader.insert(QByteArray("Location"), redirectLocation.toUtf8()); connection->respond(HTTPConnection::StatusCode302, "", HTTPConnection::DefaultContentType, redirectHeader); + return true; } // if the last thing is a trailing slash then we want to look for index file @@ -87,8 +105,8 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, QStringList possibleIndexFiles = QStringList() << "index.html" << "index.shtml"; foreach (const QString& possibleIndexFilename, possibleIndexFiles) { - if (QFileInfo(_documentRoot + subPath + possibleIndexFilename).exists()) { - filePath = _documentRoot + subPath + possibleIndexFilename; + if (QFileInfo(absoluteFilePath + possibleIndexFilename).exists()) { + filePath = absoluteFilePath + possibleIndexFilename; break; } } diff --git a/libraries/entities-renderer/CMakeLists.txt b/libraries/entities-renderer/CMakeLists.txt index 3ee063ff7a..12b9b3dea5 100644 --- a/libraries/entities-renderer/CMakeLists.txt +++ b/libraries/entities-renderer/CMakeLists.txt @@ -1,7 +1,7 @@ set(TARGET_NAME entities-renderer) AUTOSCRIBE_SHADER_LIB(gpu graphics procedural render render-utils) setup_hifi_library(Network Script) -link_hifi_libraries(shared gpu procedural graphics model-networking script-engine render render-utils image qml ui pointers) +link_hifi_libraries(shared workload gpu procedural graphics model-networking script-engine render render-utils image qml ui pointers) include_hifi_library_headers(networking) include_hifi_library_headers(gl) include_hifi_library_headers(ktx) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 600d1c32a8..129391e43a 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -42,7 +42,19 @@ size_t std::hash::operator()(const EntityItemID& id) const { return qHash(id); } std::function EntityTreeRenderer::_entitiesShouldFadeFunction; -std::function EntityTreeRenderer::_renderDebugHullsOperator = [] { return false; }; + +QString resolveScriptURL(const QString& scriptUrl) { + auto normalizedScriptUrl = DependencyManager::get()->normalizeURL(scriptUrl); + QUrl url { normalizedScriptUrl }; + if (url.isLocalFile()) { + // Outside of the ScriptEngine, /~/ resolves to the /resources directory. + // Inside of the ScriptEngine, /~/ resolves to the /scripts directory. + // Here we expand local paths in case they are /~/ paths, so they aren't + // incorrectly recognized as being located in /scripts when utilized in ScriptEngine. + return PathUtils::expandToLocalDataAbsolutePath(url).toString(); + } + return normalizedScriptUrl; +} EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState, AbstractScriptingServicesInterface* scriptingServices) : @@ -194,6 +206,7 @@ void EntityTreeRenderer::clear() { } // remove all entities from the scene + _space->clear(); auto scene = _viewState->getMain3DScene(); if (scene) { render::Transaction transaction; @@ -221,7 +234,7 @@ void EntityTreeRenderer::reloadEntityScripts() { const auto& renderer = entry.second; const auto& entity = renderer->getEntity(); if (!entity->getScript().isEmpty()) { - _entitiesScriptEngine->loadEntityScript(entity->getEntityItemID(), entity->getScript(), true); + _entitiesScriptEngine->loadEntityScript(entity->getEntityItemID(), resolveScriptURL(entity->getScript()), true); } } } @@ -278,6 +291,16 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r if (!entity->isParentPathComplete()) { continue; } + if (entity->getSpaceIndex() == -1) { + std::unique_lock lock(_spaceLock); + auto spaceIndex = _space->allocateID(); + workload::Sphere sphere(entity->getWorldPosition(), entity->getBoundingRadius()); + workload::Transaction transaction; + transaction.reset(spaceIndex, sphere, workload::Owner(entity)); + _space->enqueueTransaction(transaction); + entity->setSpaceIndex(spaceIndex); + connect(entity.get(), &EntityItem::spaceUpdate, this, &EntityTreeRenderer::handleSpaceUpdate, Qt::QueuedConnection); + } auto entityID = entity->getEntityItemID(); processedIds.insert(entityID); @@ -287,7 +310,6 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r } } - if (!processedIds.empty()) { for (const auto& processedId : processedIds) { _entitiesToAdd.erase(processedId); @@ -382,7 +404,6 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene uint64_t expiry = updateStart + timeBudget; // process the sorted renderables - std::unordered_map::iterator itr; size_t numSorted = sortedRenderables.size(); while (!sortedRenderables.empty() && usecTimestampNow() < expiry) { const auto renderable = sortedRenderables.top().getRenderer(); @@ -407,10 +428,12 @@ void EntityTreeRenderer::update(bool simulate) { EntityTreePointer tree = std::static_pointer_cast(_tree); // here we update _currentFrame and _lastAnimated and sync with the server properties. - tree->update(simulate); - - // Update the rendereable entities as needed { + PerformanceTimer perfTimer("tree::update"); + tree->update(simulate); + } + + { // Update the rendereable entities as needed PROFILE_RANGE(simulation_physics, "Scene"); PerformanceTimer sceneTimer("scene"); auto scene = _viewState->getMain3DScene(); @@ -422,6 +445,24 @@ void EntityTreeRenderer::update(bool simulate) { scene->enqueueTransaction(transaction); } } + { + PerformanceTimer perfTimer("workload::transaction"); + workload::Transaction spaceTransaction; + { // update proxies in the workload::Space + std::unique_lock lock(_spaceLock); + spaceTransaction.update(_spaceUpdates); + _spaceUpdates.clear(); + } + { + std::vector staleProxies; + tree->swapStaleProxies(staleProxies); + spaceTransaction.remove(staleProxies); + { + std::unique_lock lock(_spaceLock); + _space->enqueueTransaction(spaceTransaction); + } + } + } if (simulate) { // Handle enter/leave entity logic @@ -438,6 +479,11 @@ void EntityTreeRenderer::update(bool simulate) { } } +void EntityTreeRenderer::handleSpaceUpdate(std::pair proxyUpdate) { + std::unique_lock lock(_spaceLock); + _spaceUpdates.emplace_back(proxyUpdate.first, proxyUpdate.second); +} + bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QVector* entitiesContainingAvatar) { bool didUpdate = false; float radius = 0.01f; // for now, assume 0.01 meter radius, because we actually check the point inside later @@ -914,8 +960,7 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, bool entity->scriptHasUnloaded(); } if (shouldLoad) { - scriptUrl = DependencyManager::get()->normalizeURL(scriptUrl); - _entitiesScriptEngine->loadEntityScript(entityID, scriptUrl, reload); + _entitiesScriptEngine->loadEntityScript(entityID, resolveScriptURL(scriptUrl), reload); entity->scriptHasPreloaded(); } } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 882ec2fd5b..f810aa64b6 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -24,6 +24,7 @@ #include #include #include +#include class AbstractScriptingServicesInterface; class AbstractViewStateInterface; @@ -116,14 +117,13 @@ public: EntityItemPointer getEntity(const EntityItemID& id); void onEntityChanged(const EntityItemID& id); - static void setRenderDebugHullsOperator(std::function renderDebugHullsOperator) { _renderDebugHullsOperator = renderDebugHullsOperator; } - static bool shouldRenderDebugHulls() { return _renderDebugHullsOperator(); } + // Access the workload Space + workload::SpacePointer getWorkloadSpace() const { return _space; } signals: void enterEntity(const EntityItemID& entityItemID); void leaveEntity(const EntityItemID& entityItemID); void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); - void setRenderDebugHulls(); public slots: void addingEntity(const EntityItemID& entityID); @@ -139,6 +139,8 @@ public slots: EntityRendererPointer renderableForEntityId(const EntityItemID& id) const; render::ItemID renderableIdForEntityId(const EntityItemID& id) const; + void handleSpaceUpdate(std::pair proxyUpdate); + protected: virtual OctreePointer createTree() override { EntityTreePointer newTree = EntityTreePointer(new EntityTree(true)); @@ -260,7 +262,9 @@ private: static CalculateEntityLoadingPriority _calculateEntityLoadingPriorityFunc; static std::function _entitiesShouldFadeFunction; - static std::function _renderDebugHullsOperator; + mutable std::mutex _spaceLock; + workload::SpacePointer _space{ new workload::Space() }; + workload::Transaction::Updates _spaceUpdates; }; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index ae4c13d96f..78801df715 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -284,8 +284,8 @@ bool EntityRenderer::addToScene(const ScenePointer& scene, Transaction& transact makeStatusGetters(_entity, statusGetters); renderPayload->addStatusGetters(statusGetters); transaction.resetItem(_renderItemID, renderPayload); - updateInScene(scene, transaction); onAddToScene(_entity); + updateInScene(scene, transaction); return true; } @@ -363,6 +363,20 @@ bool EntityRenderer::needsRenderUpdateFromEntity(const EntityItemPointer& entity return false; } +void EntityRenderer::updateModelTransformAndBound() { + bool success = false; + auto newModelTransform = _entity->getTransformToCenter(success); + if (success) { + _modelTransform = newModelTransform; + } + + success = false; + auto bound = _entity->getAABox(success); + if (success) { + _bound = bound; + } +} + void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity) { DETAILED_PROFILE_RANGE(simulation_physics, __FUNCTION__); withWriteLock([&] { @@ -372,15 +386,7 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa } _prevIsTransparent = transparent; - bool success = false; - auto bound = entity->getAABox(success); - if (success) { - _bound = bound; - } - auto newModelTransform = entity->getTransformToCenter(success); - if (success) { - _modelTransform = newModelTransform; - } + updateModelTransformAndBound(); _moving = entity->isMovingRelativeToParent(); _visible = entity->getVisible(); @@ -419,4 +425,4 @@ void EntityRenderer::addMaterial(graphics::MaterialLayer material, const std::st void EntityRenderer::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { std::lock_guard lock(_materialsLock); _materials[parentMaterialName].remove(material); -} \ No newline at end of file +} diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index e1ce2ed39e..496649eb5f 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -97,6 +97,7 @@ protected: virtual void doRender(RenderArgs* args) = 0; bool isFading() const { return _isFading; } + void updateModelTransformAndBound(); virtual bool isTransparent() const { return _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) < 1.0f : false; } inline bool isValidRenderItem() const { return _renderItemID != Item::INVALID_ITEM_ID; } @@ -140,6 +141,7 @@ protected: bool _needsRenderUpdate { false }; // Only touched on the rendering thread bool _renderUpdateQueued{ false }; + Transform _renderTransform; std::unordered_map _materials; std::mutex _materialsLock; diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.h b/libraries/entities-renderer/src/RenderableMaterialEntityItem.h index 96c720f79f..168041a842 100644 --- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.h +++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.h @@ -35,7 +35,6 @@ private: glm::vec2 _materialMappingPos; glm::vec2 _materialMappingScale; float _materialMappingRot; - Transform _renderTransform; std::shared_ptr _drawMaterial; }; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index a91534668c..34936c2c48 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -23,7 +23,6 @@ #include #include -#include #include #include #include @@ -35,8 +34,6 @@ #include "EntitiesRendererLogging.h" -static CollisionRenderMeshCache collisionMeshCache; - void ModelEntityWrapper::setModel(const ModelPointer& model) { withWriteLock([&] { if (_model != model) { @@ -191,7 +188,7 @@ bool RenderableModelEntityItem::needsUpdateModelBounds() const { } } - return model->needsReload(); + return false; } void RenderableModelEntityItem::updateModelBounds() { @@ -281,24 +278,34 @@ EntityItemProperties RenderableModelEntityItem::getProperties(EntityPropertyFlag return properties; } -bool RenderableModelEntityItem::supportsDetailedRayIntersection() const { - return isModelLoaded(); +bool RenderableModelEntityItem::supportsDetailedIntersection() const { + return true; } bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { auto model = getModel(); - if (!model) { - return true; + if (!model || !isModelLoaded()) { + return false; } - // qCDebug(entitiesrenderer) << "RenderableModelEntityItem::findDetailedRayIntersection() precisionPicking:" - // << precisionPicking; return model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo, precisionPicking, false); } +bool RenderableModelEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, + glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { + auto model = getModel(); + if (!model || !isModelLoaded()) { + return false; + } + + return model->findParabolaIntersectionAgainstSubMeshes(origin, velocity, acceleration, parabolicDistance, + face, surfaceNormal, extraInfo, precisionPicking, false); +} + void RenderableModelEntityItem::getCollisionGeometryResource() { QUrl hullURL(getCompoundShapeURL()); QUrlQuery queryArgs(hullURL); @@ -704,14 +711,6 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { adjustShapeInfoByRegistration(shapeInfo); } -void RenderableModelEntityItem::setCollisionShape(const btCollisionShape* shape) { - const void* key = static_cast(shape); - if (_collisionMeshKey != key) { - _collisionMeshKey = key; - emit requestCollisionGeometryUpdate(); - } -} - void RenderableModelEntityItem::setJointMap(std::vector jointMap) { if (jointMap.size() > 0) { _jointMap = jointMap; @@ -1052,10 +1051,7 @@ using namespace render; using namespace render::entities; ModelEntityRenderer::ModelEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { - connect(DependencyManager::get().data(), &EntityTreeRenderer::setRenderDebugHulls, this, [&] { - _needsCollisionGeometryUpdate = true; - emit requestRenderUpdate(); - }); + } void ModelEntityRenderer::setKey(bool didVisualGeometryRequestSucceed) { @@ -1182,19 +1178,8 @@ void ModelEntityRenderer::animate(const TypedEntityPointer& entity) { } bool ModelEntityRenderer::needsRenderUpdate() const { - ModelPointer model; - withReadLock([&] { - model = _model; - }); - - if (model) { - if (_needsJointSimulation || _moving || _animating) { - return true; - } - - // When the individual mesh parts of a model finish fading, they will mark their Model as needing updating - // we will watch for that and ask the model to update it's render items - if (_parsedModelURL != model->getURL()) { + if (resultWithReadLock([&] { + if (_moving || _animating) { return true; } @@ -1202,23 +1187,40 @@ bool ModelEntityRenderer::needsRenderUpdate() const { return true; } + if (!_prevModelLoaded) { + return true; + } + + return false; + })) { + return true; + } + + ModelPointer model; + QUrl parsedModelURL; + withReadLock([&] { + model = _model; + parsedModelURL = _parsedModelURL; + }); + + if (model) { + // When the individual mesh parts of a model finish fading, they will mark their Model as needing updating + // we will watch for that and ask the model to update it's render items + if (parsedModelURL != model->getURL()) { + return true; + } + if (model->needsReload()) { return true; } - // FIXME what is the difference between these two? if (model->needsFixupInScene()) { return true; } - // FIXME what is the difference between these two? ^^^^ if (model->getRenderItemsNeedUpdate()) { return true; } - - if (_needsCollisionGeometryUpdate) { - return true; - } } return Parent::needsRenderUpdate(); } @@ -1229,7 +1231,7 @@ bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin return true; } - if (_lastModelURL != entity->getModelURL()) { + if (_parsedModelURL != entity->getModelURL()) { return true; } @@ -1242,10 +1244,6 @@ bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin return true; } - if (_renderAnimationProperties != entity->getAnimationProperties()) { - return true; - } - if (_animating != entity->isAnimatingSomething()) { return true; } @@ -1259,7 +1257,7 @@ bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin }); if (model && model->isLoaded()) { - if (!entity->_dimensionsInitialized || entity->_needsInitialSimulation) { + if (!entity->_dimensionsInitialized || entity->_needsInitialSimulation || !entity->_originalTexturesRead) { return true; } @@ -1284,28 +1282,18 @@ bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin return false; } -void ModelEntityRenderer::setCollisionMeshKey(const void*key) { - if (key != _collisionMeshKey) { - if (_collisionMeshKey) { - collisionMeshCache.releaseMesh(_collisionMeshKey); - } - _collisionMeshKey = key; - } -} - void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { DETAILED_PROFILE_RANGE(simulation_physics, __FUNCTION__); if (_hasModel != entity->hasModel()) { - _hasModel = entity->hasModel(); + withWriteLock([&] { + _hasModel = entity->hasModel(); + }); } - _marketplaceEntity = entity->getMarketplaceID().length() != 0; - _animating = entity->isAnimatingSomething(); - withWriteLock([&] { - if (_lastModelURL != entity->getModelURL()) { - _lastModelURL = entity->getModelURL(); - _parsedModelURL = QUrl(_lastModelURL); + _animating = entity->isAnimatingSomething(); + if (_parsedModelURL != entity->getModelURL()) { + _parsedModelURL = QUrl(entity->getModelURL()); } }); @@ -1313,7 +1301,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce ModelPointer model; withReadLock([&] { model = _model; }); if (!_hasModel) { - if ((bool)model) { + if (model) { model->removeFromScene(scene, transaction); withWriteLock([&] { _model.reset(); }); transaction.updateItem(getRenderItemID(), [](PayloadProxyInterface& data) { @@ -1327,8 +1315,9 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } // Check for addition - if (_hasModel && !(bool)_model) { + if (_hasModel && !model) { model = std::make_shared(nullptr, entity.get()); + connect(model.get(), &Model::requestRenderUpdate, this, &ModelEntityRenderer::requestRenderUpdate); connect(model.get(), &Model::setURLFinished, this, [&](bool didVisualGeometryRequestSucceed) { setKey(didVisualGeometryRequestSucceed); emit requestRenderUpdate(); @@ -1338,27 +1327,34 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } _didLastVisualGeometryRequestSucceed = didVisualGeometryRequestSucceed; }); - connect(model.get(), &Model::requestRenderUpdate, this, &ModelEntityRenderer::requestRenderUpdate); - connect(entity.get(), &RenderableModelEntityItem::requestCollisionGeometryUpdate, this, &ModelEntityRenderer::flagForCollisionGeometryUpdate); model->setLoadingPriority(EntityTreeRenderer::getEntityLoadingPriority(*entity)); entity->setModel(model); withWriteLock([&] { _model = model; }); } // From here on, we are guaranteed a populated model - withWriteLock([&] { - if (_parsedModelURL != model->getURL()) { + if (_parsedModelURL != model->getURL()) { + withWriteLock([&] { _texturesLoaded = false; model->setURL(_parsedModelURL); - } - }); + }); + } // Nothing else to do unless the model is loaded if (!model->isLoaded()) { + withWriteLock([&] { + _prevModelLoaded = false; + }); + emit requestRenderUpdate(); return; + } else if (!_prevModelLoaded) { + withWriteLock([&] { + _prevModelLoaded = true; + }); } // Check for initializing the model + // FIXME: There are several places below here where we are modifying the entity, which we should not be doing from the renderable if (!entity->_dimensionsInitialized) { EntityItemProperties properties; properties.setLastEdited(usecTimestampNow()); // we must set the edit time since we're editing it @@ -1376,16 +1372,16 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce // Default to _originalTextures to avoid remapping immediately and lagging on load entity->_originalTextures = model->getTextures(); entity->_originalTexturesRead = true; - _currentTextures = entity->_originalTextures; } if (_lastTextures != entity->getTextures()) { - _texturesLoaded = false; - _lastTextures = entity->getTextures(); + withWriteLock([&] { + _texturesLoaded = false; + _lastTextures = entity->getTextures(); + }); auto newTextures = parseTexturesToMap(_lastTextures, entity->_originalTextures); - if (newTextures != _currentTextures) { + if (newTextures != model->getTextures()) { model->setTextures(newTextures); - _currentTextures = newTextures; } } if (entity->_needsJointSimulation) { @@ -1394,14 +1390,11 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce entity->updateModelBounds(); entity->stopModelOverrideIfNoParent(); - render::hifi::Tag tagMask = getTagMask(); if (model->isVisible() != _visible) { - // FIXME: this seems like it could be optimized if we tracked our last known visible state in - // the renderable item. As it stands now the model checks it's visible/invisible state - // so most of the time we don't do anything in this function. model->setVisibleInScene(_visible, scene); } + render::hifi::Tag tagMask = getTagMask(); if (model->getTagMask() != tagMask) { model->setTagMask(tagMask, scene); } @@ -1412,26 +1405,6 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce model->setCanCastShadow(_canCastShadow, scene); } - if (_needsCollisionGeometryUpdate) { - setCollisionMeshKey(entity->getCollisionMeshKey()); - _needsCollisionGeometryUpdate = false; - ShapeType type = entity->getShapeType(); - if (DependencyManager::get()->shouldRenderDebugHulls() && type != SHAPE_TYPE_STATIC_MESH && type != SHAPE_TYPE_NONE) { - // NOTE: it is OK if _collisionMeshKey is nullptr - graphics::MeshPointer mesh = collisionMeshCache.getMesh(_collisionMeshKey); - // NOTE: the model will render the collisionGeometry if it has one - _model->setCollisionMesh(mesh); - } else { - if (_collisionMeshKey) { - // release mesh - collisionMeshCache.releaseMesh(_collisionMeshKey); - } - // clear model's collision geometry - graphics::MeshPointer mesh = nullptr; - _model->setCollisionMesh(mesh); - } - } - { DETAILED_PROFILE_RANGE(simulation_physics, "Fixup"); if (model->needsFixupInScene()) { @@ -1450,7 +1423,9 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce } if (!_texturesLoaded && model->getGeometry() && model->getGeometry()->areTexturesLoaded()) { - _texturesLoaded = true; + withWriteLock([&] { + _texturesLoaded = true; + }); model->updateRenderItems(); } else if (!_texturesLoaded) { emit requestRenderUpdate(); @@ -1487,11 +1462,6 @@ void ModelEntityRenderer::setIsVisibleInSecondaryCamera(bool value) { setKey(_didLastVisualGeometryRequestSucceed); } -void ModelEntityRenderer::flagForCollisionGeometryUpdate() { - _needsCollisionGeometryUpdate = true; - emit requestRenderUpdate(); -} - // NOTE: this only renders the "meta" portion of the Model, namely it renders debugging items void ModelEntityRenderer::doRender(RenderArgs* args) { DETAILED_PROFILE_RANGE(render_detail, "MetaModelRender"); @@ -1503,15 +1473,15 @@ void ModelEntityRenderer::doRender(RenderArgs* args) { batch.setModelTransform(getModelTransform()); // we want to include the scale as well DependencyManager::get()->renderWireCubeInstance(args, batch, greenColor); - // Enqueue updates for the next frame #if WANT_EXTRA_DEBUGGING - // debugging... - gpu::Batch& batch = *args->_batch; - _model->renderDebugMeshBoxes(batch); + ModelPointer model; + withReadLock([&] { + model = _model; + }); + if (model) { + model->renderDebugMeshBoxes(batch); + } #endif - - // Remap textures for the next frame to avoid flicker - // remapTextures(); } void ModelEntityRenderer::mapJoints(const TypedEntityPointer& entity, const QStringList& modelJointNames) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index f1748ca069..84591b8afe 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -66,11 +66,15 @@ public: void doInitialModelSimulation(); void updateModelBounds(); - virtual bool supportsDetailedRayIntersection() const override; + virtual bool supportsDetailedIntersection() const override; virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; virtual void setShapeType(ShapeType type) override; virtual void setCompoundShapeURL(const QString& url) override; @@ -78,8 +82,6 @@ public: virtual bool isReadyToComputeShape() const override; virtual void computeShapeInfo(ShapeInfo& shapeInfo) override; - void setCollisionShape(const btCollisionShape* shape) override; - virtual bool contains(const glm::vec3& point) const override; void stopModelOverrideIfNoParent(); @@ -112,10 +114,6 @@ public: virtual QStringList getJointNames() const override; bool getMeshes(MeshProxyList& result) override; // deprecated - const void* getCollisionMeshKey() const { return _collisionMeshKey; } - -signals: - void requestCollisionGeometryUpdate(); private: bool needsUpdateModelBounds() const; @@ -130,7 +128,6 @@ private: QVariantMap _originalTextures; bool _dimensionsInitialized { true }; bool _needsJointSimulation { false }; - const void* _collisionMeshKey { nullptr }; }; namespace render { namespace entities { @@ -161,8 +158,6 @@ protected: virtual bool needsRenderUpdate() const override; virtual void doRender(RenderArgs* args) override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; - void flagForCollisionGeometryUpdate(); - void setCollisionMeshKey(const void* key); render::hifi::Tag getTagMask() const override; @@ -171,7 +166,7 @@ protected: private: void animate(const TypedEntityPointer& entity); void mapJoints(const TypedEntityPointer& entity, const QStringList& modelJointNames); - bool jointsMapped() const { return _jointMappingURL == _renderAnimationProperties.getURL() && _jointMappingCompleted; } + bool jointsMapped() const { return _jointMappingCompleted; } // Transparency is handled in ModelMeshPartPayload virtual bool isTransparent() const override { return false; } @@ -180,33 +175,26 @@ private: ModelPointer _model; GeometryResource::Pointer _compoundShapeResource; QString _lastTextures; - QVariantMap _currentTextures; bool _texturesLoaded { false }; - AnimationPropertyGroup _renderAnimationProperties; int _lastKnownCurrentFrame { -1 }; #ifdef MODEL_ENTITY_USE_FADE_EFFECT bool _hasTransitioned{ false }; #endif - bool _needsJointSimulation { false }; - bool _needsCollisionGeometryUpdate { false }; const void* _collisionMeshKey { nullptr }; // used on client side bool _jointMappingCompleted{ false }; QVector _jointMapping; // domain is index into model-joints, range is index into animation-joints - QString _jointMappingURL; AnimationPointer _animation; - QString _lastModelURL; QUrl _parsedModelURL; - bool _marketplaceEntity { false }; - bool _shouldHighlight { false }; bool _animating { false }; uint64_t _lastAnimated { 0 }; render::ItemKey _itemKey { render::ItemKey::Builder().withTypeMeta() }; bool _didLastVisualGeometryRequestSucceed { true }; + bool _prevModelLoaded { false }; void processMaterials(); }; diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 881c39c0bd..73f46245c4 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -95,15 +95,16 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi auto newParticleProperties = entity->getParticleProperties(); if (!newParticleProperties.valid()) { qCWarning(entitiesrenderer) << "Bad particle properties"; - if (!entity->getParticleProperties().valid()) { - qCWarning(entitiesrenderer) << "Bad particle properties"; - } } if (resultWithReadLock([&]{ return _particleProperties != newParticleProperties; })) { _timeUntilNextEmit = 0; withWriteLock([&]{ _particleProperties = newParticleProperties; + if (!_prevEmitterShouldTrailInitialized) { + _prevEmitterShouldTrailInitialized = true; + _prevEmitterShouldTrail = _particleProperties.emission.shouldTrail; + } }); } _emitting = entity->getIsEmitting(); @@ -125,6 +126,14 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi }); } } + + void* key = (void*)this; + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this] () { + withWriteLock([&] { + updateModelTransformAndBound(); + _renderTransform = getModelTransform(); + }); + }); } void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { @@ -139,7 +148,12 @@ void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEn particleUniforms.color.middle = _particleProperties.getColorMiddle(); particleUniforms.color.finish = _particleProperties.getColorFinish(); particleUniforms.color.spread = _particleProperties.getColorSpread(); + particleUniforms.spin.start = _particleProperties.spin.range.start; + particleUniforms.spin.middle = _particleProperties.spin.gradient.target; + particleUniforms.spin.finish = _particleProperties.spin.range.finish; + particleUniforms.spin.spread = _particleProperties.spin.gradient.spread; particleUniforms.lifespan = _particleProperties.lifespan; + particleUniforms.rotateWithEntity = _particleProperties.rotateWithEntity ? 1 : 0; }); // Update particle uniforms memcpy(&_uniformBuffer.edit(), &particleUniforms, sizeof(ParticleUniforms)); @@ -171,7 +185,7 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa const auto& azimuthFinish = particleProperties.azimuth.finish; const auto& emitDimensions = particleProperties.emission.dimensions; const auto& emitAcceleration = particleProperties.emission.acceleration.target; - auto emitOrientation = particleProperties.emission.orientation; + auto emitOrientation = baseTransform.getRotation() * particleProperties.emission.orientation; const auto& emitRadiusStart = glm::max(particleProperties.radiusStart, EPSILON); // Avoid math complications at center const auto& emitSpeed = particleProperties.emission.speed.target; const auto& speedSpread = particleProperties.emission.speed.spread; @@ -180,16 +194,15 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa particle.seed = randFloatInRange(-1.0f, 1.0f); particle.expiration = now + (uint64_t)(particleProperties.lifespan * USECS_PER_SECOND); - if (particleProperties.emission.shouldTrail) { - particle.position = baseTransform.getTranslation(); - emitOrientation = baseTransform.getRotation() * emitOrientation; - } + + particle.relativePosition = glm::vec3(0.0f); + particle.basePosition = baseTransform.getTranslation(); // Position, velocity, and acceleration if (polarStart == 0.0f && polarFinish == 0.0f && emitDimensions.z == 0.0f) { // Emit along z-axis from position - particle.velocity = (emitSpeed + 0.2f * speedSpread) * (emitOrientation * Vectors::UNIT_Z); + particle.velocity = (emitSpeed + randFloatInRange(-1.0f, 1.0f) * speedSpread) * (emitOrientation * Vectors::UNIT_Z); particle.acceleration = emitAcceleration + randFloatInRange(-1.0f, 1.0f) * accelerationSpread; } else { @@ -198,10 +211,9 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa // - Distribute points relatively evenly over ellipsoid surface // - Distribute points relatively evenly within ellipsoid volume - float elevationMinZ = sin(PI_OVER_TWO - polarFinish); - float elevationMaxZ = sin(PI_OVER_TWO - polarStart); - // float elevation = asin(elevationMinZ + (elevationMaxZ - elevationMinZ) * randFloat()); - float elevation = asin(elevationMinZ + (elevationMaxZ - elevationMinZ) *randFloat()); + float elevationMinZ = sinf(PI_OVER_TWO - polarFinish); + float elevationMaxZ = sinf(PI_OVER_TWO - polarStart); + float elevation = asinf(elevationMinZ + (elevationMaxZ - elevationMinZ) * randFloat()); float azimuth; if (azimuthFinish >= azimuthStart) { @@ -233,7 +245,7 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa radii.y > 0.0f ? y / (radii.y * radii.y) : 0.0f, radii.z > 0.0f ? z / (radii.z * radii.z) : 0.0f )); - particle.position += emitOrientation * emitPosition; + particle.relativePosition += emitOrientation * emitPosition; } particle.velocity = (emitSpeed + randFloatInRange(-1.0f, 1.0f) * speedSpread) * (emitOrientation * emitDirection); @@ -258,8 +270,8 @@ void ParticleEffectEntityRenderer::stepSimulation() { particleProperties = _particleProperties; }); + const auto& modelTransform = getModelTransform(); if (_emitting && particleProperties.emitting()) { - const auto& modelTransform = getModelTransform(); uint64_t emitInterval = particleProperties.emitIntervalUsecs(); if (emitInterval > 0 && interval >= _timeUntilNextEmit) { auto timeRemaining = interval; @@ -284,15 +296,23 @@ void ParticleEffectEntityRenderer::stepSimulation() { const float deltaTime = (float)interval / (float)USECS_PER_SECOND; // update the particles for (auto& particle : _cpuParticles) { + if (_prevEmitterShouldTrail != particleProperties.emission.shouldTrail) { + if (_prevEmitterShouldTrail) { + particle.relativePosition = particle.relativePosition + particle.basePosition - modelTransform.getTranslation(); + } + particle.basePosition = modelTransform.getTranslation(); + } particle.integrate(deltaTime); } + _prevEmitterShouldTrail = particleProperties.emission.shouldTrail; // Build particle primitives static GpuParticles gpuParticles; gpuParticles.clear(); gpuParticles.reserve(_cpuParticles.size()); // Reserve space - std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [](const CpuParticle& particle) { - return GpuParticle(particle.position, glm::vec2(particle.lifetime, particle.seed)); + std::transform(_cpuParticles.begin(), _cpuParticles.end(), std::back_inserter(gpuParticles), [&particleProperties, &modelTransform](const CpuParticle& particle) { + glm::vec3 position = particle.relativePosition + (particleProperties.emission.shouldTrail ? particle.basePosition : modelTransform.getTranslation()); + return GpuParticle(position, glm::vec2(particle.lifetime, particle.seed)); }); // Update particle buffer @@ -309,7 +329,6 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) { return; } - // FIXME migrate simulation to a compute stage stepSimulation(); @@ -321,12 +340,11 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) { } Transform transform; - // In trail mode, the particles are created in world space. - // so we only set a transform if they're not in trail mode - if (!_particleProperties.emission.shouldTrail) { - transform = getModelTransform(); - transform.setScale(vec3(1)); - } + // The particles are in world space, so the transform is unused, except for the rotation, which we use + // if the particles are marked rotateWithEntity + withReadLock([&] { + transform.setRotation(_renderTransform.getRotation()); + }); batch.setModelTransform(transform); batch.setUniformBuffer(PARTICLE_UNIFORM_SLOT, _uniformBuffer); batch.setInputFormat(_vertexFormat); @@ -335,5 +353,3 @@ void ParticleEffectEntityRenderer::doRender(RenderArgs* args) { auto numParticles = _particleBuffer->getSize() / sizeof(GpuParticle); batch.drawInstanced((gpu::uint32)numParticles, gpu::TRIANGLE_STRIP, (gpu::uint32)VERTEX_PER_PARTICLE); } - - diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h index be2641c0c9..7655918c58 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h @@ -45,36 +45,23 @@ private: // CPU particles // FIXME either switch to GPU compute particles or switch to simd updating of the particles -#if 1 struct CpuParticle { - float seed{ 0.0f }; + float seed { 0.0f }; uint64_t expiration { 0 }; float lifetime { 0.0f }; - glm::vec3 position; + glm::vec3 basePosition; + glm::vec3 relativePosition; glm::vec3 velocity; glm::vec3 acceleration; void integrate(float deltaTime) { glm::vec3 atSquared = (0.5f * deltaTime * deltaTime) * acceleration; - position += velocity * deltaTime + atSquared; + relativePosition += velocity * deltaTime + atSquared; velocity += acceleration * deltaTime; lifetime += deltaTime; } }; using CpuParticles = std::deque; -#else - struct CpuParticles { - std::vector seeds; - std::vector lifetimes; - std::vector positions; - std::vector velocities; - std::vector accelerations; - - size_t size() const; - void resize(size_t size); - void integrate(float deltaTime); - }; -#endif template @@ -88,15 +75,18 @@ private: struct ParticleUniforms { InterpolationData radius; InterpolationData color; // rgba + InterpolationData spin; float lifespan; - glm::vec3 spare; + int rotateWithEntity; + glm::vec2 spare; }; - static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties); void stepSimulation(); particle::Properties _particleProperties; + bool _prevEmitterShouldTrail; + bool _prevEmitterShouldTrailInitialized { false }; CpuParticles _cpuParticles; bool _emitting { false }; uint64_t _timeUntilNextEmit { 0 }; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 0211daff1e..2de6316d74 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -567,8 +567,7 @@ public: bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const -{ + QVariantMap& extraInfo, bool precisionPicking) const { // TODO -- correctly pick against marching-cube generated meshes if (!precisionPicking) { // just intersect with bounding box @@ -605,7 +604,6 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o voxelBox += result3 + Vectors::HALF; float voxelDistance; - bool hit = voxelBox.findRayIntersection(glm::vec3(originInVoxel), glm::vec3(directionInVoxel), voxelDistance, face, surfaceNormal); @@ -615,6 +613,87 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o return hit; } +bool RenderablePolyVoxEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + // TODO -- correctly pick against marching-cube generated meshes + if (!precisionPicking) { + // just intersect with bounding box + return true; + } + + glm::mat4 wtvMatrix = worldToVoxelMatrix(); + glm::vec4 originInVoxel = wtvMatrix * glm::vec4(origin, 1.0f); + glm::vec4 velocityInVoxel = wtvMatrix * glm::vec4(velocity, 0.0f); + glm::vec4 accelerationInVoxel = wtvMatrix * glm::vec4(acceleration, 0.0f); + + // find the first intersection with the voxel bounding box (slightly enlarged so we can catch voxels that touch the sides) + bool success; + glm::vec3 center = getCenterPosition(success); + glm::vec3 dimensions = getScaledDimensions(); + const float FIRST_BOX_HALF_SCALE = 0.51f; + AABox voxelBox1(wtvMatrix * vec4(center - FIRST_BOX_HALF_SCALE * dimensions, 1.0f), + wtvMatrix * vec4(2.0f * FIRST_BOX_HALF_SCALE * dimensions, 0.0f)); + bool hit1; + float parabolicDistance1; + // If we're starting inside the box, our first point is originInVoxel + if (voxelBox1.contains(originInVoxel)) { + parabolicDistance1 = 0.0f; + hit1 = true; + } else { + BoxFace face1; + glm::vec3 surfaceNormal1; + hit1 = voxelBox1.findParabolaIntersection(glm::vec3(originInVoxel), glm::vec3(velocityInVoxel), glm::vec3(accelerationInVoxel), + parabolicDistance1, face1, surfaceNormal1); + } + + if (hit1) { + // find the second intersection, which should be with the inside of the box (use a slightly large box again) + const float SECOND_BOX_HALF_SCALE = 0.52f; + AABox voxelBox2(wtvMatrix * vec4(center - SECOND_BOX_HALF_SCALE * dimensions, 1.0f), + wtvMatrix * vec4(2.0f * SECOND_BOX_HALF_SCALE * dimensions, 0.0f)); + glm::vec4 originInVoxel2 = originInVoxel + velocityInVoxel * parabolicDistance1 + 0.5f * accelerationInVoxel * parabolicDistance1 * parabolicDistance1; + glm::vec4 velocityInVoxel2 = velocityInVoxel + accelerationInVoxel * parabolicDistance1; + glm::vec4 accelerationInVoxel2 = accelerationInVoxel; + float parabolicDistance2; + BoxFace face2; + glm::vec3 surfaceNormal2; + // this should always be true + if (voxelBox2.findParabolaIntersection(glm::vec3(originInVoxel2), glm::vec3(velocityInVoxel2), glm::vec3(accelerationInVoxel2), + parabolicDistance2, face2, surfaceNormal2)) { + const int MAX_SECTIONS = 15; + PolyVox::RaycastResult raycastResult = PolyVox::RaycastResults::Completed; + glm::vec4 result = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f); + glm::vec4 segmentStartVoxel = originInVoxel2; + for (int i = 0; i < MAX_SECTIONS; i++) { + float t = parabolicDistance2 * ((float)(i + 1)) / ((float)MAX_SECTIONS); + glm::vec4 segmentEndVoxel = originInVoxel2 + velocityInVoxel2 * t + 0.5f * accelerationInVoxel2 * t * t; + raycastResult = doRayCast(segmentStartVoxel, segmentEndVoxel, result); + if (raycastResult != PolyVox::RaycastResults::Completed) { + // We hit something! + break; + } + segmentStartVoxel = segmentEndVoxel; + } + + if (raycastResult == PolyVox::RaycastResults::Completed) { + // the parabola completed its path -- nothing was hit. + return false; + } + + glm::vec3 result3 = glm::vec3(result); + + AABox voxelBox; + voxelBox += result3 - Vectors::HALF; + voxelBox += result3 + Vectors::HALF; + + return voxelBox.findParabolaIntersection(glm::vec3(originInVoxel), glm::vec3(velocityInVoxel), glm::vec3(accelerationInVoxel), + parabolicDistance, face, surfaceNormal); + } + } + return false; +} PolyVox::RaycastResult RenderablePolyVoxEntityItem::doRayCast(glm::vec4 originInVoxel, glm::vec4 farInVoxel, diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 7077ae799b..7afb9b41b4 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -51,11 +51,15 @@ public: int getOnCount() const override { return _onCount; } - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const override; + OctreeElementPointer& element, float& distance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const vec3& accleration, + OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; virtual void setVoxelData(const QByteArray& voxelData) override; virtual void setVoxelVolumeSize(const glm::vec3& voxelVolumeSize) override; diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 69068b81d2..c50b3bd760 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -97,16 +97,23 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce addMaterial(graphics::MaterialLayer(_material, 0), "0"); _shape = entity->getShape(); - _position = entity->getWorldPosition(); - _dimensions = entity->getScaledDimensions(); - _orientation = entity->getWorldOrientation(); - _renderTransform = getModelTransform(); + }); - if (_shape == entity::Sphere) { - _renderTransform.postScale(SPHERE_ENTITY_SCALE); - } + void* key = (void*)this; + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this] () { + withWriteLock([&] { + auto entity = getEntity(); + _position = entity->getWorldPosition(); + _dimensions = entity->getScaledDimensions(); + _orientation = entity->getWorldOrientation(); + updateModelTransformAndBound(); + _renderTransform = getModelTransform(); + if (_shape == entity::Sphere) { + _renderTransform.postScale(SPHERE_ENTITY_SCALE); + } - _renderTransform.postScale(_dimensions); + _renderTransform.postScale(_dimensions); + });; }); } diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h index 463ef187fc..7700aa6ef0 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.h +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h @@ -40,7 +40,6 @@ private: Procedural _procedural; QString _lastUserData; - Transform _renderTransform; entity::Shape _shape { entity::Sphere }; std::shared_ptr _material; glm::vec3 _position; diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index e58eb540e8..08a3b585e4 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -20,6 +20,7 @@ #include "GLMHelpers.h" +using namespace render; using namespace render::entities; static const int FIXED_FONT_POINT_SIZE = 40; @@ -64,10 +65,20 @@ bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint return false; } +void TextEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { + void* key = (void*)this; + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] () { + withWriteLock([&] { + _dimensions = entity->getScaledDimensions(); + updateModelTransformAndBound(); + _renderTransform = getModelTransform(); + }); + }); +} + void TextEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { _textColor = toGlm(entity->getTextColorX()); _backgroundColor = toGlm(entity->getBackgroundColorX()); - _dimensions = entity->getScaledDimensions(); _faceCamera = entity->getFaceCamera(); _lineHeight = entity->getLineHeight(); _text = entity->getText(); @@ -76,24 +87,28 @@ void TextEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointe void TextEntityRenderer::doRender(RenderArgs* args) { PerformanceTimer perfTimer("RenderableTextEntityItem::render"); - + + Transform modelTransform; + glm::vec3 dimensions; + withReadLock([&] { + modelTransform = _renderTransform; + dimensions = _dimensions; + }); static const float SLIGHTLY_BEHIND = -0.005f; float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; bool transparent = fadeRatio < 1.0f; glm::vec4 textColor = glm::vec4(_textColor, fadeRatio); glm::vec4 backgroundColor = glm::vec4(_backgroundColor, fadeRatio); - const glm::vec3& dimensions = _dimensions; - + // Render background glm::vec3 minCorner = glm::vec3(0.0f, -dimensions.y, SLIGHTLY_BEHIND); glm::vec3 maxCorner = glm::vec3(dimensions.x, 0.0f, SLIGHTLY_BEHIND); - - + + // Batch render calls Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; - const auto& modelTransform = getModelTransform(); auto transformToTopLeft = modelTransform; if (_faceCamera) { //rotate about vertical to face the camera @@ -105,7 +120,7 @@ void TextEntityRenderer::doRender(RenderArgs* args) { } transformToTopLeft.postTranslate(dimensions * glm::vec3(-0.5f, 0.5f, 0.0f)); // Go to the top left transformToTopLeft.setScale(1.0f); // Use a scale of one so that the text is not deformed - + batch.setModelTransform(transformToTopLeft); auto geometryCache = DependencyManager::get(); if (!_geometryID) { @@ -113,11 +128,11 @@ void TextEntityRenderer::doRender(RenderArgs* args) { } geometryCache->bindSimpleProgram(batch, false, transparent, false, false, false); geometryCache->renderQuad(batch, minCorner, maxCorner, backgroundColor, _geometryID); - + float scale = _lineHeight / _textRenderer->getFontSize(); transformToTopLeft.setScale(scale); // Scale to have the correct line height batch.setModelTransform(transformToTopLeft); - + float leftMargin = 0.1f * _lineHeight, topMargin = 0.1f * _lineHeight; glm::vec2 bounds = glm::vec2(dimensions.x - 2.0f * leftMargin, dimensions.y - 2.0f * topMargin); diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.h b/libraries/entities-renderer/src/RenderableTextEntityItem.h index b0a72cf253..ac7f2b620f 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.h +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.h @@ -27,6 +27,7 @@ public: ~TextEntityRenderer(); private: virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; + virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override; virtual void doRender(RenderArgs* args) override; int _geometryID{ 0 }; @@ -39,6 +40,6 @@ private: float _lineHeight; }; -} } +} } #endif // hifi_RenderableTextEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 693e3d0cf4..004a965c28 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -149,8 +149,8 @@ void WebEntityRenderer::onTimeout() { } void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { - // If the content type has changed, or the old content type was QML, we need to - // destroy the existing surface (because surfaces don't support changing the root + // If the content type has changed, or the old content type was QML, we need to + // destroy the existing surface (because surfaces don't support changing the root // object, so subsequent loads of content just overlap the existing content bool urlChanged = false; { @@ -178,33 +178,35 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene withWriteLock([&] { - if (_contentType == ContentType::NoContent) { - return; - } - // This work must be done on the main thread // If we couldn't create a new web surface, exit if (!hasWebSurface() && !buildWebSurface(entity)) { return; } - + if (urlChanged && _contentType == ContentType::HtmlContent) { _webSurface->getRootItem()->setProperty(URL_PROPERTY, _lastSourceUrl); } - if (_contextPosition != entity->getWorldPosition()) { - // update globalPosition - _contextPosition = entity->getWorldPosition(); - _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(_contextPosition)); - } + void* key = (void*)this; + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] () { + withWriteLock([&] { + if (_contextPosition != entity->getWorldPosition()) { + // update globalPosition + _contextPosition = entity->getWorldPosition(); + _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(_contextPosition)); + } - _lastDPI = entity->getDPI(); - _lastLocked = entity->getLocked(); + _lastDPI = entity->getDPI(); + _lastLocked = entity->getLocked(); - glm::vec2 windowSize = getWindowSize(entity); - _webSurface->resize(QSize(windowSize.x, windowSize.y)); - _renderTransform = getModelTransform(); - _renderTransform.postScale(entity->getScaledDimensions()); + glm::vec2 windowSize = getWindowSize(entity); + _webSurface->resize(QSize(windowSize.x, windowSize.y)); + updateModelTransformAndBound(); + _renderTransform = getModelTransform(); + _renderTransform.postScale(entity->getScaledDimensions()); + }); + }); }); } @@ -297,7 +299,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { if (_contentType == ContentType::HtmlContent) { // We special case YouTube URLs since we know they are videos that we should play with at least 30 FPS. - // FIXME this doesn't handle redirects or shortened URLs, consider using a signaling method from the + // FIXME this doesn't handle redirects or shortened URLs, consider using a signaling method from the // web entity if (QUrl(_lastSourceUrl).host().endsWith("youtube.com", Qt::CaseInsensitive)) { _webSurface->setMaxFps(YOUTUBE_MAX_FPS); @@ -309,7 +311,13 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { }); } else if (_contentType == ContentType::QmlContent) { _webSurface->load(_lastSourceUrl); + } else if (_contentType == ContentType::NoContent) { + // Show empty white panel + _webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) { + item->setProperty(URL_PROPERTY, ""); + }); } + _fadeStartTime = usecTimestampNow(); _webSurface->resume(); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 3100014e9b..1ba8ed0ec7 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -68,7 +68,6 @@ private: bool _lastLocked; QTimer _timer; uint64_t _lastRenderTime { 0 }; - Transform _renderTransform; }; } } // namespace diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index 2f8fd47b79..c48679e5d4 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -123,7 +123,6 @@ private: bool _pendingSkyboxTexture{ false }; QString _proceduralUserData; - Transform _renderTransform; }; } } // namespace diff --git a/libraries/entities-renderer/src/textured_particle.slv b/libraries/entities-renderer/src/textured_particle.slv index 1d4261b1cc..22254c0ab0 100644 --- a/libraries/entities-renderer/src/textured_particle.slv +++ b/libraries/entities-renderer/src/textured_particle.slv @@ -11,6 +11,7 @@ // <@include gpu/Transform.slh@> +<@include gpu/Noise.slh@> <$declareStandardTransform()$> @@ -26,11 +27,20 @@ struct Colors { vec4 finish; vec4 spread; }; +struct Spin { + float start; + float middle; + float finish; + float spread; +}; struct ParticleUniforms { Radii radius; Colors color; - vec4 lifespan; // x is lifespan, 3 spare floats + Spin spin; + float lifespan; + int rotateWithEntity; + vec2 spare; }; layout(std140) uniform particleBuffer { @@ -43,15 +53,6 @@ layout(location=2) in vec2 inColor; // This is actual Lifetime + Seed out vec4 varColor; out vec2 varTexcoord; -const int NUM_VERTICES_PER_PARTICLE = 4; -// This ordering ensures that un-rotated particles render upright in the viewer. -const vec4 UNIT_QUAD[NUM_VERTICES_PER_PARTICLE] = vec4[NUM_VERTICES_PER_PARTICLE]( - vec4(-1.0, 1.0, 0.0, 0.0), - vec4(-1.0, -1.0, 0.0, 0.0), - vec4(1.0, 1.0, 0.0, 0.0), - vec4(1.0, -1.0, 0.0, 0.0) -); - float bezierInterpolate(float y1, float y2, float y3, float u) { // https://en.wikipedia.org/wiki/Bezier_curve return (1.0 - u) * (1.0 - u) * y1 + 2.0 * (1.0 - u) * u * y2 + u * u * y3; @@ -102,6 +103,15 @@ vec4 interpolate3Vec4(vec4 y1, vec4 y2, vec4 y3, float u) { interpolate3Points(y1.w, y2.w, y3.w, u)); } +const int NUM_VERTICES_PER_PARTICLE = 4; +const vec2 TEX_COORDS[NUM_VERTICES_PER_PARTICLE] = vec2[NUM_VERTICES_PER_PARTICLE]( + vec2(-1.0, 0.0), + vec2(-1.0, 1.0), + vec2(0.0, 0.0), + vec2(0.0, 1.0) +); + + void main(void) { TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); @@ -112,22 +122,54 @@ void main(void) { int twoTriID = gl_VertexID - particleID * NUM_VERTICES_PER_PARTICLE; // Particle properties - float age = inColor.x / particle.lifespan.x; + float age = inColor.x / particle.lifespan; float seed = inColor.y; - // Pass the texcoord and the z texcoord is representing the texture icon - // Offset for corrected vertex ordering. - varTexcoord = vec2((UNIT_QUAD[twoTriID].xy -1.0) * vec2(0.5, -0.5)); + // Pass the texcoord + varTexcoord = TEX_COORDS[twoTriID].xy; varColor = interpolate3Vec4(particle.color.start, particle.color.middle, particle.color.finish, age); + vec3 colorSpread = 2.0 * vec3(hifi_hash(seed), hifi_hash(seed * 2.0), hifi_hash(seed * 3.0)) - 1.0; + varColor.rgb = clamp(varColor.rgb + colorSpread * particle.color.spread.rgb, vec3(0), vec3(1)); + float alphaSpread = 2.0 * hifi_hash(seed * 4.0) - 1.0; + varColor.a = clamp(varColor.a + alphaSpread * particle.color.spread.a, 0.0, 1.0); + + float spin = interpolate3Points(particle.spin.start, particle.spin.middle, particle.spin.finish, age); + float spinSpread = 2.0 * hifi_hash(seed * 5.0) - 1.0; + spin = spin + spinSpread * particle.spin.spread; // anchor point in eye space float radius = interpolate3Points(particle.radius.start, particle.radius.middle, particle.radius.finish, age); - vec4 quadPos = radius * UNIT_QUAD[twoTriID]; + float radiusSpread = 2.0 * hifi_hash(seed * 6.0) - 1.0; + radius = max(radius + radiusSpread * particle.radius.spread, 0.0); - vec4 anchorPoint; - vec4 _inPosition = vec4(inPosition, 1.0); - <$transformModelToEyePos(cam, obj, _inPosition, anchorPoint)$> + // inPosition is in world space + vec4 anchorPoint = cam._view * vec4(inPosition, 1.0); - vec4 eyePos = anchorPoint + quadPos; + mat3 view3 = mat3(cam._view); + vec3 UP = vec3(0, 1, 0); + vec3 modelUpWorld; + <$transformModelToWorldDir(cam, obj, UP, modelUpWorld)$> + vec3 upWorld = mix(UP, normalize(modelUpWorld), particle.rotateWithEntity); + vec3 upEye = normalize(view3 * upWorld); + vec3 FORWARD = vec3(0, 0, -1); + vec3 particleRight = normalize(cross(FORWARD, upEye)); + vec3 particleUp = cross(particleRight, FORWARD); // don't need to normalize + // This ordering ensures that un-rotated particles render upright in the viewer. + vec3 UNIT_QUAD[NUM_VERTICES_PER_PARTICLE] = vec3[NUM_VERTICES_PER_PARTICLE]( + normalize(-particleRight + particleUp), + normalize(-particleRight - particleUp), + normalize(particleRight + particleUp), + normalize(particleRight - particleUp) + ); + float c = cos(spin); + float s = sin(spin); + mat4 rotation = mat4( + c, -s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ); + vec4 quadPos = radius * vec4(UNIT_QUAD[twoTriID], 0.0); + vec4 eyePos = anchorPoint + rotation * quadPos; <$transformEyeToClipPos(cam, eyePos, gl_Position)$> } diff --git a/libraries/entities/src/AnimationPropertyGroup.cpp b/libraries/entities/src/AnimationPropertyGroup.cpp index 2db85eb7ac..95bdae43b9 100644 --- a/libraries/entities/src/AnimationPropertyGroup.cpp +++ b/libraries/entities/src/AnimationPropertyGroup.cpp @@ -22,13 +22,14 @@ const float AnimationPropertyGroup::MAXIMUM_POSSIBLE_FRAME = 100000.0f; bool operator==(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b) { return - (a._currentFrame == b._currentFrame) && (a._running == b._running) && (a._loop == b._loop) && (a._hold == b._hold) && (a._firstFrame == b._firstFrame) && (a._lastFrame == b._lastFrame) && + (a._fps == b._fps) && + (a._allowTranslation == b._allowTranslation) && (a._url == b._url); } @@ -40,6 +41,8 @@ bool operator!=(const AnimationPropertyGroup& a, const AnimationPropertyGroup& b (a._hold != b._hold) || (a._firstFrame != b._firstFrame) || (a._lastFrame != b._lastFrame) || + (a._fps != b._fps) || + (a._allowTranslation != b._allowTranslation) || (a._url != b._url); } diff --git a/libraries/entities/src/DeleteEntityOperator.cpp b/libraries/entities/src/DeleteEntityOperator.cpp index d369e08ecf..1dca171ae3 100644 --- a/libraries/entities/src/DeleteEntityOperator.cpp +++ b/libraries/entities/src/DeleteEntityOperator.cpp @@ -95,7 +95,7 @@ bool DeleteEntityOperator::preRecursion(const OctreeElementPointer& element) { EntityItemPointer theEntity = details.entity; bool entityDeleted = entityTreeElement->removeEntityItem(theEntity, true); // remove it from the element assert(entityDeleted); - (void)entityDeleted; // quite warning + (void)entityDeleted; // quiet warning about unused variable _tree->clearEntityMapEntry(details.entity->getEntityItemID()); _foundCount++; } diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index 9ca102d016..0982775b09 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -84,9 +84,15 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, EntityTreePointer entityTree, EntityItemID entityItemID, const EntityItemProperties& properties) { - if (properties.getClientOnly() && properties.getOwningAvatarID() == _myAvatar->getID()) { - // this is an avatar-based entity --> update our avatar-data rather than sending to the entity-server - queueEditAvatarEntityMessage(type, entityTree, entityItemID, properties); + if (properties.getClientOnly()) { + if (!_myAvatar) { + qCWarning(entities) << "Suppressing entity edit message: cannot send clientOnly edit with no myAvatar"; + } else if (properties.getOwningAvatarID() == _myAvatar->getID()) { + // this is an avatar-based entity --> update our avatar-data rather than sending to the entity-server + queueEditAvatarEntityMessage(type, entityTree, entityItemID, properties); + } else { + qCWarning(entities) << "Suppressing entity edit message: cannot send clientOnly edit for another avatar"; + } return; } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 70881fbc40..8e382fabd4 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -367,6 +367,10 @@ int EntityItem::expectedBytes() { return MINIMUM_HEADER_BYTES; } +const uint8_t PENDING_STATE_NOTHING = 0; +const uint8_t PENDING_STATE_TAKE = 1; +const uint8_t PENDING_STATE_RELEASE = 2; + // clients use this method to unpack FULL updates from entity-server int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { setSourceUUID(args.sourceUUID); @@ -678,7 +682,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // setters to ignore what the server says. filterRejection = newSimOwner.getID().isNull(); if (weOwnSimulation) { - if (newSimOwner.getID().isNull() && !_simulationOwner.pendingRelease(lastEditedFromBufferAdjusted)) { + if (newSimOwner.getID().isNull() && !pendingRelease(lastEditedFromBufferAdjusted)) { // entity-server is trying to clear our ownership (probably at our own request) // but we actually want to own it, therefore we ignore this clear event // and pretend that we own it (e.g. we assume we'll receive ownership soon) @@ -693,32 +697,53 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // recompute weOwnSimulation for later weOwnSimulation = _simulationOwner.matchesValidID(myNodeID); } - } else if (_simulationOwner.pendingTake(now - maxPingRoundTrip)) { - // we sent a bid already but maybe before this packet was sent from the server - weOwnSimulation = true; + } else if (_pendingOwnershipState == PENDING_STATE_TAKE) { + // we're waiting to receive acceptance of a bid + // this ownership data either satisifies our bid or does not + bool bidIsSatisfied = newSimOwner.getID() == myNodeID && + (newSimOwner.getPriority() == _pendingOwnershipPriority || + (_pendingOwnershipPriority == VOLUNTEER_SIMULATION_PRIORITY && + newSimOwner.getPriority() == RECRUIT_SIMULATION_PRIORITY)); + if (newSimOwner.getID().isNull()) { - // the entity-server is trying to clear someone else's ownership + // the entity-server is clearing someone else's ownership if (!_simulationOwner.isNull()) { markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID); somethingChanged = true; _simulationOwner.clearCurrentOwner(); } - } else if (newSimOwner.getID() == myNodeID) { - // the entity-server is awarding us ownership which is what we want - _simulationOwner.set(newSimOwner); + } else { + if (newSimOwner.getID() != _simulationOwner.getID()) { + markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID); + } + if (_simulationOwner.set(newSimOwner)) { + // the entity-server changed ownership + somethingChanged = true; + } + } + if (bidIsSatisfied || (somethingChanged && _pendingOwnershipTimestamp < now - maxPingRoundTrip)) { + // the bid has been satisfied, or it has been invalidated by data sent AFTER the bid should have been received + // in either case: accept our fate and clear pending state + _pendingOwnershipState = PENDING_STATE_NOTHING; + _pendingOwnershipPriority = 0; + } + weOwnSimulation = bidIsSatisfied || (_simulationOwner.getID() == myNodeID); + } else { + // we are not waiting to take ownership + if (newSimOwner.getID() != _simulationOwner.getID()) { + markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID); + } + if (_simulationOwner.set(newSimOwner)) { + // the entity-server changed ownership... + somethingChanged = true; + if (newSimOwner.getID() == myNodeID) { + // we have recieved ownership + weOwnSimulation = true; + // accept our fate and clear pendingState (just in case) + _pendingOwnershipState = PENDING_STATE_NOTHING; + _pendingOwnershipPriority = 0; + } } - } else if (newSimOwner.matchesValidID(myNodeID) && !_simulationOwner.pendingTake(now)) { - // entity-server tells us that we have simulation ownership while we never requested this for this EntityItem, - // this could happen when the user reloads the cache and entity tree. - markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID); - somethingChanged = true; - _simulationOwner.clearCurrentOwner(); - weOwnSimulation = false; - } else if (_simulationOwner.set(newSimOwner)) { - markDirtyFlags(Simulation::DIRTY_SIMULATOR_ID); - somethingChanged = true; - // recompute weOwnSimulation for later - weOwnSimulation = _simulationOwner.matchesValidID(myNodeID); } } @@ -1333,18 +1358,39 @@ void EntityItem::getAllTerseUpdateProperties(EntityItemProperties& properties) c properties._accelerationChanged = true; } -void EntityItem::flagForOwnershipBid(uint8_t priority) { - markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY); - auto nodeList = DependencyManager::get(); - if (_simulationOwner.matchesValidID(nodeList->getSessionUUID())) { - // we already own it - _simulationOwner.promotePriority(priority); - } else { - // we don't own it yet - _simulationOwner.setPendingPriority(priority, usecTimestampNow()); +void EntityItem::setScriptSimulationPriority(uint8_t priority) { + uint8_t newPriority = stillHasGrabActions() ? glm::max(priority, SCRIPT_GRAB_SIMULATION_PRIORITY) : priority; + if (newPriority != _scriptSimulationPriority) { + // set the dirty flag to trigger a bid or ownership update + markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY); + _scriptSimulationPriority = newPriority; } } +void EntityItem::clearScriptSimulationPriority() { + // DO NOT markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY) here, because this + // is only ever called from the code that actually handles the dirty flags, and it knows best. + _scriptSimulationPriority = stillHasGrabActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : 0; +} + +void EntityItem::setPendingOwnershipPriority(uint8_t priority) { + _pendingOwnershipTimestamp = usecTimestampNow(); + _pendingOwnershipPriority = priority; + _pendingOwnershipState = (_pendingOwnershipPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE; +} + +bool EntityItem::pendingRelease(uint64_t timestamp) const { + return _pendingOwnershipPriority == 0 && + _pendingOwnershipState == PENDING_STATE_RELEASE && + _pendingOwnershipTimestamp >= timestamp; +} + +bool EntityItem::stillWaitingToTakeOwnership(uint64_t timestamp) const { + return _pendingOwnershipPriority > 0 && + _pendingOwnershipState == PENDING_STATE_TAKE && + _pendingOwnershipTimestamp >= timestamp; +} + bool EntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = false; @@ -1977,10 +2023,6 @@ void EntityItem::clearSimulationOwnership() { } -void EntityItem::setPendingOwnershipPriority(uint8_t priority, const quint64& timestamp) { - _simulationOwner.setPendingPriority(priority, timestamp); -} - QString EntityItem::actionsToDebugString() { QString result; QVector serializedActions; @@ -2076,6 +2118,7 @@ bool EntityItem::updateAction(EntitySimulationPointer simulation, const QUuid& a } bool EntityItem::removeAction(EntitySimulationPointer simulation, const QUuid& actionID) { + // TODO: some action bool success = false; withWriteLock([&] { checkWaitingToRemove(simulation); @@ -2371,11 +2414,7 @@ bool EntityItem::shouldSuppressLocationEdits() const { } // if any of the ancestors are MyAvatar, suppress - if (isChildOfMyAvatar()) { - return true; - } - - return false; + return isChildOfMyAvatar(); } QList EntityItem::getActionsOfType(EntityDynamicType typeToGet) const { @@ -2403,12 +2442,17 @@ void EntityItem::locationChanged(bool tellPhysics) { } } SpatiallyNestable::locationChanged(tellPhysics); // tell all the children, also + std::pair data(_spaceIndex, glm::vec4(getWorldPosition(), _boundingRadius)); + emit spaceUpdate(data); somethingChangedNotification(); } void EntityItem::dimensionsChanged() { requiresRecalcBoxes(); SpatiallyNestable::dimensionsChanged(); // Do what you have to do + _boundingRadius = 0.5f * glm::length(getScaledDimensions()); + std::pair data(_spaceIndex, glm::vec4(getWorldPosition(), _boundingRadius)); + emit spaceUpdate(data); somethingChangedNotification(); } @@ -3012,6 +3056,11 @@ void EntityItem::retrieveMarketplacePublicKey() { }); } +void EntityItem::setSpaceIndex(int32_t index) { + assert(_spaceIndex == -1); + _spaceIndex = index; +} + void EntityItem::preDelete() { } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 0acf8dbbc1..47ae8de9ad 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -159,11 +159,15 @@ public: virtual void debugDump() const; - virtual bool supportsDetailedRayIntersection() const { return false; } + virtual bool supportsDetailedIntersection() const { return false; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { return true; } + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { return true; } // attributes applicable to all entity types EntityTypes::EntityType getType() const { return _type; } @@ -185,6 +189,7 @@ public: /// Dimensions in meters (0.0 - TREE_SCALE) glm::vec3 getScaledDimensions() const; virtual void setScaledDimensions(const glm::vec3& value); + virtual glm::vec3 getRaycastDimensions() const { return getScaledDimensions(); } inline const glm::vec3 getUnscaledDimensions() const { return _unscaledDimensions; } virtual void setUnscaledDimensions(const glm::vec3& value); @@ -239,7 +244,7 @@ public: // position, size, and bounds related helpers virtual AACube getMaximumAACube(bool& success) const override; AACube getMinimumAACube(bool& success) const; - AABox getAABox(bool& success) const; /// axis aligned bounding box in world-frame (meters) + virtual AABox getAABox(bool& success) const; /// axis aligned bounding box in world-frame (meters) using SpatiallyNestable::getQueryAACube; virtual AACube getQueryAACube(bool& success) const override; @@ -311,14 +316,21 @@ public: const SimulationOwner& getSimulationOwner() const { return _simulationOwner; } void setSimulationOwner(const QUuid& id, uint8_t priority); void setSimulationOwner(const SimulationOwner& owner); - void promoteSimulationPriority(uint8_t priority); uint8_t getSimulationPriority() const { return _simulationOwner.getPriority(); } QUuid getSimulatorID() const { return _simulationOwner.getID(); } void clearSimulationOwnership(); - void setPendingOwnershipPriority(uint8_t priority, const quint64& timestamp); - uint8_t getPendingOwnershipPriority() const { return _simulationOwner.getPendingPriority(); } - void rememberHasSimulationOwnershipBid() const; + + // TODO: move this "ScriptSimulationPriority" and "PendingOwnership" stuff into EntityMotionState + // but first would need to do some other cleanup. In the meantime these live here as "scratch space" + // to allow libs that don't know about each other to communicate. + void setScriptSimulationPriority(uint8_t priority); + void clearScriptSimulationPriority(); + uint8_t getScriptSimulationPriority() const { return _scriptSimulationPriority; } + void setPendingOwnershipPriority(uint8_t priority); + uint8_t getPendingOwnershipPriority() const { return _pendingOwnershipPriority; } + bool pendingRelease(uint64_t timestamp) const; + bool stillWaitingToTakeOwnership(uint64_t timestamp) const; // Certifiable Properties QString getItemName() const; @@ -370,8 +382,6 @@ public: /// return preferred shape type (actual physical shape may differ) virtual ShapeType getShapeType() const { return SHAPE_TYPE_NONE; } - virtual void setCollisionShape(const btCollisionShape* shape) {} - void setPosition(const glm::vec3& value); virtual void setParentID(const QUuid& parentID) override; virtual void setShapeType(ShapeType type) { /* do nothing */ } @@ -411,7 +421,6 @@ public: void getAllTerseUpdateProperties(EntityItemProperties& properties) const; - void flagForOwnershipBid(uint8_t priority); void flagForMotionStateChange() { _flags |= Simulation::DIRTY_MOTION_TYPE; } QString actionsToDebugString(); @@ -500,6 +509,10 @@ public: void setCauterized(bool value) { _cauterized = value; } bool getCauterized() const { return _cauterized; } + float getBoundingRadius() const { return _boundingRadius; } + void setSpaceIndex(int32_t index); + int32_t getSpaceIndex() const { return _spaceIndex; } + virtual void preDelete(); virtual void postParentFixup() {} @@ -517,6 +530,7 @@ public: signals: void requestRenderUpdate(); + void spaceUpdate(std::pair data); protected: QHash _changeHandlers; @@ -668,6 +682,17 @@ protected: quint64 _lastUpdatedQueryAACubeTimestamp { 0 }; uint64_t _simulationOwnershipExpiry { 0 }; + float _boundingRadius { 0.0f }; + int32_t _spaceIndex { -1 }; // index to proxy in workload::Space + + // TODO: move this "scriptSimulationPriority" and "pendingOwnership" stuff into EntityMotionState + // but first would need to do some other cleanup. In the meantime these live here as "scratch space" + // to allow libs that don't know about each other to communicate. + uint64_t _pendingOwnershipTimestamp { 0 }; // timestamp of last owenership change request + uint8_t _pendingOwnershipPriority { 0 }; // priority of last ownership change request + uint8_t _pendingOwnershipState { 0 }; // TAKE or RELEASE + uint8_t _scriptSimulationPriority { 0 }; // target priority based on script operations + bool _cauterized { false }; // if true, don't draw because it would obscure 1st-person camera bool _cloneable { ENTITY_ITEM_DEFAULT_CLONEABLE }; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 84e248b74d..2d0fcd0496 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -369,6 +369,11 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot); CHECK_PROPERTY_CHANGE(PROP_MATERIAL_DATA, materialData); CHECK_PROPERTY_CHANGE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera); + CHECK_PROPERTY_CHANGE(PROP_PARTICLE_SPIN, particleSpin); + CHECK_PROPERTY_CHANGE(PROP_SPIN_SPREAD, spinSpread); + CHECK_PROPERTY_CHANGE(PROP_SPIN_START, spinStart); + CHECK_PROPERTY_CHANGE(PROP_SPIN_FINISH, spinFinish); + CHECK_PROPERTY_CHANGE(PROP_PARTICLE_ROTATE_WITH_ENTITY, rotateWithEntity); // Certifiable Properties CHECK_PROPERTY_CHANGE(PROP_ITEM_NAME, itemName); @@ -854,7 +859,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * default is Earth's gravity value. * @property {vec3} accelerationSpread=0,0,0 - The spread in accelerations that each particle is given. If * emitAccelerations == {x: 0, y: -9.8, z: 0} and accelerationSpread == - * {x: 0, y: 1, z: 0}, each particle will have an acceleration in the range, {x: 0, y: -10.8, z: 0} + * {x: 0, y: 1, z: 0}, each particle will have an acceleration in the range {x: 0, y: -10.8, z: 0} * – {x: 0, y: -8.8, z: 0}. * @property {Vec3} dimensions - The dimensions of the particle effect, i.e., a bounding box containing all the particles * during their lifetimes, assuming that emitterShouldTrail is false. Read-only. @@ -879,30 +884,44 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {number} azimuthStart=-Math.PI - The angle in radians from the entity's local x-axis about the entity's local * z-axis at which particles start being emitted; range -Math.PIMath.PI. Particles are * emitted from the portion of the ellipsoid that lies between azimuthStart and azimuthFinish. - * @property {number} azimuthFinish=Math.PI - The angle in radians from the entity's local x-axis about the entity's local - * z-axis at which particles stop being emitted; range -Math.PIMath.PI. Particles are + * @property {number} azimuthFinish=Math.PI - The angle in radians from the entity's local x-axis about the entity's local + * z-axis at which particles stop being emitted; range -Math.PIMath.PI. Particles are * emitted from the portion of the ellipsoid that lies between azimuthStart and azimuthFinish. * - * @property {string} textures="" - The URL of a JPG or PNG image file to display for each particle. If you want transparency, + * @property {string} textures="" - The URL of a JPG or PNG image file to display for each particle. If you want transparency, * use PNG format. * @property {number} particleRadius=0.025 - The radius of each particle at the middle of its life. - * @property {number} radiusStart=0.025 - The radius of each particle at the start of its life. If not explicitly set, the + * @property {number} radiusStart=NAN - The radius of each particle at the start of its life. If NAN, the * particleRadius value is used. - * @property {number} radiusFinish=0.025 - The radius of each particle at the end of its life. If not explicitly set, the + * @property {number} radiusFinish=NAN - The radius of each particle at the end of its life. If NAN, the * particleRadius value is used. - * @property {number} radiusSpread=0 - Currently not used. + * @property {number} radiusSpread=0 - The spread in radius that each particle is given. If particleRadius == 0.5 + * and radiusSpread == 0.25, each particle will have a radius in the range 0.250.75. * @property {Color} color=255,255,255 - The color of each particle at the middle of its life. - * @property {Color} colorStart=255,255,255 - The color of each particle at the start of its life. If not explicitly set, the + * @property {Color} colorStart=NAN,NAN,NAN - The color of each particle at the start of its life. If any of the values are NAN, the * color value is used. - * @property {Color} colorFinish=255,255,255 - The color of each particle at the end of its life. If not explicitly set, the + * @property {Color} colorFinish=NAN,NAN,NAN - The color of each particle at the end of its life. If any of the values are NAN, the * color value is used. - * @property {Color} colorSpread=0,0,0 - Currently not used. + * @property {Color} colorSpread=0,0,0 - The spread in color that each particle is given. If + * color == {red: 100, green: 100, blue: 100} and colorSpread == + * {red: 10, green: 25, blue: 50}, each particle will have an acceleration in the range {red: 90, green: 75, blue: 50} + * – {red: 110, green: 125, blue: 150}. * @property {number} alpha=1 - The alpha of each particle at the middle of its life. - * @property {number} alphaStart=1 - The alpha of each particle at the start of its life. If not explicitly set, the + * @property {number} alphaStart=NAN - The alpha of each particle at the start of its life. If NAN, the * alpha value is used. - * @property {number} alphaFinish=1 - The alpha of each particle at the end of its life. If not explicitly set, the + * @property {number} alphaFinish=NAN - The alpha of each particle at the end of its life. If NAN, the * alpha value is used. - * @property {number} alphaSpread=0 - Currently not used. + * @property {number} alphaSpread=0 - The spread in alpha that each particle is given. If alpha == 0.5 + * and alphaSpread == 0.25, each particle will have an alpha in the range 0.250.75. + * @property {number} particleSpin=0 - The spin of each particle at the middle of its life. In the range -2*PI2*PI. + * @property {number} spinStart=NaN - The spin of each particle at the start of its life. In the range -2*PI2*PI. + * If NaN, the particleSpin value is used. + * @property {number} spinFinish=NaN - The spin of each particle at the end of its life. In the range -2*PI2*PI. + * If NaN, the particleSpin value is used. + * @property {number} spinSpread=0 - The spread in spin that each particle is given. In the range 02*PI. If particleSpin == PI + * and spinSpread == PI/2, each particle will have a spin in the range PI/23*PI/2. + * @property {boolean} rotateWithEntity=false - Whether or not the particles' spin will rotate with the entity. If false, when particleSpin == 0, the particles will point + * up in the world. If true, they will point towards the entity's up vector, based on its orientation. * * @property {ShapeType} shapeType="none" - Currently not used. Read-only. * @@ -1286,6 +1305,11 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_START, alphaStart); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_FINISH, alphaFinish); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMITTER_SHOULD_TRAIL, emitterShouldTrail); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARTICLE_SPIN, particleSpin); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SPIN_SPREAD, spinSpread); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SPIN_START, spinStart); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SPIN_FINISH, spinFinish); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARTICLE_ROTATE_WITH_ENTITY, rotateWithEntity); } // Models only @@ -1499,13 +1523,13 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool } COPY_PROPERTY_FROM_QSCRIPTVALUE(lastEditedBy, QUuid, setLastEditedBy); - COPY_PROPERTY_FROM_QSCRIPTVALUE(position, glmVec3, setPosition); - COPY_PROPERTY_FROM_QSCRIPTVALUE(dimensions, glmVec3, setDimensions); - COPY_PROPERTY_FROM_QSCRIPTVALUE(rotation, glmQuat, setRotation); + COPY_PROPERTY_FROM_QSCRIPTVALUE(position, vec3, setPosition); + COPY_PROPERTY_FROM_QSCRIPTVALUE(dimensions, vec3, setDimensions); + COPY_PROPERTY_FROM_QSCRIPTVALUE(rotation, quat, setRotation); COPY_PROPERTY_FROM_QSCRIPTVALUE(density, float, setDensity); - COPY_PROPERTY_FROM_QSCRIPTVALUE(velocity, glmVec3, setVelocity); - COPY_PROPERTY_FROM_QSCRIPTVALUE(gravity, glmVec3, setGravity); - COPY_PROPERTY_FROM_QSCRIPTVALUE(acceleration, glmVec3, setAcceleration); + COPY_PROPERTY_FROM_QSCRIPTVALUE(velocity, vec3, setVelocity); + COPY_PROPERTY_FROM_QSCRIPTVALUE(gravity, vec3, setGravity); + COPY_PROPERTY_FROM_QSCRIPTVALUE(acceleration, vec3, setAcceleration); COPY_PROPERTY_FROM_QSCRIPTVALUE(damping, float, setDamping); COPY_PROPERTY_FROM_QSCRIPTVALUE(restitution, float, setRestitution); COPY_PROPERTY_FROM_QSCRIPTVALUE(friction, float, setFriction); @@ -1513,15 +1537,15 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(script, QString, setScript); COPY_PROPERTY_FROM_QSCRIPTVALUE(scriptTimestamp, quint64, setScriptTimestamp); COPY_PROPERTY_FROM_QSCRIPTVALUE(serverScripts, QString, setServerScripts); - COPY_PROPERTY_FROM_QSCRIPTVALUE(registrationPoint, glmVec3, setRegistrationPoint); - COPY_PROPERTY_FROM_QSCRIPTVALUE(angularVelocity, glmVec3, setAngularVelocity); + COPY_PROPERTY_FROM_QSCRIPTVALUE(registrationPoint, vec3, setRegistrationPoint); + COPY_PROPERTY_FROM_QSCRIPTVALUE(angularVelocity, vec3, setAngularVelocity); COPY_PROPERTY_FROM_QSCRIPTVALUE(angularDamping, float, setAngularDamping); COPY_PROPERTY_FROM_QSCRIPTVALUE(visible, bool, setVisible); COPY_PROPERTY_FROM_QSCRIPTVALUE(canCastShadow, bool, setCanCastShadow); COPY_PROPERTY_FROM_QSCRIPTVALUE(color, xColor, setColor); COPY_PROPERTY_FROM_QSCRIPTVALUE(colorSpread, xColor, setColorSpread); - COPY_PROPERTY_FROM_QSCRIPTVALUE(colorStart, xColor, setColorStart); - COPY_PROPERTY_FROM_QSCRIPTVALUE(colorFinish, xColor, setColorFinish); + COPY_PROPERTY_FROM_QSCRIPTVALUE(colorStart, vec3, setColorStart); + COPY_PROPERTY_FROM_QSCRIPTVALUE(colorFinish, vec3, setColorFinish); COPY_PROPERTY_FROM_QSCRIPTVALUE(alpha, float, setAlpha); COPY_PROPERTY_FROM_QSCRIPTVALUE(alphaSpread, float, setAlphaSpread); COPY_PROPERTY_FROM_QSCRIPTVALUE(alphaStart, float, setAlphaStart); @@ -1555,15 +1579,15 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(emitRate, float, setEmitRate); COPY_PROPERTY_FROM_QSCRIPTVALUE(emitSpeed, float, setEmitSpeed); COPY_PROPERTY_FROM_QSCRIPTVALUE(speedSpread, float, setSpeedSpread); - COPY_PROPERTY_FROM_QSCRIPTVALUE(emitOrientation, glmQuat, setEmitOrientation); - COPY_PROPERTY_FROM_QSCRIPTVALUE(emitDimensions, glmVec3, setEmitDimensions); + COPY_PROPERTY_FROM_QSCRIPTVALUE(emitOrientation, quat, setEmitOrientation); + COPY_PROPERTY_FROM_QSCRIPTVALUE(emitDimensions, vec3, setEmitDimensions); COPY_PROPERTY_FROM_QSCRIPTVALUE(emitRadiusStart, float, setEmitRadiusStart); COPY_PROPERTY_FROM_QSCRIPTVALUE(polarStart, float, setPolarStart); COPY_PROPERTY_FROM_QSCRIPTVALUE(polarFinish, float, setPolarFinish); COPY_PROPERTY_FROM_QSCRIPTVALUE(azimuthStart, float, setAzimuthStart); COPY_PROPERTY_FROM_QSCRIPTVALUE(azimuthFinish, float, setAzimuthFinish); - COPY_PROPERTY_FROM_QSCRIPTVALUE(emitAcceleration, glmVec3, setEmitAcceleration); - COPY_PROPERTY_FROM_QSCRIPTVALUE(accelerationSpread, glmVec3, setAccelerationSpread); + COPY_PROPERTY_FROM_QSCRIPTVALUE(emitAcceleration, vec3, setEmitAcceleration); + COPY_PROPERTY_FROM_QSCRIPTVALUE(accelerationSpread, vec3, setAccelerationSpread); COPY_PROPERTY_FROM_QSCRIPTVALUE(particleRadius, float, setParticleRadius); COPY_PROPERTY_FROM_QSCRIPTVALUE(radiusSpread, float, setRadiusSpread); COPY_PROPERTY_FROM_QSCRIPTVALUE(radiusStart, float, setRadiusStart); @@ -1573,11 +1597,16 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(materialMappingMode, MaterialMappingMode); COPY_PROPERTY_FROM_QSCRIPTVALUE(priority, quint16, setPriority); COPY_PROPERTY_FROM_QSCRIPTVALUE(parentMaterialName, QString, setParentMaterialName); - COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingPos, glmVec2, setMaterialMappingPos); - COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingScale, glmVec2, setMaterialMappingScale); + COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingPos, vec2, setMaterialMappingPos); + COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingScale, vec2, setMaterialMappingScale); COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingRot, float, setMaterialMappingRot); COPY_PROPERTY_FROM_QSCRIPTVALUE(materialData, QString, setMaterialData); COPY_PROPERTY_FROM_QSCRIPTVALUE(isVisibleInSecondaryCamera, bool, setIsVisibleInSecondaryCamera); + COPY_PROPERTY_FROM_QSCRIPTVALUE(particleSpin, float, setParticleSpin); + COPY_PROPERTY_FROM_QSCRIPTVALUE(spinSpread, float, setSpinSpread); + COPY_PROPERTY_FROM_QSCRIPTVALUE(spinStart, float, setSpinStart); + COPY_PROPERTY_FROM_QSCRIPTVALUE(spinFinish, float, setSpinFinish); + COPY_PROPERTY_FROM_QSCRIPTVALUE(rotateWithEntity, bool, setRotateWithEntity); // Certifiable Properties COPY_PROPERTY_FROM_QSCRIPTVALUE(itemName, QString, setItemName); @@ -1601,7 +1630,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(skyboxMode, SkyboxMode); COPY_PROPERTY_FROM_QSCRIPTVALUE(sourceUrl, QString, setSourceUrl); - COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, glmVec3, setVoxelVolumeSize); + COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, vec3, setVoxelVolumeSize); COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelData, QByteArray, setVoxelData); COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelSurfaceStyle, uint16_t, setVoxelSurfaceStyle); COPY_PROPERTY_FROM_QSCRIPTVALUE(lineWidth, float, setLineWidth); @@ -1648,11 +1677,11 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(parentJointIndex, quint16, setParentJointIndex); COPY_PROPERTY_FROM_QSCRIPTVALUE(queryAACube, AACube, setQueryAACube); - COPY_PROPERTY_FROM_QSCRIPTVALUE(localPosition, glmVec3, setLocalPosition); - COPY_PROPERTY_FROM_QSCRIPTVALUE(localRotation, glmQuat, setLocalRotation); - COPY_PROPERTY_FROM_QSCRIPTVALUE(localVelocity, glmVec3, setLocalVelocity); - COPY_PROPERTY_FROM_QSCRIPTVALUE(localAngularVelocity, glmVec3, setLocalAngularVelocity); - COPY_PROPERTY_FROM_QSCRIPTVALUE(localDimensions, glmVec3, setLocalDimensions); + COPY_PROPERTY_FROM_QSCRIPTVALUE(localPosition, vec3, setLocalPosition); + COPY_PROPERTY_FROM_QSCRIPTVALUE(localRotation, quat, setLocalRotation); + COPY_PROPERTY_FROM_QSCRIPTVALUE(localVelocity, vec3, setLocalVelocity); + COPY_PROPERTY_FROM_QSCRIPTVALUE(localAngularVelocity, vec3, setLocalAngularVelocity); + COPY_PROPERTY_FROM_QSCRIPTVALUE(localDimensions, vec3, setLocalDimensions); COPY_PROPERTY_FROM_QSCRIPTVALUE(jointRotationsSet, qVectorBool, setJointRotationsSet); COPY_PROPERTY_FROM_QSCRIPTVALUE(jointRotations, qVectorQuat, setJointRotations); @@ -1746,6 +1775,11 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(radiusSpread); COPY_PROPERTY_IF_CHANGED(radiusStart); COPY_PROPERTY_IF_CHANGED(radiusFinish); + COPY_PROPERTY_IF_CHANGED(particleSpin); + COPY_PROPERTY_IF_CHANGED(spinSpread); + COPY_PROPERTY_IF_CHANGED(spinStart); + COPY_PROPERTY_IF_CHANGED(spinFinish); + COPY_PROPERTY_IF_CHANGED(rotateWithEntity); // Certifiable Properties COPY_PROPERTY_IF_CHANGED(itemName); @@ -1896,8 +1930,8 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_COLLISION_SOUND_URL, CollisionSoundURL, collisionSoundURL, QString); ADD_PROPERTY_TO_MAP(PROP_COLOR, Color, color, xColor); ADD_PROPERTY_TO_MAP(PROP_COLOR_SPREAD, ColorSpread, colorSpread, xColor); - ADD_PROPERTY_TO_MAP(PROP_COLOR_START, ColorStart, colorStart, xColor); - ADD_PROPERTY_TO_MAP(PROP_COLOR_FINISH, ColorFinish, colorFinish, xColor); + ADD_PROPERTY_TO_MAP(PROP_COLOR_START, ColorStart, colorStart, vec3); + ADD_PROPERTY_TO_MAP(PROP_COLOR_FINISH, ColorFinish, colorFinish, vec3); ADD_PROPERTY_TO_MAP(PROP_ALPHA, Alpha, alpha, float); ADD_PROPERTY_TO_MAP(PROP_ALPHA_SPREAD, AlphaSpread, alphaSpread, float); ADD_PROPERTY_TO_MAP(PROP_ALPHA_START, AlphaStart, alphaStart, float); @@ -1952,13 +1986,19 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_MODE, MaterialMappingMode, materialMappingMode, MaterialMappingMode); ADD_PROPERTY_TO_MAP(PROP_MATERIAL_PRIORITY, Priority, priority, quint16); ADD_PROPERTY_TO_MAP(PROP_PARENT_MATERIAL_NAME, ParentMaterialName, parentMaterialName, QString); - ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, glmVec2); - ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, glmVec2); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, vec2); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, vec2); ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float); ADD_PROPERTY_TO_MAP(PROP_MATERIAL_DATA, MaterialData, materialData, QString); ADD_PROPERTY_TO_MAP(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool); + ADD_PROPERTY_TO_MAP(PROP_PARTICLE_SPIN, ParticleSpin, particleSpin, float); + ADD_PROPERTY_TO_MAP(PROP_SPIN_SPREAD, SpinSpread, spinSpread, float); + ADD_PROPERTY_TO_MAP(PROP_SPIN_START, SpinStart, spinStart, float); + ADD_PROPERTY_TO_MAP(PROP_SPIN_FINISH, SpinFinish, spinFinish, float); + ADD_PROPERTY_TO_MAP(PROP_PARTICLE_ROTATE_WITH_ENTITY, RotateWithEntity, rotateWithEntity, float); + // Certifiable Properties ADD_PROPERTY_TO_MAP(PROP_ITEM_NAME, ItemName, itemName, QString); ADD_PROPERTY_TO_MAP(PROP_ITEM_DESCRIPTION, ItemDescription, itemDescription, QString); @@ -2287,6 +2327,11 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_ALPHA_START, properties.getAlphaStart()); APPEND_ENTITY_PROPERTY(PROP_ALPHA_FINISH, properties.getAlphaFinish()); APPEND_ENTITY_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, properties.getEmitterShouldTrail()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_SPIN, properties.getParticleSpin()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_SPREAD, properties.getSpinSpread()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_START, properties.getSpinStart()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_FINISH, properties.getSpinFinish()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, properties.getRotateWithEntity()) } if (properties.getType() == EntityTypes::Zone) { @@ -2423,9 +2468,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy if (appendState != OctreeElement::COMPLETED) { didntFitProperties = propertiesDidntFit; } - } - if (success) { packetData->endSubTree(); const char* finalizedData = reinterpret_cast(packetData->getFinalizedData()); @@ -2436,7 +2479,6 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy buffer.resize(finalizedSize); } else { qCDebug(entities) << "ERROR - encoded edit message doesn't fit in output buffer."; - success = false; appendState = OctreeElement::NONE; // if we got here, then we didn't include the item // maybe we should assert!!! } @@ -2444,7 +2486,6 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy packetData->discardSubTree(); } - return appendState; } @@ -2660,12 +2701,17 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RADIUS_START, float, setRadiusStart); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RADIUS_FINISH, float, setRadiusFinish); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR_SPREAD, xColor, setColorSpread); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR_START, xColor, setColorStart); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR_FINISH, xColor, setColorFinish); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR_START, vec3, setColorStart); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR_FINISH, vec3, setColorFinish); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA_SPREAD, float, setAlphaSpread); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA_START, float, setAlphaStart); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA_FINISH, float, setAlphaFinish); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMITTER_SHOULD_TRAIL, bool, setEmitterShouldTrail); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_SPIN, float, setParticleSpin); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SPIN_SPREAD, float, setSpinSpread); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SPIN_START, float, setSpinStart); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SPIN_FINISH, float, setSpinFinish); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_ROTATE_WITH_ENTITY, bool, setRotateWithEntity); } if (properties.getType() == EntityTypes::Zone) { @@ -2733,8 +2779,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_MODE, MaterialMappingMode, setMaterialMappingMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_PRIORITY, quint16, setPriority); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARENT_MATERIAL_NAME, QString, setParentMaterialName); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_POS, glmVec2, setMaterialMappingPos); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_SCALE, glmVec2, setMaterialMappingScale); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_POS, vec2, setMaterialMappingPos); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_SCALE, vec2, setMaterialMappingScale); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_MAPPING_ROT, float, setMaterialMappingRot); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_DATA, QString, setMaterialData); } @@ -2804,7 +2850,7 @@ QVector EntityItemProperties::unpackStrokeColors(const QByteArray& st float r = (uint8_t)strokeColors[i++] / 255.0f; float g = (uint8_t)strokeColors[i++] / 255.0f; float b = (uint8_t)strokeColors[i++] / 255.0f; - unpackedStrokeColors[j++] = glmVec3(r, g, b); + unpackedStrokeColors[j++] = vec3(r, g, b); } } else { qCDebug(entities) << "WARNING - Expected received size for stroke colors does not match. Expected: " @@ -2834,7 +2880,6 @@ bool EntityItemProperties::encodeEraseEntityMessage(const EntityItemID& entityIt outputLength = sizeof(numberOfIds); memcpy(copyAt, entityItemID.toRfc4122().constData(), NUM_BYTES_RFC4122_UUID); - copyAt += NUM_BYTES_RFC4122_UUID; outputLength += NUM_BYTES_RFC4122_UUID; buffer.resize(outputLength); @@ -2856,7 +2901,6 @@ bool EntityItemProperties::encodeCloneEntityMessage(const EntityItemID& entityID outputLength += NUM_BYTES_RFC4122_UUID; memcpy(copyAt, newEntityID.toRfc4122().constData(), NUM_BYTES_RFC4122_UUID); - copyAt += NUM_BYTES_RFC4122_UUID; outputLength += NUM_BYTES_RFC4122_UUID; buffer.resize(outputLength); @@ -2937,6 +2981,7 @@ void EntityItemProperties::markAllChanged() { _shapeTypeChanged = true; _isEmittingChanged = true; + _emitterShouldTrailChanged = true; _maxParticlesChanged = true; _lifespanChanged = true; _emitRateChanged = true; @@ -2955,15 +3000,17 @@ void EntityItemProperties::markAllChanged() { _radiusSpreadChanged = true; _colorSpreadChanged = true; _alphaSpreadChanged = true; - - // Only mark the following as changed if their values are specified in the properties when the particle is created. If their - // values are specified then they are marked as changed in getChangedProperties(). - //_radiusStartChanged = true; - //_radiusFinishChanged = true; - //_colorStartChanged = true; - //_colorFinishChanged = true; - //_alphaStartChanged = true; - //_alphaFinishChanged = true; + _radiusStartChanged = true; + _radiusFinishChanged = true; + _colorStartChanged = true; + _colorFinishChanged = true; + _alphaStartChanged = true; + _alphaFinishChanged = true; + _particleSpinChanged = true; + _spinStartChanged = true; + _spinFinishChanged = true; + _spinSpreadChanged = true; + _rotateWithEntityChanged = true; _materialURLChanged = true; _materialMappingModeChanged = true; @@ -3312,6 +3359,21 @@ QList EntityItemProperties::listChangedProperties() { if (radiusFinishChanged()) { out += "radiusFinish"; } + if (particleSpinChanged()) { + out += "particleSpin"; + } + if (spinSpreadChanged()) { + out += "spinSpread"; + } + if (spinStartChanged()) { + out += "spinStart"; + } + if (spinFinishChanged()) { + out += "spinFinish"; + } + if (rotateWithEntityChanged()) { + out += "rotateWithEntity"; + } if (materialURLChanged()) { out += "materialURL"; } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index e46eb73910..04e54c54a5 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -134,14 +134,14 @@ public: DEFINE_PROPERTY_REF(PROP_SCRIPT, Script, script, QString, ENTITY_ITEM_DEFAULT_SCRIPT); DEFINE_PROPERTY(PROP_SCRIPT_TIMESTAMP, ScriptTimestamp, scriptTimestamp, quint64, ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP); DEFINE_PROPERTY_REF(PROP_COLLISION_SOUND_URL, CollisionSoundURL, collisionSoundURL, QString, ENTITY_ITEM_DEFAULT_COLLISION_SOUND_URL); - DEFINE_PROPERTY_REF(PROP_COLOR, Color, color, xColor, particle::DEFAULT_COLOR); - DEFINE_PROPERTY_REF(PROP_COLOR_SPREAD, ColorSpread, colorSpread, xColor, particle::DEFAULT_COLOR_SPREAD); - DEFINE_PROPERTY_REF(PROP_COLOR_START, ColorStart, colorStart, xColor, particle::DEFAULT_COLOR); - DEFINE_PROPERTY_REF(PROP_COLOR_FINISH, ColorFinish, colorFinish, xColor, particle::DEFAULT_COLOR); + DEFINE_PROPERTY_REF(PROP_COLOR, Color, color, xColor, ParticleEffectEntityItem::DEFAULT_XCOLOR); + DEFINE_PROPERTY_REF(PROP_COLOR_SPREAD, ColorSpread, colorSpread, xColor, ParticleEffectEntityItem::DEFAULT_XCOLOR_SPREAD); + DEFINE_PROPERTY_REF(PROP_COLOR_START, ColorStart, colorStart, vec3, particle::DEFAULT_COLOR_UNINITIALIZED); + DEFINE_PROPERTY_REF(PROP_COLOR_FINISH, ColorFinish, colorFinish, vec3, particle::DEFAULT_COLOR_UNINITIALIZED); DEFINE_PROPERTY(PROP_ALPHA, Alpha, alpha, float, particle::DEFAULT_ALPHA); DEFINE_PROPERTY(PROP_ALPHA_SPREAD, AlphaSpread, alphaSpread, float, particle::DEFAULT_ALPHA_SPREAD); - DEFINE_PROPERTY(PROP_ALPHA_START, AlphaStart, alphaStart, float, particle::DEFAULT_ALPHA); - DEFINE_PROPERTY(PROP_ALPHA_FINISH, AlphaFinish, alphaFinish, float, particle::DEFAULT_ALPHA); + DEFINE_PROPERTY(PROP_ALPHA_START, AlphaStart, alphaStart, float, particle::DEFAULT_ALPHA_START); + DEFINE_PROPERTY(PROP_ALPHA_FINISH, AlphaFinish, alphaFinish, float, particle::DEFAULT_ALPHA_FINISH); DEFINE_PROPERTY_REF(PROP_MODEL_URL, ModelURL, modelURL, QString, ""); DEFINE_PROPERTY_REF(PROP_COMPOUND_SHAPE_URL, CompoundShapeURL, compoundShapeURL, QString, ""); DEFINE_PROPERTY_REF(PROP_REGISTRATION_POINT, RegistrationPoint, registrationPoint, glm::vec3, ENTITY_ITEM_DEFAULT_REGISTRATION_POINT); @@ -228,13 +228,19 @@ public: DEFINE_PROPERTY_REF_ENUM(PROP_MATERIAL_MAPPING_MODE, MaterialMappingMode, materialMappingMode, MaterialMappingMode, UV); DEFINE_PROPERTY_REF(PROP_MATERIAL_PRIORITY, Priority, priority, quint16, 0); DEFINE_PROPERTY_REF(PROP_PARENT_MATERIAL_NAME, ParentMaterialName, parentMaterialName, QString, "0"); - DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, glmVec2, glm::vec2(0, 0)); - DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, glmVec2, glm::vec2(1, 1)); + DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, vec2, glm::vec2(0, 0)); + DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, vec2, glm::vec2(1, 1)); DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float, 0); DEFINE_PROPERTY_REF(PROP_MATERIAL_DATA, MaterialData, materialData, QString, ""); DEFINE_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool, ENTITY_ITEM_DEFAULT_VISIBLE_IN_SECONDARY_CAMERA); + DEFINE_PROPERTY(PROP_PARTICLE_SPIN, ParticleSpin, particleSpin, float, particle::DEFAULT_PARTICLE_SPIN); + DEFINE_PROPERTY(PROP_SPIN_SPREAD, SpinSpread, spinSpread, float, particle::DEFAULT_SPIN_SPREAD); + DEFINE_PROPERTY(PROP_SPIN_START, SpinStart, spinStart, float, particle::DEFAULT_SPIN_START); + DEFINE_PROPERTY(PROP_SPIN_FINISH, SpinFinish, spinFinish, float, particle::DEFAULT_SPIN_FINISH); + DEFINE_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, RotateWithEntity, rotateWithEntity, bool, particle::DEFAULT_ROTATE_WITH_ENTITY); + // Certifiable Properties - related to Proof of Purchase certificates DEFINE_PROPERTY_REF(PROP_ITEM_NAME, ItemName, itemName, QString, ENTITY_ITEM_DEFAULT_ITEM_NAME); DEFINE_PROPERTY_REF(PROP_ITEM_DESCRIPTION, ItemDescription, itemDescription, QString, ENTITY_ITEM_DEFAULT_ITEM_DESCRIPTION); @@ -249,11 +255,11 @@ public: DEFINE_PROPERTY_REF(PROP_STATIC_CERTIFICATE_VERSION, StaticCertificateVersion, staticCertificateVersion, quint32, ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION); // these are used when bouncing location data into and out of scripts - DEFINE_PROPERTY_REF(PROP_LOCAL_POSITION, LocalPosition, localPosition, glmVec3, ENTITY_ITEM_ZERO_VEC3); - DEFINE_PROPERTY_REF(PROP_LOCAL_ROTATION, LocalRotation, localRotation, glmQuat, ENTITY_ITEM_DEFAULT_ROTATION); - DEFINE_PROPERTY_REF(PROP_LOCAL_VELOCITY, LocalVelocity, localVelocity, glmVec3, ENTITY_ITEM_ZERO_VEC3); - DEFINE_PROPERTY_REF(PROP_LOCAL_ANGULAR_VELOCITY, LocalAngularVelocity, localAngularVelocity, glmVec3, ENTITY_ITEM_ZERO_VEC3); - DEFINE_PROPERTY_REF(PROP_LOCAL_DIMENSIONS, LocalDimensions, localDimensions, glmVec3, ENTITY_ITEM_ZERO_VEC3); + DEFINE_PROPERTY_REF(PROP_LOCAL_POSITION, LocalPosition, localPosition, vec3, ENTITY_ITEM_ZERO_VEC3); + DEFINE_PROPERTY_REF(PROP_LOCAL_ROTATION, LocalRotation, localRotation, quat, ENTITY_ITEM_DEFAULT_ROTATION); + DEFINE_PROPERTY_REF(PROP_LOCAL_VELOCITY, LocalVelocity, localVelocity, vec3, ENTITY_ITEM_ZERO_VEC3); + DEFINE_PROPERTY_REF(PROP_LOCAL_ANGULAR_VELOCITY, LocalAngularVelocity, localAngularVelocity, vec3, ENTITY_ITEM_ZERO_VEC3); + DEFINE_PROPERTY_REF(PROP_LOCAL_DIMENSIONS, LocalDimensions, localDimensions, vec3, ENTITY_ITEM_ZERO_VEC3); DEFINE_PROPERTY_REF(PROP_JOINT_ROTATIONS_SET, JointRotationsSet, jointRotationsSet, QVector, QVector()); DEFINE_PROPERTY_REF(PROP_JOINT_ROTATIONS, JointRotations, jointRotations, QVector, QVector()); @@ -339,7 +345,7 @@ public: void clearSimulationOwner(); void setSimulationOwner(const QUuid& id, uint8_t priority); void setSimulationOwner(const QByteArray& data); - void promoteSimulationPriority(uint8_t priority) { _simulationOwner.promotePriority(priority); } + void setSimulationPriority(uint8_t priority) { _simulationOwner.setPriority(priority); } void setActionDataDirty() { _actionDataChanged = true; } diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h index 3bbff6cfa6..213460ec50 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -185,9 +185,6 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const AACube& v) { retu properties.setProperty(#P, V); \ } -typedef glm::vec2 glmVec2; -typedef glm::vec3 glmVec3; -typedef glm::quat glmQuat; typedef QVector qVectorVec3; typedef QVector qVectorQuat; typedef QVector qVectorBool; @@ -224,7 +221,7 @@ inline QByteArray QByteArray_convertFromScriptValue(const QScriptValue& v, bool& return QByteArray::fromBase64(b64.toUtf8()); } -inline glmVec2 glmVec2_convertFromScriptValue(const QScriptValue& v, bool& isValid) { +inline glm::vec2 vec2_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = false; /// assume it can't be converted QScriptValue x = v.property("x"); QScriptValue y = v.property("y"); @@ -241,11 +238,20 @@ inline glmVec2 glmVec2_convertFromScriptValue(const QScriptValue& v, bool& isVal return glm::vec2(0); } -inline glmVec3 glmVec3_convertFromScriptValue(const QScriptValue& v, bool& isValid) { +inline glm::vec3 vec3_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = false; /// assume it can't be converted QScriptValue x = v.property("x"); QScriptValue y = v.property("y"); QScriptValue z = v.property("z"); + if (!x.isValid()) { + x = v.property("red"); + } + if (!y.isValid()) { + y = v.property("green"); + } + if (!z.isValid()) { + z = v.property("blue"); + } if (x.isValid() && y.isValid() && z.isValid()) { glm::vec3 newValue(0); newValue.x = x.toVariant().toFloat(); @@ -288,7 +294,7 @@ inline qVectorBool qVectorBool_convertFromScriptValue(const QScriptValue& v, boo return qVectorBoolFromScriptValue(v); } -inline glmQuat glmQuat_convertFromScriptValue(const QScriptValue& v, bool& isValid) { +inline glm::quat quat_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = false; /// assume it can't be converted QScriptValue x = v.property("x"); QScriptValue y = v.property("y"); @@ -317,6 +323,15 @@ inline xColor xColor_convertFromScriptValue(const QScriptValue& v, bool& isValid QScriptValue r = v.property("red"); QScriptValue g = v.property("green"); QScriptValue b = v.property("blue"); + if (!r.isValid()) { + r = v.property("x"); + } + if (!g.isValid()) { + g = v.property("y"); + } + if (!b.isValid()) { + b = v.property("z"); + } if (r.isValid() && g.isValid() && b.isValid()) { newValue.red = r.toVariant().toInt(); newValue.green = g.toVariant().toInt(); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index d43a991f22..156c5d9dd4 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -251,6 +251,12 @@ enum EntityPropertyList { PROP_VISIBLE_IN_SECONDARY_CAMERA, // not sent over the wire, only used locally + PROP_PARTICLE_SPIN, + PROP_SPIN_START, + PROP_SPIN_FINISH, + PROP_SPIN_SPREAD, + PROP_PARTICLE_ROTATE_WITH_ENTITY, + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 98407a74c6..8fd87e068a 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -290,7 +290,7 @@ bool EntityScriptingInterface::addLocalEntityCopy(EntityItemProperties& properti entity->setLastBroadcast(usecTimestampNow()); // since we're creating this object we will immediately volunteer to own its simulation - entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY); + entity->setScriptSimulationPriority(VOLUNTEER_SIMULATION_PRIORITY); properties.setLastEdited(entity->getLastEdited()); } else { qCDebug(entities) << "script failed to add new Entity to local Octree"; @@ -494,7 +494,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& } else { // we make a bid for simulation ownership properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY); - entity->flagForOwnershipBid(SCRIPT_POKE_SIMULATION_PRIORITY); + entity->setScriptSimulationPriority(SCRIPT_POKE_SIMULATION_PRIORITY); } } if (properties.queryAACubeRelatedPropertyChanged()) { @@ -739,15 +739,15 @@ QVector EntityScriptingInterface::findEntitiesInFrustum(QVariantMap frust const QString POSITION_PROPERTY = "position"; bool positionOK = frustum.contains(POSITION_PROPERTY); - glm::vec3 position = positionOK ? qMapToGlmVec3(frustum[POSITION_PROPERTY]) : glm::vec3(); + glm::vec3 position = positionOK ? qMapToVec3(frustum[POSITION_PROPERTY]) : glm::vec3(); const QString ORIENTATION_PROPERTY = "orientation"; bool orientationOK = frustum.contains(ORIENTATION_PROPERTY); - glm::quat orientation = orientationOK ? qMapToGlmQuat(frustum[ORIENTATION_PROPERTY]) : glm::quat(); + glm::quat orientation = orientationOK ? qMapToQuat(frustum[ORIENTATION_PROPERTY]) : glm::quat(); const QString PROJECTION_PROPERTY = "projection"; bool projectionOK = frustum.contains(PROJECTION_PROPERTY); - glm::mat4 projection = projectionOK ? qMapToGlmMat4(frustum[PROJECTION_PROPERTY]) : glm::mat4(); + glm::mat4 projection = projectionOK ? qMapToMat4(frustum[PROJECTION_PROPERTY]) : glm::mat4(); const QString CENTER_RADIUS_PROPERTY = "centerRadius"; bool centerRadiusOK = frustum.contains(CENTER_RADIUS_PROPERTY); @@ -871,6 +871,30 @@ RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorke return result; } +ParabolaToEntityIntersectionResult EntityScriptingInterface::findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking, + const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) { + PROFILE_RANGE(script_entities, __FUNCTION__); + + return findParabolaIntersectionWorker(parabola, Octree::Lock, precisionPicking, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly); +} + +ParabolaToEntityIntersectionResult EntityScriptingInterface::findParabolaIntersectionWorker(const PickParabola& parabola, + Octree::lockType lockType, bool precisionPicking, const QVector& entityIdsToInclude, + const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) { + + + ParabolaToEntityIntersectionResult result; + if (_entityTree) { + OctreeElementPointer element; + result.entityID = _entityTree->findParabolaIntersection(parabola, + entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, precisionPicking, + element, result.intersection, result.distance, result.parabolicDistance, result.face, result.surfaceNormal, + result.extraInfo, lockType, &result.accurate); + result.intersects = !result.entityID.isNull(); + } + return result; +} + bool EntityScriptingInterface::reloadServerScripts(QUuid entityID) { auto client = DependencyManager::get(); return client->reloadServerScript(entityID); @@ -1025,75 +1049,17 @@ bool EntityScriptingInterface::getDrawZoneBoundaries() const { return ZoneEntityItem::getDrawZoneBoundaries(); } -RayToEntityIntersectionResult::RayToEntityIntersectionResult() : - intersects(false), - accurate(true), // assume it's accurate - entityID(), - distance(0), - face() -{ -} - QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& value) { - PROFILE_RANGE(script_entities, __FUNCTION__); - QScriptValue obj = engine->newObject(); obj.setProperty("intersects", value.intersects); obj.setProperty("accurate", value.accurate); QScriptValue entityItemValue = EntityItemIDtoScriptValue(engine, value.entityID); obj.setProperty("entityID", entityItemValue); - obj.setProperty("distance", value.distance); - - QString faceName = ""; - // handle BoxFace - /**jsdoc - *

A BoxFace specifies the face of an axis-aligned (AA) box. - * - * - * - * - * - * - * - * - * - * - * - * - * - *
ValueDescription
"MIN_X_FACE"The minimum x-axis face.
"MAX_X_FACE"The maximum x-axis face.
"MIN_Y_FACE"The minimum y-axis face.
"MAX_Y_FACE"The maximum y-axis face.
"MIN_Z_FACE"The minimum z-axis face.
"MAX_Z_FACE"The maximum z-axis face.
"UNKNOWN_FACE"Unknown value.
- * @typedef {string} BoxFace - */ - // FIXME: Move enum to string function to BoxBase.cpp. - switch (value.face) { - case MIN_X_FACE: - faceName = "MIN_X_FACE"; - break; - case MAX_X_FACE: - faceName = "MAX_X_FACE"; - break; - case MIN_Y_FACE: - faceName = "MIN_Y_FACE"; - break; - case MAX_Y_FACE: - faceName = "MAX_Y_FACE"; - break; - case MIN_Z_FACE: - faceName = "MIN_Z_FACE"; - break; - case MAX_Z_FACE: - faceName = "MAX_Z_FACE"; - break; - case UNKNOWN_FACE: - faceName = "UNKNOWN_FACE"; - break; - } - obj.setProperty("face", faceName); + obj.setProperty("face", boxFaceToString(value.face)); QScriptValue intersection = vec3toScriptValue(engine, value.intersection); obj.setProperty("intersection", intersection); - QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal); obj.setProperty("surfaceNormal", surfaceNormal); obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo)); @@ -1101,29 +1067,13 @@ QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, c } void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, RayToEntityIntersectionResult& value) { - PROFILE_RANGE(script_entities, __FUNCTION__); - value.intersects = object.property("intersects").toVariant().toBool(); value.accurate = object.property("accurate").toVariant().toBool(); QScriptValue entityIDValue = object.property("entityID"); - // EntityItemIDfromScriptValue(entityIDValue, value.entityID); quuidFromScriptValue(entityIDValue, value.entityID); value.distance = object.property("distance").toVariant().toFloat(); + value.face = boxFaceFromString(object.property("face").toVariant().toString()); - QString faceName = object.property("face").toVariant().toString(); - if (faceName == "MIN_X_FACE") { - value.face = MIN_X_FACE; - } else if (faceName == "MAX_X_FACE") { - value.face = MAX_X_FACE; - } else if (faceName == "MIN_Y_FACE") { - value.face = MIN_Y_FACE; - } else if (faceName == "MAX_Y_FACE") { - value.face = MAX_Y_FACE; - } else if (faceName == "MIN_Z_FACE") { - value.face = MIN_Z_FACE; - } else { - value.face = MAX_Z_FACE; - }; QScriptValue intersection = object.property("intersection"); if (intersection.isValid()) { vec3FromScriptValue(intersection, value.intersection); @@ -1269,6 +1219,8 @@ bool EntityScriptingInterface::appendPoint(QUuid entityID, const glm::vec3& poin EntityItemPointer entity = static_cast(_entityTree->findEntityByEntityItemID(entityID)); if (!entity) { qCDebug(entities) << "EntityScriptingInterface::setPoints no entity with ID" << entityID; + // There is no entity + return false; } EntityTypes::EntityType entityType = entity->getType(); @@ -1363,7 +1315,7 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, } action->setIsMine(true); success = entity->addAction(simulation, action); - entity->flagForOwnershipBid(SCRIPT_GRAB_SIMULATION_PRIORITY); + entity->setScriptSimulationPriority(SCRIPT_GRAB_SIMULATION_PRIORITY); return false; // Physics will cause a packet to be sent, so don't send from here. }); if (success) { @@ -1379,7 +1331,7 @@ bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid& return actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) { bool success = entity->updateAction(simulation, actionID, arguments); if (success) { - entity->flagForOwnershipBid(SCRIPT_GRAB_SIMULATION_PRIORITY); + entity->setScriptSimulationPriority(SCRIPT_GRAB_SIMULATION_PRIORITY); } return success; }); @@ -1393,7 +1345,7 @@ bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid& success = entity->removeAction(simulation, actionID); if (success) { // reduce from grab to poke - entity->flagForOwnershipBid(SCRIPT_POKE_SIMULATION_PRIORITY); + entity->setScriptSimulationPriority(SCRIPT_POKE_SIMULATION_PRIORITY); } return false; // Physics will cause a packet to be sent, so don't send from here. }); diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 50df825e5f..a166d513d3 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -71,22 +71,31 @@ private: // "accurate" is currently always true because the ray intersection is always performed with an Octree::Lock. class RayToEntityIntersectionResult { public: - RayToEntityIntersectionResult(); - bool intersects; - bool accurate; + bool intersects { false }; + bool accurate { true }; QUuid entityID; - float distance; + float distance { 0.0f }; BoxFace face; glm::vec3 intersection; glm::vec3 surfaceNormal; QVariantMap extraInfo; }; - Q_DECLARE_METATYPE(RayToEntityIntersectionResult) - QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& results); void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, RayToEntityIntersectionResult& results); +class ParabolaToEntityIntersectionResult { +public: + bool intersects { false }; + bool accurate { true }; + QUuid entityID; + float distance { 0.0f }; + float parabolicDistance { 0.0f }; + BoxFace face; + glm::vec3 intersection; + glm::vec3 surfaceNormal; + QVariantMap extraInfo; +}; /**jsdoc * The Entities API provides facilities to create and interact with entities. Entities are 2D and 3D objects that are visible @@ -131,6 +140,12 @@ public: void resetActivityTracking(); ActivityTracking getActivityTracking() const { return _activityTracking; } + + // TODO: expose to script? + ParabolaToEntityIntersectionResult findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking, + const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, + bool visibleOnly, bool collidableOnly); + public slots: /**jsdoc @@ -1895,6 +1910,11 @@ private: bool precisionPicking, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, bool visibleOnly = false, bool collidableOnly = false); + /// actually does the work of finding the parabola intersection, can be called in locking mode or tryLock mode + ParabolaToEntityIntersectionResult findParabolaIntersectionWorker(const PickParabola& parabola, Octree::lockType lockType, + bool precisionPicking, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, + bool visibleOnly = false, bool collidableOnly = false); + EntityTreePointer _entityTree; std::recursive_mutex _entitiesScriptEngineLock; diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index ba088cb7fd..05e71a8f06 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -31,6 +31,7 @@ void EntitySimulation::setEntityTree(EntityTreePointer tree) { void EntitySimulation::updateEntities() { QMutexLocker lock(&_mutex); uint64_t now = usecTimestampNow(); + PerformanceTimer perfTimer("EntitySimulation::updateEntities"); // these methods may accumulate entries in _entitiesToBeDeleted expireMortalEntities(now); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 3d96107071..66dd6adfb5 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -63,6 +63,27 @@ public: EntityItemID entityID; }; +class ParabolaArgs { +public: + // Inputs + glm::vec3 origin; + glm::vec3 velocity; + glm::vec3 acceleration; + const QVector& entityIdsToInclude; + const QVector& entityIdsToDiscard; + bool visibleOnly; + bool collidableOnly; + bool precisionPicking; + + // Outputs + OctreeElementPointer& element; + float& parabolicDistance; + BoxFace& face; + glm::vec3& surfaceNormal; + QVariantMap& extraInfo; + EntityItemID entityID; +}; + EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage) @@ -97,6 +118,7 @@ void EntityTree::eraseAllOctreeElements(bool createNewRoot) { if (_simulation) { _simulation->clearEntities(); } + _staleProxies.clear(); QHash localMap; localMap.swap(_entityMap); this->withWriteLock([&] { @@ -276,10 +298,11 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { } _isDirty = true; - emit addingEntity(entity->getEntityItemID()); // find and hook up any entities with this entity as a (previously) missing parent fixupNeedsParentFixups(); + + emit addingEntity(entity->getEntityItemID()); } bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode) { @@ -359,21 +382,34 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti // the sender is trying to take or continue ownership if (entity->getSimulatorID().isNull()) { // the sender is taking ownership - properties.promoteSimulationPriority(RECRUIT_SIMULATION_PRIORITY); + if (properties.getSimulationOwner().getPriority() == VOLUNTEER_SIMULATION_PRIORITY) { + // the entity-server always promotes VOLUNTEER to RECRUIT to avoid ownership thrash + // when dynamic objects first activate and multiple participants bid simultaneously + properties.setSimulationPriority(RECRUIT_SIMULATION_PRIORITY); + } simulationBlocked = false; } else if (entity->getSimulatorID() == senderID) { // the sender is asserting ownership, maybe changing priority simulationBlocked = false; + // the entity-server always promotes VOLUNTEER to RECRUIT to avoid ownership thrash + // when dynamic objects first activate and multiple participants bid simultaneously + if (properties.getSimulationOwner().getPriority() == VOLUNTEER_SIMULATION_PRIORITY) { + properties.setSimulationPriority(RECRUIT_SIMULATION_PRIORITY); + } } else { // the sender is trying to steal ownership from another simulator // so we apply the rules for ownership change: // (1) higher priority wins - // (2) equal priority wins if ownership filter has expired except... + // (2) equal priority wins if ownership filter has expired + // (3) VOLUNTEER priority is promoted to RECRUIT uint8_t oldPriority = entity->getSimulationPriority(); uint8_t newPriority = properties.getSimulationOwner().getPriority(); if (newPriority > oldPriority || (newPriority == oldPriority && properties.getSimulationOwner().hasExpired())) { simulationBlocked = false; + if (properties.getSimulationOwner().getPriority() == VOLUNTEER_SIMULATION_PRIORITY) { + properties.setSimulationPriority(RECRUIT_SIMULATION_PRIORITY); + } } } if (!simulationBlocked) { @@ -391,6 +427,7 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti } if (simulationBlocked) { // squash ownership and physics-related changes. + // TODO? replace these eight calls with just one? properties.setSimulationOwnerChanged(false); properties.setPositionChanged(false); properties.setRotationChanged(false); @@ -729,6 +766,12 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) if (theEntity->isSimulated()) { _simulation->prepareEntityForDelete(theEntity); } + + // keep a record of valid stale spaceIndices so they can be removed from the Space + int32_t spaceIndex = theEntity->getSpaceIndex(); + if (spaceIndex != -1) { + _staleProxies.push_back(spaceIndex); + } } } @@ -798,8 +841,7 @@ EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm: BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType, bool* accurateResult) { RayArgs args = { origin, direction, entityIdsToInclude, entityIdsToDiscard, - visibleOnly, collidableOnly, precisionPicking, - element, distance, face, surfaceNormal, extraInfo, EntityItemID() }; + visibleOnly, collidableOnly, precisionPicking, element, distance, face, surfaceNormal, extraInfo, EntityItemID() }; distance = FLT_MAX; bool requireLock = lockType == Octree::Lock; @@ -814,6 +856,47 @@ EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm: return args.entityID; } +bool findParabolaIntersectionOp(const OctreeElementPointer& element, void* extraData) { + ParabolaArgs* args = static_cast(extraData); + bool keepSearching = true; + EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast(element); + EntityItemID entityID = entityTreeElementPointer->findParabolaIntersection(args->origin, args->velocity, args->acceleration, keepSearching, + args->element, args->parabolicDistance, args->face, args->surfaceNormal, args->entityIdsToInclude, + args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->extraInfo, args->precisionPicking); + if (!entityID.isNull()) { + args->entityID = entityID; + } + return keepSearching; +} + +EntityItemID EntityTree::findParabolaIntersection(const PickParabola& parabola, + QVector entityIdsToInclude, QVector entityIdsToDiscard, + bool visibleOnly, bool collidableOnly, bool precisionPicking, + OctreeElementPointer& element, glm::vec3& intersection, float& distance, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, + Octree::lockType lockType, bool* accurateResult) { + ParabolaArgs args = { parabola.origin, parabola.velocity, parabola.acceleration, entityIdsToInclude, entityIdsToDiscard, + visibleOnly, collidableOnly, precisionPicking, element, parabolicDistance, face, surfaceNormal, extraInfo, EntityItemID() }; + parabolicDistance = FLT_MAX; + distance = FLT_MAX; + + bool requireLock = lockType == Octree::Lock; + bool lockResult = withReadLock([&] { + recurseTreeWithOperation(findParabolaIntersectionOp, &args); + }, requireLock); + + if (accurateResult) { + *accurateResult = lockResult; // if user asked to accuracy or result, let them know this is accurate + } + + if (!args.entityID.isNull()) { + intersection = parabola.origin + parabola.velocity * parabolicDistance + 0.5f * parabola.acceleration * parabolicDistance * parabolicDistance; + distance = glm::distance(intersection, parabola.origin); + } + + return args.entityID; +} + EntityItemPointer EntityTree::findClosestEntity(const glm::vec3& position, float targetRadius) { FindNearPointArgs args = { position, targetRadius, false, NULL, FLT_MAX }; @@ -1853,6 +1936,7 @@ void EntityTree::addToNeedsParentFixupList(EntityItemPointer entity) { void EntityTree::update(bool simulate) { PROFILE_RANGE(simulation_physics, "UpdateTree"); + PerformanceTimer perfTimer("updateTree"); withWriteLock([&] { fixupNeedsParentFixups(); if (simulate && _simulation) { @@ -2498,6 +2582,13 @@ bool EntityTree::readFromMap(QVariantMap& map) { } } + // Zero out the spread values that were fixed in version ParticleEntityFix so they behave the same as before + if (contentVersion < (int)EntityVersion::ParticleEntityFix) { + properties.setRadiusSpread(0.0f); + properties.setAlphaSpread(0.0f); + properties.setColorSpread({0, 0, 0}); + } + EntityItemPointer entity = addEntity(entityItemID, properties); if (!entity) { qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType(); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index d5097b9a12..2f971b8566 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -95,7 +95,14 @@ public: virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, QVector entityIdsToInclude, QVector entityIdsToDiscard, bool visibleOnly, bool collidableOnly, bool precisionPicking, - OctreeElementPointer& node, float& distance, + OctreeElementPointer& element, float& distance, + BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, + Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL); + + virtual EntityItemID findParabolaIntersection(const PickParabola& parabola, + QVector entityIdsToInclude, QVector entityIdsToDiscard, + bool visibleOnly, bool collidableOnly, bool precisionPicking, + OctreeElementPointer& element, glm::vec3& intersection, float& distance, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL); @@ -274,6 +281,8 @@ public: void setMyAvatar(std::shared_ptr myAvatar) { _myAvatar = myAvatar; } + void swapStaleProxies(std::vector& proxies) { proxies.swap(_staleProxies); } + void setIsServerlessMode(bool value) { _serverlessDomain = value; } bool isServerlessMode() const { return _serverlessDomain; } @@ -408,6 +417,8 @@ private: static std::function _addMaterialToOverlayOperator; static std::function _removeMaterialFromOverlayOperator; + std::vector _staleProxies; + bool _serverlessDomain { false }; std::map _namedPaths; diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index cbcddfc57b..5974fce6c5 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -159,7 +159,7 @@ EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, con } // by default, we only allow intersections with leaves with content - if (!canRayIntersect()) { + if (!canPickIntersect()) { return result; // we don't intersect with non-leaves, and we keep searching } @@ -168,7 +168,7 @@ EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, con QVariantMap localExtraInfo; float distanceToElementDetails = distance; EntityItemID entityID = findDetailedRayIntersection(origin, direction, element, distanceToElementDetails, - face, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, + localFace, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, localExtraInfo, precisionPicking); if (!entityID.isNull() && distanceToElementDetails < distance) { distance = distanceToElementDetails; @@ -214,7 +214,7 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori glm::mat4 entityToWorldMatrix = translation * rotation; glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); - glm::vec3 dimensions = entity->getScaledDimensions(); + glm::vec3 dimensions = entity->getRaycastDimensions(); glm::vec3 registrationPoint = entity->getRegistrationPoint(); glm::vec3 corner = -(dimensions * registrationPoint); @@ -232,7 +232,7 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori localFace, localSurfaceNormal)) { if (entityFrameBox.contains(entityFrameOrigin) || localDistance < distance) { // now ask the entity if we actually intersect - if (entity->supportsDetailedRayIntersection()) { + if (entity->supportsDetailedIntersection()) { QVariantMap localExtraInfo; if (entity->findDetailedRayIntersection(origin, direction, element, localDistance, localFace, localSurfaceNormal, localExtraInfo, precisionPicking)) { @@ -250,7 +250,8 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori if (localDistance < distance && entity->getType() != EntityTypes::ParticleEffect) { distance = localDistance; face = localFace; - surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 1.0f)); + surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 0.0f)); + extraInfo = QVariantMap(); entityID = entity->getEntityItemID(); } } @@ -287,6 +288,144 @@ bool EntityTreeElement::findSpherePenetration(const glm::vec3& center, float rad return result; } +EntityItemID EntityTreeElement::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, bool& keepSearching, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, + const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, + QVariantMap& extraInfo, bool precisionPicking) { + + EntityItemID result; + float distanceToElementCube = std::numeric_limits::max(); + BoxFace localFace; + glm::vec3 localSurfaceNormal; + + // if the parabola doesn't intersect with our cube OR the distance to element is less than current best distance + // we can stop searching! + bool hit = _cube.findParabolaIntersection(origin, velocity, acceleration, distanceToElementCube, localFace, localSurfaceNormal); + if (!hit || (!_cube.contains(origin) && distanceToElementCube > parabolicDistance)) { + keepSearching = false; // no point in continuing to search + return result; // we did not intersect + } + + // by default, we only allow intersections with leaves with content + if (!canPickIntersect()) { + return result; // we don't intersect with non-leaves, and we keep searching + } + + // if the distance to the element cube is not less than the current best distance, then it's not possible + // for any details inside the cube to be closer so we don't need to consider them. + QVariantMap localExtraInfo; + float distanceToElementDetails = parabolicDistance; + // We can precompute the world-space parabola normal and reuse it for the parabola plane intersects AABox sphere check + glm::vec3 vectorOnPlane = velocity; + if (glm::dot(glm::normalize(velocity), glm::normalize(acceleration)) > 1.0f - EPSILON) { + // Handle the degenerate case where velocity is parallel to acceleration + // We pick t = 1 and calculate a second point on the plane + vectorOnPlane = velocity + 0.5f * acceleration; + } + // Get the normal of the plane, the cross product of two vectors on the plane + glm::vec3 normal = glm::normalize(glm::cross(vectorOnPlane, acceleration)); + EntityItemID entityID = findDetailedParabolaIntersection(origin, velocity, acceleration, normal, element, distanceToElementDetails, + localFace, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, + localExtraInfo, precisionPicking); + if (!entityID.isNull() && distanceToElementDetails < parabolicDistance) { + parabolicDistance = distanceToElementDetails; + face = localFace; + surfaceNormal = localSurfaceNormal; + extraInfo = localExtraInfo; + result = entityID; + } + return result; +} + +EntityItemID EntityTreeElement::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& normal, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, + const QVector& entityIdsToInclude, const QVector& entityIDsToDiscard, + bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking) { + + // only called if we do intersect our bounding cube, but find if we actually intersect with entities... + int entityNumber = 0; + EntityItemID entityID; + forEachEntity([&](EntityItemPointer entity) { + // use simple line-sphere for broadphase check + // (this is faster and more likely to cull results than the filter check below so we do it first) + bool success; + AABox entityBox = entity->getAABox(success); + if (!success) { + return; + } + + // Instead of checking parabolaInstersectsBoundingSphere here, we are just going to check if the plane + // defined by the parabola slices the sphere. The solution to parabolaIntersectsBoundingSphere is cubic, + // the solution to which is more computationally expensive than the quadratic AABox::findParabolaIntersection + // below + if (!entityBox.parabolaPlaneIntersectsBoundingSphere(origin, velocity, acceleration, normal)) { + return; + } + + // check RayPick filter settings + if ((visibleOnly && !entity->isVisible()) + || (collidableOnly && (entity->getCollisionless() || entity->getShapeType() == SHAPE_TYPE_NONE)) + || (entityIdsToInclude.size() > 0 && !entityIdsToInclude.contains(entity->getID())) + || (entityIDsToDiscard.size() > 0 && entityIDsToDiscard.contains(entity->getID())) ) { + return; + } + + // extents is the entity relative, scaled, centered extents of the entity + glm::mat4 rotation = glm::mat4_cast(entity->getWorldOrientation()); + glm::mat4 translation = glm::translate(entity->getWorldPosition()); + glm::mat4 entityToWorldMatrix = translation * rotation; + glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); + + glm::vec3 dimensions = entity->getRaycastDimensions(); + glm::vec3 registrationPoint = entity->getRegistrationPoint(); + glm::vec3 corner = -(dimensions * registrationPoint); + + AABox entityFrameBox(corner, dimensions); + + glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); + glm::vec3 entityFrameVelocity = glm::vec3(worldToEntityMatrix * glm::vec4(velocity, 0.0f)); + glm::vec3 entityFrameAcceleration = glm::vec3(worldToEntityMatrix * glm::vec4(acceleration, 0.0f)); + + // we can use the AABox's ray intersection by mapping our origin and direction into the entity frame + // and testing intersection there. + float localDistance; + BoxFace localFace; + glm::vec3 localSurfaceNormal; + if (entityFrameBox.findParabolaIntersection(entityFrameOrigin, entityFrameVelocity, entityFrameAcceleration, localDistance, + localFace, localSurfaceNormal)) { + if (entityFrameBox.contains(entityFrameOrigin) || localDistance < parabolicDistance) { + // now ask the entity if we actually intersect + if (entity->supportsDetailedIntersection()) { + QVariantMap localExtraInfo; + if (entity->findDetailedParabolaIntersection(origin, velocity, acceleration, element, localDistance, + localFace, localSurfaceNormal, localExtraInfo, precisionPicking)) { + if (localDistance < parabolicDistance) { + parabolicDistance = localDistance; + face = localFace; + surfaceNormal = localSurfaceNormal; + extraInfo = localExtraInfo; + entityID = entity->getEntityItemID(); + } + } + } else { + // if the entity type doesn't support a detailed intersection, then just return the non-AABox results + // Never intersect with particle entities + if (localDistance < parabolicDistance && entity->getType() != EntityTypes::ParticleEffect) { + parabolicDistance = localDistance; + face = localFace; + surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 0.0f)); + extraInfo = QVariantMap(); + entityID = entity->getEntityItemID(); + } + } + } + } + entityNumber++; + }); + return entityID; +} + EntityItemPointer EntityTreeElement::getClosestEntity(glm::vec3 position) const { EntityItemPointer closestEntity = NULL; float closestEntityDistance = FLT_MAX; @@ -312,7 +451,7 @@ void EntityTreeElement::getEntities(const glm::vec3& searchPosition, float searc glm::vec3 penetration; if (!success || entityBox.findSpherePenetration(searchPosition, searchRadius, penetration)) { - glm::vec3 dimensions = entity->getScaledDimensions(); + glm::vec3 dimensions = entity->getRaycastDimensions(); // FIXME - consider allowing the entity to determine penetration so that // entities could presumably dull actuall hull testing if they wanted to diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index 76e1e40812..d6f9db08d6 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -134,9 +134,9 @@ public: virtual bool isRendered() const override { return getShouldRender(); } virtual bool deleteApproved() const override { return !hasEntities(); } - virtual bool canRayIntersect() const override { return hasEntities(); } + virtual bool canPickIntersect() const override { return hasEntities(); } virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& node, float& distance, + bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking = false); @@ -148,6 +148,16 @@ public: virtual bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const override; + virtual EntityItemID findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, bool& keepSearching, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, + const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, + QVariantMap& extraInfo, bool precisionPicking = false); + virtual EntityItemID findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& normal, const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, const QVector& entityIdsToInclude, + const QVector& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, + QVariantMap& extraInfo, bool precisionPicking); template void forEachEntity(F f) const { diff --git a/libraries/entities/src/KeyLightPropertyGroup.cpp b/libraries/entities/src/KeyLightPropertyGroup.cpp index e0b6ae6e96..67e6f05921 100644 --- a/libraries/entities/src/KeyLightPropertyGroup.cpp +++ b/libraries/entities/src/KeyLightPropertyGroup.cpp @@ -35,13 +35,13 @@ void KeyLightPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desired void KeyLightPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, color, xColor, setColor); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, intensity, float, setIntensity); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, direction, glmVec3, setDirection); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, direction, vec3, setDirection); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(keyLight, castShadows, bool, setCastShadows); // legacy property support COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightColor, xColor, setColor, getColor); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightIntensity, float, setIntensity, getIntensity); - COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightDirection, glmVec3, setDirection, getDirection); + COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightDirection, vec3, setDirection, getDirection); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(keyLightCastShadows, bool, setCastShadows, getCastShadows); } diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index e95af7ebf9..1db67fc0b6 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -309,3 +309,14 @@ bool LightEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const return _lightsArePickable; } +bool LightEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + // TODO: consider if this is really what we want to do. We've made it so that "lights are pickable" is a global state + // this is probably reasonable since there's typically only one tree you'd be picking on at a time. Technically we could + // be on the clipboard and someone might be trying to use the parabola intersection API there. Anyway... if you ever try to + // do parabola intersection testing off of trees other than the main tree of the main entity renderer, then we'll need to + // fix this mechanism. + return _lightsArePickable; +} diff --git a/libraries/entities/src/LightEntityItem.h b/libraries/entities/src/LightEntityItem.h index 4d0bde3718..518cb18de2 100644 --- a/libraries/entities/src/LightEntityItem.h +++ b/libraries/entities/src/LightEntityItem.h @@ -84,11 +84,15 @@ public: bool lightPropertiesChanged() const { return _lightPropertiesChanged; } void resetLightPropertiesChanged(); - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; private: // properties of a light diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h index 84f9acf5f5..7c21b5c9d2 100644 --- a/libraries/entities/src/LineEntityItem.h +++ b/libraries/entities/src/LineEntityItem.h @@ -58,12 +58,17 @@ class LineEntityItem : public EntityItem { virtual ShapeType getShapeType() const override { return SHAPE_TYPE_NONE; } // never have a ray intersection pick a LineEntityItem. - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override { return false; } + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, + bool precisionPicking) const override { return false; } bool pointsChanged() const { return _pointsChanged; } void resetPointsChanged(); virtual void debugDump() const override; diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index d1fc3d2775..238f41b05f 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -91,6 +91,8 @@ bool operator==(const Properties& a, const Properties& b) { (a.color == b.color) && (a.alpha == b.alpha) && (a.radius == b.radius) && + (a.spin == b.spin) && + (a.rotateWithEntity == b.rotateWithEntity) && (a.radiusStart == b.radiusStart) && (a.lifespan == b.lifespan) && (a.maxParticles == b.maxParticles) && @@ -130,7 +132,11 @@ bool Properties::valid() const { (radius.gradient.target == glm::clamp(radius.gradient.target, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && (radius.range.start == glm::clamp(radius.range.start, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && (radius.range.finish == glm::clamp(radius.range.finish, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && - (radius.gradient.spread == glm::clamp(radius.gradient.spread, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)); + (radius.gradient.spread == glm::clamp(radius.gradient.spread, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) && + (spin.gradient.target == glm::clamp(spin.gradient.target, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && + (spin.range.start == glm::clamp(spin.range.start, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && + (spin.range.finish == glm::clamp(spin.range.finish, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) && + (spin.gradient.spread == glm::clamp(spin.gradient.spread, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)); } bool Properties::emitting() const { @@ -145,6 +151,8 @@ uint64_t Properties::emitIntervalUsecs() const { return 0; } +const xColor ParticleEffectEntityItem::DEFAULT_XCOLOR = xColor(static_cast(DEFAULT_COLOR.r), static_cast(DEFAULT_COLOR.g), static_cast(DEFAULT_COLOR.b)); +const xColor ParticleEffectEntityItem::DEFAULT_XCOLOR_SPREAD = xColor(static_cast(DEFAULT_COLOR_SPREAD.r), static_cast(DEFAULT_COLOR_SPREAD.g), static_cast(DEFAULT_COLOR_SPREAD.b)); EntityItemPointer ParticleEffectEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity(new ParticleEffectEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); @@ -152,18 +160,6 @@ EntityItemPointer ParticleEffectEntityItem::factory(const EntityItemID& entityID return entity; } -#if 0 -void ParticleEffectEntityItem::checkValid() { - bool result; - withReadLock([&] { - result = _particleProperties.valid(); - }); - if (!result) { - qCWarning(entities) << "Invalid particle properties"; - } -} -#endif - // our non-pure virtual subclass for now... ParticleEffectEntityItem::ParticleEffectEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) @@ -180,15 +176,13 @@ void ParticleEffectEntityItem::setAlpha(float alpha) { void ParticleEffectEntityItem::setAlphaStart(float alphaStart) { withWriteLock([&] { - _particleProperties.alpha.range.start = glm::clamp(alphaStart, MINIMUM_ALPHA, MAXIMUM_ALPHA); - _isAlphaStartInitialized = true; + _particleProperties.alpha.range.start = glm::isnan(alphaStart) ? alphaStart : glm::clamp(alphaStart, MINIMUM_ALPHA, MAXIMUM_ALPHA); }); } void ParticleEffectEntityItem::setAlphaFinish(float alphaFinish) { withWriteLock([&] { - _particleProperties.alpha.range.finish = glm::clamp(alphaFinish, MINIMUM_ALPHA, MAXIMUM_ALPHA); - _isAlphaFinishInitialized = true; + _particleProperties.alpha.range.finish = glm::isnan(alphaFinish) ? alphaFinish : glm::clamp(alphaFinish, MINIMUM_ALPHA, MAXIMUM_ALPHA); }); } @@ -199,9 +193,13 @@ void ParticleEffectEntityItem::setAlphaSpread(float alphaSpread) { } void ParticleEffectEntityItem::setLifespan(float lifespan) { - withWriteLock([&] { - _particleProperties.lifespan = glm::clamp(lifespan, MINIMUM_LIFESPAN, MAXIMUM_LIFESPAN); - }); + lifespan = glm::clamp(lifespan, MINIMUM_LIFESPAN, MAXIMUM_LIFESPAN); + if (lifespan != _particleProperties.lifespan) { + withWriteLock([&] { + _particleProperties.lifespan = lifespan; + }); + computeAndUpdateDimensions(); + } } void ParticleEffectEntityItem::setEmitRate(float emitRate) { @@ -232,10 +230,12 @@ void ParticleEffectEntityItem::setSpeedSpread(float speedSpread) { void ParticleEffectEntityItem::setEmitOrientation(const glm::quat& emitOrientation_) { auto emitOrientation = glm::normalize(emitOrientation_); - withWriteLock([&] { - _particleProperties.emission.orientation = emitOrientation; - }); - computeAndUpdateDimensions(); + if (emitOrientation != _particleProperties.emission.orientation) { + withWriteLock([&] { + _particleProperties.emission.orientation = emitOrientation; + }); + computeAndUpdateDimensions(); + } } void ParticleEffectEntityItem::setEmitDimensions(const glm::vec3& emitDimensions_) { @@ -281,15 +281,9 @@ void ParticleEffectEntityItem::setAzimuthFinish(float azimuthFinish) { void ParticleEffectEntityItem::setEmitAcceleration(const glm::vec3& emitAcceleration_) { auto emitAcceleration = glm::clamp(emitAcceleration_, vec3(MINIMUM_EMIT_ACCELERATION), vec3(MAXIMUM_EMIT_ACCELERATION)); if (emitAcceleration != _particleProperties.emission.acceleration.target) { - if (!_particleProperties.valid()) { - qCWarning(entities) << "Bad particle data"; - } withWriteLock([&] { _particleProperties.emission.acceleration.target = emitAcceleration; }); - if (!_particleProperties.valid()) { - qCWarning(entities) << "Bad particle data"; - } computeAndUpdateDimensions(); } } @@ -305,31 +299,82 @@ void ParticleEffectEntityItem::setAccelerationSpread(const glm::vec3& accelerati } void ParticleEffectEntityItem::setParticleRadius(float particleRadius) { - withWriteLock([&] { - _particleProperties.radius.gradient.target = glm::clamp(particleRadius, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS); - }); + particleRadius = glm::clamp(particleRadius, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS); + if (particleRadius != _particleProperties.radius.gradient.target) { + withWriteLock([&] { + _particleProperties.radius.gradient.target = particleRadius; + }); + computeAndUpdateDimensions(); + } } void ParticleEffectEntityItem::setRadiusStart(float radiusStart) { - withWriteLock([&] { - _particleProperties.radius.range.start = glm::clamp(radiusStart, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS); - _isRadiusStartInitialized = true; - }); + radiusStart = glm::isnan(radiusStart) ? radiusStart : glm::clamp(radiusStart, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS); + if (radiusStart != _particleProperties.radius.range.start) { + withWriteLock([&] { + _particleProperties.radius.range.start = radiusStart; + }); + computeAndUpdateDimensions(); + } } void ParticleEffectEntityItem::setRadiusFinish(float radiusFinish) { - withWriteLock([&] { - _particleProperties.radius.range.finish = glm::clamp(radiusFinish, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS); - _isRadiusFinishInitialized = true; - }); + radiusFinish = glm::isnan(radiusFinish) ? radiusFinish : glm::clamp(radiusFinish, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS); + if (radiusFinish != _particleProperties.radius.range.finish) { + withWriteLock([&] { + _particleProperties.radius.range.finish = radiusFinish; + }); + computeAndUpdateDimensions(); + } } -void ParticleEffectEntityItem::setRadiusSpread(float radiusSpread) { - withWriteLock([&] { - _particleProperties.radius.gradient.spread = glm::clamp(radiusSpread, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS); - }); +void ParticleEffectEntityItem::setRadiusSpread(float radiusSpread) { + radiusSpread = glm::clamp(radiusSpread, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS); + if (radiusSpread != _particleProperties.radius.gradient.spread) { + withWriteLock([&] { + _particleProperties.radius.gradient.spread = radiusSpread; + }); + computeAndUpdateDimensions(); + } } +void ParticleEffectEntityItem::setParticleSpin(float particleSpin) { + particleSpin = glm::clamp(particleSpin, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN); + if (particleSpin != _particleProperties.spin.gradient.target) { + withWriteLock([&] { + _particleProperties.spin.gradient.target = particleSpin; + }); + } +} + +void ParticleEffectEntityItem::setSpinStart(float spinStart) { + spinStart = + glm::isnan(spinStart) ? spinStart : glm::clamp(spinStart, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN); + if (spinStart != _particleProperties.spin.range.start) { + withWriteLock([&] { + _particleProperties.spin.range.start = spinStart; + }); + } +} + +void ParticleEffectEntityItem::setSpinFinish(float spinFinish) { + spinFinish = + glm::isnan(spinFinish) ? spinFinish : glm::clamp(spinFinish, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN); + if (spinFinish != _particleProperties.spin.range.finish) { + withWriteLock([&] { + _particleProperties.spin.range.finish = spinFinish; + }); + } +} + +void ParticleEffectEntityItem::setSpinSpread(float spinSpread) { + spinSpread = glm::clamp(spinSpread, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN); + if (spinSpread != _particleProperties.spin.gradient.spread) { + withWriteLock([&] { + _particleProperties.spin.gradient.spread = spinSpread; + }); + } +} void ParticleEffectEntityItem::computeAndUpdateDimensions() { particle::Properties particleProperties; @@ -342,8 +387,15 @@ void ParticleEffectEntityItem::computeAndUpdateDimensions() { glm::vec3 velocity = particleProperties.emission.speed.target * direction; glm::vec3 velocitySpread = particleProperties.emission.speed.spread * direction; glm::vec3 maxVelocity = glm::abs(velocity) + velocitySpread; - glm::vec3 maxAccleration = glm::abs(_acceleration) + particleProperties.emission.acceleration.spread; - glm::vec3 maxDistance = 0.5f * particleProperties.emission.dimensions + time * maxVelocity + (0.5f * time * time) * maxAccleration; + glm::vec3 maxAccleration = glm::abs(particleProperties.emission.acceleration.target) + particleProperties.emission.acceleration.spread; + float maxRadius = particleProperties.radius.gradient.target; + if (!glm::isnan(particleProperties.radius.range.start)) { + maxRadius = glm::max(maxRadius, particleProperties.radius.range.start); + } + if (!glm::isnan(particleProperties.radius.range.finish)) { + maxRadius = glm::max(maxRadius, particleProperties.radius.range.finish); + } + glm::vec3 maxDistance = 0.5f * particleProperties.emission.dimensions + time * maxVelocity + (0.5f * time * time) * maxAccleration + vec3(maxRadius + particleProperties.radius.gradient.spread); if (isNaN(maxDistance)) { qCWarning(entities) << "Bad particle data"; return; @@ -389,7 +441,11 @@ EntityItemProperties ParticleEffectEntityItem::getProperties(EntityPropertyFlags COPY_ENTITY_PROPERTY_TO_PROPERTIES(alphaFinish, getAlphaFinish); COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitterShouldTrail, getEmitterShouldTrail); - + COPY_ENTITY_PROPERTY_TO_PROPERTIES(particleSpin, getParticleSpin); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(spinSpread, getSpinSpread); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(spinStart, getSpinStart); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(spinFinish, getSpinFinish); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(rotateWithEntity, getRotateWithEntity); return properties; } @@ -427,6 +483,11 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert SET_ENTITY_PROPERTY_FROM_PROPERTIES(alphaFinish, setAlphaFinish); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitterShouldTrail, setEmitterShouldTrail); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(particleSpin, setParticleSpin); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(spinSpread, setSpinSpread); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(spinStart, setSpinStart); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(spinFinish, setSpinFinish); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotateWithEntity, setRotateWithEntity); if (somethingChanged) { bool wantDebug = false; @@ -441,18 +502,26 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert return somethingChanged; } -void ParticleEffectEntityItem::setColor(const rgbColor& value) { - memcpy(_particleColorHack, value, sizeof(rgbColor)); - _particleProperties.color.gradient.target.red = value[RED_INDEX]; - _particleProperties.color.gradient.target.green = value[GREEN_INDEX]; - _particleProperties.color.gradient.target.blue = value[BLUE_INDEX]; +void ParticleEffectEntityItem::setColor(const vec3& value) { + withWriteLock([&] { + _particleProperties.color.gradient.target = value; + }); } void ParticleEffectEntityItem::setColor(const xColor& value) { - _particleProperties.color.gradient.target = value; - _particleColorHack[RED_INDEX] = value.red; - _particleColorHack[GREEN_INDEX] = value.green; - _particleColorHack[BLUE_INDEX] = value.blue; + withWriteLock([&] { + _particleProperties.color.gradient.target.r = value.red; + _particleProperties.color.gradient.target.g = value.green; + _particleProperties.color.gradient.target.b = value.blue; + }); +} + +xColor ParticleEffectEntityItem::getXColor() const { + xColor color; + color.red = _particleProperties.color.gradient.target.r; + color.green = _particleProperties.color.gradient.target.g; + color.blue = _particleProperties.color.gradient.target.b; + return color; } int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, @@ -463,15 +532,15 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch int bytesRead = 0; const unsigned char* dataAt = data; - READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor); + READ_ENTITY_PROPERTY(PROP_COLOR, xColor, setColor); READ_ENTITY_PROPERTY(PROP_EMITTING_PARTICLES, bool, setIsEmitting); READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); READ_ENTITY_PROPERTY(PROP_MAX_PARTICLES, quint32, setMaxParticles); READ_ENTITY_PROPERTY(PROP_LIFESPAN, float, setLifespan); READ_ENTITY_PROPERTY(PROP_EMIT_RATE, float, setEmitRate); - READ_ENTITY_PROPERTY(PROP_EMIT_ACCELERATION, glm::vec3, setEmitAcceleration); - READ_ENTITY_PROPERTY(PROP_ACCELERATION_SPREAD, glm::vec3, setAccelerationSpread); + READ_ENTITY_PROPERTY(PROP_EMIT_ACCELERATION, vec3, setEmitAcceleration); + READ_ENTITY_PROPERTY(PROP_ACCELERATION_SPREAD, vec3, setAccelerationSpread); READ_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float, setParticleRadius); READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, setTextures); @@ -480,8 +549,8 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch READ_ENTITY_PROPERTY(PROP_RADIUS_FINISH, float, setRadiusFinish); READ_ENTITY_PROPERTY(PROP_COLOR_SPREAD, xColor, setColorSpread); - READ_ENTITY_PROPERTY(PROP_COLOR_START, xColor, setColorStart); - READ_ENTITY_PROPERTY(PROP_COLOR_FINISH, xColor, setColorFinish); + READ_ENTITY_PROPERTY(PROP_COLOR_START, vec3, setColorStart); + READ_ENTITY_PROPERTY(PROP_COLOR_FINISH, vec3, setColorFinish); READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); READ_ENTITY_PROPERTY(PROP_ALPHA_SPREAD, float, setAlphaSpread); READ_ENTITY_PROPERTY(PROP_ALPHA_START, float, setAlphaStart); @@ -489,8 +558,8 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch READ_ENTITY_PROPERTY(PROP_EMIT_SPEED, float, setEmitSpeed); READ_ENTITY_PROPERTY(PROP_SPEED_SPREAD, float, setSpeedSpread); - READ_ENTITY_PROPERTY(PROP_EMIT_ORIENTATION, glm::quat, setEmitOrientation); - READ_ENTITY_PROPERTY(PROP_EMIT_DIMENSIONS, glm::vec3, setEmitDimensions); + READ_ENTITY_PROPERTY(PROP_EMIT_ORIENTATION, quat, setEmitOrientation); + READ_ENTITY_PROPERTY(PROP_EMIT_DIMENSIONS, vec3, setEmitDimensions); READ_ENTITY_PROPERTY(PROP_EMIT_RADIUS_START, float, setEmitRadiusStart); READ_ENTITY_PROPERTY(PROP_POLAR_START, float, setPolarStart); READ_ENTITY_PROPERTY(PROP_POLAR_FINISH, float, setPolarFinish); @@ -499,6 +568,12 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch READ_ENTITY_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, bool, setEmitterShouldTrail); + READ_ENTITY_PROPERTY(PROP_PARTICLE_SPIN, float, setParticleSpin); + READ_ENTITY_PROPERTY(PROP_SPIN_SPREAD, float, setSpinSpread); + READ_ENTITY_PROPERTY(PROP_SPIN_START, float, setSpinStart); + READ_ENTITY_PROPERTY(PROP_SPIN_FINISH, float, setSpinFinish); + READ_ENTITY_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, bool, setRotateWithEntity); + return bytesRead; } @@ -535,6 +610,11 @@ EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstrea requestedProperties += PROP_AZIMUTH_START; requestedProperties += PROP_AZIMUTH_FINISH; requestedProperties += PROP_EMITTER_SHOULD_TRAIL; + requestedProperties += PROP_PARTICLE_SPIN; + requestedProperties += PROP_SPIN_SPREAD; + requestedProperties += PROP_SPIN_START; + requestedProperties += PROP_SPIN_FINISH; + requestedProperties += PROP_PARTICLE_ROTATE_WITH_ENTITY; return requestedProperties; } @@ -548,7 +628,7 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, OctreeElement::AppendState& appendState) const { bool successPropertyFits = true; - APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); + APPEND_ENTITY_PROPERTY(PROP_COLOR, getXColor()); APPEND_ENTITY_PROPERTY(PROP_EMITTING_PARTICLES, getIsEmitting()); APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType()); APPEND_ENTITY_PROPERTY(PROP_MAX_PARTICLES, getMaxParticles()); @@ -578,6 +658,11 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_START, getAzimuthStart()); APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_FINISH, getAzimuthFinish()); APPEND_ENTITY_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, getEmitterShouldTrail()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_SPIN, getParticleSpin()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_SPREAD, getSpinSpread()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_START, getSpinStart()); + APPEND_ENTITY_PROPERTY(PROP_SPIN_FINISH, getSpinFinish()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, getRotateWithEntity()); } @@ -585,10 +670,10 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, void ParticleEffectEntityItem::debugDump() const { quint64 now = usecTimestampNow(); qCDebug(entities) << "PA EFFECT EntityItem id:" << getEntityItemID() << "---------------------------------------------"; - qCDebug(entities) << " color:" << - _particleProperties.color.gradient.target.red << "," << - _particleProperties.color.gradient.target.green << "," << - _particleProperties.color.gradient.target.blue; + qCDebug(entities) << " color:" << + _particleProperties.color.gradient.target.r << "," << + _particleProperties.color.gradient.target.g << "," << + _particleProperties.color.gradient.target.b; qCDebug(entities) << " position:" << debugTreeVector(getWorldPosition()); qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions()); qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); @@ -604,15 +689,9 @@ void ParticleEffectEntityItem::setShapeType(ShapeType type) { } void ParticleEffectEntityItem::setMaxParticles(quint32 maxParticles) { - _particleProperties.maxParticles = glm::clamp(maxParticles, MINIMUM_MAX_PARTICLES, MAXIMUM_MAX_PARTICLES); -} - -QString ParticleEffectEntityItem::getTextures() const { - QString result; - withReadLock([&] { - result = _particleProperties.textures; + withWriteLock([&] { + _particleProperties.maxParticles = glm::clamp(maxParticles, MINIMUM_MAX_PARTICLES, MAXIMUM_MAX_PARTICLES); }); - return result; } void ParticleEffectEntityItem::setTextures(const QString& textures) { @@ -621,25 +700,81 @@ void ParticleEffectEntityItem::setTextures(const QString& textures) { }); } +void ParticleEffectEntityItem::setColorStart(const vec3& colorStart) { + withWriteLock([&] { + _particleProperties.color.range.start = colorStart; + }); +} + +void ParticleEffectEntityItem::setColorFinish(const vec3& colorFinish) { + withWriteLock([&] { + _particleProperties.color.range.finish = colorFinish; + }); +} + +void ParticleEffectEntityItem::setColorSpread(const xColor& value) { + withWriteLock([&] { + _particleProperties.color.gradient.spread.r = value.red; + _particleProperties.color.gradient.spread.g = value.green; + _particleProperties.color.gradient.spread.b = value.blue; + }); +} + +xColor ParticleEffectEntityItem::getColorSpread() const { + xColor color; + color.red = _particleProperties.color.gradient.spread.r; + color.green = _particleProperties.color.gradient.spread.g; + color.blue = _particleProperties.color.gradient.spread.b; + return color; +} + +void ParticleEffectEntityItem::setEmitterShouldTrail(bool emitterShouldTrail) { + withWriteLock([&] { + _particleProperties.emission.shouldTrail = emitterShouldTrail; + }); +} + +void ParticleEffectEntityItem::setRotateWithEntity(bool rotateWithEntity) { + withWriteLock([&] { + _particleProperties.rotateWithEntity = rotateWithEntity; + }); +} + particle::Properties ParticleEffectEntityItem::getParticleProperties() const { particle::Properties result; - withReadLock([&] { - result = _particleProperties; - }); + withReadLock([&] { + result = _particleProperties; - // Special case the properties that get treated differently if they're unintialized - result.color.range.start = getColorStart(); - result.color.range.finish = getColorFinish(); - result.alpha.range.start = getAlphaStart(); - result.alpha.range.finish = getAlphaFinish(); - result.radius.range.start = getRadiusStart(); - result.radius.range.finish = getRadiusFinish(); + // Special case the properties that get treated differently if they're unintialized + if (glm::any(glm::isnan(result.color.range.start))) { + result.color.range.start = getColor(); + } + if (glm::any(glm::isnan(result.color.range.finish))) { + result.color.range.finish = getColor(); + } + if (glm::isnan(result.alpha.range.start)) { + result.alpha.range.start = getAlpha(); + } + if (glm::isnan(result.alpha.range.finish)) { + result.alpha.range.finish = getAlpha(); + } + if (glm::isnan(result.radius.range.start)) { + result.radius.range.start = getParticleRadius(); + } + if (glm::isnan(result.radius.range.finish)) { + result.radius.range.finish = getParticleRadius(); + } + if (glm::isnan(result.spin.range.start)) { + result.spin.range.start = getParticleSpin(); + } + if (glm::isnan(result.spin.range.finish)) { + result.spin.range.finish = getParticleSpin(); + } + }); if (!result.valid()) { qCWarning(entities) << "failed validation"; } return result; -} - - +} \ No newline at end of file diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index 9c0fd0ef95..02284768ce 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -19,12 +19,14 @@ namespace particle { static const float SCRIPT_MAXIMUM_PI = 3.1416f; // Round up so that reasonable property values work - static const xColor DEFAULT_COLOR = { 255, 255, 255 }; - static const xColor DEFAULT_COLOR_SPREAD = { 0, 0, 0 }; + static const float UNINITIALIZED = NAN; + static const vec3 DEFAULT_COLOR = { 255, 255, 255 }; + static const vec3 DEFAULT_COLOR_UNINITIALIZED = { UNINITIALIZED, UNINITIALIZED, UNINITIALIZED }; + static const vec3 DEFAULT_COLOR_SPREAD = { 0, 0, 0 }; static const float DEFAULT_ALPHA = 1.0f; static const float DEFAULT_ALPHA_SPREAD = 0.0f; - static const float DEFAULT_ALPHA_START = DEFAULT_ALPHA; - static const float DEFAULT_ALPHA_FINISH = DEFAULT_ALPHA; + static const float DEFAULT_ALPHA_START = UNINITIALIZED; + static const float DEFAULT_ALPHA_FINISH = UNINITIALIZED; static const float MINIMUM_ALPHA = 0.0f; static const float MAXIMUM_ALPHA = 1.0f; static const quint32 DEFAULT_MAX_PARTICLES = 1000; @@ -37,7 +39,7 @@ namespace particle { static const float MINIMUM_EMIT_RATE = 0.0f; static const float MAXIMUM_EMIT_RATE = 100000.0f; static const float DEFAULT_EMIT_SPEED = 5.0f; - static const float MINIMUM_EMIT_SPEED = 0.0f; + static const float MINIMUM_EMIT_SPEED = -1000.0f; static const float MAXIMUM_EMIT_SPEED = 1000.0f; // Approx mach 3 static const float DEFAULT_SPEED_SPREAD = 1.0f; static const glm::quat DEFAULT_EMIT_ORIENTATION = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_X); // Vertical @@ -65,10 +67,17 @@ namespace particle { static const float MINIMUM_PARTICLE_RADIUS = 0.0f; static const float MAXIMUM_PARTICLE_RADIUS = (float)TREE_SCALE; static const float DEFAULT_RADIUS_SPREAD = 0.0f; - static const float DEFAULT_RADIUS_START = DEFAULT_PARTICLE_RADIUS; - static const float DEFAULT_RADIUS_FINISH = DEFAULT_PARTICLE_RADIUS; + static const float DEFAULT_RADIUS_START = UNINITIALIZED; + static const float DEFAULT_RADIUS_FINISH = UNINITIALIZED; + static const float DEFAULT_PARTICLE_SPIN = 0.0f; + static const float DEFAULT_SPIN_START = UNINITIALIZED; + static const float DEFAULT_SPIN_FINISH = UNINITIALIZED; + static const float DEFAULT_SPIN_SPREAD = 0.0f; + static const float MINIMUM_PARTICLE_SPIN = -2.0f * SCRIPT_MAXIMUM_PI; + static const float MAXIMUM_PARTICLE_SPIN = 2.0f * SCRIPT_MAXIMUM_PI; static const QString DEFAULT_TEXTURES = ""; static const bool DEFAULT_EMITTER_SHOULD_TRAIL = false; + static const bool DEFAULT_ROTATE_WITH_ENTITY = false; template struct Range { @@ -145,12 +154,14 @@ namespace particle { }; struct Properties { - RangeGradient color{ DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_COLOR_SPREAD }; - RangeGradient alpha{ DEFAULT_ALPHA, DEFAULT_ALPHA_START, DEFAULT_ALPHA_FINISH, DEFAULT_ALPHA_SPREAD }; - float radiusStart{ DEFAULT_EMIT_RADIUS_START }; - RangeGradient radius{ DEFAULT_PARTICLE_RADIUS, DEFAULT_RADIUS_START, DEFAULT_RADIUS_FINISH, DEFAULT_RADIUS_SPREAD }; - float lifespan{ DEFAULT_LIFESPAN }; - uint32_t maxParticles{ DEFAULT_MAX_PARTICLES }; + RangeGradient color { DEFAULT_COLOR, DEFAULT_COLOR_UNINITIALIZED, DEFAULT_COLOR_UNINITIALIZED, DEFAULT_COLOR_SPREAD }; + RangeGradient alpha { DEFAULT_ALPHA, DEFAULT_ALPHA_START, DEFAULT_ALPHA_FINISH, DEFAULT_ALPHA_SPREAD }; + float radiusStart { DEFAULT_EMIT_RADIUS_START }; + RangeGradient radius { DEFAULT_PARTICLE_RADIUS, DEFAULT_RADIUS_START, DEFAULT_RADIUS_FINISH, DEFAULT_RADIUS_SPREAD }; + RangeGradient spin { DEFAULT_PARTICLE_SPIN, DEFAULT_SPIN_START, DEFAULT_SPIN_FINISH, DEFAULT_SPIN_SPREAD }; + bool rotateWithEntity { DEFAULT_ROTATE_WITH_ENTITY }; + float lifespan { DEFAULT_LIFESPAN }; + uint32_t maxParticles { DEFAULT_MAX_PARTICLES }; EmitProperties emission; Range polar { DEFAULT_POLAR_START, DEFAULT_POLAR_FINISH }; Range azimuth { DEFAULT_AZIMUTH_START, DEFAULT_AZIMUTH_FINISH }; @@ -166,6 +177,8 @@ namespace particle { Properties& operator =(const Properties& other) { color = other.color; alpha = other.alpha; + spin = other.spin; + rotateWithEntity = other.rotateWithEntity; radius = other.radius; lifespan = other.lifespan; maxParticles = other.maxParticles; @@ -176,10 +189,10 @@ namespace particle { return *this; } - glm::vec4 getColorStart() const { return glm::vec4(ColorUtils::sRGBToLinearVec3(toGlm(color.range.start)), alpha.range.start); } - glm::vec4 getColorMiddle() const { return glm::vec4(ColorUtils::sRGBToLinearVec3(toGlm(color.gradient.target)), alpha.gradient.target); } - glm::vec4 getColorFinish() const { return glm::vec4(ColorUtils::sRGBToLinearVec3(toGlm(color.range.finish)), alpha.range.finish); } - glm::vec4 getColorSpread() const { return glm::vec4(ColorUtils::sRGBToLinearVec3(toGlm(color.gradient.spread)), alpha.gradient.spread); } + vec4 getColorStart() const { return vec4(ColorUtils::sRGBToLinearVec3(color.range.start / 255.0f), alpha.range.start); } + vec4 getColorMiddle() const { return vec4(ColorUtils::sRGBToLinearVec3(color.gradient.target / 255.0f), alpha.gradient.target); } + vec4 getColorFinish() const { return vec4(ColorUtils::sRGBToLinearVec3(color.range.finish / 255.0f), alpha.range.finish); } + vec4 getColorSpread() const { return vec4(ColorUtils::sRGBToLinearVec3(color.gradient.spread / 255.0f), alpha.gradient.spread); } }; } // namespace particles @@ -215,37 +228,29 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; - const rgbColor& getColor() const { return _particleColorHack; } - xColor getXColor() const { return _particleProperties.color.gradient.target; } - glm::vec3 getColorRGB() const { return ColorUtils::sRGBToLinearVec3(toGlm(getXColor())); } + xColor getXColor() const; + vec3 getColor() const { return _particleProperties.color.gradient.target; } - void setColor(const rgbColor& value); + void setColor(const vec3& value); void setColor(const xColor& value); - bool _isColorStartInitialized = false; - void setColorStart(const xColor& colorStart) { _particleProperties.color.range.start = colorStart; _isColorStartInitialized = true; } - xColor getColorStart() const { return _isColorStartInitialized ? _particleProperties.color.range.start : getXColor(); } - glm::vec3 getColorStartRGB() const { return _isColorStartInitialized ? ColorUtils::sRGBToLinearVec3(toGlm(_particleProperties.color.range.start)) : getColorRGB(); } + void setColorStart(const vec3& colorStart); + vec3 getColorStart() const { return _particleProperties.color.range.start; } - bool _isColorFinishInitialized = false; - void setColorFinish(const xColor& colorFinish) { _particleProperties.color.range.finish = colorFinish; _isColorFinishInitialized = true; } - xColor getColorFinish() const { return _isColorFinishInitialized ? _particleProperties.color.range.finish : getXColor(); } - glm::vec3 getColorFinishRGB() const { return _isColorFinishInitialized ? ColorUtils::sRGBToLinearVec3(toGlm(_particleProperties.color.range.finish)) : getColorRGB(); } + void setColorFinish(const vec3& colorFinish); + vec3 getColorFinish() const { return _particleProperties.color.range.finish; } - void setColorSpread(const xColor& colorSpread) { _particleProperties.color.gradient.spread = colorSpread; } - xColor getColorSpread() const { return _particleProperties.color.gradient.spread; } - glm::vec3 getColorSpreadRGB() const { return ColorUtils::sRGBToLinearVec3(toGlm(_particleProperties.color.gradient.spread)); } + void setColorSpread(const xColor& colorSpread); + xColor getColorSpread() const; void setAlpha(float alpha); float getAlpha() const { return _particleProperties.alpha.gradient.target; } - bool _isAlphaStartInitialized = false; void setAlphaStart(float alphaStart); - float getAlphaStart() const { return _isAlphaStartInitialized ? _particleProperties.alpha.range.start : _particleProperties.alpha.gradient.target; } + float getAlphaStart() const { return _particleProperties.alpha.range.start; } - bool _isAlphaFinishInitialized = false; void setAlphaFinish(float alphaFinish); - float getAlphaFinish() const { return _isAlphaFinishInitialized ? _particleProperties.alpha.range.finish : _particleProperties.alpha.gradient.target; } + float getAlphaFinish() const { return _particleProperties.alpha.range.finish; } void setAlphaSpread(float alphaSpread); float getAlphaSpread() const { return _particleProperties.alpha.gradient.spread; } @@ -303,31 +308,46 @@ public: void setParticleRadius(float particleRadius); float getParticleRadius() const { return _particleProperties.radius.gradient.target; } - bool _isRadiusStartInitialized = false; void setRadiusStart(float radiusStart); - float getRadiusStart() const { return _isRadiusStartInitialized ? _particleProperties.radius.range.start : _particleProperties.radius.gradient.target; } + float getRadiusStart() const { return _particleProperties.radius.range.start; } - bool _isRadiusFinishInitialized = false; void setRadiusFinish(float radiusFinish); - float getRadiusFinish() const { return _isRadiusFinishInitialized ? _particleProperties.radius.range.finish : _particleProperties.radius.gradient.target; } + float getRadiusFinish() const { return _particleProperties.radius.range.finish; } void setRadiusSpread(float radiusSpread); float getRadiusSpread() const { return _particleProperties.radius.gradient.spread; } + void setParticleSpin(float particleSpin); + float getParticleSpin() const { return _particleProperties.spin.gradient.target; } + + void setSpinStart(float spinStart); + float getSpinStart() const { return _particleProperties.spin.range.start; } + + void setSpinFinish(float spinFinish); + float getSpinFinish() const { return _particleProperties.spin.range.finish; } + + void setSpinSpread(float spinSpread); + float getSpinSpread() const { return _particleProperties.spin.gradient.spread; } + + void setRotateWithEntity(bool rotateWithEntity); + bool getRotateWithEntity() const { return _particleProperties.rotateWithEntity; } + void computeAndUpdateDimensions(); - QString getTextures() const; void setTextures(const QString& textures); + QString getTextures() const { return _particleProperties.textures; } bool getEmitterShouldTrail() const { return _particleProperties.emission.shouldTrail; } - void setEmitterShouldTrail(bool emitterShouldTrail) { _particleProperties.emission.shouldTrail = emitterShouldTrail; } + void setEmitterShouldTrail(bool emitterShouldTrail); - virtual bool supportsDetailedRayIntersection() const override { return false; } + virtual bool supportsDetailedIntersection() const override { return false; } - particle::Properties getParticleProperties() const; + particle::Properties getParticleProperties() const; + + static const xColor DEFAULT_XCOLOR; + static const xColor DEFAULT_XCOLOR_SPREAD; protected: - rgbColor _particleColorHack; particle::Properties _particleProperties; bool _isEmitting { true }; diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index c76419af02..52ec8e8c2d 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -89,11 +89,15 @@ class PolyLineEntityItem : public EntityItem { // never have a ray intersection pick a PolyLineEntityItem. - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override { return false; } + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, 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 {}; // FIXME: this is suspicious! diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 4dfe7b9535..d2ca4db124 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -42,11 +42,15 @@ class PolyVoxEntityItem : public EntityItem { bool& somethingChanged) override; // never have a ray intersection pick a PolyVoxEntityItem. - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override { return false; } + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override { return false; } virtual void debugDump() const override; diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 943ae2e462..e4ea1470c1 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -250,7 +250,7 @@ void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) { } } -bool ShapeEntityItem::supportsDetailedRayIntersection() const { +bool ShapeEntityItem::supportsDetailedIntersection() const { return _shape == entity::Sphere; } @@ -273,6 +273,7 @@ bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3 hitAt = glm::vec3(entityToWorldMatrix * glm::vec4(entityFrameHitAt, 1.0f)); distance = glm::distance(origin, hitAt); bool success; + // FIXME: this is only correct for uniformly scaled spheres surfaceNormal = glm::normalize(hitAt - getCenterPosition(success)); if (!success) { return false; @@ -282,6 +283,30 @@ bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const return false; } +bool ShapeEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + // determine the parabola in the frame of the entity transformed from a unit sphere + glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix(); + glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); + glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f)); + glm::vec3 entityFrameVelocity = glm::vec3(worldToEntityMatrix * glm::vec4(velocity, 0.0f)); + glm::vec3 entityFrameAcceleration = glm::vec3(worldToEntityMatrix * glm::vec4(acceleration, 0.0f)); + + // NOTE: unit sphere has center of 0,0,0 and radius of 0.5 + if (findParabolaSphereIntersection(entityFrameOrigin, entityFrameVelocity, entityFrameAcceleration, glm::vec3(0.0f), 0.5f, parabolicDistance)) { + bool success; + // FIXME: this is only correct for uniformly scaled spheres + surfaceNormal = glm::normalize((origin + velocity * parabolicDistance + 0.5f * acceleration * parabolicDistance * parabolicDistance) - getCenterPosition(success)); + if (!success) { + return false; + } + return true; + } + return false; +} + void ShapeEntityItem::debugDump() const { quint64 now = usecTimestampNow(); qCDebug(entities) << "SHAPE EntityItem id:" << getEntityItemID() << "---------------------------------------------"; diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index adc33b764b..ded5df15fe 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -90,11 +90,15 @@ public: bool shouldBePhysical() const override { return !isDead(); } - bool supportsDetailedRayIntersection() const override; + bool supportsDetailedIntersection() const override; bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; void debugDump() const override; diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index cf42e93821..28dc7b26c4 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -53,20 +53,29 @@ void SimpleEntitySimulation::updateEntitiesInternal(uint64_t now) { } void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) { - if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) { + if (entity->getSimulatorID().isNull()) { QMutexLocker lock(&_mutex); - _simpleKinematicEntities.insert(entity); - entity->setLastSimulated(usecTimestampNow()); - } - if (!entity->getSimulatorID().isNull()) { + if (entity->getDynamic()) { + // we don't allow dynamic objects to move without an owner so nothing to do here + } else if (entity->isMovingRelativeToParent()) { + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr != _simpleKinematicEntities.end()) { + _simpleKinematicEntities.insert(entity); + entity->setLastSimulated(usecTimestampNow()); + } + } + } else { QMutexLocker lock(&_mutex); _entitiesWithSimulationOwner.insert(entity); _nextStaleOwnershipExpiry = glm::min(_nextStaleOwnershipExpiry, entity->getSimulationOwnershipExpiry()); - } else if (entity->getDynamic() && entity->hasLocalVelocity()) { - QMutexLocker lock(&_mutex); - _entitiesThatNeedSimulationOwner.insert(entity); - uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; - _nextOwnerlessExpiry = glm::min(_nextOwnerlessExpiry, expiry); + + if (entity->isMovingRelativeToParent()) { + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr != _simpleKinematicEntities.end()) { + _simpleKinematicEntities.insert(entity); + entity->setLastSimulated(usecTimestampNow()); + } + } } } @@ -77,32 +86,50 @@ void SimpleEntitySimulation::removeEntityInternal(EntityItemPointer entity) { } void SimpleEntitySimulation::changeEntityInternal(EntityItemPointer entity) { - { - QMutexLocker lock(&_mutex); - if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) { - int numKinematicEntities = _simpleKinematicEntities.size(); - _simpleKinematicEntities.insert(entity); - if (numKinematicEntities != _simpleKinematicEntities.size()) { - entity->setLastSimulated(usecTimestampNow()); + uint32_t flags = entity->getDirtyFlags(); + if ((flags & Simulation::DIRTY_SIMULATOR_ID) || (flags & Simulation::DIRTY_VELOCITIES)) { + if (entity->getSimulatorID().isNull()) { + QMutexLocker lock(&_mutex); + _entitiesWithSimulationOwner.remove(entity); + + if (entity->getDynamic()) { + // we don't allow dynamic objects to move without an owner + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr != _simpleKinematicEntities.end()) { + _simpleKinematicEntities.erase(itr); + } + } else if (entity->isMovingRelativeToParent()) { + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr == _simpleKinematicEntities.end()) { + _simpleKinematicEntities.insert(entity); + entity->setLastSimulated(usecTimestampNow()); + } + } else { + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr != _simpleKinematicEntities.end()) { + _simpleKinematicEntities.erase(itr); + } } } else { - _simpleKinematicEntities.remove(entity); + QMutexLocker lock(&_mutex); + _entitiesWithSimulationOwner.insert(entity); + _nextStaleOwnershipExpiry = glm::min(_nextStaleOwnershipExpiry, entity->getSimulationOwnershipExpiry()); + _entitiesThatNeedSimulationOwner.remove(entity); + + if (entity->isMovingRelativeToParent()) { + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr == _simpleKinematicEntities.end()) { + _simpleKinematicEntities.insert(entity); + entity->setLastSimulated(usecTimestampNow()); + } + } else { + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr != _simpleKinematicEntities.end()) { + _simpleKinematicEntities.erase(itr); + } + } } } - if (entity->getSimulatorID().isNull()) { - QMutexLocker lock(&_mutex); - _entitiesWithSimulationOwner.remove(entity); - if (entity->getDynamic() && entity->hasLocalVelocity()) { - _entitiesThatNeedSimulationOwner.insert(entity); - uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; - _nextOwnerlessExpiry = glm::min(_nextOwnerlessExpiry, expiry); - } - } else { - QMutexLocker lock(&_mutex); - _entitiesWithSimulationOwner.insert(entity); - _nextStaleOwnershipExpiry = glm::min(_nextStaleOwnershipExpiry, entity->getSimulationOwnershipExpiry()); - _entitiesThatNeedSimulationOwner.remove(entity); - } entity->clearDirtyFlags(); } @@ -131,6 +158,12 @@ void SimpleEntitySimulation::expireStaleOwnerships(uint64_t now) { uint64_t expiry = entity->getSimulationOwnershipExpiry(); if (now > expiry) { itemItr = _entitiesWithSimulationOwner.erase(itemItr); + if (entity->getDynamic()) { + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr != _simpleKinematicEntities.end()) { + _simpleKinematicEntities.erase(itr); + } + } // remove ownership and dirty all the tree elements that contain the it entity->clearSimulationOwnership(); diff --git a/libraries/entities/src/SimulationOwner.cpp b/libraries/entities/src/SimulationOwner.cpp index b312c6deb9..69fbf0ae16 100644 --- a/libraries/entities/src/SimulationOwner.cpp +++ b/libraries/entities/src/SimulationOwner.cpp @@ -16,29 +16,20 @@ #include -const uint8_t PENDING_STATE_NOTHING = 0; -const uint8_t PENDING_STATE_TAKE = 1; -const uint8_t PENDING_STATE_RELEASE = 2; - // static const int SimulationOwner::NUM_BYTES_ENCODED = NUM_BYTES_RFC4122_UUID + 1; SimulationOwner::SimulationOwner() : _id(), _expiry(0), - _pendingBidTimestamp(0), - _priority(0), - _pendingBidPriority(0), - _pendingState(PENDING_STATE_NOTHING) + _priority(0) { } SimulationOwner::SimulationOwner(const QUuid& id, uint8_t priority) : _id(id), _expiry(0), - _pendingBidTimestamp(0), - _priority(priority), - _pendingBidPriority(0) + _priority(priority) { } @@ -61,22 +52,13 @@ bool SimulationOwner::fromByteArray(const QByteArray& data) { void SimulationOwner::clear() { _id = QUuid(); _expiry = 0; - _pendingBidTimestamp = 0; _priority = 0; - _pendingBidPriority = 0; - _pendingState = PENDING_STATE_NOTHING; } void SimulationOwner::setPriority(uint8_t priority) { _priority = priority; } -void SimulationOwner::promotePriority(uint8_t priority) { - if (priority > _priority) { - _priority = priority; - } -} - bool SimulationOwner::setID(const QUuid& id) { if (_id != id) { _id = id; @@ -101,25 +83,11 @@ bool SimulationOwner::set(const SimulationOwner& owner) { return setID(owner._id) || oldPriority != _priority; } -void SimulationOwner::setPendingPriority(uint8_t priority, uint64_t timestamp) { - _pendingBidPriority = priority; - _pendingBidTimestamp = timestamp; - _pendingState = (_pendingBidPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE; -} - void SimulationOwner::updateExpiry() { const uint64_t OWNERSHIP_LOCKOUT_EXPIRY = 200 * USECS_PER_MSEC; _expiry = usecTimestampNow() + OWNERSHIP_LOCKOUT_EXPIRY; } -bool SimulationOwner::pendingRelease(uint64_t timestamp) { - return _pendingBidPriority == 0 && _pendingState == PENDING_STATE_RELEASE && _pendingBidTimestamp >= timestamp; -} - -bool SimulationOwner::pendingTake(uint64_t timestamp) { - return _pendingBidPriority > 0 && _pendingState == PENDING_STATE_TAKE && _pendingBidTimestamp >= timestamp; -} - void SimulationOwner::clearCurrentOwner() { _id = QUuid(); _expiry = 0; diff --git a/libraries/entities/src/SimulationOwner.h b/libraries/entities/src/SimulationOwner.h index cc2069dcc8..353255728c 100644 --- a/libraries/entities/src/SimulationOwner.h +++ b/libraries/entities/src/SimulationOwner.h @@ -19,74 +19,75 @@ #include // HighFidelity uses a distributed physics simulation where multiple "participants" simulate portions -// of the same world. When portions overlap only one participant is allowed to be the authority for any -// particular object. For a simulated entity the authoritative participant is called the simulation "owner" and -// their duty is to send transform/velocity updates for the entity to the central entity-server. -// The entity-server relays updates to other participants who apply them as "state synchronization" -// to their own simulation. +// of the same domain. Even when portions overlap only one participant is allowed to be the current +// authority for any particular object's physical simulation. The authoritative participant is called the +// "simulation owner" and its duty is to send "state synchronization" (transform/velocity) updates for an +// entity to the entity-server. The entity-server relays updates to other participants who apply them to +// their own simulation. // // Participants acquire ownership by sending a "bid" to the entity-server. The bid is a properties update: -// { -// "simulationOwner": { "ownerID" : sessionID, "priority" : priority }, +// { "simulationOwner": { "ownerID" : sessionID, "priority" : priority }, // transform/velocity properties // } // -// The entity-server is the authority as to who owns what and may reject a bid. -// The rules for handling a bid are as follows: +// The entity-server is the authority as to who owns what and may reject a bid. The rules for handling a +// bid are as follows: // // (1) A bid may be refused for special ownership restrictions, but otherwise... // // (2) A bid at higher priority is accepted // -// (3) A bid at equal priority is rejected if receieved within a grace-period (200msec) -// of the last ownership transition, otherwise it is accepted +// (3) A bid at equal priority is accepted unless it was received shortly after (within 200msec) of the +// last ownership change. This to avoid rapid ownership transitions should multiple participants +// bid simultaneously. // -// (4) The current owner is the only participant allowed to clear ownership (entity-server can override). +// (4) The current owner is the only participant allowed to clear their ownership or adjust priority. // -// (5) The current owner is the only participant allowed to adjust priority (entity-server can override). -// -// (6) If an owner does not update the transform or velocities of an owned entity within some period +// (5) If an owner does not update the transform or velocities of an owned entity within some period // (5 seconds) then ownership is cleared and the entity's velocities are zeroed. This to handle // the case when an owner drops off the network. // // The priority of a participant's bid depends on how "interested" it is in the entity's motion. The rules // for bidding are as follows: // -// (7) A participant (almost) never assumes that a bid is accepted by the entity-server. It packs the -// simulation owner and priority as if they really did change but doesn't actually modify them -// locally. Thus, if the bid packet is lost the participant will re-send after some period. -// The participant only updates its knowledge of who owns what when it recieves an update from the -// entity-server. An exception is when the participant creates a moving entity: it assumes it starts -// off owning any moving entities it creates. +// (6) A participant (almost) never assumes its bid is accepted by the entity-server. It packs the +// simulation owner properties as if they really did change but doesn't actually modify them +// locally. Instead it waits to hear back from the entity-server for bid acceptance. If the entity +// remains unowned the participant will resend the bid (assuming the bid pakcet was lost). The +// exception is when the participant creates a moving entity: it assumes it starts off owning any +// moving entities it creates. // -// (8) When an unowned entity becomes active in the physics simulation the participant will -// start a timer and if the entity is still unowned after some period (0.5 seconds) -// it will bid at priority = VOLUNTEER (=2). The entity-server never grants ownership at VOLUNTEER +// (7) When an entity becomes active in the physics simulation but is not owned the participant will +// start a timer and if it is still unowned after expiry (0.5 seconds) the participant will +// bid at priority = VOLUNTEER (=2). The entity-server never grants ownership at VOLUNTEER // priority: when a VOLUNTEER bid is accepted the entity-server always promotes the priority to -// RECRUIT (VOLUNTEER + 1); this to avoid a race-condition which might rapidly transition ownership +// RECRUIT (=VOLUNTEER + 1); this to avoid a race-condition which might rapidly transition ownership // when multiple participants (with variable ping-times to the server) bid simultaneously for a // recently activated entity. // -// (9) When a participant changes an entity's transform/velocity it will bid at priority = POKE (=127) +// (8) When a participant's script changes an entity's transform/velocity the participant will bid at +// priority = POKE (=127) // -// (10) When an entity touches MyAvatar the participant it will bid at priority = POKE. +// (9) When an entity collides against MyAvatar the participant will bid at priority = POKE. // -// (11) When a participant grabs an entity it will bid at priority = GRAB (=128). +// (10) When a participant grabs an entity it will bid at priority = GRAB (=128). This to allow UserA +// to whack UserB with a "sword" without losing ownership, since UserB will bid at POKE. If UserB +// wants to contest for ownership they must also GRAB it. // -// (12) When entityA, locally owned at priority = N, collides with an unowned entityB the owner will -// also bid for entityB at priority = N-1 (or VOLUNTEER, whichever is larger). +// (11) When EntityA, locally owned at priority = N, collides with an unowned EntityB the owner will +// also bid for EntityB at priority = N-1 (or VOLUNTEER, whichever is larger). // -// (13) When an entity comes to rest and is deactivated in the physics simulation the owner will -// send an update to: clear their ownerhsip, set priority to zero, and set the object's -// velocities to be zero. As per a normal bid, the owner does NOT assume that its ownership -// has been cleared until it hears from the entity-server. This, if the packet is lost the -// owner will re-send after some period. +// (12) When an entity comes to rest and is deactivated in the physics simulation the owner will send +// an update to: clear their ownerhsip, set priority to zero, and set the entity's velocities to +// zero. As per a normal bid, the owner does NOT assume its ownership has been cleared until +// it hears back from the entity-server. Thus, if the packet is lost the owner will re-send after +// expiry. // -// (14) When an entity's ownership priority drops below VOLUNTEER other participants may bid for it -// immediately at priority = VOLUNTEER. +// (13) When an entity is still active but the owner no longer wants to own it, the owner will drop its +// priority to YIELD (=1, below VOLUNTEER) thereby signalling to other participants to bid for it. // -// (15) When an entity is still active but the owner no longer wants to own it, it will drop its priority -// to YIELD (=1, less than VOLUNTEER) thereby signalling to other participants to bid for it. +// (14) When an entity's ownership priority drops to YIELD (=1, below VOLUNTEER) other participants may +// bid for it immediately at VOLUNTEER. // const uint8_t YIELD_SIMULATION_PRIORITY = 1; const uint8_t VOLUNTEER_SIMULATION_PRIORITY = YIELD_SIMULATION_PRIORITY + 1; @@ -119,24 +120,18 @@ public: void clear(); void setPriority(uint8_t priority); - void promotePriority(uint8_t priority); // return true if id is changed bool setID(const QUuid& id); bool set(const QUuid& id, uint8_t priority); bool set(const SimulationOwner& owner); - void setPendingPriority(uint8_t priority, uint64_t timestamp); bool isNull() const { return _id.isNull(); } bool matchesValidID(const QUuid& id) const { return _id == id && !_id.isNull(); } void updateExpiry(); - bool hasExpired() const { return usecTimestampNow() > _expiry; } - uint8_t getPendingPriority() const { return _pendingBidPriority; } - bool pendingRelease(uint64_t timestamp); // return true if valid pending RELEASE - bool pendingTake(uint64_t timestamp); // return true if valid pending TAKE void clearCurrentOwner(); bool operator>=(uint8_t priority) const { return _priority >= priority; } @@ -153,10 +148,7 @@ public: private: QUuid _id; // owner uint64_t _expiry; // time when ownership can transition at equal priority - uint64_t _pendingBidTimestamp; // time when pending bid was set uint8_t _priority; // priority of current owner - uint8_t _pendingBidPriority; // priority at which we'd like to own it - uint8_t _pendingState; // NOTHING, TAKE, or RELEASE }; diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 56e12e66d9..f130995bb5 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -127,17 +127,55 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits } bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, - BoxFace& face, glm::vec3& surfaceNormal, - QVariantMap& extraInfo, bool precisionPicking) const { + OctreeElementPointer& element, float& distance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { glm::vec3 dimensions = getScaledDimensions(); glm::vec2 xyDimensions(dimensions.x, dimensions.y); glm::quat rotation = getWorldOrientation(); - glm::vec3 position = getWorldPosition() + rotation * - (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); - // FIXME - should set face and surfaceNormal - return findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance); + if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { + glm::vec3 forward = rotation * Vectors::FRONT; + if (glm::dot(forward, direction) > 0.0f) { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } else { + face = MIN_Z_FACE; + surfaceNormal = forward; + } + return true; + } + return false; +} + +bool TextEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + glm::vec3 dimensions = getScaledDimensions(); + glm::vec2 xyDimensions(dimensions.x, dimensions.y); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - position); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + + if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { + float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + glm::vec3 forward = rotation * Vectors::FRONT; + if (localIntersectionVelocityZ > 0.0f) { + face = MIN_Z_FACE; + surfaceNormal = forward; + } else { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } + return true; + } + return false; } void TextEntityItem::setText(const QString& value) { diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index efdc84bcd8..4ce5ef3297 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -45,11 +45,15 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; static const QString DEFAULT_TEXT; void setText(const QString& value); diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index f3159ba3f8..0070eb538c 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -113,8 +113,44 @@ bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const g glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { - surfaceNormal = rotation * Vectors::UNIT_Z; - face = glm::dot(surfaceNormal, direction) > 0 ? MIN_Z_FACE : MAX_Z_FACE; + glm::vec3 forward = rotation * Vectors::FRONT; + if (glm::dot(forward, direction) > 0.0f) { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } else { + face = MIN_Z_FACE; + surfaceNormal = forward; + } + return true; + } else { + return false; + } +} + +bool WebEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + glm::vec3 dimensions = getScaledDimensions(); + glm::vec2 xyDimensions(dimensions.x, dimensions.y); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - position); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + + if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { + float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + glm::vec3 forward = rotation * Vectors::FRONT; + if (localIntersectionVelocityZ > 0.0f) { + face = MIN_Z_FACE; + surfaceNormal = forward; + } else { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } return true; } else { return false; diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index 1179f22ded..2fa2033445 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -44,11 +44,15 @@ public: EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; virtual void setSourceUrl(const QString& value); QString getSourceUrl() const; diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 3a6095b89f..f2550e5d3c 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -294,10 +294,16 @@ void ZoneEntityItem::setCompoundShapeURL(const QString& url) { } bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const { + return _zonesArePickable; +} +bool ZoneEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { return _zonesArePickable; } diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 3a9c7cb1e6..0aaa32a57a 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -102,11 +102,15 @@ public: void resetRenderingPropertiesChanged(); - virtual bool supportsDetailedRayIntersection() const override { return true; } + virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; virtual void debugDump() const override; diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index b830313723..d0efed3df4 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1816,7 +1816,6 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS } } - float clusterScale = extractUniformScale(fbxCluster.inverseBindMatrix); glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform; ShapeVertices& points = shapeVertices.at(jointIndex); @@ -1832,7 +1831,7 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS if (weight >= EXPANSION_WEIGHT_THRESHOLD) { // transform to joint-frame and save for later const glm::mat4 vertexTransform = meshToJoint * glm::translate(extracted.mesh.vertices.at(newIndex)); - points.push_back(extractTranslation(vertexTransform) * clusterScale); + points.push_back(extractTranslation(vertexTransform)); } // look for an unused slot in the weights vector @@ -1886,12 +1885,11 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS FBXJoint& joint = geometry.joints[jointIndex]; // transform cluster vertices to joint-frame and save for later - float clusterScale = extractUniformScale(firstFBXCluster.inverseBindMatrix); glm::mat4 meshToJoint = glm::inverse(joint.bindTransform) * modelTransform; ShapeVertices& points = shapeVertices.at(jointIndex); foreach (const glm::vec3& vertex, extracted.mesh.vertices) { const glm::mat4 vertexTransform = meshToJoint * glm::translate(vertex); - points.push_back(extractTranslation(vertexTransform) * clusterScale); + points.push_back(extractTranslation(vertexTransform)); } // Apply geometric offset, if present, by transforming the vertices directly diff --git a/libraries/fbx/src/GLTFReader.cpp b/libraries/fbx/src/GLTFReader.cpp index 1fa4b3873e..300e6f4846 100644 --- a/libraries/fbx/src/GLTFReader.cpp +++ b/libraries/fbx/src/GLTFReader.cpp @@ -21,14 +21,17 @@ #include #include + #include #include #include +#include #include #include #include +#include #include "FBXReader.h" @@ -786,13 +789,18 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { QVector raw_vertices; QVector raw_normals; - addArrayOfType(indicesBuffer.blob, + bool success = addArrayOfType(indicesBuffer.blob, indicesBufferview.byteOffset + indicesAccBoffset, - indicesBufferview.byteLength, + indicesAccessor.count, part.triangleIndices, indicesAccessor.type, indicesAccessor.componentType); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF INDICES data for model " << _url; + continue; + } + QList keys = primitive.attributes.values.keys(); foreach(auto &key, keys) { @@ -805,44 +813,60 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { int accBoffset = accessor.defined["byteOffset"] ? accessor.byteOffset : 0; if (key == "POSITION") { QVector vertices; - addArrayOfType(buffer.blob, + success = addArrayOfType(buffer.blob, bufferview.byteOffset + accBoffset, - bufferview.byteLength, vertices, + accessor.count, vertices, accessor.type, accessor.componentType); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF POSITION data for model " << _url; + continue; + } for (int n = 0; n < vertices.size(); n = n + 3) { mesh.vertices.push_back(glm::vec3(vertices[n], vertices[n + 1], vertices[n + 2])); } } else if (key == "NORMAL") { QVector normals; - addArrayOfType(buffer.blob, + success = addArrayOfType(buffer.blob, bufferview.byteOffset + accBoffset, - bufferview.byteLength, + accessor.count, normals, accessor.type, accessor.componentType); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF NORMAL data for model " << _url; + continue; + } for (int n = 0; n < normals.size(); n = n + 3) { mesh.normals.push_back(glm::vec3(normals[n], normals[n + 1], normals[n + 2])); } } else if (key == "TEXCOORD_0") { QVector texcoords; - addArrayOfType(buffer.blob, + success = addArrayOfType(buffer.blob, bufferview.byteOffset + accBoffset, - bufferview.byteLength, + accessor.count, texcoords, accessor.type, accessor.componentType); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_0 data for model " << _url; + continue; + } for (int n = 0; n < texcoords.size(); n = n + 2) { mesh.texCoords.push_back(glm::vec2(texcoords[n], texcoords[n + 1])); } } else if (key == "TEXCOORD_1") { QVector texcoords; - addArrayOfType(buffer.blob, + success = addArrayOfType(buffer.blob, bufferview.byteOffset + accBoffset, - bufferview.byteLength, + accessor.count, texcoords, accessor.type, accessor.componentType); + if (!success) { + qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_1 data for model " << _url; + continue; + } for (int n = 0; n < texcoords.size(); n = n + 2) { mesh.texCoords1.push_back(glm::vec2(texcoords[n], texcoords[n + 1])); } @@ -888,8 +912,16 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping, const QUrl& url, bool loadLightmaps, float lightmapLevel) { + _url = url; + // Normalize url for local files + QUrl normalizeUrl = DependencyManager::get()->normalizeURL(url); + if (normalizeUrl.scheme().isEmpty() || (normalizeUrl.scheme() == "file")) { + QString localFileName = PathUtils::expandToLocalDataAbsolutePath(normalizeUrl).toLocalFile(); + _url = QUrl(QFileInfo(localFileName).absoluteFilePath()); + } + parseGLTF(model); //_file.dump(); FBXGeometry* geometryPtr = new FBXGeometry(); @@ -904,6 +936,7 @@ FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping bool GLTFReader::readBinary(const QString& url, QByteArray& outdata) { QUrl binaryUrl = _url.resolved(QUrl(url).fileName()); + qCDebug(modelformat) << "binaryUrl: " << binaryUrl << " OriginalUrl: " << _url; bool success; std::tie(success, outdata) = requestData(binaryUrl); @@ -1018,13 +1051,12 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia fbxmat.opacityTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.baseColorTexture]); fbxmat.albedoTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.baseColorTexture]); fbxmat.useAlbedoMap = true; - fbxmat.metallicTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.baseColorTexture]); - fbxmat.useMetallicMap = true; } if (material.pbrMetallicRoughness.defined["metallicRoughnessTexture"]) { fbxmat.roughnessTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.metallicRoughnessTexture]); fbxmat.useRoughnessMap = true; - + fbxmat.metallicTexture = getFBXTexture(_file.textures[material.pbrMetallicRoughness.metallicRoughnessTexture]); + fbxmat.useMetallicMap = true; } if (material.pbrMetallicRoughness.defined["roughnessFactor"]) { fbxmat._material->setRoughness(material.pbrMetallicRoughness.roughnessFactor); @@ -1043,7 +1075,7 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia } template -bool GLTFReader::readArray(const QByteArray& bin, int byteOffset, int byteLength, +bool GLTFReader::readArray(const QByteArray& bin, int byteOffset, int count, QVector& outarray, int accessorType) { QDataStream blobstream(bin); @@ -1051,142 +1083,77 @@ bool GLTFReader::readArray(const QByteArray& bin, int byteOffset, int byteLength blobstream.setVersion(QDataStream::Qt_5_9); blobstream.setFloatingPointPrecision(QDataStream::FloatingPointPrecision::SinglePrecision); - int vsize = byteLength / sizeof(T); - - qCDebug(modelformat) << "size1: " << vsize; + qCDebug(modelformat) << "size1: " << count; int dataskipped = blobstream.skipRawData(byteOffset); qCDebug(modelformat) << "dataskipped: " << dataskipped; - - while (outarray.size() < vsize) { - - T value1, value2, value3, value4, - value5, value6, value7, value8, - value9, value10, value11, value12, - value13, value14, value15, value16; - - if (accessorType == GLTFAccessorType::SCALAR) { - - blobstream >> value1; - - outarray.push_back(value1); - } else if (accessorType == GLTFAccessorType::VEC2) { - - blobstream >> value1; - blobstream >> value2; - - outarray.push_back(value1); - outarray.push_back(value2); - } else if (accessorType == GLTFAccessorType::VEC3) { - - blobstream >> value1; - blobstream >> value2; - blobstream >> value3; - - outarray.push_back(value1); - outarray.push_back(value2); - outarray.push_back(value3); - } else if (accessorType == GLTFAccessorType::VEC4 || accessorType == GLTFAccessorType::MAT2) { - - blobstream >> value1; - blobstream >> value2; - blobstream >> value3; - blobstream >> value4; - - outarray.push_back(value1); - outarray.push_back(value2); - outarray.push_back(value3); - outarray.push_back(value4); - } else if (accessorType == GLTFAccessorType::MAT3) { - - blobstream >> value1; - blobstream >> value2; - blobstream >> value3; - blobstream >> value4; - blobstream >> value5; - blobstream >> value6; - blobstream >> value7; - blobstream >> value8; - blobstream >> value9; - - outarray.push_back(value1); - outarray.push_back(value2); - outarray.push_back(value3); - outarray.push_back(value4); - outarray.push_back(value5); - outarray.push_back(value6); - outarray.push_back(value7); - outarray.push_back(value8); - outarray.push_back(value9); - } else if (accessorType == GLTFAccessorType::MAT4) { - - blobstream >> value1; - blobstream >> value2; - blobstream >> value3; - blobstream >> value4; - blobstream >> value5; - blobstream >> value6; - blobstream >> value7; - blobstream >> value8; - blobstream >> value9; - blobstream >> value10; - blobstream >> value11; - blobstream >> value12; - blobstream >> value13; - blobstream >> value14; - blobstream >> value15; - blobstream >> value16; - - outarray.push_back(value1); - outarray.push_back(value2); - outarray.push_back(value3); - outarray.push_back(value4); - outarray.push_back(value5); - outarray.push_back(value6); - outarray.push_back(value7); - outarray.push_back(value8); - outarray.push_back(value9); - outarray.push_back(value10); - outarray.push_back(value11); - outarray.push_back(value12); - outarray.push_back(value13); - outarray.push_back(value14); - outarray.push_back(value15); - outarray.push_back(value16); - + int bufferCount = 0; + switch (accessorType) { + case GLTFAccessorType::SCALAR: + bufferCount = 1; + break; + case GLTFAccessorType::VEC2: + bufferCount = 2; + break; + case GLTFAccessorType::VEC3: + bufferCount = 3; + break; + case GLTFAccessorType::VEC4: + bufferCount = 4; + break; + case GLTFAccessorType::MAT2: + bufferCount = 4; + break; + case GLTFAccessorType::MAT3: + bufferCount = 9; + break; + case GLTFAccessorType::MAT4: + bufferCount = 16; + break; + default: + qWarning(modelformat) << "Unknown accessorType: " << accessorType; + blobstream.unsetDevice(); + return false; + } + for (int i = 0; i < count; i++) { + for (int j = 0; j < bufferCount; j++) { + if (!blobstream.atEnd()) { + T value; + blobstream >> value; + outarray.push_back(value); + } else { + blobstream.unsetDevice(); + return false; + } } } + blobstream.unsetDevice(); return true; } template -bool GLTFReader::addArrayOfType(const QByteArray& bin, int byteOffset, int byteLength, +bool GLTFReader::addArrayOfType(const QByteArray& bin, int byteOffset, int count, QVector& outarray, int accessorType, int componentType) { switch (componentType) { case GLTFAccessorComponentType::BYTE: {} case GLTFAccessorComponentType::UNSIGNED_BYTE: { - readArray(bin, byteOffset, byteLength, outarray, accessorType); - break; + return readArray(bin, byteOffset, count, outarray, accessorType); } case GLTFAccessorComponentType::SHORT: { - readArray(bin, byteOffset, byteLength, outarray, accessorType); - break; + return readArray(bin, byteOffset, count, outarray, accessorType); } case GLTFAccessorComponentType::UNSIGNED_INT: { - readArray(bin, byteOffset, byteLength, outarray, accessorType); - break; + return readArray(bin, byteOffset, count, outarray, accessorType); } case GLTFAccessorComponentType::UNSIGNED_SHORT: { - readArray(bin, byteOffset, byteLength, outarray, accessorType); - break; + return readArray(bin, byteOffset, count, outarray, accessorType); } case GLTFAccessorComponentType::FLOAT: { - readArray(bin, byteOffset, byteLength, outarray, accessorType); - break; + return readArray(bin, byteOffset, count, outarray, accessorType); } } - return true; + return false; } void GLTFReader::retriangulate(const QVector& inIndices, const QVector& in_vertices, diff --git a/libraries/fbx/src/GLTFReader.h b/libraries/fbx/src/GLTFReader.h index 28c1d8282f..2183256b87 100644 --- a/libraries/fbx/src/GLTFReader.h +++ b/libraries/fbx/src/GLTFReader.h @@ -762,11 +762,11 @@ private: bool readBinary(const QString& url, QByteArray& outdata); template - bool readArray(const QByteArray& bin, int byteOffset, int byteLength, + bool readArray(const QByteArray& bin, int byteOffset, int count, QVector& outarray, int accessorType); template - bool addArrayOfType(const QByteArray& bin, int byteOffset, int byteLength, + bool addArrayOfType(const QByteArray& bin, int byteOffset, int count, QVector& outarray, int accessorType, int componentType); void retriangulate(const QVector& in_indices, const QVector& in_vertices, diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h index 622c8f1081..6faccb1527 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackend.h @@ -278,6 +278,7 @@ protected: struct InputStageState { bool _invalidFormat { true }; + bool _lastUpdateStereoState{ false }; bool _hadColorAttribute{ true }; Stream::FormatPointer _format; std::string _formatKey; diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp index 4145eb6061..77e1f90f66 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendInput.cpp @@ -156,6 +156,14 @@ void GLBackend::do_setIndirectBuffer(const Batch& batch, size_t paramOffset) { } void GLBackend::updateInput() { + bool isStereoNow = isStereo(); + // track stereo state change potentially happening wihtout changing the input format + // this is a rare case requesting to invalid the format +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + _input._invalidFormat |= (isStereoNow != _input._lastUpdateStereoState); +#endif + _input._lastUpdateStereoState = isStereoNow; + if (_input._invalidFormat) { InputStageState::ActivationCache newActivation; @@ -213,7 +221,7 @@ void GLBackend::updateInput() { (void)CHECK_GL_ERROR(); } #ifdef GPU_STEREO_DRAWCALL_INSTANCED - glVertexBindingDivisor(bufferChannelNum, frequency * (isStereo() ? 2 : 1)); + glVertexBindingDivisor(bufferChannelNum, frequency * (isStereoNow ? 2 : 1)); #else glVertexBindingDivisor(bufferChannelNum, frequency); #endif diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp index 91f1d8bb8c..adea3292e1 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp @@ -48,12 +48,12 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { return; } - // check the program cache - // pick the program version - // check the program cache - // pick the program version + // check the program cache + // pick the program version + // check the program cache + // pick the program version #ifdef GPU_STEREO_CAMERA_BUFFER - GLuint glprogram = pipelineObject->_program->getProgram((GLShader::Version) isStereo()); + GLuint glprogram = pipelineObject->_program->getProgram((GLShader::Version)isStereo()); #else GLuint glprogram = pipelineObject->_program->getProgram(); #endif @@ -85,10 +85,11 @@ void GLBackend::do_setPipeline(const Batch& batch, size_t paramOffset) { } else { cameraCorrectionBuffer = syncGPUObject(*_pipeline._cameraCorrectionBufferIdentity._buffer); } + // Invalidate uniform buffer cache slot + _uniform._buffers[_pipeline._cameraCorrectionLocation].reset(); glBindBufferRange(GL_UNIFORM_BUFFER, _pipeline._cameraCorrectionLocation, cameraCorrectionBuffer->_id, 0, sizeof(CameraCorrection)); - } - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._invalidProgram = false; } } @@ -97,7 +98,7 @@ void GLBackend::updatePipeline() { if (_pipeline._invalidProgram) { // doing it here is aproblem for calls to glUniform.... so will do it on assing... glUseProgram(_pipeline._program); - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); _pipeline._invalidProgram = false; } @@ -106,12 +107,12 @@ void GLBackend::updatePipeline() { // first reset to default what should be // the fields which were not to default and are default now resetPipelineState(_pipeline._state->_signature); - + // Update the signature cache with what's going to be touched _pipeline._stateSignatureCache |= _pipeline._state->_signature; // And perform - for (auto command: _pipeline._state->_commands) { + for (auto command : _pipeline._state->_commands) { command->run(this); } } else { @@ -142,8 +143,8 @@ void GLBackend::releaseUniformBuffer(uint32_t slot) { if (buf) { auto* object = Backend::getGPUObject(*buf); if (object) { - glBindBufferBase(GL_UNIFORM_BUFFER, slot, 0); // RELEASE - (void) CHECK_GL_ERROR(); + glBindBufferBase(GL_UNIFORM_BUFFER, slot, 0); // RELEASE + (void)CHECK_GL_ERROR(); } buf.reset(); } @@ -157,8 +158,9 @@ void GLBackend::resetUniformStage() { void GLBackend::do_setUniformBuffer(const Batch& batch, size_t paramOffset) { GLuint slot = batch._params[paramOffset + 3]._uint; - if (slot >(GLuint)MAX_NUM_UNIFORM_BUFFERS) { - qCDebug(gpugllogging) << "GLBackend::do_setUniformBuffer: Trying to set a uniform Buffer at slot #" << slot << " which doesn't exist. MaxNumUniformBuffers = " << getMaxNumUniformBuffers(); + if (slot > (GLuint)MAX_NUM_UNIFORM_BUFFERS) { + qCDebug(gpugllogging) << "GLBackend::do_setUniformBuffer: Trying to set a uniform Buffer at slot #" << slot + << " which doesn't exist. MaxNumUniformBuffers = " << getMaxNumUniformBuffers(); return; } BufferPointer uniformBuffer = batch._buffers.get(batch._params[paramOffset + 2]._uint); @@ -169,7 +171,7 @@ void GLBackend::do_setUniformBuffer(const Batch& batch, size_t paramOffset) { releaseUniformBuffer(slot); return; } - + // check cache before thinking if (_uniform._buffers[slot] == uniformBuffer) { return; @@ -181,7 +183,7 @@ void GLBackend::do_setUniformBuffer(const Batch& batch, size_t paramOffset) { glBindBufferRange(GL_UNIFORM_BUFFER, slot, object->_buffer, rangeStart, rangeSize); _uniform._buffers[slot] = uniformBuffer; - (void) CHECK_GL_ERROR(); + (void)CHECK_GL_ERROR(); } else { releaseUniformBuffer(slot); return; @@ -195,27 +197,28 @@ void GLBackend::releaseResourceTexture(uint32_t slot) { if (object) { GLuint target = object->_target; glActiveTexture(GL_TEXTURE0 + slot); - glBindTexture(target, 0); // RELEASE - (void) CHECK_GL_ERROR(); + glBindTexture(target, 0); // RELEASE + (void)CHECK_GL_ERROR(); } tex.reset(); } } void GLBackend::resetResourceStage() { - for (uint32_t i = 0; i < _resource._buffers.size(); i++) { + uint32_t i; + for (i = 0; i < _resource._buffers.size(); i++) { releaseResourceBuffer(i); } - for (uint32_t i = 0; i < _resource._textures.size(); i++) { + for (i = 0; i < _resource._textures.size(); i++) { releaseResourceTexture(i); } } - void GLBackend::do_setResourceBuffer(const Batch& batch, size_t paramOffset) { GLuint slot = batch._params[paramOffset + 1]._uint; if (slot >= (GLuint)MAX_NUM_RESOURCE_BUFFERS) { - qCDebug(gpugllogging) << "GLBackend::do_setResourceBuffer: Trying to set a resource Buffer at slot #" << slot << " which doesn't exist. MaxNumResourceBuffers = " << getMaxNumResourceBuffers(); + qCDebug(gpugllogging) << "GLBackend::do_setResourceBuffer: Trying to set a resource Buffer at slot #" << slot + << " which doesn't exist. MaxNumResourceBuffers = " << getMaxNumResourceBuffers(); return; } @@ -236,7 +239,7 @@ void GLBackend::do_setResourceBuffer(const Batch& batch, size_t paramOffset) { // If successful bind then cache it if (bindResourceBuffer(slot, resourceBuffer)) { _resource._buffers[slot] = resourceBuffer; - } else { // else clear slot and cache + } else { // else clear slot and cache releaseResourceBuffer(slot); return; } @@ -244,8 +247,9 @@ void GLBackend::do_setResourceBuffer(const Batch& batch, size_t paramOffset) { void GLBackend::do_setResourceTexture(const Batch& batch, size_t paramOffset) { GLuint slot = batch._params[paramOffset + 1]._uint; - if (slot >= (GLuint) MAX_NUM_RESOURCE_TEXTURES) { - qCDebug(gpugllogging) << "GLBackend::do_setResourceTexture: Trying to set a resource Texture at slot #" << slot << " which doesn't exist. MaxNumResourceTextures = " << getMaxNumResourceTextures(); + if (slot >= (GLuint)MAX_NUM_RESOURCE_TEXTURES) { + qCDebug(gpugllogging) << "GLBackend::do_setResourceTexture: Trying to set a resource Texture at slot #" << slot + << " which doesn't exist. MaxNumResourceTextures = " << getMaxNumResourceTextures(); return; } @@ -264,11 +268,14 @@ void GLBackend::bindResourceTexture(uint32_t slot, const TexturePointer& resourc void GLBackend::do_setResourceFramebufferSwapChainTexture(const Batch& batch, size_t paramOffset) { GLuint slot = batch._params[paramOffset + 1]._uint; if (slot >= (GLuint)MAX_NUM_RESOURCE_TEXTURES) { - qCDebug(gpugllogging) << "GLBackend::do_setResourceFramebufferSwapChainTexture: Trying to set a resource Texture at slot #" << slot << " which doesn't exist. MaxNumResourceTextures = " << getMaxNumResourceTextures(); + qCDebug(gpugllogging) + << "GLBackend::do_setResourceFramebufferSwapChainTexture: Trying to set a resource Texture at slot #" << slot + << " which doesn't exist. MaxNumResourceTextures = " << getMaxNumResourceTextures(); return; } - auto swapChain = std::static_pointer_cast(batch._swapChains.get(batch._params[paramOffset + 0]._uint)); + auto swapChain = + std::static_pointer_cast(batch._swapChains.get(batch._params[paramOffset + 0]._uint)); if (!swapChain) { releaseResourceTexture(slot); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp index 35d292cd46..ed356acf68 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp @@ -168,7 +168,9 @@ void GLBackend::TransformStageState::update(size_t commandIndex, const StereoSta void GLBackend::TransformStageState::bindCurrentCamera(int eye) const { if (_currentCameraOffset != INVALID_OFFSET) { - glBindBufferRange(GL_UNIFORM_BUFFER, TRANSFORM_CAMERA_SLOT, _cameraBuffer, _currentCameraOffset + eye * _cameraUboSize, sizeof(CameraBufferElement)); + static_assert(TRANSFORM_CAMERA_SLOT >= MAX_NUM_UNIFORM_BUFFERS, "TransformCamera may overlap pipeline uniform buffer slots. Invalidate uniform buffer slot cache for safety (call _uniform._buffers[TRANSFORM_CAMERA_SLOT].reset())."); + glBindBufferRange(GL_UNIFORM_BUFFER, TRANSFORM_CAMERA_SLOT, _cameraBuffer, _currentCameraOffset + eye * _cameraUboSize, + sizeof(CameraBufferElement)); } } diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLTexture.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLTexture.cpp index e11f8f01c7..e9494a1271 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLTexture.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLTexture.cpp @@ -59,7 +59,11 @@ const size_t GLVariableAllocationSupport::MAX_BUFFER_SIZE = MAX_TRANSFER_SIZE; GLenum GLTexture::getGLTextureType(const Texture& texture) { switch (texture.getType()) { case Texture::TEX_2D: - return GL_TEXTURE_2D; + if (!texture.isArray()) { + return GL_TEXTURE_2D; + } else { + return GL_TEXTURE_2D_ARRAY; + } break; case Texture::TEX_CUBE: @@ -77,6 +81,7 @@ GLenum GLTexture::getGLTextureType(const Texture& texture) { uint8_t GLTexture::getFaceCount(GLenum target) { switch (target) { case GL_TEXTURE_2D: + case GL_TEXTURE_2D_ARRAY: return TEXTURE_2D_NUM_FACES; case GL_TEXTURE_CUBE_MAP: return TEXTURE_CUBE_NUM_FACES; @@ -86,17 +91,22 @@ uint8_t GLTexture::getFaceCount(GLenum target) { } } const std::vector& GLTexture::getFaceTargets(GLenum target) { - static std::vector cubeFaceTargets { + static const std::vector cubeFaceTargets { GL_TEXTURE_CUBE_MAP_POSITIVE_X, GL_TEXTURE_CUBE_MAP_NEGATIVE_X, GL_TEXTURE_CUBE_MAP_POSITIVE_Y, GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, GL_TEXTURE_CUBE_MAP_POSITIVE_Z, GL_TEXTURE_CUBE_MAP_NEGATIVE_Z }; - static std::vector faceTargets { + static const std::vector faceTargets { GL_TEXTURE_2D }; + static const std::vector arrayFaceTargets{ + GL_TEXTURE_2D_ARRAY + }; switch (target) { case GL_TEXTURE_2D: return faceTargets; + case GL_TEXTURE_2D_ARRAY: + return arrayFaceTargets; case GL_TEXTURE_CUBE_MAP: return cubeFaceTargets; default: diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp index 42bd56e6e4..9dcb08f0b7 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendInput.cpp @@ -25,6 +25,14 @@ void GL41Backend::resetInputStage() { } void GL41Backend::updateInput() { + bool isStereoNow = isStereo(); + // track stereo state change potentially happening wihtout changing the input format + // this is a rare case requesting to invalid the format +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + _input._invalidFormat |= (isStereoNow != _input._lastUpdateStereoState); +#endif + _input._lastUpdateStereoState = isStereoNow; + if (_input._invalidFormat || _input._invalidBuffers.any()) { if (_input._invalidFormat) { @@ -111,7 +119,7 @@ void GL41Backend::updateInput() { reinterpret_cast(pointer + perLocationStride * (GLuint)locNum)); } #ifdef GPU_STEREO_DRAWCALL_INSTANCED - glVertexAttribDivisor(slot + (GLuint)locNum, attrib._frequency * (isStereo() ? 2 : 1)); + glVertexAttribDivisor(slot + (GLuint)locNum, attrib._frequency * (isStereoNow ? 2 : 1)); #else glVertexAttribDivisor(slot + (GLuint)locNum, attrib._frequency); #endif diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp index a5ef2d92e1..1d512103bd 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendOutput.cpp @@ -64,7 +64,12 @@ public: } if (gltexture) { - glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, gltexture->_texture, 0); + if (gltexture->_target == GL_TEXTURE_2D) { + glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, gltexture->_texture, 0); + } else { + glFramebufferTextureLayer(GL_FRAMEBUFFER, colorAttachments[unit], gltexture->_texture, 0, + b._subresource); + } _colorBuffers.push_back(colorAttachments[unit]); } else { glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, 0, 0); @@ -91,7 +96,12 @@ public: } if (gltexture) { - glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0); + if (gltexture->_target == GL_TEXTURE_2D) { + glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0); + } else { + glFramebufferTextureLayer(GL_FRAMEBUFFER, attachement, gltexture->_texture, 0, + _gpuObject.getDepthStencilBufferSubresource()); + } } else { glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, 0, 0); } diff --git a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp index a255cc5878..97b1a96a1d 100644 --- a/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl41/GL41BackendTexture.cpp @@ -182,7 +182,7 @@ void GL41Texture::syncSampler() const { glTexParameteri(_target, GL_TEXTURE_MAG_FILTER, fm.magFilter); if (sampler.doComparison()) { - glTexParameteri(_target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE_ARB); + glTexParameteri(_target, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); glTexParameteri(_target, GL_TEXTURE_COMPARE_FUNC, COMPARISON_TO_GL[sampler.getComparisonFunction()]); } else { glTexParameteri(_target, GL_TEXTURE_COMPARE_MODE, GL_NONE); @@ -197,7 +197,7 @@ void GL41Texture::syncSampler() const { glTexParameterf(_target, GL_TEXTURE_MIN_LOD, (float)sampler.getMinMip()); glTexParameterf(_target, GL_TEXTURE_MAX_LOD, (sampler.getMaxMip() == Sampler::MAX_MIP_LEVEL ? 1000.f : sampler.getMaxMip())); - glTexParameterf(_target, GL_TEXTURE_MAX_ANISOTROPY_EXT, sampler.getMaxAnisotropy()); + glTexParameterf(_target, GL_TEXTURE_MAX_ANISOTROPY, sampler.getMaxAnisotropy()); } using GL41FixedAllocationTexture = GL41Backend::GL41FixedAllocationTexture; @@ -215,12 +215,19 @@ GL41FixedAllocationTexture::~GL41FixedAllocationTexture() { void GL41FixedAllocationTexture::allocateStorage() const { const GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); const auto numMips = _gpuObject.getNumMips(); + const auto numSlices = _gpuObject.getNumSlices(); // glTextureStorage2D(_id, mips, texelFormat.internalFormat, dimensions.x, dimensions.y); for (GLint level = 0; level < numMips; level++) { Vec3u dimensions = _gpuObject.evalMipDimensions(level); for (GLenum target : getFaceTargets(_target)) { - glTexImage2D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, texelFormat.type, nullptr); + if (!_gpuObject.isArray()) { + glTexImage2D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, + texelFormat.type, nullptr); + } else { + glTexImage3D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, numSlices, 0, + texelFormat.format, texelFormat.type, nullptr); + } } } diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp index 34bf6774f7..cc3e609bda 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendInput.cpp @@ -27,6 +27,14 @@ void GL45Backend::resetInputStage() { } void GL45Backend::updateInput() { + bool isStereoNow = isStereo(); + // track stereo state change potentially happening wihtout changing the input format + // this is a rare case requesting to invalid the format +#ifdef GPU_STEREO_DRAWCALL_INSTANCED + _input._invalidFormat |= (isStereoNow != _input._lastUpdateStereoState); +#endif + _input._lastUpdateStereoState = isStereoNow; + if (_input._invalidFormat) { InputStageState::ActivationCache newActivation; @@ -84,7 +92,7 @@ void GL45Backend::updateInput() { (void)CHECK_GL_ERROR(); } #ifdef GPU_STEREO_DRAWCALL_INSTANCED - glVertexBindingDivisor(bufferChannelNum, frequency * (isStereo() ? 2 : 1)); + glVertexBindingDivisor(bufferChannelNum, frequency * (isStereoNow ? 2 : 1)); #else glVertexBindingDivisor(bufferChannelNum, frequency); #endif diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp index ca53d6c624..86332558e3 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendOutput.cpp @@ -60,7 +60,11 @@ public: } if (gltexture) { - glNamedFramebufferTexture(_id, colorAttachments[unit], gltexture->_texture, 0); + if (gltexture->_target == GL_TEXTURE_2D) { + glNamedFramebufferTexture(_id, colorAttachments[unit], gltexture->_texture, 0); + } else { + glNamedFramebufferTextureLayer(_id, colorAttachments[unit], gltexture->_texture, 0, b._subresource); + } _colorBuffers.push_back(colorAttachments[unit]); } else { glNamedFramebufferTexture(_id, colorAttachments[unit], 0, 0); @@ -87,14 +91,18 @@ public: } if (gltexture) { - glNamedFramebufferTexture(_id, attachement, gltexture->_texture, 0); + if (gltexture->_target == GL_TEXTURE_2D) { + glNamedFramebufferTexture(_id, attachement, gltexture->_texture, 0); + } else { + glNamedFramebufferTextureLayer(_id, attachement, gltexture->_texture, 0, + _gpuObject.getDepthStencilBufferSubresource()); + } } else { glNamedFramebufferTexture(_id, attachement, 0, 0); } _depthStamp = _gpuObject.getDepthStamp(); } - // Last but not least, define where we draw if (!_colorBuffers.empty()) { glNamedFramebufferDrawBuffers(_id, (GLsizei)_colorBuffers.size(), _colorBuffers.data()); diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp index a95426a06d..f1f388d501 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendShader.cpp @@ -72,7 +72,7 @@ int GL45Backend::makeResourceBufferSlots(const ShaderObject& shaderProgram, cons auto requestedBinding = slotBindings.find(info.name); if (requestedBinding != slotBindings.end()) { info.binding = (*requestedBinding)._location; - glUniformBlockBinding(glprogram, info.index, info.binding); + glShaderStorageBlockBinding(glprogram, info.index, info.binding); resourceBufferSlotMap[info.binding] = info.index; } } @@ -88,7 +88,7 @@ int GL45Backend::makeResourceBufferSlots(const ShaderObject& shaderProgram, cons auto slotIt = std::find_if(resourceBufferSlotMap.begin(), resourceBufferSlotMap.end(), GLBackend::isUnusedSlot); if (slotIt != resourceBufferSlotMap.end()) { info.binding = slotIt - resourceBufferSlotMap.begin(); - glUniformBlockBinding(glprogram, info.index, info.binding); + glShaderStorageBlockBinding(glprogram, info.index, info.binding); } else { // This should never happen, an active ssbo cannot find an available slot among the max available?! info.binding = -1; diff --git a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp index fda7ac22dd..e02f12819e 100644 --- a/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp +++ b/libraries/gpu-gl/src/gpu/gl45/GL45BackendTexture.cpp @@ -152,7 +152,7 @@ public: glSamplerParameteri(result, GL_TEXTURE_MIN_FILTER, fm.minFilter); glSamplerParameteri(result, GL_TEXTURE_MAG_FILTER, fm.magFilter); if (sampler.doComparison()) { - glSamplerParameteri(result, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE_ARB); + glSamplerParameteri(result, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); glSamplerParameteri(result, GL_TEXTURE_COMPARE_FUNC, COMPARISON_TO_GL[sampler.getComparisonFunction()]); } else { glSamplerParameteri(result, GL_TEXTURE_COMPARE_MODE, GL_NONE); @@ -341,7 +341,7 @@ void GL45Texture::syncSampler() const { glTextureParameteri(_id, GL_TEXTURE_MAG_FILTER, fm.magFilter); if (sampler.doComparison()) { - glTextureParameteri(_id, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE_ARB); + glTextureParameteri(_id, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); glTextureParameteri(_id, GL_TEXTURE_COMPARE_FUNC, COMPARISON_TO_GL[sampler.getComparisonFunction()]); } else { glTextureParameteri(_id, GL_TEXTURE_COMPARE_MODE, GL_NONE); @@ -374,8 +374,13 @@ void GL45FixedAllocationTexture::allocateStorage() const { const GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); const auto dimensions = _gpuObject.getDimensions(); const auto mips = _gpuObject.getNumMips(); + const auto numSlices = _gpuObject.getNumSlices(); - glTextureStorage2D(_id, mips, texelFormat.internalFormat, dimensions.x, dimensions.y); + if (!_gpuObject.isArray()) { + glTextureStorage2D(_id, mips, texelFormat.internalFormat, dimensions.x, dimensions.y); + } else { + glTextureStorage3D(_id, mips, texelFormat.internalFormat, dimensions.x, dimensions.y, numSlices); + } glTextureParameteri(_id, GL_TEXTURE_BASE_LEVEL, 0); glTextureParameteri(_id, GL_TEXTURE_MAX_LEVEL, mips - 1); diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp index 0bf1548a4b..9c3a83ce13 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp @@ -64,7 +64,12 @@ public: } if (gltexture) { - glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, gltexture->_texture, 0); + if (gltexture->_target == GL_TEXTURE_2D) { + glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, gltexture->_texture, 0); + } else { + glFramebufferTextureLayer(GL_FRAMEBUFFER, colorAttachments[unit], gltexture->_texture, 0, + b._subresource); + } _colorBuffers.push_back(colorAttachments[unit]); } else { glFramebufferTexture2D(GL_FRAMEBUFFER, colorAttachments[unit], GL_TEXTURE_2D, 0, 0); @@ -91,7 +96,12 @@ public: } if (gltexture) { - glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0); + if (gltexture->_target == GL_TEXTURE_2D) { + glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, gltexture->_texture, 0); + } else { + glFramebufferTextureLayer(GL_FRAMEBUFFER, attachement, gltexture->_texture, 0, + _gpuObject.getDepthStencilBufferSubresource()); + } } else { glFramebufferTexture2D(GL_FRAMEBUFFER, attachement, GL_TEXTURE_2D, 0, 0); } diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp index bbc02c2af6..911dfb8bb8 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendTexture.cpp @@ -268,16 +268,27 @@ GLsizei getCompressedImageSize(int width, int height, GLenum internalFormat) { void GLESFixedAllocationTexture::allocateStorage() const { const GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(_gpuObject.getTexelFormat()); const auto numMips = _gpuObject.getNumMips(); + const auto numSlices = _gpuObject.getNumSlices(); // glTextureStorage2D(_id, mips, texelFormat.internalFormat, dimensions.x, dimensions.y); for (GLint level = 0; level < numMips; level++) { Vec3u dimensions = _gpuObject.evalMipDimensions(level); for (GLenum target : getFaceTargets(_target)) { if (texelFormat.isCompressed()) { - glCompressedTexImage2D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, - getCompressedImageSize(dimensions.x, dimensions.y, texelFormat.internalFormat), nullptr); + auto size = getCompressedImageSize(dimensions.x, dimensions.y, texelFormat.internalFormat); + if (!_gpuObject.isArray()) { + glCompressedTexImage2D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, size, nullptr); + } else { + glCompressedTexImage3D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, numSlices, 0, size * numSlices, nullptr); + } } else { - glTexImage2D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, texelFormat.type, nullptr); + if (!_gpuObject.isArray()) { + glTexImage2D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, 0, texelFormat.format, + texelFormat.type, nullptr); + } else { + glTexImage3D(target, level, texelFormat.internalFormat, dimensions.x, dimensions.y, numSlices, 0, + texelFormat.format, texelFormat.type, nullptr); + } } } } diff --git a/libraries/gpu/src/gpu/Frame.cpp b/libraries/gpu/src/gpu/Frame.cpp index d08a8ab56d..f1001d97d2 100644 --- a/libraries/gpu/src/gpu/Frame.cpp +++ b/libraries/gpu/src/gpu/Frame.cpp @@ -21,10 +21,7 @@ Frame::~Frame() { framebuffer.reset(); } - assert(bufferUpdates.empty()); - if (!bufferUpdates.empty()) { - qFatal("Buffer sync error... frame destroyed without buffer updates being applied"); - } + bufferUpdates.clear(); } void Frame::finish() { diff --git a/libraries/gpu/src/gpu/Noise.slh b/libraries/gpu/src/gpu/Noise.slh new file mode 100644 index 0000000000..b390945957 --- /dev/null +++ b/libraries/gpu/src/gpu/Noise.slh @@ -0,0 +1,297 @@ + + +// Shader includes portions of webgl-noise: +// Description : Array and textureless GLSL 2D/3D/4D simplex +// noise functions. +// Author : Ian McEwan, Ashima Arts. +// Maintainer : ijm +// Lastmod : 20110822 (ijm) +// License : Copyright (C) 2011 Ashima Arts. All rights reserved. +// Distributed under the MIT License. See LICENSE file. +// https://github.com/ashima/webgl-noise +// + +<@if not NOISE_SLH@> +<@def NOISE_SLH@> + +float mod289(float x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec2 mod289(vec2 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec3 mod289(vec3 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +vec4 mod289(vec4 x) { + return x - floor(x * (1.0 / 289.0)) * 289.0; +} + +float permute(float x) { + return mod289(((x*34.0)+1.0)*x); +} + +vec3 permute(vec3 x) { + return mod289(((x*34.0)+1.0)*x); +} + +vec4 permute(vec4 x) { + return mod289(((x*34.0)+1.0)*x); +} + +float taylorInvSqrt(float r) { + return 1.79284291400159 - 0.85373472095314 * r; +} + +vec4 taylorInvSqrt(vec4 r) { + return 1.79284291400159 - 0.85373472095314 * r; +} + +vec4 grad4(float j, vec4 ip) { + const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0); + vec4 p, s; + + p.xyz = floor(fract(vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0; + p.w = 1.5 - dot(abs(p.xyz), ones.xyz); + s = vec4(lessThan(p, vec4(0.0))); + p.xyz = p.xyz + (s.xyz * 2.0 - 1.0) * s.www; + + return p; +} + +// (sqrt(5) - 1)/4 = F4, used once below +#define F4 0.309016994374947451 + +float snoise(vec4 v) { + const vec4 C = vec4(0.138196601125011, // (5 - sqrt(5))/20 G4 + 0.276393202250021, // 2 * G4 + 0.414589803375032, // 3 * G4 + -0.447213595499958); // -1 + 4 * G4 + + // First corner + vec4 i = floor(v + dot(v, vec4(F4))); + vec4 x0 = v - i + dot(i, C.xxxx); + + // Other corners + + // Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI) + vec4 i0; + vec3 isX = step(x0.yzw, x0.xxx); + vec3 isYZ = step(x0.zww, x0.yyz); + i0.x = isX.x + isX.y + isX.z; + i0.yzw = 1.0 - isX; + i0.y += isYZ.x + isYZ.y; + i0.zw += 1.0 - isYZ.xy; + i0.z += isYZ.z; + i0.w += 1.0 - isYZ.z; + + // i0 now contains the unique values 0,1,2,3 in each channel + vec4 i3 = clamp(i0, 0.0, 1.0); + vec4 i2 = clamp(i0 - 1.0, 0.0, 1.0); + vec4 i1 = clamp(i0 - 2.0, 0.0, 1.0); + + vec4 x1 = x0 - i1 + C.xxxx; + vec4 x2 = x0 - i2 + C.yyyy; + vec4 x3 = x0 - i3 + C.zzzz; + vec4 x4 = x0 + C.wwww; + + // Permutations + i = mod289(i); + float j0 = permute(permute(permute(permute(i.w) + i.z) + i.y) + i.x); + vec4 j1 = permute( + permute( + permute( + permute(i.w + vec4(i1.w, i2.w, i3.w, 1.0)) + i.z + + vec4(i1.z, i2.z, i3.z, 1.0)) + i.y + + vec4(i1.y, i2.y, i3.y, 1.0)) + i.x + + vec4(i1.x, i2.x, i3.x, 1.0)); + + // Gradients: 7x7x6 points over a cube, mapped onto a 4-cross polytope + // 7*7*6 = 294, which is close to the ring size 17*17 = 289. + vec4 ip = vec4(1.0 / 294.0, 1.0 / 49.0, 1.0 / 7.0, 0.0); + + vec4 p0 = grad4(j0, ip); + vec4 p1 = grad4(j1.x, ip); + vec4 p2 = grad4(j1.y, ip); + vec4 p3 = grad4(j1.z, ip); + vec4 p4 = grad4(j1.w, ip); + + // Normalise gradients + vec4 norm = taylorInvSqrt( + vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); + p0 *= norm.x; + p1 *= norm.y; + p2 *= norm.z; + p3 *= norm.w; + p4 *= taylorInvSqrt(dot(p4, p4)); + + // Mix contributions from the five corners + vec3 m0 = max(0.6 - vec3(dot(x0, x0), dot(x1, x1), dot(x2, x2)), 0.0); + vec2 m1 = max(0.6 - vec2(dot(x3, x3), dot(x4, x4)), 0.0); + m0 = m0 * m0; + m1 = m1 * m1; + return 49.0 + * (dot(m0 * m0, vec3(dot(p0, x0), dot(p1, x1), dot(p2, x2))) + + dot(m1 * m1, vec2(dot(p3, x3), dot(p4, x4)))); + +} + +float snoise(vec3 v) { + const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0); + const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); + + // First corner + vec3 i = floor(v + dot(v, C.yyy)); + vec3 x0 = v - i + dot(i, C.xxx); + + // Other corners + vec3 g = step(x0.yzx, x0.xyz); + vec3 l = 1.0 - g; + vec3 i1 = min(g.xyz, l.zxy); + vec3 i2 = max(g.xyz, l.zxy); + + vec3 x1 = x0 - i1 + C.xxx; + vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y + vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y + + // Permutations + i = mod289(i); + vec4 p = permute( + permute( + permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y + + vec4(0.0, i1.y, i2.y, 1.0)) + i.x + + vec4(0.0, i1.x, i2.x, 1.0)); + + // Gradients: 7x7 points over a square, mapped onto an octahedron. + // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) + float n_ = 0.142857142857; // 1.0/7.0 + vec3 ns = n_ * D.wyz - D.xzx; + + vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7) + + vec4 x_ = floor(j * ns.z); + vec4 y_ = floor(j - 7.0 * x_); // mod(j,N) + + vec4 x = x_ * ns.x + ns.yyyy; + vec4 y = y_ * ns.x + ns.yyyy; + vec4 h = 1.0 - abs(x) - abs(y); + + vec4 b0 = vec4(x.xy, y.xy); + vec4 b1 = vec4(x.zw, y.zw); + + //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; + //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; + vec4 s0 = floor(b0) * 2.0 + 1.0; + vec4 s1 = floor(b1) * 2.0 + 1.0; + vec4 sh = -step(h, vec4(0.0)); + + vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; + vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww; + + vec3 p0 = vec3(a0.xy, h.x); + vec3 p1 = vec3(a0.zw, h.y); + vec3 p2 = vec3(a1.xy, h.z); + vec3 p3 = vec3(a1.zw, h.w); + + //Normalise gradients + vec4 norm = taylorInvSqrt( + vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); + p0 *= norm.x; + p1 *= norm.y; + p2 *= norm.z; + p3 *= norm.w; + + // Mix final noise value + vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), + 0.0); + m = m * m; + return 42.0 + * dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3))); +} + +float snoise(vec2 v) { + const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0 + 0.366025403784439, // 0.5*(sqrt(3.0)-1.0) + -0.577350269189626, // -1.0 + 2.0 * C.x + 0.024390243902439); // 1.0 / 41.0 + // First corner + vec2 i = floor(v + dot(v, C.yy)); + vec2 x0 = v - i + dot(i, C.xx); + + // Other corners + vec2 i1; + i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); + vec4 x12 = x0.xyxy + C.xxzz; + x12.xy -= i1; + + // Permutations + i = mod289(i); // Avoid truncation effects in permutation + vec3 p = permute( + permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0)); + + vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), + 0.0); + m = m * m; + m = m * m; + + // Gradients: 41 points uniformly over a line, mapped onto a diamond. + // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) + + vec3 x = 2.0 * fract(p * C.www) - 1.0; + vec3 h = abs(x) - 0.5; + vec3 ox = floor(x + 0.5); + vec3 a0 = x - ox; + + // Normalise gradients implicitly by scaling m + // Approximation of: m *= inversesqrt( a0*a0 + h*h ); + m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h); + + // Compute final noise value at P + vec3 g; + g.x = a0.x * x0.x + h.x * x0.y; + g.yz = a0.yz * x12.xz + h.yz * x12.yw; + return 130.0 * dot(m, g); +} + +// https://www.shadertoy.com/view/lsfGRr +float hifi_hash(float n) { + return fract(sin(n) * 43758.5453); +} + +float hifi_noise(in vec2 x) { + vec2 p = floor(x); + vec2 f = fract(x); + + f = f * f * (3.0 - 2.0 * f); + + float n = p.x + p.y * 57.0; + + return mix(mix(hifi_hash(n + 0.0), hifi_hash(n + 1.0), f.x), + mix(hifi_hash(n + 57.0), hifi_hash(n + 58.0), f.x), f.y); +} + +// https://www.shadertoy.com/view/MdX3Rr +// https://en.wikipedia.org/wiki/Fractional_Brownian_motion +float hifi_fbm(in vec2 p) { + const mat2 m2 = mat2(0.8, -0.6, 0.6, 0.8); + float f = 0.0; + f += 0.5000 * hifi_noise(p); p = m2 * p * 2.02; + f += 0.2500 * hifi_noise(p); p = m2 * p * 2.03; + f += 0.1250 * hifi_noise(p); p = m2 * p * 2.01; + f += 0.0625 * hifi_noise(p); + + return f / 0.9375; +} + +<@endif@> \ No newline at end of file diff --git a/libraries/gpu/src/gpu/Sysmem.cpp b/libraries/gpu/src/gpu/Sysmem.cpp index a642d40478..a2f3b60c36 100644 --- a/libraries/gpu/src/gpu/Sysmem.cpp +++ b/libraries/gpu/src/gpu/Sysmem.cpp @@ -63,13 +63,13 @@ Sysmem& Sysmem::operator=(const Sysmem& sysmem) { Sysmem::~Sysmem() { deallocateMemory( _data, _size ); - _data = NULL; + _data = nullptr; _size = 0; } Size Sysmem::allocate(Size size) { if (size != _size) { - Byte* newData = NULL; + Byte* newData = nullptr; Size newSize = 0; if (size > 0) { Size allocated = allocateMemory(&newData, size); @@ -90,7 +90,7 @@ Size Sysmem::allocate(Size size) { Size Sysmem::resize(Size size) { if (size != _size) { - Byte* newData = NULL; + Byte* newData = nullptr; Size newSize = 0; if (size > 0) { Size allocated = allocateMemory(&newData, size); @@ -124,7 +124,7 @@ Size Sysmem::setData( Size size, const Byte* bytes ) { } Size Sysmem::setSubData( Size offset, Size size, const Byte* bytes) { - if (size && ((offset + size) <= getSize()) && bytes) { + if (_data && size && ((offset + size) <= getSize()) && bytes) { memcpy( _data + offset, bytes, size ); return size; } diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index a92243f808..34262b0cd9 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -184,6 +184,10 @@ TexturePointer Texture::createRenderBuffer(const Element& texelFormat, uint16 wi return create(TextureUsageType::RENDERBUFFER, TEX_2D, texelFormat, width, height, 1, 1, 0, numMips, sampler); } +TexturePointer Texture::createRenderBufferArray(const Element& texelFormat, uint16 width, uint16 height, uint16 numSlices, uint16 numMips, const Sampler& sampler) { + return create(TextureUsageType::RENDERBUFFER, TEX_2D, texelFormat, width, height, 1, 1, numSlices, numMips, sampler); +} + TexturePointer Texture::create1D(const Element& texelFormat, uint16 width, uint16 numMips, const Sampler& sampler) { return create(TextureUsageType::RESOURCE, TEX_1D, texelFormat, width, 1, 1, 1, 0, numMips, sampler); } @@ -192,6 +196,10 @@ TexturePointer Texture::create2D(const Element& texelFormat, uint16 width, uint1 return create(TextureUsageType::RESOURCE, TEX_2D, texelFormat, width, height, 1, 1, 0, numMips, sampler); } +TexturePointer Texture::create2DArray(const Element& texelFormat, uint16 width, uint16 height, uint16 numSlices, uint16 numMips, const Sampler& sampler) { + return create(TextureUsageType::STRICT_RESOURCE, TEX_2D, texelFormat, width, height, 1, 1, numSlices, numMips, sampler); +} + TexturePointer Texture::createStrict(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips, const Sampler& sampler) { return create(TextureUsageType::STRICT_RESOURCE, TEX_2D, texelFormat, width, height, 1, 1, 0, numMips, sampler); } diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 09b2bc9475..9ad5dc0816 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -374,9 +374,11 @@ public: static const uint16 SINGLE_MIP = 1; static TexturePointer create1D(const Element& texelFormat, uint16 width, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler()); static TexturePointer create2D(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler()); + static TexturePointer create2DArray(const Element& texelFormat, uint16 width, uint16 height, uint16 numSlices, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler()); static TexturePointer create3D(const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler()); static TexturePointer createCube(const Element& texelFormat, uint16 width, uint16 numMips = 1, const Sampler& sampler = Sampler()); static TexturePointer createRenderBuffer(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler()); + static TexturePointer createRenderBufferArray(const Element& texelFormat, uint16 width, uint16 height, uint16 numSlices, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler()); static TexturePointer createStrict(const Element& texelFormat, uint16 width, uint16 height, uint16 numMips = SINGLE_MIP, const Sampler& sampler = Sampler()); static TexturePointer createExternal(const ExternalRecycler& recycler, const Sampler& sampler = Sampler()); diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 839cb915e2..1b7b552078 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -140,7 +140,6 @@ struct IrradianceKTXPayload { data += sizeof(Version); memcpy(&_irradianceSH, data, sizeof(SphericalHarmonics)); - data += sizeof(SphericalHarmonics); return true; } @@ -516,7 +515,7 @@ TexturePointer Texture::build(const ktx::KTXDescriptor& descriptor) { header.getPixelHeight(), header.getPixelDepth(), 1, // num Samples - header.getNumberOfSlices(), + header.isArray() ? header.getNumberOfSlices() : 0, header.getNumberOfLevels(), samplerDesc); texture->setUsage(gpuktxKeyValue._usage); diff --git a/libraries/gpu/src/gpu/Transform.slh b/libraries/gpu/src/gpu/Transform.slh index 50c0bc13ed..864a106350 100644 --- a/libraries/gpu/src/gpu/Transform.slh +++ b/libraries/gpu/src/gpu/Transform.slh @@ -238,7 +238,7 @@ TransformObject getTransformObject() { <@endfunc@> <@func transformModelToWorldDir(cameraTransform, objectTransform, modelDir, worldDir)@> - { // transformModelToEyeDir + { // transformModelToWorldDir vec3 mr0 = <$objectTransform$>._modelInverse[0].xyz; vec3 mr1 = <$objectTransform$>._modelInverse[1].xyz; vec3 mr2 = <$objectTransform$>._modelInverse[2].xyz; diff --git a/libraries/graphics/src/graphics/Haze.cpp b/libraries/graphics/src/graphics/Haze.cpp index d5a060b90b..ded48429ba 100644 --- a/libraries/graphics/src/graphics/Haze.cpp +++ b/libraries/graphics/src/graphics/Haze.cpp @@ -177,9 +177,9 @@ void Haze::setHazeBaseReference(const float hazeBaseReference) { void Haze::setHazeBackgroundBlend(const float hazeBackgroundBlend) { auto& params = _hazeParametersBuffer.get(); - - if (params.hazeBackgroundBlend != hazeBackgroundBlend) { - _hazeParametersBuffer.edit().hazeBackgroundBlend = hazeBackgroundBlend; + auto newBlend = 1.0f - hazeBackgroundBlend; + if (params.hazeBackgroundBlend != newBlend) { + _hazeParametersBuffer.edit().hazeBackgroundBlend = newBlend; } } diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 63a4725f64..7fc3a73f87 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -46,12 +46,9 @@ bool DEV_DECIMATE_TEXTURES = false; std::atomic DECIMATED_TEXTURE_COUNT{ 0 }; std::atomic RECTIFIED_TEXTURE_COUNT{ 0 }; -static const auto HDR_FORMAT = gpu::Element::COLOR_R11G11B10; - -static std::atomic compressColorTextures { false }; -static std::atomic compressNormalTextures { false }; -static std::atomic compressGrayscaleTextures { false }; -static std::atomic compressCubeTextures { false }; +// we use a ref here to work around static order initialization +// possibly causing the element not to be constructed yet +static const auto& HDR_FORMAT = gpu::Element::COLOR_R11G11B10; uint rectifyDimension(const uint& dimension) { if (dimension == 0) { @@ -126,112 +123,63 @@ TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, con } gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, true, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, true, abortProcessing); } gpu::TexturePointer TextureUsage::create2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); } gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); } gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); } gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); } gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, false, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); } gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, true, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, compress, true, abortProcessing); } gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, false, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); } gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, true, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, true, abortProcessing); } gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, false, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); } gpu::TexturePointer TextureUsage::createCubeTextureFromImage(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, true, abortProcessing); + bool compress, const std::atomic& abortProcessing) { + return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, true, abortProcessing); } gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(QImage&& srcImage, const std::string& srcImageName, - const std::atomic& abortProcessing) { - return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); -} - - -bool isColorTexturesCompressionEnabled() { -#if CPU_MIPMAPS - return compressColorTextures.load(); -#else - return false; -#endif -} - -bool isNormalTexturesCompressionEnabled() { -#if CPU_MIPMAPS - return compressNormalTextures.load(); -#else - return false; -#endif -} - -bool isGrayscaleTexturesCompressionEnabled() { -#if CPU_MIPMAPS - return compressGrayscaleTextures.load(); -#else - return false; -#endif -} - -bool isCubeTexturesCompressionEnabled() { -#if CPU_MIPMAPS - return compressCubeTextures.load(); -#else - return false; -#endif -} - -void setColorTexturesCompressionEnabled(bool enabled) { - compressColorTextures.store(enabled); -} - -void setNormalTexturesCompressionEnabled(bool enabled) { - compressNormalTextures.store(enabled); -} - -void setGrayscaleTexturesCompressionEnabled(bool enabled) { - compressGrayscaleTextures.store(enabled); -} - -void setCubeTexturesCompressionEnabled(bool enabled) { - compressCubeTextures.store(enabled); + bool compress, const std::atomic& abortProcessing) { + return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, compress, false, abortProcessing); } static float denormalize(float value, const float minValue) { @@ -253,17 +201,11 @@ uint32 packR11G11B10F(const glm::vec3& color) { return glm::packF2x11_1x10(ucolor); } -QImage processRawImageData(QByteArray&& content, const std::string& filename) { - // Take a local copy to force move construction - // https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#f18-for-consume-parameters-pass-by-x-and-stdmove-the-parameter - QByteArray localCopy = std::move(content); - +QImage processRawImageData(QIODevice& content, const std::string& filename) { // Help the QImage loader by extracting the image file format from the url filename ext. // Some tga are not created properly without it. auto filenameExtension = filename.substr(filename.find_last_of('.') + 1); - QBuffer buffer; - buffer.setData(localCopy); - QImageReader imageReader(&buffer, filenameExtension.c_str()); + QImageReader imageReader(&content, filenameExtension.c_str()); if (imageReader.canRead()) { return imageReader.read(); @@ -271,8 +213,8 @@ QImage processRawImageData(QByteArray&& content, const std::string& filename) { // Extension could be incorrect, try to detect the format from the content QImageReader newImageReader; newImageReader.setDecideFormatFromContent(true); - buffer.setData(localCopy); - newImageReader.setDevice(&buffer); + content.reset(); + newImageReader.setDevice(&content); if (newImageReader.canRead()) { qCWarning(imagelogging) << "Image file" << filename.c_str() << "has extension" << filenameExtension.c_str() @@ -284,11 +226,14 @@ QImage processRawImageData(QByteArray&& content, const std::string& filename) { return QImage(); } -gpu::TexturePointer processImage(QByteArray&& content, const std::string& filename, +gpu::TexturePointer processImage(std::shared_ptr content, const std::string& filename, int maxNumPixels, TextureUsage::Type textureType, - const std::atomic& abortProcessing) { + bool compress, const std::atomic& abortProcessing) { - QImage image = processRawImageData(std::move(content), filename); + QImage image = processRawImageData(*content.get(), filename); + // Texture content can take up a lot of memory. Here we release our ownership of that content + // in case it can be released. + content.reset(); int imageWidth = image.width(); int imageHeight = image.height(); @@ -314,7 +259,7 @@ gpu::TexturePointer processImage(QByteArray&& content, const std::string& filena } auto loader = TextureUsage::getTextureLoaderForType(textureType); - auto texture = loader(std::move(image), filename, abortProcessing); + auto texture = loader(std::move(image), filename, compress, abortProcessing); return texture; } @@ -804,7 +749,7 @@ void processTextureAlpha(const QImage& srcImage, bool& validAlpha, bool& alphaAs validAlpha = (numOpaques != NUM_PIXELS); } -gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, bool isStrict, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureColorFromImage"); QImage image = processSourceImage(std::move(srcImage), false); @@ -825,7 +770,7 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcIma if ((image.width() > 0) && (image.height() > 0)) { gpu::Element formatMip; gpu::Element formatGPU; - if (isColorTexturesCompressionEnabled()) { + if (compress) { if (validAlpha) { // NOTE: This disables BC1a compression because it was producing odd artifacts on text textures // for the tutorial. Instead we use BC3 (which is larger) but doesn't produce the same artifacts). @@ -833,6 +778,7 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcIma } else { formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_SRGB; } + formatMip = formatGPU; } else { #ifdef USE_GLES // GLES does not support GL_BGRA @@ -941,7 +887,8 @@ QImage processBumpMap(QImage&& image) { return result; } gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, - bool isBumpMap, const std::atomic& abortProcessing) { + bool compress, bool isBumpMap, + const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureNormalMapFromImage"); QImage image = processSourceImage(std::move(srcImage), false); @@ -958,7 +905,7 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& sr if ((image.width() > 0) && (image.height() > 0)) { gpu::Element formatMip; gpu::Element formatGPU; - if (isNormalTexturesCompressionEnabled()) { + if (compress) { formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_XY; } else { #ifdef USE_GLES @@ -980,7 +927,7 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& sr } gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, - bool isInvertedPixels, + bool compress, bool isInvertedPixels, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureGrayscaleFromImage"); QImage image = processSourceImage(std::move(srcImage), false); @@ -998,7 +945,7 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& sr if ((image.width() > 0) && (image.height() > 0)) { gpu::Element formatMip; gpu::Element formatGPU; - if (isGrayscaleTexturesCompressionEnabled()) { + if (compress) { formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_RED; } else { #ifdef USE_GLES @@ -1345,7 +1292,7 @@ QImage convertToHDRFormat(QImage&& srcImage, gpu::Element format) { } gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, - bool generateIrradiance, + bool compress, bool generateIrradiance, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage"); @@ -1373,7 +1320,7 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcI gpu::Element formatMip; gpu::Element formatGPU; - if (isCubeTexturesCompressionEnabled()) { + if (compress) { formatGPU = gpu::Element::COLOR_COMPRESSED_BCX_HDR_RGB; } else { #ifdef USE_GLES diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h index 39f5ea3bab..ccf4845fca 100644 --- a/libraries/image/src/image/Image.h +++ b/libraries/image/src/image/Image.h @@ -41,60 +41,50 @@ enum Type { UNUSED_TEXTURE }; -using TextureLoader = std::function&)>; +using TextureLoader = std::function&)>; TextureLoader getTextureLoaderForType(Type type, const QVariantMap& options = QVariantMap()); gpu::TexturePointer create2DTextureFromImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createStrict2DTextureFromImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createAlbedoTextureFromImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createEmissiveTextureFromImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createNormalTextureFromNormalImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createNormalTextureFromBumpImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createRoughnessTextureFromImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createRoughnessTextureFromGlossImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createMetallicTextureFromImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createCubeTextureFromImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); gpu::TexturePointer createLightmapTextureFromImage(QImage&& image, const std::string& srcImageName, - const std::atomic& abortProcessing); + bool compress, const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool isStrict, - const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, bool isBumpMap, - const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool isInvertedPixels, - const std::atomic& abortProcessing); -gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool generateIrradiance, - const std::atomic& abortProcessing); +gpu::TexturePointer process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, + bool isStrict, const std::atomic& abortProcessing); +gpu::TexturePointer process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, + bool isBumpMap, const std::atomic& abortProcessing); +gpu::TexturePointer process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, + bool isInvertedPixels, const std::atomic& abortProcessing); +gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool compress, + bool generateIrradiance, const std::atomic& abortProcessing); } // namespace TextureUsage const QStringList getSupportedFormats(); -bool isColorTexturesCompressionEnabled(); -bool isNormalTexturesCompressionEnabled(); -bool isGrayscaleTexturesCompressionEnabled(); -bool isCubeTexturesCompressionEnabled(); - -void setColorTexturesCompressionEnabled(bool enabled); -void setNormalTexturesCompressionEnabled(bool enabled); -void setGrayscaleTexturesCompressionEnabled(bool enabled); -void setCubeTexturesCompressionEnabled(bool enabled); - -gpu::TexturePointer processImage(QByteArray&& content, const std::string& url, +gpu::TexturePointer processImage(std::shared_ptr content, const std::string& url, int maxNumPixels, TextureUsage::Type textureType, - const std::atomic& abortProcessing = false); + bool compress = false, const std::atomic& abortProcessing = false); } // namespace image diff --git a/libraries/ktx/src/TextureMeta.cpp b/libraries/ktx/src/TextureMeta.cpp index 3a2abf24c4..c8427c1f60 100644 --- a/libraries/ktx/src/TextureMeta.cpp +++ b/libraries/ktx/src/TextureMeta.cpp @@ -33,6 +33,9 @@ bool TextureMeta::deserialize(const QByteArray& data, TextureMeta* meta) { if (root.contains("original")) { meta->original = root["original"].toString(); } + if (root.contains("uncompressed")) { + meta->uncompressed = root["uncompressed"].toString(); + } if (root.contains("compressed")) { auto compressed = root["compressed"].toObject(); for (auto it = compressed.constBegin(); it != compressed.constEnd(); it++) { @@ -57,6 +60,7 @@ QByteArray TextureMeta::serialize() { compressed[name] = kv.second.toString(); } root["original"] = original.toString(); + root["uncompressed"] = uncompressed.toString(); root["compressed"] = compressed; doc.setObject(root); diff --git a/libraries/ktx/src/TextureMeta.h b/libraries/ktx/src/TextureMeta.h index 6582c29e70..5450fee110 100644 --- a/libraries/ktx/src/TextureMeta.h +++ b/libraries/ktx/src/TextureMeta.h @@ -35,6 +35,7 @@ struct TextureMeta { QByteArray serialize(); QUrl original; + QUrl uncompressed; std::unordered_map availableTextureTypes; }; diff --git a/libraries/ktx/src/ktx/KTX.h b/libraries/ktx/src/ktx/KTX.h index 54a8188a42..d755a482e3 100644 --- a/libraries/ktx/src/ktx/KTX.h +++ b/libraries/ktx/src/ktx/KTX.h @@ -163,6 +163,7 @@ namespace ktx { uint32_t getPixelDepth() const { return (pixelDepth ? pixelDepth : 1); } uint32_t getNumberOfSlices() const { return (numberOfArrayElements ? numberOfArrayElements : 1); } uint32_t getNumberOfLevels() const { return (numberOfMipmapLevels ? numberOfMipmapLevels : 1); } + bool isArray() const { return numberOfArrayElements > 0; } bool isCompressed() const { return glFormat == COMPRESSED_FORMAT; } uint32_t evalMaxDimension() const; diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 716d2aeab1..f669e8aaef 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -422,7 +422,7 @@ bool Geometry::areTexturesLoaded() const { } // Failed texture downloads need to be considered as 'loaded' // or the object will never fade in - bool finished = texture->isLoaded() || texture->isFailed(); + bool finished = texture->isFailed() || (texture->isLoaded() && texture->getGPUTexture() && texture->getGPUTexture()->isDefined()); if (!finished) { return true; } @@ -434,8 +434,11 @@ bool Geometry::areTexturesLoaded() const { } // If material textures are loaded, check the material translucency + // FIXME: This should not be done here. The opacity map should already be reset in Material::setTextureMap. + // However, currently that code can be called before the albedo map is defined, so resetOpacityMap will fail. + // Geometry::areTexturesLoaded() is called repeatedly until it returns true, so we do the check here for now const auto albedoTexture = material->_textures[NetworkMaterial::MapChannel::ALBEDO_MAP]; - if (albedoTexture.texture && albedoTexture.texture->getGPUTexture()) { + if (albedoTexture.texture) { material->resetOpacityMap(); } } diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index ed21fd35bc..40b31cac53 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -50,6 +50,8 @@ #include +#include + Q_LOGGING_CATEGORY(trace_resource_parse_image, "trace.resource.parse.image") Q_LOGGING_CATEGORY(trace_resource_parse_image_raw, "trace.resource.parse.image.raw") Q_LOGGING_CATEGORY(trace_resource_parse_image_ktx, "trace.resource.parse.image.ktx") @@ -277,7 +279,7 @@ gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::Te return nullptr; } auto loader = image::TextureUsage::getTextureLoaderForType(type, options); - return gpu::TexturePointer(loader(std::move(image), path.toStdString(), false)); + return gpu::TexturePointer(loader(std::move(image), path.toStdString(), false, false)); } QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, @@ -964,7 +966,6 @@ void NetworkTexture::loadMetaContent(const QByteArray& content) { return; } - auto& backend = DependencyManager::get()->getGPUContext()->getBackend(); for (auto pair : meta.availableTextureTypes) { gpu::Element elFormat; @@ -990,6 +991,21 @@ void NetworkTexture::loadMetaContent(const QByteArray& content) { } } +#ifndef Q_OS_ANDROID + if (!meta.uncompressed.isEmpty()) { + _currentlyLoadingResourceType = ResourceType::KTX; + _activeUrl = _activeUrl.resolved(meta.uncompressed); + + auto textureCache = DependencyManager::get(); + auto self = _self.lock(); + if (!self) { + return; + } + QMetaObject::invokeMethod(this, "attemptRequest", Qt::QueuedConnection); + return; + } +#endif + if (!meta.original.isEmpty()) { _currentlyLoadingResourceType = ResourceType::ORIGINAL; _activeUrl = _activeUrl.resolved(meta.original); @@ -1143,7 +1159,8 @@ void ImageReader::read() { PROFILE_RANGE_EX(resource_parse_image_raw, __FUNCTION__, 0xffff0000, 0); // IMPORTANT: _content is empty past this point - texture = image::processImage(std::move(_content), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType()); + auto buffer = std::shared_ptr((QIODevice*)new OwningBuffer(std::move(_content))); + texture = image::processImage(std::move(buffer), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType()); if (!texture) { qCWarning(modelnetworking) << "Could not process:" << _url; diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 05f0ec12b5..5b3196a2bf 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -46,21 +47,18 @@ Q_DECLARE_METATYPE(JSONCallbackParameters) const QString ACCOUNTS_GROUP = "accounts"; -JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, const QString& jsonCallbackMethod, - QObject* errorCallbackReceiver, const QString& errorCallbackMethod, - QObject* updateReceiver, const QString& updateSlot) : - jsonCallbackReceiver(jsonCallbackReceiver), +JSONCallbackParameters::JSONCallbackParameters(QObject* callbackReceiver, + const QString& jsonCallbackMethod, + const QString& errorCallbackMethod) : + callbackReceiver(callbackReceiver), jsonCallbackMethod(jsonCallbackMethod), - errorCallbackReceiver(errorCallbackReceiver), - errorCallbackMethod(errorCallbackMethod), - updateReciever(updateReceiver), - updateSlot(updateSlot) + errorCallbackMethod(errorCallbackMethod) { } -QJsonObject AccountManager::dataObjectFromResponse(QNetworkReply &requestReply) { - QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); +QJsonObject AccountManager::dataObjectFromResponse(QNetworkReply* requestReply) { + QJsonObject jsonObject = QJsonDocument::fromJson(requestReply->readAll()).object(); static const QString STATUS_KEY = "status"; static const QString DATA_KEY = "data"; @@ -74,8 +72,7 @@ QJsonObject AccountManager::dataObjectFromResponse(QNetworkReply &requestReply) AccountManager::AccountManager(UserAgentGetter userAgentGetter) : _userAgentGetter(userAgentGetter), - _authURL(), - _pendingCallbackMap() + _authURL() { qRegisterMetaType("OAuthAccessToken"); qRegisterMetaTypeStreamOperators("OAuthAccessToken"); @@ -323,75 +320,66 @@ void AccountManager::sendRequest(const QString& path, } } - - if (!callbackParams.isEmpty()) { - // if we have information for a callback, insert the callbackParams into our local map - _pendingCallbackMap.insert(networkReply, callbackParams); - - if (callbackParams.updateReciever && !callbackParams.updateSlot.isEmpty()) { - callbackParams.updateReciever->connect(networkReply, SIGNAL(uploadProgress(qint64, qint64)), - callbackParams.updateSlot.toStdString().c_str()); + connect(networkReply, &QNetworkReply::finished, this, [this, networkReply] { + // double check if the finished network reply had a session ID in the header and make + // sure that our session ID matches that value if so + if (networkReply->hasRawHeader(METAVERSE_SESSION_ID_HEADER)) { + _sessionID = networkReply->rawHeader(METAVERSE_SESSION_ID_HEADER); } + }); + + + if (callbackParams.isEmpty()) { + connect(networkReply, &QNetworkReply::finished, networkReply, &QNetworkReply::deleteLater); + } else { + // There's a cleaner way to fire the JSON/error callbacks below and ensure that deleteLater is called for the + // request reply - unfortunately it requires Qt 5.10 which the Android build does not support as of 06/26/18 + + connect(networkReply, &QNetworkReply::finished, callbackParams.callbackReceiver, + [callbackParams, networkReply] { + if (networkReply->error() == QNetworkReply::NoError) { + if (!callbackParams.jsonCallbackMethod.isEmpty()) { + bool invoked = QMetaObject::invokeMethod(callbackParams.callbackReceiver, + qPrintable(callbackParams.jsonCallbackMethod), + Q_ARG(QNetworkReply*, networkReply)); + + if (!invoked) { + QString error = "Could not invoke " + callbackParams.jsonCallbackMethod + " with QNetworkReply* " + + "on callbackReceiver."; + qCWarning(networking) << error; + Q_ASSERT_X(invoked, "AccountManager::passErrorToCallback", qPrintable(error)); + } + } else { + if (VERBOSE_HTTP_REQUEST_DEBUGGING) { + qCDebug(networking) << "Received JSON response from metaverse API that has no matching callback."; + qCDebug(networking) << QJsonDocument::fromJson(networkReply->readAll()); + } + } + } else { + if (!callbackParams.errorCallbackMethod.isEmpty()) { + bool invoked = QMetaObject::invokeMethod(callbackParams.callbackReceiver, + qPrintable(callbackParams.errorCallbackMethod), + Q_ARG(QNetworkReply*, networkReply)); + + if (!invoked) { + QString error = "Could not invoke " + callbackParams.errorCallbackMethod + " with QNetworkReply* " + + "on callbackReceiver."; + qCWarning(networking) << error; + Q_ASSERT_X(invoked, "AccountManager::passErrorToCallback", qPrintable(error)); + } + + } else { + if (VERBOSE_HTTP_REQUEST_DEBUGGING) { + qCDebug(networking) << "Received error response from metaverse API that has no matching callback."; + qCDebug(networking) << "Error" << networkReply->error() << "-" << networkReply->errorString(); + qCDebug(networking) << networkReply->readAll(); + } + } + } + + networkReply->deleteLater(); + }); } - - // if we ended up firing of a request, hook up to it now - connect(networkReply, SIGNAL(finished()), SLOT(processReply())); - } -} - -void AccountManager::processReply() { - QNetworkReply* requestReply = reinterpret_cast(sender()); - - if (requestReply->error() == QNetworkReply::NoError) { - if (requestReply->hasRawHeader(METAVERSE_SESSION_ID_HEADER)) { - _sessionID = requestReply->rawHeader(METAVERSE_SESSION_ID_HEADER); - } - passSuccessToCallback(requestReply); - } else { - passErrorToCallback(requestReply); - } - requestReply->deleteLater(); -} - -void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) { - JSONCallbackParameters callbackParams = _pendingCallbackMap.value(requestReply); - - if (callbackParams.jsonCallbackReceiver) { - // invoke the right method on the callback receiver - QMetaObject::invokeMethod(callbackParams.jsonCallbackReceiver, qPrintable(callbackParams.jsonCallbackMethod), - Q_ARG(QNetworkReply&, *requestReply)); - - // remove the related reply-callback group from the map - _pendingCallbackMap.remove(requestReply); - - } else { - if (VERBOSE_HTTP_REQUEST_DEBUGGING) { - qCDebug(networking) << "Received JSON response from metaverse API that has no matching callback."; - qCDebug(networking) << QJsonDocument::fromJson(requestReply->readAll()); - } - - requestReply->deleteLater(); - } -} - -void AccountManager::passErrorToCallback(QNetworkReply* requestReply) { - JSONCallbackParameters callbackParams = _pendingCallbackMap.value(requestReply); - - if (callbackParams.errorCallbackReceiver) { - // invoke the right method on the callback receiver - QMetaObject::invokeMethod(callbackParams.errorCallbackReceiver, qPrintable(callbackParams.errorCallbackMethod), - Q_ARG(QNetworkReply&, *requestReply)); - - // remove the related reply-callback group from the map - _pendingCallbackMap.remove(requestReply); - } else { - if (VERBOSE_HTTP_REQUEST_DEBUGGING) { - qCDebug(networking) << "Received error response from metaverse API that has no matching callback."; - qCDebug(networking) << "Error" << requestReply->error() << "-" << requestReply->errorString(); - qCDebug(networking) << requestReply->readAll(); - } - - requestReply->deleteLater(); } } @@ -817,16 +805,15 @@ void AccountManager::processGeneratedKeypair(QByteArray publicKey, QByteArray pr // setup callback parameters so we know once the keypair upload has succeeded or failed JSONCallbackParameters callbackParameters; - callbackParameters.jsonCallbackReceiver = this; + callbackParameters.callbackReceiver = this; callbackParameters.jsonCallbackMethod = "publicKeyUploadSucceeded"; - callbackParameters.errorCallbackReceiver = this; callbackParameters.errorCallbackMethod = "publicKeyUploadFailed"; sendRequest(uploadPath, AccountManagerAuth::Optional, QNetworkAccessManager::PutOperation, callbackParameters, QByteArray(), requestMultiPart); } -void AccountManager::publicKeyUploadSucceeded(QNetworkReply& reply) { +void AccountManager::publicKeyUploadSucceeded(QNetworkReply* reply) { qCDebug(networking) << "Uploaded public key to Metaverse API. RSA keypair generation is completed."; // public key upload complete - store the matching private key and persist the account to settings @@ -838,23 +825,17 @@ void AccountManager::publicKeyUploadSucceeded(QNetworkReply& reply) { _isWaitingForKeypairResponse = false; emit newKeypair(); - - // delete the reply object now that we are done with it - reply.deleteLater(); } -void AccountManager::publicKeyUploadFailed(QNetworkReply& reply) { +void AccountManager::publicKeyUploadFailed(QNetworkReply* reply) { // the public key upload has failed - qWarning() << "Public key upload failed from AccountManager" << reply.errorString(); + qWarning() << "Public key upload failed from AccountManager" << reply->errorString(); // we aren't waiting for a response any longer _isWaitingForKeypairResponse = false; // clear our pending private key _pendingPrivateKey.clear(); - - // delete the reply object now that we are done with it - reply.deleteLater(); } void AccountManager::handleKeypairGenerationError() { diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 9068966512..a79b69fe2b 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -28,18 +28,14 @@ class JSONCallbackParameters { public: - JSONCallbackParameters(QObject* jsonCallbackReceiver = nullptr, const QString& jsonCallbackMethod = QString(), - QObject* errorCallbackReceiver = nullptr, const QString& errorCallbackMethod = QString(), - QObject* updateReceiver = nullptr, const QString& updateSlot = QString()); + JSONCallbackParameters(QObject* callbackReceiver = nullptr, const QString& jsonCallbackMethod = QString(), + const QString& errorCallbackMethod = QString()); - bool isEmpty() const { return !jsonCallbackReceiver && !errorCallbackReceiver; } + bool isEmpty() const { return !callbackReceiver; } - QObject* jsonCallbackReceiver; + QObject* callbackReceiver; QString jsonCallbackMethod; - QObject* errorCallbackReceiver; QString errorCallbackMethod; - QObject* updateReciever; - QString updateSlot; }; namespace AccountManagerAuth { @@ -90,7 +86,7 @@ public: DataServerAccountInfo& getAccountInfo() { return _accountInfo; } void setAccountInfo(const DataServerAccountInfo &newAccountInfo); - static QJsonObject dataObjectFromResponse(QNetworkReply& requestReply); + static QJsonObject dataObjectFromResponse(QNetworkReply* requestReply); QUuid getSessionID() const { return _sessionID; } void setSessionID(const QUuid& sessionID); @@ -126,11 +122,10 @@ signals: void newKeypair(); private slots: - void processReply(); void handleKeypairGenerationError(); void processGeneratedKeypair(QByteArray publicKey, QByteArray privateKey); - void publicKeyUploadSucceeded(QNetworkReply& reply); - void publicKeyUploadFailed(QNetworkReply& reply); + void publicKeyUploadSucceeded(QNetworkReply* reply); + void publicKeyUploadFailed(QNetworkReply* reply); void generateNewKeypair(bool isUserKeypair = true, const QUuid& domainID = QUuid()); private: @@ -146,8 +141,6 @@ private: UserAgentGetter _userAgentGetter; QUrl _authURL; - - QMap _pendingCallbackMap; DataServerAccountInfo _accountInfo; bool _isWaitingForTokenRefresh { false }; diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 317be194b8..00e552af89 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -215,9 +215,8 @@ const JSONCallbackParameters& AddressManager::apiCallbackParameters() { static JSONCallbackParameters callbackParams; if (!hasSetupParameters) { - callbackParams.jsonCallbackReceiver = this; + callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "handleAPIResponse"; - callbackParams.errorCallbackReceiver = this; callbackParams.errorCallbackMethod = "handleAPIError"; } @@ -377,8 +376,8 @@ void AddressManager::handleLookupString(const QString& lookupString, bool fromSu const QString DATA_OBJECT_DOMAIN_KEY = "domain"; -void AddressManager::handleAPIResponse(QNetworkReply& requestReply) { - QJsonObject responseObject = QJsonDocument::fromJson(requestReply.readAll()).object(); +void AddressManager::handleAPIResponse(QNetworkReply* requestReply) { + QJsonObject responseObject = QJsonDocument::fromJson(requestReply->readAll()).object(); QJsonObject dataObject = responseObject["data"].toObject(); // Lookup succeeded, don't keep re-trying it (especially on server restarts) @@ -396,7 +395,7 @@ void AddressManager::handleAPIResponse(QNetworkReply& requestReply) { const char OVERRIDE_PATH_KEY[] = "override_path"; const char LOOKUP_TRIGGER_KEY[] = "lookup_trigger"; -void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const QNetworkReply& reply) { +void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const QNetworkReply* reply) { const QString DATA_OBJECT_PLACE_KEY = "place"; const QString DATA_OBJECT_USER_LOCATION_KEY = "location"; @@ -461,7 +460,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const emit possibleDomainChangeRequiredViaICEForID(iceServerAddress, domainID); } - LookupTrigger trigger = (LookupTrigger) reply.property(LOOKUP_TRIGGER_KEY).toInt(); + LookupTrigger trigger = (LookupTrigger) reply->property(LOOKUP_TRIGGER_KEY).toInt(); // set our current root place id to the ID that came back @@ -495,7 +494,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const } // check if we had a path to override the path returned - QString overridePath = reply.property(OVERRIDE_PATH_KEY).toString(); + QString overridePath = reply->property(OVERRIDE_PATH_KEY).toString(); if (!overridePath.isEmpty() && overridePath != "/") { // make sure we don't re-handle an overriden path if this was a refresh of info from API @@ -543,10 +542,10 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const } } -void AddressManager::handleAPIError(QNetworkReply& errorReply) { - qCDebug(networking) << "AddressManager API error -" << errorReply.error() << "-" << errorReply.errorString(); +void AddressManager::handleAPIError(QNetworkReply* errorReply) { + qCDebug(networking) << "AddressManager API error -" << errorReply->error() << "-" << errorReply->errorString(); - if (errorReply.error() == QNetworkReply::ContentNotFoundError) { + if (errorReply->error() == QNetworkReply::ContentNotFoundError) { // if this is a lookup that has no result, don't keep re-trying it _previousLookup.clear(); @@ -853,17 +852,16 @@ void AddressManager::refreshPreviousLookup() { void AddressManager::copyAddress() { if (QThread::currentThread() != qApp->thread()) { - QMetaObject::invokeMethod(this, "copyAddress"); + QMetaObject::invokeMethod(qApp, "copyToClipboard", Q_ARG(QString, currentShareableAddress().toString())); return; } - // assume that the address is being copied because the user wants a shareable address QGuiApplication::clipboard()->setText(currentShareableAddress().toString()); } void AddressManager::copyPath() { if (QThread::currentThread() != qApp->thread()) { - QMetaObject::invokeMethod(this, "copyPath"); + QMetaObject::invokeMethod(qApp, "copyToClipboard", Q_ARG(QString, currentPath())); return; } @@ -874,14 +872,14 @@ QString AddressManager::getDomainID() const { return DependencyManager::get()->getDomainHandler().getUUID().toString(); } -void AddressManager::handleShareableNameAPIResponse(QNetworkReply& requestReply) { +void AddressManager::handleShareableNameAPIResponse(QNetworkReply* requestReply) { // make sure that this response is for the domain we're currently connected to auto domainID = DependencyManager::get()->getDomainHandler().getUUID(); - if (requestReply.url().toString().contains(uuidStringWithoutCurlyBraces(domainID))) { + if (requestReply->url().toString().contains(uuidStringWithoutCurlyBraces(domainID))) { // check for a name or default name in the API response - QJsonObject responseObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + QJsonObject responseObject = QJsonDocument::fromJson(requestReply->readAll()).object(); QJsonObject domainObject = responseObject["domain"].toObject(); const QString DOMAIN_NAME_KEY = "name"; @@ -917,7 +915,7 @@ void AddressManager::lookupShareableNameForDomainID(const QUuid& domainID) { // no error callback handling // in the case of an error we simply assume there is no default place name - callbackParams.jsonCallbackReceiver = this; + callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "handleShareableNameAPIResponse"; DependencyManager::get()->sendRequest(GET_DOMAIN_ID.arg(uuidStringWithoutCurlyBraces(domainID)), diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 38eb7ee670..37b85a9acd 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -417,13 +417,13 @@ signals: void goForwardPossible(bool isPossible); private slots: - void handleAPIResponse(QNetworkReply& requestReply); - void handleAPIError(QNetworkReply& errorReply); + void handleAPIResponse(QNetworkReply* requestReply); + void handleAPIError(QNetworkReply* errorReply); - void handleShareableNameAPIResponse(QNetworkReply& requestReply); + void handleShareableNameAPIResponse(QNetworkReply* requestReply); private: - void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply& reply); + void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply* reply); // Set host and port, and return `true` if it was changed. bool setHost(const QString& host, LookupTrigger trigger, quint16 port = 0); diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 871dc26899..39c8b5b1a1 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -475,13 +475,16 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { + if (_checkInPacketsSinceLastReply > MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { // we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS // so emit our signal that says that qCDebug(networking) << "Limit of silent domain checkins reached"; emit limitOfSilentDomainCheckInsReached(); + return true; + } else { + return false; } } diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 4d98391104..a428110db6 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -96,7 +96,7 @@ public: void softReset(); int getCheckInPacketsSinceLastReply() const { return _checkInPacketsSinceLastReply; } - void sentCheckInPacket(); + bool checkInPacketTimeout(); void clearPendingCheckins() { _checkInPacketsSinceLastReply = 0; } /**jsdoc diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 012a891698..502874fbfb 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -815,8 +815,13 @@ unsigned int LimitedNodeList::broadcastToNodes(std::unique_ptr packet, eachNode([&](const SharedNodePointer& node){ if (node && destinationNodeTypes.contains(node->getType())) { - sendUnreliablePacket(*packet, *node); - ++n; + if (packet->isReliable()) { + auto packetCopy = NLPacket::createCopy(*packet); + sendPacket(std::move(packetCopy), *node); + } else { + sendUnreliablePacket(*packet, *node); + } + ++n; } }); diff --git a/libraries/networking/src/NLPacket.cpp b/libraries/networking/src/NLPacket.cpp index f946e97bf4..620e60945b 100644 --- a/libraries/networking/src/NLPacket.cpp +++ b/libraries/networking/src/NLPacket.cpp @@ -199,7 +199,9 @@ void NLPacket::readVersion() { } void NLPacket::readSourceID() { - if (!PacketTypeEnum::getNonSourcedPackets().contains(_type)) { + if (PacketTypeEnum::getNonSourcedPackets().contains(_type)) { + _sourceID = NULL_LOCAL_ID; + } else { _sourceID = sourceIDInHeader(*this); } } diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 4920ea97c7..2ce734dd26 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -289,6 +289,12 @@ void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes) } void NodeList::sendDomainServerCheckIn() { + + if (!_sendDomainServerCheckInEnabled) { + qCDebug(networking) << "Refusing to send a domain-server check in while it is disabled."; + return; + } + if (thread() != QThread::currentThread()) { QMetaObject::invokeMethod(this, "sendDomainServerCheckIn", Qt::QueuedConnection); return; @@ -305,7 +311,8 @@ void NodeList::sendDomainServerCheckIn() { } else if (_domainHandler.getIP().isNull() && _domainHandler.requiresICE()) { qCDebug(networking) << "Waiting for ICE discovered domain-server socket. Will not send domain-server check in."; handleICEConnectionToDomainServer(); - } else if (!_domainHandler.getIP().isNull()) { + // let the domain handler know we are due to send a checkin packet + } else if (!_domainHandler.getIP().isNull() && !_domainHandler.checkInPacketTimeout()) { PacketType domainPacketType = !_domainHandler.isConnected() ? PacketType::DomainConnectRequest : PacketType::DomainListRequest; @@ -419,10 +426,15 @@ void NodeList::sendDomainServerCheckIn() { flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendDSCheckIn); + // Send duplicate check-ins in the exponentially increasing sequence 1, 1, 2, 4, ... + int outstandingCheckins = _domainHandler.getCheckInPacketsSinceLastReply(); + int checkinCount = outstandingCheckins > 1 ? std::pow(2, outstandingCheckins - 2) : 1; + for (int i = 1; i < checkinCount; ++i) { + auto packetCopy = domainPacket->createCopy(*domainPacket); + sendPacket(std::move(packetCopy), _domainHandler.getSockAddr()); + } sendPacket(std::move(domainPacket), _domainHandler.getSockAddr()); - - // let the domain handler know we sent another check in or connect packet - _domainHandler.sentCheckInPacket(); + } } @@ -549,7 +561,7 @@ void NodeList::handleICEConnectionToDomainServer() { _domainHandler.getICEClientID(), _domainHandler.getPendingDomainID()); } -} +} void NodeList::pingPunchForDomainServer() { // make sure if we're here that we actually still need to ping the domain-server diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index c5cf5e9524..e135bc937d 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -90,6 +90,9 @@ public: bool getRequestsDomainListData() { return _requestsDomainListData; } void setRequestsDomainListData(bool isRequesting); + bool getSendDomainServerCheckInEnabled() { return _sendDomainServerCheckInEnabled; } + void setSendDomainServerCheckInEnabled(bool enabled) { _sendDomainServerCheckInEnabled = enabled; } + void removeFromIgnoreMuteSets(const QUuid& nodeID); virtual bool isDomainServer() const override { return false; } @@ -169,6 +172,8 @@ private: QTimer _keepAlivePingTimer; bool _requestsDomainListData { false }; + bool _sendDomainServerCheckInEnabled { true }; + mutable QReadWriteLock _ignoredSetLock; tbb::concurrent_unordered_set _ignoredNodeIDs; mutable QReadWriteLock _personalMutedSetLock; @@ -180,7 +185,7 @@ private: #if defined(Q_OS_ANDROID) Setting::Handle _ignoreRadiusEnabled { "IgnoreRadiusEnabled", false }; #else - Setting::Handle _ignoreRadiusEnabled { "IgnoreRadiusEnabled", true }; + Setting::Handle _ignoreRadiusEnabled { "IgnoreRadiusEnabled", false }; // False, until such time as it is made to work better. #endif #if (PR_BUILD || DEV_BUILD) diff --git a/libraries/networking/src/PacketSender.cpp b/libraries/networking/src/PacketSender.cpp index 6288743c46..14cfde8d18 100644 --- a/libraries/networking/src/PacketSender.cpp +++ b/libraries/networking/src/PacketSender.cpp @@ -267,8 +267,6 @@ bool PacketSender::nonThreadedProcess() { // Keep average packets and time for "second half" of check interval _lastPPSCheck += (elapsedSinceLastCheck / 2); _packetsOverCheckInterval = (_packetsOverCheckInterval / 2); - - elapsedSinceLastCheck = now - _lastPPSCheck; } } @@ -296,12 +294,10 @@ bool PacketSender::nonThreadedProcess() { DependencyManager::get()->sendPacketList(std::move(packetPair.second.second), *packetPair.first); } - packetsSentThisCall += packetCount; _packetsOverCheckInterval += packetCount; _totalPacketsSent += packetCount; - _totalBytesSent += packetSize; emit packetSent(packetSize); // FIXME should include number of packets? _lastSendTime = now; diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 0b1334daba..d07420f87e 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -706,7 +706,16 @@ void Resource::handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTota } void Resource::handleReplyFinished() { - Q_ASSERT_X(_request, "Resource::handleReplyFinished", "Request should not be null while in handleReplyFinished"); + if (!_request || _request != sender()) { + // This can happen in the edge case that a request is timed out, but a `finished` signal is emitted before it is deleted. + qWarning(networking) << "Received signal Resource::handleReplyFinished from ResourceRequest that is not the current" + << " request: " << sender() << ", " << _request; + PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID), { + { "from_cache", false }, + { "size_mb", _bytesTotal / 1000000.0 } + }); + return; + } PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID), { { "from_cache", _request->loadedFromCache() }, @@ -715,15 +724,8 @@ void Resource::handleReplyFinished() { setSize(_bytesTotal); - if (!_request || _request != sender()) { - // This can happen in the edge case that a request is timed out, but a `finished` signal is emitted before it is deleted. - qWarning(networking) << "Received signal Resource::handleReplyFinished from ResourceRequest that is not the current" - << " request: " << sender() << ", " << _request; - return; - } - ResourceCache::requestCompleted(_self); - + auto result = _request->getResult(); if (result == ResourceRequest::Success) { auto extraInfo = _url == _activeUrl ? "" : QString(", %1").arg(_activeUrl.toDisplayString()); @@ -733,7 +735,7 @@ void Resource::handleReplyFinished() { if (!relativePathURL.isEmpty()) { _effectiveBaseURL = relativePathURL; } - + auto data = _request->getData(); emit loaded(data); downloadFinished(data); diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index 6df15129a2..553f0d0a61 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -39,8 +39,13 @@ ResourceManager::ResourceManager(bool atpSupportEnabled) : _atpSupportEnabled(at } ResourceManager::~ResourceManager() { - _thread.terminate(); - _thread.wait(); + if (_thread.isRunning()) { + _thread.quit(); + static const auto MAX_RESOURCE_MANAGER_THREAD_QUITTING_TIME = MSECS_PER_SECOND / 2; + if (!_thread.wait(MAX_RESOURCE_MANAGER_THREAD_QUITTING_TIME)) { + _thread.terminate(); + } + } } void ResourceManager::setUrlPrefixOverride(const QString& prefix, const QString& replacement) { diff --git a/libraries/networking/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h index 9372cfa667..a1737641c1 100644 --- a/libraries/networking/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -29,34 +29,16 @@ public: void addPacketStatsAndSendStatsPacket(QJsonObject statsObject); public slots: - // JSDoc: Overridden in Agent.h. /// threaded run of assignment virtual void run() = 0; - /**jsdoc - * @function Agent.stop - * @deprecated This function is being removed from the API. - */ Q_INVOKABLE virtual void stop() { setFinished(true); } - /**jsdoc - * @function Agent.sendStatsPacket - * @deprecated This function is being removed from the API. - */ virtual void sendStatsPacket(); - /**jsdoc - * @function Agent.clearQueuedCheckIns - * @deprecated This function is being removed from the API. - */ void clearQueuedCheckIns() { _numQueuedCheckIns = 0; } signals: - /**jsdoc - * @function Agent.finished - * @returns {Signal} - * @deprecated This function is being removed from the API. - */ void finished(); protected: @@ -68,10 +50,6 @@ protected: int _numQueuedCheckIns { 0 }; protected slots: - /**jsdoc - * @function Agent.domainSettingsRequestFailed - * @deprecated This function is being removed from the API. - */ void domainSettingsRequestFailed(); private slots: diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index 7a92d4bad9..a5ee417939 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -65,7 +65,7 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall // if no callbacks specified, call our owns if (params.isEmpty()) { - params.errorCallbackReceiver = this; + params.callbackReceiver = this; params.errorCallbackMethod = "requestError"; } @@ -75,8 +75,8 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall params, NULL, multipart); } -void UserActivityLogger::requestError(QNetworkReply& errorReply) { - qCDebug(networking) << errorReply.error() << "-" << errorReply.errorString(); +void UserActivityLogger::requestError(QNetworkReply* errorReply) { + qCDebug(networking) << errorReply->error() << "-" << errorReply->errorString(); } void UserActivityLogger::launch(QString applicationVersion, bool previousSessionCrashed, int previousSessionRuntime) { diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h index b44c60eba7..e4b91b1e81 100644 --- a/libraries/networking/src/UserActivityLogger.h +++ b/libraries/networking/src/UserActivityLogger.h @@ -50,7 +50,7 @@ public slots: void wentTo(AddressManager::LookupTrigger trigger, QString destinationType, QString destinationName); private slots: - void requestError(QNetworkReply& errorReply); + void requestError(QNetworkReply* errorReply); private: UserActivityLogger(); diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index c1fe6ccd85..0bc86a28ad 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -239,7 +239,7 @@ void Connection::sync() { sendACK(); } - if (_lossList.getLength() > 0) { + if (_congestionControl->shouldNAK() && _lossList.getLength() > 0) { // check if we need to re-transmit a loss list // we do this if it has been longer than the current nakInterval since we last sent auto now = p_high_resolution_clock::now(); @@ -271,10 +271,13 @@ void Connection::sendACK(bool wasCausedBySyncTimeout) { SequenceNumber nextACKNumber = nextACK(); Q_ASSERT_X(nextACKNumber >= _lastSentACK, "Connection::sendACK", "Sending lower ACK, something is wrong"); - - if (nextACKNumber == _lastSentACK) { - // We already sent this ACK, but check if we should re-send it. - if (nextACKNumber < _lastReceivedAcknowledgedACK) { + + // if our congestion control doesn't want to send an ACK for every packet received + // check if we already sent this ACK + if (_congestionControl->_ackInterval > 1 && nextACKNumber == _lastSentACK) { + + // if we use ACK2s, check if the receiving side already confirmed receipt of this ACK + if (_congestionControl->shouldACK2() && nextACKNumber < _lastReceivedAcknowledgedACK) { // we already got an ACK2 for this ACK we would be sending, don't bother return; } @@ -287,11 +290,11 @@ void Connection::sendACK(bool wasCausedBySyncTimeout) { } } // we have received new packets since the last sent ACK + // or our congestion control dictates that we always send ACKs // update the last sent ACK _lastSentACK = nextACKNumber; - _ackPacket->reset(); // We need to reset it every time. // pack in the ACK sub-sequence number @@ -448,20 +451,22 @@ bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, in // mark our last receive time as now (to push the potential expiry farther) _lastReceiveTime = p_high_resolution_clock::now(); - - // check if this is a packet pair we should estimate bandwidth from, or just a regular packet - if (((uint32_t) sequenceNumber & 0xF) == 0) { - _receiveWindow.onProbePair1Arrival(); - } else if (((uint32_t) sequenceNumber & 0xF) == 1) { - // only use this packet for bandwidth estimation if we didn't just receive a control packet in its place - if (!_receivedControlProbeTail) { - _receiveWindow.onProbePair2Arrival(); - } else { - // reset our control probe tail marker so the next probe that comes with data can be used - _receivedControlProbeTail = false; + + if (_congestionControl->shouldProbe()) { + // check if this is a packet pair we should estimate bandwidth from, or just a regular packet + if (((uint32_t) sequenceNumber & 0xF) == 0) { + _receiveWindow.onProbePair1Arrival(); + } else if (((uint32_t) sequenceNumber & 0xF) == 1) { + // only use this packet for bandwidth estimation if we didn't just receive a control packet in its place + if (!_receivedControlProbeTail) { + _receiveWindow.onProbePair2Arrival(); + } else { + // reset our control probe tail marker so the next probe that comes with data can be used + _receivedControlProbeTail = false; + } } - } + _receiveWindow.onPacketArrival(); // If this is not the next sequence number, report loss diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 253a17c6f9..d0bd7fc872 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -33,14 +33,14 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return static_cast(EntityVersion::CollisionMask16Bytes); + return static_cast(EntityVersion::ParticleSpin); case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::ConicalFrustums); case PacketType::AvatarIdentity: case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::FixMannequinDefaultAvatarFeet); + return static_cast(AvatarMixerPacketVersion::ProceduralFaceMovementFlagsAndBlendshapes); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); // ICE packets @@ -92,6 +92,8 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(PingVersion::IncludeConnectionID); case PacketType::AvatarQuery: return static_cast(AvatarQueryVersion::ConicalFrustums); + case PacketType::AvatarIdentityRequest: + return 22; default: return 21; } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 9faa92725f..2ffadfef4b 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -57,7 +57,7 @@ public: ICEServerQuery, OctreeStats, UNUSED_PACKET_TYPE_1, - UNUSED_PACKET_TYPE_2, + AvatarIdentityRequest, AssignmentClientStatus, NoisyMute, AvatarIdentity, @@ -236,7 +236,10 @@ enum class EntityVersion : PacketVersion { ShadowControl, MaterialData, CloneableData, - CollisionMask16Bytes + CollisionMask16Bytes, + YieldSimulationOwnership, + ParticleEntityFix, + ParticleSpin }; enum class EntityScriptCallMethodVersion : PacketVersion { @@ -283,7 +286,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { UpdatedMannequinDefaultAvatar, AvatarJointDefaultPoseFlags, FBXReaderNodeReparenting, - FixMannequinDefaultAvatarFeet + FixMannequinDefaultAvatarFeet, + ProceduralFaceMovementFlagsAndBlendshapes }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 4d4303698b..a068a806ec 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -229,7 +229,7 @@ qint64 Socket::writeDatagram(const QByteArray& datagram, const HifiSockAddr& soc if (bytesWritten < 0) { // when saturating a link this isn't an uncommon message - suppress it so it doesn't bomb the debug - HIFI_FCDEBUG(networking(), "Socket::writeDatagram" << _udpSocket.error() << "-" << qPrintable(_udpSocket.errorString()) ); + HIFI_FCDEBUG(networking(), "Socket::writeDatagram" << _udpSocket.error()); } return bytesWritten; @@ -513,7 +513,7 @@ std::vector Socket::getConnectionSockAddrs() { } void Socket::handleSocketError(QAbstractSocket::SocketError socketError) { - HIFI_FCDEBUG(networking(), "udt::Socket error - " << socketError << _udpSocket.errorString()); + HIFI_FCDEBUG(networking(), "udt::Socket error - " << socketError); } void Socket::handleStateChanged(QAbstractSocket::SocketState socketState) { diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 5f943fabf2..9bb0e25982 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -400,7 +401,6 @@ int Octree::readElementData(const OctreeElementPointer& destinationElement, cons // tell the element to read the subsequent data int rootDataSize = _rootElement->readElementDataFromBuffer(nodeData + bytesRead, bytesLeftToRead, args); bytesRead += rootDataSize; - bytesLeftToRead -= rootDataSize; } return bytesRead; @@ -972,12 +972,19 @@ bool Octree::writeToJSONFile(const char* fileName, const OctreeElementPointer& e return false; } - QFile persistFile(fileName); + QSaveFile persistFile(fileName); bool success = false; if (persistFile.open(QIODevice::WriteOnly)) { - success = persistFile.write(jsonDataForFile) != -1; + if (persistFile.write(jsonDataForFile) != -1) { + success = persistFile.commit(); + if (!success) { + qCritical() << "Failed to commit to JSON save file:" << persistFile.errorString(); + } + } else { + qCritical("Failed to write to JSON file."); + } } else { - qCritical("Could not write to JSON description of entities."); + qCritical("Failed to open JSON file for writing."); } return success; diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index b7857c3e6c..e9e7504e24 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -99,7 +99,7 @@ public: virtual bool deleteApproved() const { return true; } - virtual bool canRayIntersect() const { return isLeaf(); } + virtual bool canPickIntersect() const { return isLeaf(); } /// \param center center of sphere in meters /// \param radius radius of sphere in meters /// \param[out] penetration pointing into cube from sphere diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index 3dc051675d..8b1d766418 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -35,32 +36,152 @@ #include "OctreeUtils.h" #include "OctreeDataUtils.h" -const int OctreePersistThread::DEFAULT_PERSIST_INTERVAL = 1000 * 30; // every 30 seconds +constexpr std::chrono::seconds OctreePersistThread::DEFAULT_PERSIST_INTERVAL { 30 }; +constexpr std::chrono::milliseconds TIME_BETWEEN_PROCESSING { 10 }; -OctreePersistThread::OctreePersistThread(OctreePointer tree, const QString& filename, const QString& backupDirectory, int persistInterval, - bool wantBackup, const QJsonObject& settings, bool debugTimestampNow, - QString persistAsFileType, const QByteArray& replacementData) : +constexpr int MAX_OCTREE_REPLACEMENT_BACKUP_FILES_COUNT { 20 }; +constexpr int64_t MAX_OCTREE_REPLACEMENT_BACKUP_FILES_SIZE_BYTES { 50 * 1000 * 1000 }; + +OctreePersistThread::OctreePersistThread(OctreePointer tree, const QString& filename, std::chrono::milliseconds persistInterval, + bool debugTimestampNow, QString persistAsFileType) : _tree(tree), _filename(filename), - _backupDirectory(backupDirectory), _persistInterval(persistInterval), + _lastPersistCheck(std::chrono::steady_clock::now()), _initialLoadComplete(false), - _replacementData(replacementData), _loadTimeUSecs(0), - _lastCheck(0), - _wantBackup(wantBackup), _debugTimestampNow(debugTimestampNow), _lastTimeDebug(0), _persistAsFileType(persistAsFileType) { - parseSettings(settings); - - // in case the persist filename has an extension that doesn't match the file type QString sansExt = fileNameWithoutExtension(_filename, PERSIST_EXTENSIONS); _filename = sansExt + "." + _persistAsFileType; } +void OctreePersistThread::start() { + cleanupOldReplacementBackups(); + + auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); + packetReceiver.registerListener(PacketType::OctreeDataFileReply, this, "handleOctreeDataFileReply"); + + auto nodeList = DependencyManager::get(); + const DomainHandler& domainHandler = nodeList->getDomainHandler(); + + auto packet = NLPacket::create(PacketType::OctreeDataFileRequest, -1, true, false); + + OctreeUtils::RawOctreeData data; + qCDebug(octree) << "Reading octree data from" << _filename; + if (data.readOctreeDataInfoFromFile(_filename)) { + qCDebug(octree) << "Current octree data: ID(" << data.id << ") DataVersion(" << data.version << ")"; + packet->writePrimitive(true); + auto id = data.id.toRfc4122(); + packet->write(id); + packet->writePrimitive(data.version); + } else { + qCWarning(octree) << "No octree data found"; + packet->writePrimitive(false); + } + + qCDebug(octree) << "Sending OctreeDataFileRequest to DS"; + nodeList->sendPacket(std::move(packet), domainHandler.getSockAddr()); +} + +void OctreePersistThread::handleOctreeDataFileReply(QSharedPointer message) { + if (_initialLoadComplete) { + qCWarning(octree) << "Received OctreeDataFileReply after initial load had completed"; + return; + } + + bool includesNewData; + message->readPrimitive(&includesNewData); + QByteArray replacementData; + OctreeUtils::RawOctreeData data; + bool hasValidOctreeData { false }; + if (includesNewData) { + replacementData = message->readAll(); + replaceData(replacementData); + hasValidOctreeData = data.readOctreeDataInfoFromFile(_filename); + qDebug() << "Got OctreeDataFileReply, new data sent"; + } else { + qDebug() << "Got OctreeDataFileReply, current entity data is sufficient"; + + OctreeUtils::RawEntityData data; + qCDebug(octree) << "Reading octree data from" << _filename; + if (data.readOctreeDataInfoFromFile(_filename)) { + hasValidOctreeData = true; + if (data.id.isNull()) { + qCDebug(octree) << "Current octree data has a null id, updating"; + data.resetIdAndVersion(); + + QFile file(_filename); + if (file.open(QIODevice::WriteOnly)) { + auto entityData = data.toGzippedByteArray(); + file.write(entityData); + file.close(); + } else { + qCDebug(octree) << "Failed to update octree data"; + } + } + } + } + + quint64 loadStarted = usecTimestampNow(); + qCDebug(octree) << "loading Octrees from file: " << _filename << "..."; + + if (hasValidOctreeData) { + qDebug() << "Setting entity version info to: " << data.id << data.version; + _tree->setOctreeVersionInfo(data.id, data.version); + } + + bool persistentFileRead; + + _tree->withWriteLock([&] { + PerformanceWarning warn(true, "Loading Octree File", true); + + persistentFileRead = _tree->readFromFile(_filename.toLocal8Bit().constData()); + _tree->pruneTree(); + }); + + quint64 loadDone = usecTimestampNow(); + _loadTimeUSecs = loadDone - loadStarted; + + _tree->clearDirtyBit(); // the tree is clean since we just loaded it + qCDebug(octree, "DONE loading Octrees from file... fileRead=%s", debug::valueOf(persistentFileRead)); + + unsigned long nodeCount = OctreeElement::getNodeCount(); + unsigned long internalNodeCount = OctreeElement::getInternalNodeCount(); + unsigned long leafNodeCount = OctreeElement::getLeafNodeCount(); + qCDebug(octree, "Nodes after loading scene %lu nodes %lu internal %lu leaves", nodeCount, internalNodeCount, leafNodeCount); + + bool wantDebug = false; + if (wantDebug) { + double usecPerGet = (double)OctreeElement::getGetChildAtIndexTime() + / (double)OctreeElement::getGetChildAtIndexCalls(); + qCDebug(octree) << "getChildAtIndexCalls=" << OctreeElement::getGetChildAtIndexCalls() + << " getChildAtIndexTime=" << OctreeElement::getGetChildAtIndexTime() << " perGet=" << usecPerGet; + + double usecPerSet = (double)OctreeElement::getSetChildAtIndexTime() + / (double)OctreeElement::getSetChildAtIndexCalls(); + qCDebug(octree) << "setChildAtIndexCalls=" << OctreeElement::getSetChildAtIndexCalls() + << " setChildAtIndexTime=" << OctreeElement::getSetChildAtIndexTime() << " perSet=" << usecPerSet; + } + + _initialLoadComplete = true; + + // Since we just loaded the persistent file, we can consider ourselves as having just persisted + _lastPersistCheck = std::chrono::steady_clock::now(); + + if (replacementData.isNull()) { + sendLatestEntityDataToDS(); + } + + QTimer::singleShot(TIME_BETWEEN_PROCESSING.count(), this, &OctreePersistThread::process); + + emit loadCompleted(); +} + + QString OctreePersistThread::getPersistFileMimeType() const { if (_persistAsFileType == "json") { return "application/json"; @@ -70,72 +191,6 @@ QString OctreePersistThread::getPersistFileMimeType() const { return ""; } -void OctreePersistThread::parseSettings(const QJsonObject& settings) { - if (settings["backups"].isArray()) { - const QJsonArray& backupRules = settings["backups"].toArray(); - qCDebug(octree) << "BACKUP RULES:"; - - foreach (const QJsonValue& value, backupRules) { - - QJsonObject obj = value.toObject(); - - int interval = 0; - int count = 0; - - QJsonValue intervalVal = obj["backupInterval"]; - if (intervalVal.isString()) { - interval = intervalVal.toString().toInt(); - } else { - interval = intervalVal.toInt(); - } - - QJsonValue countVal = obj["maxBackupVersions"]; - if (countVal.isString()) { - count = countVal.toString().toInt(); - } else { - count = countVal.toInt(); - } - - qCDebug(octree) << " Name:" << obj["Name"].toString(); - qCDebug(octree) << " format:" << obj["format"].toString(); - qCDebug(octree) << " interval:" << interval; - qCDebug(octree) << " count:" << count; - - BackupRule newRule = { obj["Name"].toString(), interval, obj["format"].toString(), count, 0}; - - newRule.lastBackup = getMostRecentBackupTimeInUsecs(obj["format"].toString()); - - if (newRule.lastBackup > 0) { - quint64 now = usecTimestampNow(); - quint64 sinceLastBackup = now - newRule.lastBackup; - qCDebug(octree) << " lastBackup:" << qPrintable(formatUsecTime(sinceLastBackup)) << "ago"; - } else { - qCDebug(octree) << " lastBackup: NEVER"; - } - - _backupRules << newRule; - } - } else { - qCDebug(octree) << "BACKUP RULES: NONE"; - } -} - -quint64 OctreePersistThread::getMostRecentBackupTimeInUsecs(const QString& format) { - - quint64 mostRecentBackupInUsecs = 0; - - QString mostRecentBackupFileName; - QDateTime mostRecentBackupTime; - - bool recentBackup = getMostRecentBackup(format, mostRecentBackupFileName, mostRecentBackupTime); - - if (recentBackup) { - mostRecentBackupInUsecs = mostRecentBackupTime.toMSecsSinceEpoch() * USECS_PER_MSEC; - } - - return mostRecentBackupInUsecs; -} - void OctreePersistThread::replaceData(QByteArray data) { backupCurrentFile(); @@ -167,123 +222,24 @@ bool OctreePersistThread::backupCurrentFile() { return true; } -bool OctreePersistThread::process() { +void OctreePersistThread::process() { + _tree->update(); - if (!_initialLoadComplete) { - quint64 loadStarted = usecTimestampNow(); - qCDebug(octree) << "loading Octrees from file: " << _filename << "..."; + auto now = std::chrono::steady_clock::now(); + auto timeSinceLastPersist = now - _lastPersistCheck; - if (!_replacementData.isNull()) { - replaceData(_replacementData); - } - - OctreeUtils::RawOctreeData data; - if (data.readOctreeDataInfoFromFile(_filename)) { - qDebug() << "Setting entity version info to: " << data.id << data.dataVersion; - _tree->setOctreeVersionInfo(data.id, data.dataVersion); - } - - bool persistentFileRead; - - _tree->withWriteLock([&] { - PerformanceWarning warn(true, "Loading Octree File", true); - - // First check to make sure "lock" file doesn't exist. If it does exist, then - // our last save crashed during the save, and we want to load our most recent backup. - QString lockFileName = _filename + ".lock"; - std::ifstream lockFile(qPrintable(lockFileName), std::ios::in | std::ios::binary | std::ios::ate); - if (lockFile.is_open()) { - qCDebug(octree) << "WARNING: Octree lock file detected at startup:" << lockFileName; - - lockFile.close(); - qCDebug(octree) << "Removing lock file:" << lockFileName; - remove(qPrintable(lockFileName)); - qCDebug(octree) << "Lock file removed:" << lockFileName; - } - - persistentFileRead = _tree->readFromFile(qPrintable(_filename.toLocal8Bit())); - _tree->pruneTree(); - }); - - quint64 loadDone = usecTimestampNow(); - _loadTimeUSecs = loadDone - loadStarted; - - _tree->clearDirtyBit(); // the tree is clean since we just loaded it - qCDebug(octree, "DONE loading Octrees from file... fileRead=%s", debug::valueOf(persistentFileRead)); - - unsigned long nodeCount = OctreeElement::getNodeCount(); - unsigned long internalNodeCount = OctreeElement::getInternalNodeCount(); - unsigned long leafNodeCount = OctreeElement::getLeafNodeCount(); - qCDebug(octree, "Nodes after loading scene %lu nodes %lu internal %lu leaves", nodeCount, internalNodeCount, leafNodeCount); - - bool wantDebug = false; - if (wantDebug) { - double usecPerGet = (double)OctreeElement::getGetChildAtIndexTime() - / (double)OctreeElement::getGetChildAtIndexCalls(); - qCDebug(octree) << "getChildAtIndexCalls=" << OctreeElement::getGetChildAtIndexCalls() - << " getChildAtIndexTime=" << OctreeElement::getGetChildAtIndexTime() << " perGet=" << usecPerGet; - - double usecPerSet = (double)OctreeElement::getSetChildAtIndexTime() - / (double)OctreeElement::getSetChildAtIndexCalls(); - qCDebug(octree) << "setChildAtIndexCalls=" << OctreeElement::getSetChildAtIndexCalls() - << " setChildAtIndexTime=" << OctreeElement::getSetChildAtIndexTime() << " perSet=" << usecPerSet; - } - - _initialLoadComplete = true; - - // Since we just loaded the persistent file, we can consider ourselves as having "just checked" for persistance. - _lastCheck = usecTimestampNow(); // we just loaded, no need to save again - - // This last persist time is not really used until the file is actually persisted. It is only - // used in formatting the backup filename in cases of non-rolling backup names. However, we don't - // want an uninitialized value for this, so we set it to the current time (startup of the server) - time(&_lastPersistTime); - - if (_replacementData.isNull()) { - sendLatestEntityDataToDS(); - } - _replacementData.clear(); - - emit loadCompleted(); + if (timeSinceLastPersist > _persistInterval) { + _lastPersistCheck = now; + persist(); } - if (isStillRunning()) { - quint64 MSECS_TO_USECS = 1000; - quint64 USECS_TO_SLEEP = 10 * MSECS_TO_USECS; // every 10ms - std::this_thread::sleep_for(std::chrono::microseconds(USECS_TO_SLEEP)); - - // do our updates then check to save... - _tree->update(); - - quint64 now = usecTimestampNow(); - quint64 sinceLastSave = now - _lastCheck; - quint64 intervalToCheck = _persistInterval * MSECS_TO_USECS; - - if (sinceLastSave > intervalToCheck) { - _lastCheck = now; - persist(); - } - } - - // if we were asked to debugTimestampNow do that now... - if (_debugTimestampNow) { - quint64 now = usecTimestampNow(); - quint64 sinceLastDebug = now - _lastTimeDebug; - quint64 DEBUG_TIMESTAMP_INTERVAL = 600000000; // every 10 minutes - - if (sinceLastDebug > DEBUG_TIMESTAMP_INTERVAL) { - _lastTimeDebug = usecTimestampNow(true); // ask for debug output - } - - } - return isStillRunning(); // keep running till they terminate us + QTimer::singleShot(TIME_BETWEEN_PROCESSING.count(), this, &OctreePersistThread::process); } void OctreePersistThread::aboutToFinish() { qCDebug(octree) << "Persist thread about to finish..."; persist(); qCDebug(octree) << "Persist thread done with about to finish..."; - _stopThread = true; } QByteArray OctreePersistThread::getPersistFileContents() const { @@ -295,6 +251,36 @@ QByteArray OctreePersistThread::getPersistFileContents() const { return fileContents; } +void OctreePersistThread::cleanupOldReplacementBackups() { + QRegExp filenameRegex { ".*\\.backup\\.\\d{8}-\\d{6}$" }; + QFileInfo persistFile { _filename }; + QDir backupDir { persistFile.absolutePath() }; + backupDir.setSorting(QDir::SortFlag::Time); + backupDir.setFilter(QDir::Filter::Files); + qDebug() << "Scanning backups for cleanup:" << backupDir.absolutePath(); + + int count = 0; + int64_t totalSize = 0; + for (auto fileInfo : backupDir.entryInfoList()) { + auto absPath = fileInfo.absoluteFilePath(); + qDebug() << " Found:" << absPath; + if (filenameRegex.exactMatch(absPath)) { + if (count >= MAX_OCTREE_REPLACEMENT_BACKUP_FILES_COUNT || totalSize > MAX_OCTREE_REPLACEMENT_BACKUP_FILES_SIZE_BYTES) { + qDebug() << " Removing:" << absPath; + QFile backup(absPath); + if (backup.remove()) { + qDebug() << " Removed backup:" << absPath; + } else { + qWarning() << " Failed to remove backup:" << absPath; + } + } + totalSize += fileInfo.size(); + count++; + } + } + qDebug() << "Found" << count << "backups"; +} + void OctreePersistThread::persist() { if (_tree->isDirty() && _initialLoadComplete) { @@ -304,27 +290,14 @@ void OctreePersistThread::persist() { qCDebug(octree) << "DONE pruning Octree before saving..."; }); - qCDebug(octree) << "persist operation calling backup..."; - backup(); // handle backup if requested - qCDebug(octree) << "persist operation DONE with backup..."; - _tree->incrementPersistDataVersion(); - // create our "lock" file to indicate we're saving. - QString lockFileName = _filename + ".lock"; - std::ofstream lockFile(qPrintable(lockFileName), std::ios::out|std::ios::binary); - if(lockFile.is_open()) { - qCDebug(octree) << "saving Octree lock file created at:" << lockFileName; - - _tree->writeToFile(qPrintable(_filename), NULL, _persistAsFileType); - time(&_lastPersistTime); + qCDebug(octree) << "Saving Octree data to:" << _filename; + if (_tree->writeToFile(_filename.toLocal8Bit().constData(), nullptr, _persistAsFileType)) { _tree->clearDirtyBit(); // tree is clean after saving - qCDebug(octree) << "DONE saving Octree to file..."; - - lockFile.close(); - qCDebug(octree) << "saving Octree lock file closed:" << lockFileName; - remove(qPrintable(lockFileName)); - qCDebug(octree) << "saving Octree lock file removed:" << lockFileName; + qCDebug(octree) << "DONE persisting Octree data to" << _filename; + } else { + qCWarning(octree) << "Failed to persist Octree data to" << _filename; } sendLatestEntityDataToDS(); @@ -345,197 +318,3 @@ void OctreePersistThread::sendLatestEntityDataToDS() { qCWarning(octree) << "Failed to persist octree to DS"; } } - -void OctreePersistThread::restoreFromMostRecentBackup() { - qCDebug(octree) << "Restoring from most recent backup..."; - - QString mostRecentBackupFileName; - QDateTime mostRecentBackupTime; - - bool recentBackup = getMostRecentBackup(QString(""), mostRecentBackupFileName, mostRecentBackupTime); - - // If we found a backup file, restore from that file. - if (recentBackup) { - qCDebug(octree) << "BEST backup file:" << mostRecentBackupFileName << " last modified:" << mostRecentBackupTime.toString(); - - qCDebug(octree) << "Removing old file:" << _filename; - remove(qPrintable(_filename)); - - qCDebug(octree) << "Restoring backup file " << mostRecentBackupFileName << "..."; - bool result = QFile::copy(mostRecentBackupFileName, _filename); - if (result) { - qCDebug(octree) << "DONE restoring backup file " << mostRecentBackupFileName << "to" << _filename << "..."; - } else { - qCDebug(octree) << "ERROR while restoring backup file " << mostRecentBackupFileName << "to" << _filename << "..."; - perror("ERROR while restoring backup file"); - } - } else { - qCDebug(octree) << "NO BEST backup file found."; - } -} - -bool OctreePersistThread::getMostRecentBackup(const QString& format, - QString& mostRecentBackupFileName, QDateTime& mostRecentBackupTime) { - - // Based on our backup file name, determine the path and file name pattern for backup files - QFileInfo persistFileInfo(_filename); - QString path = _backupDirectory; - QString fileNamePart = persistFileInfo.fileName(); - - QStringList filters; - - if (format.isEmpty()) { - // Create a file filter that will find all backup files of this extension format - foreach(const BackupRule& rule, _backupRules) { - QString backupExtension = rule.extensionFormat; - backupExtension.replace(QRegExp("%."), "*"); - QString backupFileNamePart = fileNamePart + backupExtension; - filters << backupFileNamePart; - } - } else { - QString backupExtension = format; - backupExtension.replace(QRegExp("%."), "*"); - QString backupFileNamePart = fileNamePart + backupExtension; - filters << backupFileNamePart; - } - - bool bestBackupFound = false; - QString bestBackupFile; - QDateTime bestBackupFileTime; - - // Iterate over all of the backup files in the persist location - QDirIterator dirIterator(path, filters, QDir::Files|QDir::NoSymLinks, QDirIterator::NoIteratorFlags); - while(dirIterator.hasNext()) { - - dirIterator.next(); - QDateTime lastModified = dirIterator.fileInfo().lastModified(); - - // Based on last modified date, track the most recently modified file as the best backup - if (lastModified > bestBackupFileTime) { - bestBackupFound = true; - bestBackupFile = dirIterator.filePath(); - bestBackupFileTime = lastModified; - } - } - - // If we found a backup then return the results - if (bestBackupFound) { - mostRecentBackupFileName = bestBackupFile; - mostRecentBackupTime = bestBackupFileTime; - } - return bestBackupFound; -} - -void OctreePersistThread::rollOldBackupVersions(const BackupRule& rule) { - - if (rule.extensionFormat.contains("%N")) { - if (rule.maxBackupVersions > 0) { - qCDebug(octree) << "Rolling old backup versions for rule" << rule.name << "..."; - - QString backupFileName = _backupDirectory + "/" + QUrl(_filename).fileName(); - - // Delete maximum rolling file because rename() fails on Windows if target exists - QString backupMaxExtensionN = rule.extensionFormat; - backupMaxExtensionN.replace(QString("%N"), QString::number(rule.maxBackupVersions)); - QString backupMaxFilenameN = backupFileName + backupMaxExtensionN; - QFile backupMaxFileN(backupMaxFilenameN); - if (backupMaxFileN.exists()) { - int result = remove(qPrintable(backupMaxFilenameN)); - if (result != 0) { - qCDebug(octree) << "ERROR deleting old backup file " << backupMaxFilenameN; - } - } - - for(int n = rule.maxBackupVersions - 1; n > 0; n--) { - QString backupExtensionN = rule.extensionFormat; - QString backupExtensionNplusOne = rule.extensionFormat; - backupExtensionN.replace(QString("%N"), QString::number(n)); - backupExtensionNplusOne.replace(QString("%N"), QString::number(n+1)); - - QString backupFilenameN = findMostRecentFileExtension(backupFileName, PERSIST_EXTENSIONS) + backupExtensionN; - QString backupFilenameNplusOne = backupFileName + backupExtensionNplusOne; - - QFile backupFileN(backupFilenameN); - - if (backupFileN.exists()) { - qCDebug(octree) << "rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "..."; - int result = rename(qPrintable(backupFilenameN), qPrintable(backupFilenameNplusOne)); - if (result == 0) { - qCDebug(octree) << "DONE rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "..."; - } else { - qCDebug(octree) << "ERROR in rolling backup file " << backupFilenameN << "to" << backupFilenameNplusOne << "..."; - perror("ERROR in rolling backup file"); - } - } - } - qCDebug(octree) << "Done rolling old backup versions..."; - } else { - qCDebug(octree) << "Rolling backups for rule" << rule.name << "." - << " Max Rolled Backup Versions less than 1 [" << rule.maxBackupVersions << "]." - << " No need to roll backups..."; - } - } -} - -void OctreePersistThread::backup() { - qCDebug(octree) << "backup operation wantBackup:" << _wantBackup; - if (_wantBackup) { - quint64 now = usecTimestampNow(); - - for(int i = 0; i < _backupRules.count(); i++) { - BackupRule& rule = _backupRules[i]; - - quint64 sinceLastBackup = now - rule.lastBackup; - quint64 SECS_TO_USECS = 1000 * 1000; - quint64 intervalToBackup = rule.interval * SECS_TO_USECS; - - qCDebug(octree) << "Checking [" << rule.name << "] - Time since last backup [" << sinceLastBackup << "] " << - "compared to backup interval [" << intervalToBackup << "]..."; - - if (sinceLastBackup > intervalToBackup) { - qCDebug(octree) << "Time since last backup [" << sinceLastBackup << "] for rule [" << rule.name - << "] exceeds backup interval [" << intervalToBackup << "] doing backup now..."; - - struct tm* localTime = localtime(&_lastPersistTime); - - QString backupFileName = _backupDirectory + "/" + QUrl(_filename).fileName(); - - // check to see if they asked for version rolling format - if (rule.extensionFormat.contains("%N")) { - rollOldBackupVersions(rule); // rename all the old backup files accordingly - QString backupExtension = rule.extensionFormat; - backupExtension.replace(QString("%N"), QString("1")); - backupFileName += backupExtension; - } else { - char backupExtension[256]; - strftime(backupExtension, sizeof(backupExtension), qPrintable(rule.extensionFormat), localTime); - backupFileName += backupExtension; - } - - if (rule.maxBackupVersions > 0) { - QFile persistFile(_filename); - if (persistFile.exists()) { - qCDebug(octree) << "backing up persist file " << _filename << "to" << backupFileName << "..."; - bool result = QFile::copy(_filename, backupFileName); - if (result) { - qCDebug(octree) << "DONE backing up persist file..."; - rule.lastBackup = now; // only record successful backup in this case. - } else { - qCDebug(octree) << "ERROR in backing up persist file..."; - perror("ERROR in backing up persist file"); - } - } else { - qCDebug(octree) << "persist file " << _filename << " does not exist. " << - "nothing to backup for this rule ["<< rule.name << "]..."; - } - } else { - qCDebug(octree) << "This backup rule" << rule.name - << " has Max Rolled Backup Versions less than 1 [" << rule.maxBackupVersions << "]." - << " There are no backups to be done..."; - } - } else { - qCDebug(octree) << "Backup not needed for this rule ["<< rule.name << "]..."; - } - } - } -} diff --git a/libraries/octree/src/OctreePersistThread.h b/libraries/octree/src/OctreePersistThread.h index bde207001f..0044a8fa5a 100644 --- a/libraries/octree/src/OctreePersistThread.h +++ b/libraries/octree/src/OctreePersistThread.h @@ -18,7 +18,7 @@ #include #include "Octree.h" -class OctreePersistThread : public GenericThread { +class OctreePersistThread : public QObject { Q_OBJECT public: class BackupRule { @@ -30,37 +30,37 @@ public: quint64 lastBackup; }; - static const int DEFAULT_PERSIST_INTERVAL; + static const std::chrono::seconds DEFAULT_PERSIST_INTERVAL; - OctreePersistThread(OctreePointer tree, const QString& filename, const QString& backupDirectory, - int persistInterval = DEFAULT_PERSIST_INTERVAL, bool wantBackup = false, - const QJsonObject& settings = QJsonObject(), bool debugTimestampNow = false, - QString persistAsFileType = "json.gz", const QByteArray& replacementData = QByteArray()); + OctreePersistThread(OctreePointer tree, + const QString& filename, + std::chrono::milliseconds persistInterval = DEFAULT_PERSIST_INTERVAL, + bool debugTimestampNow = false, + QString persistAsFileType = "json.gz"); bool isInitialLoadComplete() const { return _initialLoadComplete; } quint64 getLoadElapsedTime() const { return _loadTimeUSecs; } - void aboutToFinish(); /// call this to inform the persist thread that the owner is about to finish to support final persist - QString getPersistFilename() const { return _filename; } QString getPersistFileMimeType() const; QByteArray getPersistFileContents() const; + void aboutToFinish(); /// call this to inform the persist thread that the owner is about to finish to support final persist + +public slots: + void start(); + signals: void loadCompleted(); -protected: - /// Implements generic processing behavior for this thread. - virtual bool process() override; +protected slots: + void process(); + void handleOctreeDataFileReply(QSharedPointer message); +protected: void persist(); - void backup(); - void rollOldBackupVersions(const BackupRule& rule); - void restoreFromMostRecentBackup(); - bool getMostRecentBackup(const QString& format, QString& mostRecentBackupFileName, QDateTime& mostRecentBackupTime); - quint64 getMostRecentBackupTimeInUsecs(const QString& format); - void parseSettings(const QJsonObject& settings); bool backupCurrentFile(); + void cleanupOldReplacementBackups(); void replaceData(QByteArray data); void sendLatestEntityDataToDS(); @@ -68,18 +68,12 @@ protected: private: OctreePointer _tree; QString _filename; - QString _backupDirectory; - int _persistInterval; + std::chrono::milliseconds _persistInterval; + std::chrono::steady_clock::time_point _lastPersistCheck; bool _initialLoadComplete; - QByteArray _replacementData; quint64 _loadTimeUSecs; - time_t _lastPersistTime; - quint64 _lastCheck; - bool _wantBackup; - QVector _backupRules; - bool _debugTimestampNow; quint64 _lastTimeDebug; diff --git a/libraries/octree/src/OctreeProcessor.cpp b/libraries/octree/src/OctreeProcessor.cpp index db78e985e6..beaac1198c 100644 --- a/libraries/octree/src/OctreeProcessor.cpp +++ b/libraries/octree/src/OctreeProcessor.cpp @@ -20,12 +20,6 @@ #include "OctreeLogging.h" -OctreeProcessor::OctreeProcessor() : - _tree(NULL), - _managedTree(false) -{ -} - void OctreeProcessor::init() { if (!_tree) { _tree = createTree(); @@ -34,6 +28,9 @@ void OctreeProcessor::init() { } OctreeProcessor::~OctreeProcessor() { + if (_tree) { + _tree->eraseAllOctreeElements(false); + } } void OctreeProcessor::setTree(OctreePointer newTree) { diff --git a/libraries/octree/src/OctreeProcessor.h b/libraries/octree/src/OctreeProcessor.h index 25e280abca..325b33cd15 100644 --- a/libraries/octree/src/OctreeProcessor.h +++ b/libraries/octree/src/OctreeProcessor.h @@ -28,7 +28,6 @@ class OctreeProcessor : public QObject, public QEnableSharedFromThis { Q_OBJECT public: - OctreeProcessor(); virtual ~OctreeProcessor(); virtual char getMyNodeType() const = 0; @@ -61,7 +60,7 @@ protected: virtual OctreePointer createTree() = 0; OctreePointer _tree; - bool _managedTree; + bool _managedTree { false }; SimpleMovingAverage _elementsPerPacket; SimpleMovingAverage _entitiesPerPacket; diff --git a/libraries/physics/CMakeLists.txt b/libraries/physics/CMakeLists.txt index ad082c1a6e..96a55c8477 100644 --- a/libraries/physics/CMakeLists.txt +++ b/libraries/physics/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME physics) setup_hifi_library() -link_hifi_libraries(shared fbx entities graphics) +link_hifi_libraries(shared task workload fbx entities graphics) include_hifi_library_headers(networking) include_hifi_library_headers(gpu) include_hifi_library_headers(avatars) diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 64eda975cf..cee0e6a1fa 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -262,23 +262,53 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar btVector3 linearDisplacement = clampLength(vel * dt, MAX_DISPLACEMENT); // clamp displacement to prevent tunneling. btVector3 endPos = startPos + linearDisplacement; + // resolve the simple linearDisplacement + _followLinearDisplacement += linearDisplacement; + + // now for the rotational part... btQuaternion startRot = bodyTransform.getRotation(); btQuaternion desiredRot = _followDesiredBodyTransform.getRotation(); - if (desiredRot.dot(startRot) < 0.0f) { - desiredRot = -desiredRot; + + // startRot as default rotation + btQuaternion endRot = startRot; + + // the dot product between two quaternions is equal to +/- cos(angle/2) + // where 'angle' is that of the rotation between them + float qDot = desiredRot.dot(startRot); + + // when the abs() value of the dot product is approximately 1.0 + // then the two rotations are effectively adjacent + const float MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS = 0.99999f; // corresponds to approx 0.5 degrees + if (fabsf(qDot) < MIN_DOT_PRODUCT_OF_ADJACENT_QUATERNIONS) { + if (qDot < 0.0f) { + // the quaternions are actually on opposite hyperhemispheres + // so we move one to agree with the other and negate qDot + desiredRot = -desiredRot; + qDot = -qDot; + } + btQuaternion deltaRot = desiredRot * startRot.inverse(); + + // the axis is the imaginary part, but scaled by sin(angle/2) + btVector3 axis(deltaRot.getX(), deltaRot.getY(), deltaRot.getZ()); + axis /= sqrtf(1.0f - qDot * qDot); + + // compute the angle we will resolve for this dt, but don't overshoot + float angle = 2.0f * acosf(qDot); + if ( dt < _followTimeRemaining) { + angle *= dt / _followTimeRemaining; + } + + // accumulate rotation + deltaRot = btQuaternion(axis, angle); + _followAngularDisplacement = (deltaRot * _followAngularDisplacement).normalize(); + + // in order to accumulate displacement of avatar position, we need to take _shapeLocalOffset into account. + btVector3 shapeLocalOffset = glmToBullet(_shapeLocalOffset); + + endRot = deltaRot * startRot; + btVector3 swingDisplacement = rotateVector(endRot, -shapeLocalOffset) - rotateVector(startRot, -shapeLocalOffset); + _followLinearDisplacement += swingDisplacement; } - btQuaternion deltaRot = desiredRot * startRot.inverse(); - float angularSpeed = deltaRot.getAngle() / _followTimeRemaining; - btQuaternion angularDisplacement = btQuaternion(deltaRot.getAxis(), angularSpeed * dt); - btQuaternion endRot = angularDisplacement * startRot; - - // in order to accumulate displacement of avatar position, we need to take _shapeLocalOffset into account. - btVector3 shapeLocalOffset = glmToBullet(_shapeLocalOffset); - btVector3 swingDisplacement = rotateVector(endRot, -shapeLocalOffset) - rotateVector(startRot, -shapeLocalOffset); - - _followLinearDisplacement = linearDisplacement + swingDisplacement + _followLinearDisplacement; - _followAngularDisplacement = angularDisplacement * _followAngularDisplacement; - _rigidBody->setWorldTransform(btTransform(endRot, endPos)); } _followTime += dt; @@ -378,9 +408,6 @@ void CharacterController::setState(State desiredState, const char* reason) { #else void CharacterController::setState(State desiredState) { #endif - if (!_flyingAllowed && desiredState == State::Hover) { - desiredState = State::InAir; - } if (desiredState != _state) { #ifdef DEBUG_STATE_CHANGE @@ -746,7 +773,7 @@ void CharacterController::updateState() { const float JUMP_SPEED = _scaleFactor * DEFAULT_AVATAR_JUMP_SPEED; if ((velocity.dot(_currentUp) <= (JUMP_SPEED / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) { SET_STATE(State::Ground, "hit ground"); - } else { + } else if (_flyingAllowed) { btVector3 desiredVelocity = _targetVelocity; if (desiredVelocity.length2() < MIN_TARGET_SPEED_SQUARED) { desiredVelocity = btVector3(0.0f, 0.0f, 0.0f); @@ -760,14 +787,17 @@ void CharacterController::updateState() { // Transition to hover if we are above the fall threshold SET_STATE(State::Hover, "above fall threshold"); } + } else if (!rayHasHit && !_hasSupport) { + SET_STATE(State::Hover, "no ground detected"); } break; } case State::Hover: btScalar horizontalSpeed = (velocity - velocity.dot(_currentUp) * _currentUp).length(); bool flyingFast = horizontalSpeed > (MAX_WALKING_SPEED * 0.75f); - - if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) { + if (!_flyingAllowed && rayHasHit) { + SET_STATE(State::InAir, "flying not allowed"); + } else if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) { SET_STATE(State::InAir, "near ground"); } else if (((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport) && !flyingFast) { SET_STATE(State::Ground, "touching ground"); @@ -826,9 +856,6 @@ bool CharacterController::getRigidBodyLocation(glm::vec3& avatarRigidBodyPositio void CharacterController::setFlyingAllowed(bool value) { if (value != _flyingAllowed) { _flyingAllowed = value; - if (!_flyingAllowed && _state == State::Hover) { - SET_STATE(State::InAir, "flying not allowed"); - } } } diff --git a/libraries/physics/src/CharacterGhostObject.cpp b/libraries/physics/src/CharacterGhostObject.cpp index a771a52384..78b1d9c08f 100755 --- a/libraries/physics/src/CharacterGhostObject.cpp +++ b/libraries/physics/src/CharacterGhostObject.cpp @@ -69,7 +69,7 @@ bool CharacterGhostObject::rayTest(const btVector3& start, const btVector3& end, CharacterRayResult& result) const { if (_world && _inWorld) { - _world->rayTest(start, end, result); + btGhostObject::rayTest(start, end, result); } return result.hasHit(); } diff --git a/libraries/physics/src/CharacterGhostObject.h b/libraries/physics/src/CharacterGhostObject.h index 44ab5c938a..a6efea8752 100755 --- a/libraries/physics/src/CharacterGhostObject.h +++ b/libraries/physics/src/CharacterGhostObject.h @@ -23,7 +23,7 @@ class CharacterGhostShape; -class CharacterGhostObject : public btPairCachingGhostObject { +class CharacterGhostObject : public btGhostObject { public: CharacterGhostObject() { } ~CharacterGhostObject(); diff --git a/libraries/physics/src/CollisionRenderMeshCache.cpp b/libraries/physics/src/CollisionRenderMeshCache.cpp deleted file mode 100644 index 6f66b9af10..0000000000 --- a/libraries/physics/src/CollisionRenderMeshCache.cpp +++ /dev/null @@ -1,217 +0,0 @@ -// -// CollisionRenderMeshCache.cpp -// libraries/physics/src -// -// Created by Andrew Meadows 2016.07.13 -// Copyright 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 -// - -#include "CollisionRenderMeshCache.h" - -#include - -#include -#include - -#include // for MAX_HULL_POINTS - -const int32_t MAX_HULL_INDICES = 6 * MAX_HULL_POINTS; -const int32_t MAX_HULL_NORMALS = MAX_HULL_INDICES; -float tempVertices[MAX_HULL_NORMALS]; -graphics::Index tempIndexBuffer[MAX_HULL_INDICES]; - -bool copyShapeToMesh(const btTransform& transform, const btConvexShape* shape, - gpu::BufferView& vertices, gpu::BufferView& indices, gpu::BufferView& parts, - gpu::BufferView& normals) { - assert(shape); - - btShapeHull hull(shape); - if (!hull.buildHull(shape->getMargin())) { - return false; - } - - int32_t numHullIndices = hull.numIndices(); - assert(numHullIndices <= MAX_HULL_INDICES); - - int32_t numHullVertices = hull.numVertices(); - assert(numHullVertices <= MAX_HULL_POINTS); - - { // new part - graphics::Mesh::Part part; - part._startIndex = (graphics::Index)indices.getNumElements(); - part._numIndices = (graphics::Index)numHullIndices; - // FIXME: the render code cannot handle the case where part._baseVertex != 0 - //part._baseVertex = vertices.getNumElements(); // DOES NOT WORK - part._baseVertex = 0; - - gpu::BufferView::Size numBytes = sizeof(graphics::Mesh::Part); - const gpu::Byte* data = reinterpret_cast(&part); - parts._buffer->append(numBytes, data); - parts._size = parts._buffer->getSize(); - } - - const int32_t SIZE_OF_VEC3 = 3 * sizeof(float); - graphics::Index indexOffset = (graphics::Index)vertices.getNumElements(); - - { // new indices - const uint32_t* hullIndices = hull.getIndexPointer(); - // FIXME: the render code cannot handle the case where part._baseVertex != 0 - // so we must add an offset to each index - for (int32_t i = 0; i < numHullIndices; ++i) { - tempIndexBuffer[i] = hullIndices[i] + indexOffset; - } - const gpu::Byte* data = reinterpret_cast(tempIndexBuffer); - gpu::BufferView::Size numBytes = (gpu::BufferView::Size)(sizeof(graphics::Index) * numHullIndices); - indices._buffer->append(numBytes, data); - indices._size = indices._buffer->getSize(); - } - { // new vertices - const btVector3* hullVertices = hull.getVertexPointer(); - assert(numHullVertices <= MAX_HULL_POINTS); - for (int32_t i = 0; i < numHullVertices; ++i) { - btVector3 transformedPoint = transform * hullVertices[i]; - memcpy(tempVertices + 3 * i, transformedPoint.m_floats, SIZE_OF_VEC3); - } - gpu::BufferView::Size numBytes = sizeof(float) * (3 * numHullVertices); - const gpu::Byte* data = reinterpret_cast(tempVertices); - vertices._buffer->append(numBytes, data); - vertices._size = vertices._buffer->getSize(); - } - { // new normals - // compute average point - btVector3 avgVertex(0.0f, 0.0f, 0.0f); - const btVector3* hullVertices = hull.getVertexPointer(); - for (int i = 0; i < numHullVertices; ++i) { - avgVertex += hullVertices[i]; - } - avgVertex = transform * (avgVertex * (1.0f / (float)numHullVertices)); - - for (int i = 0; i < numHullVertices; ++i) { - btVector3 norm = transform * hullVertices[i] - avgVertex; - btScalar normLength = norm.length(); - if (normLength > FLT_EPSILON) { - norm /= normLength; - } - memcpy(tempVertices + 3 * i, norm.m_floats, SIZE_OF_VEC3); - } - gpu::BufferView::Size numBytes = sizeof(float) * (3 * numHullVertices); - const gpu::Byte* data = reinterpret_cast(tempVertices); - normals._buffer->append(numBytes, data); - normals._size = vertices._buffer->getSize(); - } - return true; -} - -graphics::MeshPointer createMeshFromShape(const void* pointer) { - graphics::MeshPointer mesh; - if (!pointer) { - return mesh; - } - - // pointer must be a const btCollisionShape* (cast to void*), but it only - // needs to be valid here when its render mesh is created, after this call - // the cache doesn't care what happens to the shape behind the pointer - const btCollisionShape* shape = static_cast(pointer); - - int32_t shapeType = shape->getShapeType(); - if (shapeType == (int32_t)COMPOUND_SHAPE_PROXYTYPE || shape->isConvex()) { - // allocate buffers for it - gpu::BufferView vertices(new gpu::Buffer(), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - gpu::BufferView indices(new gpu::Buffer(), gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::INDEX)); - gpu::BufferView parts(new gpu::Buffer(), gpu::Element(gpu::VEC4, gpu::UINT32, gpu::PART)); - gpu::BufferView normals(new gpu::Buffer(), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ)); - - int32_t numSuccesses = 0; - if (shapeType == (int32_t)COMPOUND_SHAPE_PROXYTYPE) { - const btCompoundShape* compoundShape = static_cast(shape); - int32_t numSubShapes = compoundShape->getNumChildShapes(); - for (int32_t i = 0; i < numSubShapes; ++i) { - const btCollisionShape* childShape = compoundShape->getChildShape(i); - if (childShape->isConvex()) { - const btConvexShape* convexShape = static_cast(childShape); - if (copyShapeToMesh(compoundShape->getChildTransform(i), convexShape, vertices, indices, parts, normals)) { - numSuccesses++; - } - } - } - } else { - // shape is convex - const btConvexShape* convexShape = static_cast(shape); - btTransform transform; - transform.setIdentity(); - if (copyShapeToMesh(transform, convexShape, vertices, indices, parts, normals)) { - numSuccesses++; - } - } - if (numSuccesses > 0) { - mesh = std::make_shared(); - mesh->setVertexBuffer(vertices); - mesh->setIndexBuffer(indices); - mesh->setPartBuffer(parts); - mesh->addAttribute(gpu::Stream::NORMAL, normals); - } else { - // TODO: log failure message here - } - } - return mesh; -} - -CollisionRenderMeshCache::CollisionRenderMeshCache() { -} - -CollisionRenderMeshCache::~CollisionRenderMeshCache() { - _meshMap.clear(); - _pendingGarbage.clear(); -} - -graphics::MeshPointer CollisionRenderMeshCache::getMesh(CollisionRenderMeshCache::Key key) { - graphics::MeshPointer mesh; - if (key) { - CollisionMeshMap::const_iterator itr = _meshMap.find(key); - if (itr == _meshMap.end()) { - // make mesh and add it to map - mesh = createMeshFromShape(key); - if (mesh) { - _meshMap.insert(std::make_pair(key, mesh)); - } - } else { - mesh = itr->second; - } - } - const uint32_t MAX_NUM_PENDING_GARBAGE = 20; - if (_pendingGarbage.size() > MAX_NUM_PENDING_GARBAGE) { - collectGarbage(); - } - return mesh; -} - -bool CollisionRenderMeshCache::releaseMesh(CollisionRenderMeshCache::Key key) { - if (!key) { - return false; - } - CollisionMeshMap::const_iterator itr = _meshMap.find(key); - if (itr != _meshMap.end()) { - _pendingGarbage.push_back(key); - return true; - } - return false; -} - -void CollisionRenderMeshCache::collectGarbage() { - uint32_t numShapes = (uint32_t)_pendingGarbage.size(); - for (uint32_t i = 0; i < numShapes; ++i) { - CollisionRenderMeshCache::Key key = _pendingGarbage[i]; - CollisionMeshMap::const_iterator itr = _meshMap.find(key); - if (itr != _meshMap.end()) { - if ((*itr).second.use_count() == 1) { - // we hold the only reference - _meshMap.erase(itr); - } - } - } - _pendingGarbage.clear(); -} - diff --git a/libraries/physics/src/CollisionRenderMeshCache.h b/libraries/physics/src/CollisionRenderMeshCache.h deleted file mode 100644 index c5b643c0cc..0000000000 --- a/libraries/physics/src/CollisionRenderMeshCache.h +++ /dev/null @@ -1,48 +0,0 @@ -// -// CollisionRenderMeshCache.h -// libraries/physics/src -// -// Created by Andrew Meadows 2016.07.13 -// Copyright 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 -// - -#ifndef hifi_CollisionRenderMeshCache_h -#define hifi_CollisionRenderMeshCache_h - -#include -#include -#include - -#include - - -class CollisionRenderMeshCache { -public: - using Key = const void*; // must actually be a const btCollisionShape* - - CollisionRenderMeshCache(); - ~CollisionRenderMeshCache(); - - /// \return pointer to geometry - graphics::MeshPointer getMesh(Key key); - - /// \return true if geometry was found and released - bool releaseMesh(Key key); - - /// delete geometries that have zero references - void collectGarbage(); - - // validation methods - uint32_t getNumMeshes() const { return (uint32_t)_meshMap.size(); } - bool hasMesh(Key key) const { return _meshMap.find(key) == _meshMap.end(); } - -private: - using CollisionMeshMap = std::unordered_map; - CollisionMeshMap _meshMap; - std::vector _pendingGarbage; -}; - -#endif // hifi_CollisionRenderMeshCache_h diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 594ea476f6..925cfee740 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -80,7 +80,6 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer // rather than pass the legit shape pointer to the ObjectMotionState ctor above. setShape(shape); - _bidPriority = _entity->getPendingOwnershipPriority(); if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) { // client-only entities are always thus, so we cache this fact in _ownershipState _ownershipState = EntityMotionState::OwnershipState::Unownable; @@ -140,28 +139,22 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { if (_entity->getSimulatorID().isNull()) { // simulation ownership has been removed if (glm::length2(_entity->getWorldVelocity()) == 0.0f) { - // this object is coming to rest --> clear the ACTIVATION flag and _bidPriority + // TODO: also check angularVelocity + // this object is coming to rest flags &= ~Simulation::DIRTY_PHYSICS_ACTIVATION; _body->setActivationState(WANTS_DEACTIVATION); - _bidPriority = 0; const float ACTIVATION_EXPIRY = 3.0f; // something larger than the 2.0 hard coded in Bullet _body->setDeactivationTime(ACTIVATION_EXPIRY); } else { // disowned object is still moving --> start timer for ownership bid // TODO? put a delay in here proportional to distance from object? - upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY); + _bumpedPriority = glm::max(_bumpedPriority, VOLUNTEER_SIMULATION_PRIORITY); _nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; } _loopsWithoutOwner = 0; _numInactiveUpdates = 0; - } else if (isLocallyOwned()) { - // we just inherited ownership, make sure our desired priority matches what we have - upgradeBidPriority(_entity->getSimulationPriority()); - } else { - // the entity is owned by someone else, so we clear _bidPriority here - // but _bidPriority may be updated to non-zero value if this object interacts with locally owned simulation - // in which case we may try to bid again - _bidPriority = 0; + } else if (!isLocallyOwned()) { + // the entity is owned by someone else _nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; _numInactiveUpdates = 0; } @@ -170,9 +163,6 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { // The DIRTY_SIMULATOR_OWNERSHIP_PRIORITY bit means one of the following: // (1) we own it but may need to change the priority OR... // (2) we don't own it but should bid (because a local script has been changing physics properties) - uint8_t newPriority = isLocallyOwned() ? _entity->getSimulationOwner().getPriority() : _entity->getSimulationOwner().getPendingPriority(); - upgradeBidPriority(newPriority); - // reset bid expiry so that we bid ASAP _nextBidExpiry = 0; } @@ -244,7 +234,7 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const { return; } assert(entityTreeIsLocked()); - if (_motionType == MOTION_TYPE_KINEMATIC && !_entity->hasAncestorOfType(NestableType::Avatar)) { + if (_motionType == MOTION_TYPE_KINEMATIC) { BT_PROFILE("kinematicIntegration"); // This is physical kinematic motion which steps strictly by the subframe count // of the physics simulation and uses full gravity for acceleration. @@ -298,7 +288,7 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { if (_entity->getSimulatorID().isNull()) { _loopsWithoutOwner++; if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextBidExpiry) { - upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY); + _bumpedPriority = glm::max(_bumpedPriority, VOLUNTEER_SIMULATION_PRIORITY); } } } @@ -317,29 +307,17 @@ const btCollisionShape* EntityMotionState::computeNewShape() { return getShapeManager()->getShape(shapeInfo); } -void EntityMotionState::setShape(const btCollisionShape* shape) { - if (_shape != shape) { - ObjectMotionState::setShape(shape); - _entity->setCollisionShape(_shape); - } -} - bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { - // NOTE: we only get here if we think we own the simulation + // NOTE: this method is only ever called when the entity simulation is locally owned DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync"); // Since we own the simulation: make sure _bidPriority is not less than current owned priority - // because: an _bidPriority of zero indicates that we should drop ownership when we have it. - upgradeBidPriority(_entity->getSimulationPriority()); + // because: a _bidPriority of zero indicates that we should drop ownership in the send. + // TODO: need to be able to detect when logic dictates we *decrease* priority + // WIP: print info whenever _bidPriority mismatches what is known to the entity - bool parentTransformSuccess; - Transform localToWorld = _entity->getParentTransform(parentTransformSuccess); - Transform worldToLocal; - Transform worldVelocityToLocal; - if (parentTransformSuccess) { - localToWorld.evalInverse(worldToLocal); - worldVelocityToLocal = worldToLocal; - worldVelocityToLocal.setTranslation(glm::vec3(0.0f)); + if (_entity->dynamicDataNeedsTransmit()) { + return true; } int numSteps = simulationStep - _lastStep; @@ -347,7 +325,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { if (_numInactiveUpdates > 0) { const uint8_t MAX_NUM_INACTIVE_UPDATES = 20; - if (_numInactiveUpdates > MAX_NUM_INACTIVE_UPDATES || isServerlessMode()) { + if (_numInactiveUpdates > MAX_NUM_INACTIVE_UPDATES) { // clear local ownership (stop sending updates) and let the server clear itself _entity->clearSimulationOwnership(); return false; @@ -369,6 +347,10 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { return true; } + if (_body->isStaticOrKinematicObject()) { + return false; + } + _lastStep = simulationStep; if (glm::length2(_serverVelocity) > 0.0f) { // the entity-server doesn't know where avatars are, so it doesn't do simple extrapolation for children of @@ -388,12 +370,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { } } - if (_entity->dynamicDataNeedsTransmit()) { - uint8_t priority = _entity->hasActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : SCRIPT_POKE_SIMULATION_PRIORITY; - upgradeBidPriority(priority); - return true; - } - // Else we measure the error between current and extrapolated transform (according to expected behavior // of remote EntitySimulation) and return true if the error is significant. @@ -402,6 +378,12 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { // TODO: compensate for _worldOffset offset here // compute position error + bool parentTransformSuccess; + Transform localToWorld = _entity->getParentTransform(parentTransformSuccess); + Transform worldToLocal; + if (parentTransformSuccess) { + localToWorld.evalInverse(worldToLocal); + } btTransform worldTrans = _body->getWorldTransform(); glm::vec3 position = worldToLocal.transform(bulletToGLM(worldTrans.getOrigin())); @@ -421,23 +403,27 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { if (glm::length2(_serverAngularVelocity) > 0.0f) { // compute rotation error - float attenuation = powf(1.0f - _body->getAngularDamping(), dt); - _serverAngularVelocity *= attenuation; + // // Bullet caps the effective rotation velocity inside its rotation integration step, therefore // we must integrate with the same algorithm and timestep in order achieve similar results. - for (int i = 0; i < numSteps; ++i) { - _serverRotation = glm::normalize(computeBulletRotationStep(_serverAngularVelocity, - PHYSICS_ENGINE_FIXED_SUBSTEP) * _serverRotation); + float attenuation = powf(1.0f - _body->getAngularDamping(), PHYSICS_ENGINE_FIXED_SUBSTEP); + _serverAngularVelocity *= attenuation; + glm::quat rotation = computeBulletRotationStep(_serverAngularVelocity, PHYSICS_ENGINE_FIXED_SUBSTEP); + for (int i = 1; i < numSteps; ++i) { + _serverAngularVelocity *= attenuation; + rotation = computeBulletRotationStep(_serverAngularVelocity, PHYSICS_ENGINE_FIXED_SUBSTEP) * rotation; } + _serverRotation = glm::normalize(rotation * _serverRotation); + const float MIN_ROTATION_DOT = 0.99999f; // This corresponds to about 0.5 degrees of rotation + glm::quat actualRotation = worldToLocal.getRotation() * bulletToGLM(worldTrans.getRotation()); + return (fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT); } - const float MIN_ROTATION_DOT = 0.99999f; // This corresponds to about 0.5 degrees of rotation - glm::quat actualRotation = worldToLocal.getRotation() * bulletToGLM(worldTrans.getRotation()); - - return (fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT); + return false; } bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { + // NOTE: this method is only ever called when the entity simulation is locally owned DETAILED_PROFILE_RANGE(simulation_physics, "ShouldSend"); // NOTE: we expect _entity and _body to be valid in this context, since shouldSendUpdate() is only called // after doesNotNeedToSendUpdate() returns false and that call should return 'true' if _entity or _body are NULL. @@ -462,6 +448,7 @@ void EntityMotionState::updateSendVelocities() { if (!_body->isActive()) { // make sure all derivatives are zero clearObjectVelocities(); + // we pretend we sent the inactive update for this object _numInactiveUpdates = 1; } else { glm::vec3 gravity = _entity->getGravity(); @@ -526,10 +513,10 @@ void EntityMotionState::sendBid(OctreeEditPacketSender* packetSender, uint32_t s properties.setLastEdited(now); // we don't own the simulation for this entity yet, but we're sending a bid for it - uint8_t bidPriority = glm::max(_bidPriority, VOLUNTEER_SIMULATION_PRIORITY); - properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority); - // copy _bidPriority into pendingPriority... - _entity->setPendingOwnershipPriority(_bidPriority, now); + uint8_t finalBidPriority = computeFinalBidPriority(); + _entity->clearScriptSimulationPriority(); + properties.setSimulationOwner(Physics::getSessionUUID(), finalBidPriority); + _entity->setPendingOwnershipPriority(finalBidPriority); EntityTreeElementPointer element = _entity->getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; @@ -548,10 +535,10 @@ void EntityMotionState::sendBid(OctreeEditPacketSender* packetSender, uint32_t s _lastStep = step; _nextBidExpiry = now + USECS_BETWEEN_OWNERSHIP_BIDS; - // finally: clear _bidPriority - // which will may get promoted before next bid - // or maybe we'll win simulation ownership - _bidPriority = 0; + // after sending a bid/update we clear _bumpedPriority + // which might get promoted again next frame (after local script or simulation interaction) + // or we might win the bid + _bumpedPriority = 0; } void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { @@ -590,22 +577,30 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ properties.setLastEdited(now); _entity->setSimulationOwnershipExpiry(now + MAX_OUTGOING_SIMULATION_UPDATE_PERIOD); - if (_numInactiveUpdates > 0) { + if (_numInactiveUpdates > 0 && _entity->getScriptSimulationPriority() == 0) { // the entity is stopped and inactive so we tell the server we're clearing simulatorID // but we remember we do still own it... and rely on the server to tell us we don't properties.clearSimulationOwner(); - _bidPriority = 0; - _entity->setPendingOwnershipPriority(_bidPriority, now); - } else if (_bidPriority != _entity->getSimulationPriority()) { - // our desired priority has changed - if (_bidPriority == 0) { - // we should release ownership - properties.clearSimulationOwner(); - } else { - // we just need to change the priority - properties.setSimulationOwner(Physics::getSessionUUID(), _bidPriority); + _entity->setPendingOwnershipPriority(0); + } else { + uint8_t newPriority = computeFinalBidPriority(); + _entity->clearScriptSimulationPriority(); + // if we get here then we own the simulation and the object is NOT going inactive + // if newPriority is zero, then it must be outside of R1, which means we should really set it to YIELD + // which we achive by just setting it to the max of the two + newPriority = glm::max(newPriority, YIELD_SIMULATION_PRIORITY); + if (newPriority != _entity->getSimulationPriority() && + !(newPriority == VOLUNTEER_SIMULATION_PRIORITY && _entity->getSimulationPriority() == RECRUIT_SIMULATION_PRIORITY)) { + // our desired priority has changed + if (newPriority == 0) { + // we should release ownership + properties.clearSimulationOwner(); + } else { + // we just need to inform the entity-server + properties.setSimulationOwner(Physics::getSessionUUID(), newPriority); + } + _entity->setPendingOwnershipPriority(newPriority); } - _entity->setPendingOwnershipPriority(_bidPriority, now); } EntityItemID id(_entity->getID()); @@ -640,6 +635,11 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ }); _lastStep = step; + + // after sending a bid/update we clear _bumpedPriority + // which might get promoted again next frame (after local script or simulation interaction) + // or we might win the bid + _bumpedPriority = 0; } uint32_t EntityMotionState::getIncomingDirtyFlags() { @@ -686,7 +686,7 @@ uint8_t EntityMotionState::getSimulationPriority() const { } void EntityMotionState::slaveBidPriority() { - upgradeBidPriority(_entity->getSimulationPriority()); + _bumpedPriority = glm::max(_bumpedPriority, _entity->getSimulationPriority()); } // virtual @@ -697,7 +697,7 @@ QUuid EntityMotionState::getSimulatorID() const { void EntityMotionState::bump(uint8_t priority) { assert(priority != 0); - upgradeBidPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority)); + _bumpedPriority = glm::max(_bumpedPriority, --priority); } void EntityMotionState::resetMeasuredBodyAcceleration() { @@ -765,7 +765,6 @@ void EntityMotionState::setMotionType(PhysicsMotionType motionType) { resetMeasuredBodyAcceleration(); } - // virtual QString EntityMotionState::getName() const { assert(entityTreeIsLocked()); @@ -777,15 +776,15 @@ void EntityMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& ma _entity->computeCollisionGroupAndFinalMask(group, mask); } -bool EntityMotionState::shouldSendBid() { - if (_bidPriority >= glm::max(_entity->getSimulationPriority(), VOLUNTEER_SIMULATION_PRIORITY)) { - return true; - } else { - // NOTE: this 'else' case has a side-effect: it clears _bidPriority - // which may be updated next simulation step (via collision or script event) - _bidPriority = 0; - return false; - } +bool EntityMotionState::shouldSendBid() const { + // NOTE: this method is only ever called when the entity's simulation is NOT locally owned + return _body->isActive() && (_region == workload::Region::R1) && + glm::max(glm::max(VOLUNTEER_SIMULATION_PRIORITY, _bumpedPriority), _entity->getScriptSimulationPriority()) >= _entity->getSimulationPriority(); +} + +uint8_t EntityMotionState::computeFinalBidPriority() const { + return (_region == workload::Region::R1) ? + glm::max(glm::max(VOLUNTEER_SIMULATION_PRIORITY, _bumpedPriority), _entity->getScriptSimulationPriority()) : 0; } bool EntityMotionState::isLocallyOwned() const { @@ -793,8 +792,17 @@ bool EntityMotionState::isLocallyOwned() const { } bool EntityMotionState::isLocallyOwnedOrShouldBe() const { - return (_bidPriority > VOLUNTEER_SIMULATION_PRIORITY && _bidPriority > _entity->getSimulationPriority()) || - _entity->getSimulatorID() == Physics::getSessionUUID(); + // this method could also be called "shouldGenerateCollisionEventForLocalScripts()" + // because that is the only reason it's used + if (_entity->getSimulatorID() == Physics::getSessionUUID()) { + return true; + } else { + return computeFinalBidPriority() > glm::max(VOLUNTEER_SIMULATION_PRIORITY, _entity->getSimulationPriority()); + } +} + +void EntityMotionState::setRegion(uint8_t region) { + _region = region; } void EntityMotionState::initForBid() { @@ -807,10 +815,6 @@ void EntityMotionState::initForOwned() { _ownershipState = EntityMotionState::OwnershipState::LocallyOwned; } -void EntityMotionState::upgradeBidPriority(uint8_t priority) { - _bidPriority = glm::max(_bidPriority, priority); -} - void EntityMotionState::clearObjectVelocities() const { // If transform or velocities are flagged as dirty it means a network or scripted change // occured between the beginning and end of the stepSimulation() and we DON'T want to apply @@ -830,8 +834,13 @@ void EntityMotionState::clearObjectVelocities() const { _entity->setAcceleration(glm::vec3(0.0f)); } -bool EntityMotionState::isServerlessMode() { - EntityTreeElementPointer element = _entity->getElement(); - EntityTreePointer tree = element ? element->getTree() : nullptr; - return tree ? tree->isServerlessMode() : false; +void EntityMotionState::saveKinematicState(btScalar timeStep) { + _body->saveKinematicState(timeStep); + + // This is a WORKAROUND for a quirk in Bullet: due to floating point error slow spinning kinematic objects will + // have a measured angular velocity of zero. This probably isn't a bug that the Bullet team is interested in + // fixing since there is one very simple workaround: use double-precision math for the physics simulation. + // We're not ready migrate to double-precision yet so we explicitly work around it by slamming the RigidBody's + // angular velocity with the value in the entity. + _body->setAngularVelocity(glmToBullet(_entity->getWorldAngularVelocity())); } diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 603130b5ff..653e3f4252 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -14,6 +14,7 @@ #include #include +#include #include "ObjectMotionState.h" @@ -86,13 +87,18 @@ public: virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override; - bool shouldSendBid(); + bool shouldSendBid() const; + uint8_t computeFinalBidPriority() const; + bool isLocallyOwned() const override; bool isLocallyOwnedOrShouldBe() const override; // aka shouldEmitCollisionEvents() friend class PhysicalEntitySimulation; OwnershipState getOwnershipState() const { return _ownershipState; } + void setRegion(uint8_t region); + void saveKinematicState(btScalar timeStep) override; + protected: void updateSendVelocities(); uint64_t getNextBidExpiry() const { return _nextBidExpiry; } @@ -102,11 +108,7 @@ protected: void updateServerPhysicsVariables(); bool remoteSimulationOutOfSync(uint32_t simulationStep); - // changes _bidPriority only if priority is larger - void upgradeBidPriority(uint8_t priority); - - // upgradeBidPriority to value stored in _entity - void slaveBidPriority(); + void slaveBidPriority(); // computeNewBidPriority() with value stored in _entity void clearObjectVelocities() const; @@ -116,7 +118,6 @@ protected: bool isReadyToComputeShape() const override; const btCollisionShape* computeNewShape() override; - void setShape(const btCollisionShape* shape) override; void setMotionType(PhysicsMotionType motionType) override; // EntityMotionState keeps a SharedPointer to its EntityItem which is only set in the CTOR @@ -154,8 +155,8 @@ protected: uint8_t _loopsWithoutOwner; mutable uint8_t _accelerationNearlyGravityCount; uint8_t _numInactiveUpdates { 1 }; - uint8_t _bidPriority { 0 }; - bool _serverVariablesSet { false }; + uint8_t _bumpedPriority { 0 }; // the target simulation priority according to collision history + uint8_t _region { workload::Region::INVALID }; bool isServerlessMode(); }; diff --git a/libraries/physics/src/ObjectActionOffset.cpp b/libraries/physics/src/ObjectActionOffset.cpp index 4c2ed35f8e..24f8a1ba8f 100644 --- a/libraries/physics/src/ObjectActionOffset.cpp +++ b/libraries/physics/src/ObjectActionOffset.cpp @@ -157,7 +157,7 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) { QVariantMap ObjectActionOffset::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { - arguments["pointToOffsetFrom"] = glmToQMap(_pointToOffsetFrom); + arguments["pointToOffsetFrom"] = vec3ToQMap(_pointToOffsetFrom); arguments["linearTimeScale"] = _linearTimeScale; arguments["linearDistance"] = _linearDistance; }); diff --git a/libraries/physics/src/ObjectActionTractor.cpp b/libraries/physics/src/ObjectActionTractor.cpp index 4235bbd616..c5641ad347 100644 --- a/libraries/physics/src/ObjectActionTractor.cpp +++ b/libraries/physics/src/ObjectActionTractor.cpp @@ -333,9 +333,9 @@ QVariantMap ObjectActionTractor::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { arguments["linearTimeScale"] = _linearTimeScale; - arguments["targetPosition"] = glmToQMap(_desiredPositionalTarget); + arguments["targetPosition"] = vec3ToQMap(_desiredPositionalTarget); - arguments["targetRotation"] = glmToQMap(_desiredRotationalTarget); + arguments["targetRotation"] = quatToQMap(_desiredRotationalTarget); arguments["angularTimeScale"] = _angularTimeScale; arguments["otherID"] = _otherID; diff --git a/libraries/physics/src/ObjectActionTravelOriented.cpp b/libraries/physics/src/ObjectActionTravelOriented.cpp index c93cce2482..bd32ed13ff 100644 --- a/libraries/physics/src/ObjectActionTravelOriented.cpp +++ b/libraries/physics/src/ObjectActionTravelOriented.cpp @@ -161,7 +161,7 @@ bool ObjectActionTravelOriented::updateArguments(QVariantMap arguments) { QVariantMap ObjectActionTravelOriented::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { - arguments["forward"] = glmToQMap(_forward); + arguments["forward"] = vec3ToQMap(_forward); arguments["angularTimeScale"] = _angularTimeScale; }); return arguments; diff --git a/libraries/physics/src/ObjectConstraintBallSocket.cpp b/libraries/physics/src/ObjectConstraintBallSocket.cpp index b7a186e187..ddb0c75ac9 100644 --- a/libraries/physics/src/ObjectConstraintBallSocket.cpp +++ b/libraries/physics/src/ObjectConstraintBallSocket.cpp @@ -193,9 +193,9 @@ bool ObjectConstraintBallSocket::updateArguments(QVariantMap arguments) { QVariantMap ObjectConstraintBallSocket::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { - arguments["pivot"] = glmToQMap(_pivotInA); + arguments["pivot"] = vec3ToQMap(_pivotInA); arguments["otherEntityID"] = _otherID; - arguments["otherPivot"] = glmToQMap(_pivotInB); + arguments["otherPivot"] = vec3ToQMap(_pivotInB); }); return arguments; } diff --git a/libraries/physics/src/ObjectConstraintConeTwist.cpp b/libraries/physics/src/ObjectConstraintConeTwist.cpp index e2b86a9e0f..9551532182 100644 --- a/libraries/physics/src/ObjectConstraintConeTwist.cpp +++ b/libraries/physics/src/ObjectConstraintConeTwist.cpp @@ -279,11 +279,11 @@ bool ObjectConstraintConeTwist::updateArguments(QVariantMap arguments) { QVariantMap ObjectConstraintConeTwist::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { - arguments["pivot"] = glmToQMap(_pivotInA); - arguments["axis"] = glmToQMap(_axisInA); + arguments["pivot"] = vec3ToQMap(_pivotInA); + arguments["axis"] = vec3ToQMap(_axisInA); arguments["otherEntityID"] = _otherID; - arguments["otherPivot"] = glmToQMap(_pivotInB); - arguments["otherAxis"] = glmToQMap(_axisInB); + arguments["otherPivot"] = vec3ToQMap(_pivotInB); + arguments["otherAxis"] = vec3ToQMap(_axisInB); arguments["swingSpan1"] = _swingSpan1; arguments["swingSpan2"] = _swingSpan2; arguments["twistSpan"] = _twistSpan; diff --git a/libraries/physics/src/ObjectConstraintHinge.cpp b/libraries/physics/src/ObjectConstraintHinge.cpp index 0a01f413dc..67573918cb 100644 --- a/libraries/physics/src/ObjectConstraintHinge.cpp +++ b/libraries/physics/src/ObjectConstraintHinge.cpp @@ -264,11 +264,11 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) { QVariantMap ObjectConstraintHinge::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { - arguments["pivot"] = glmToQMap(_pivotInA); - arguments["axis"] = glmToQMap(_axisInA); + arguments["pivot"] = vec3ToQMap(_pivotInA); + arguments["axis"] = vec3ToQMap(_axisInA); arguments["otherEntityID"] = _otherID; - arguments["otherPivot"] = glmToQMap(_pivotInB); - arguments["otherAxis"] = glmToQMap(_axisInB); + arguments["otherPivot"] = vec3ToQMap(_pivotInB); + arguments["otherAxis"] = vec3ToQMap(_axisInB); arguments["low"] = _low; arguments["high"] = _high; if (_constraint) { diff --git a/libraries/physics/src/ObjectConstraintSlider.cpp b/libraries/physics/src/ObjectConstraintSlider.cpp index 4776e0e4a6..7865e871de 100644 --- a/libraries/physics/src/ObjectConstraintSlider.cpp +++ b/libraries/physics/src/ObjectConstraintSlider.cpp @@ -289,11 +289,11 @@ bool ObjectConstraintSlider::updateArguments(QVariantMap arguments) { QVariantMap ObjectConstraintSlider::getArguments() { QVariantMap arguments = ObjectDynamic::getArguments(); withReadLock([&] { - arguments["point"] = glmToQMap(_pointInA); - arguments["axis"] = glmToQMap(_axisInA); + arguments["point"] = vec3ToQMap(_pointInA); + arguments["axis"] = vec3ToQMap(_axisInA); arguments["otherEntityID"] = _otherID; - arguments["otherPoint"] = glmToQMap(_pointInB); - arguments["otherAxis"] = glmToQMap(_axisInB); + arguments["otherPoint"] = vec3ToQMap(_pointInB); + arguments["otherAxis"] = vec3ToQMap(_axisInB); arguments["linearLow"] = _linearLow; arguments["linearHigh"] = _linearHigh; arguments["angularLow"] = _angularLow; diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index 64d2368207..310cf7cec1 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -92,7 +92,7 @@ void ObjectMotionState::setMass(float mass) { } float ObjectMotionState::getMass() const { - if (_shape) { + if (_shape && _shape->getShapeType() != TRIANGLE_MESH_SHAPE_PROXYTYPE) { // scale the density by the current Aabb volume to get mass btTransform transform; transform.setIdentity(); @@ -155,26 +155,25 @@ void ObjectMotionState::setMotionType(PhysicsMotionType motionType) { // Update the Continuous Collision Detection (CCD) configuration settings of our RigidBody so that // CCD will be enabled automatically when its speed surpasses a certain threshold. void ObjectMotionState::updateCCDConfiguration() { - if (_body) { - if (_shape) { - // If this object moves faster than its bounding radius * RADIUS_MOTION_THRESHOLD_MULTIPLIER, - // CCD will be enabled for this object. - const auto RADIUS_MOTION_THRESHOLD_MULTIPLIER = 0.5f; + assert(_body); + if (_shape && _shape->getShapeType() != TRIANGLE_MESH_SHAPE_PROXYTYPE) { + // find minumum dimension of shape + btVector3 aabbMin, aabbMax; + btTransform transform; + transform.setIdentity(); + _shape->getAabb(transform, aabbMin, aabbMax); + aabbMin = aabbMax - aabbMin; + btScalar radius = *((btScalar*)(aabbMin) + aabbMin.minAxis()); - btVector3 center; - btScalar radius; - _shape->getBoundingSphere(center, radius); - _body->setCcdMotionThreshold(radius * RADIUS_MOTION_THRESHOLD_MULTIPLIER); + // use the minimum dimension as the radius of the CCD proxy sphere + _body->setCcdSweptSphereRadius(radius); - // TODO: Ideally the swept sphere radius would be contained by the object. Using the bounding sphere - // radius works well for spherical objects, but may cause issues with other shapes. For arbitrary - // objects we may want to consider a different approach, such as grouping rigid bodies together. - - _body->setCcdSweptSphereRadius(radius); - } else { - // Disable CCD - _body->setCcdMotionThreshold(0); - } + // also use the radius as the motion threshold for enabling CCD + _body->setCcdMotionThreshold(radius); + } else { + // disable CCD + _body->setCcdSweptSphereRadius(0.0f); + _body->setCcdMotionThreshold(0.0f); } } @@ -188,8 +187,8 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) { if (_body) { _body->setUserPointer(this); assert(_body->getCollisionShape() == _shape); + updateCCDConfiguration(); } - updateCCDConfiguration(); } } @@ -199,6 +198,9 @@ void ObjectMotionState::setShape(const btCollisionShape* shape) { getShapeManager()->releaseShape(_shape); } _shape = shape; + if (_body) { + updateCCDConfiguration(); + } } } @@ -312,7 +314,6 @@ bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* } else { _body->setCollisionShape(const_cast(newShape)); setShape(newShape); - updateCCDConfiguration(); } } if (flags & EASY_DIRTY_PHYSICS_FLAGS) { @@ -347,10 +348,16 @@ void ObjectMotionState::updateLastKinematicStep() { } void ObjectMotionState::updateBodyMassProperties() { - float mass = getMass(); - btVector3 inertia(0.0f, 0.0f, 0.0f); - _body->getCollisionShape()->calculateLocalInertia(mass, inertia); + btScalar mass = getMass(); + btVector3 inertia(1.0f, 1.0f, 1.0f); + if (mass > 0.0f) { + _body->getCollisionShape()->calculateLocalInertia(mass, inertia); + } _body->setMassProps(mass, inertia); _body->updateInertiaTensor(); } +void ObjectMotionState::saveKinematicState(btScalar timeStep) { + _body->saveKinematicState(timeStep); +} + diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index bb3c00bd5d..7439c1c38d 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -110,7 +110,7 @@ public: MotionStateType getType() const { return _type; } virtual PhysicsMotionType getMotionType() const { return _motionType; } - void setMass(float mass); + virtual void setMass(float mass); virtual float getMass() const; void setBodyLinearVelocity(const glm::vec3& velocity) const; @@ -165,6 +165,7 @@ public: virtual bool isLocallyOwned() const { return false; } virtual bool isLocallyOwnedOrShouldBe() const { return false; } // aka shouldEmitCollisionEvents() + virtual void saveKinematicState(btScalar timeStep); friend class PhysicsEngine; diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index e2970d6a03..5666e75aa1 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -48,13 +48,21 @@ void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) { QMutexLocker lock(&_mutex); assert(entity); assert(!entity->isDead()); - if (entity->shouldBePhysical()) { + uint8_t region = _space->getRegion(entity->getSpaceIndex()); + bool shouldBePhysical = region < workload::Region::R3 && entity->shouldBePhysical(); + bool canBeKinematic = region <= workload::Region::R3; + if (shouldBePhysical) { EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); - if (!motionState) { + if (motionState) { + motionState->setRegion(region); + } else { _entitiesToAddToPhysics.insert(entity); } - } else if (entity->isMovingRelativeToParent()) { - _simpleKinematicEntities.insert(entity); + } else if (canBeKinematic && entity->isMovingRelativeToParent()) { + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr == _simpleKinematicEntities.end()) { + _simpleKinematicEntities.insert(entity); + } } } @@ -120,28 +128,58 @@ void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) { QMutexLocker lock(&_mutex); assert(entity); EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); + uint8_t region = _space->getRegion(entity->getSpaceIndex()); + bool shouldBePhysical = region < workload::Region::R3 && entity->shouldBePhysical(); + bool canBeKinematic = region <= workload::Region::R3; if (motionState) { - if (!entity->shouldBePhysical()) { - // the entity should be removed from the physical simulation + if (!shouldBePhysical) { + if (motionState->isLocallyOwned()) { + // zero velocities by first deactivating the RigidBody + btRigidBody* body = motionState->getRigidBody(); + if (body) { + body->forceActivationState(ISLAND_SLEEPING); + motionState->updateSendVelocities(); // has side-effect of zeroing entity velocities for inactive body + } + + // send packet to remove ownership + // NOTE: this packet will NOT be resent if lost, but the good news is: + // the entity-server will eventually clear velocity and ownership for timeout + motionState->sendUpdate(_entityPacketSender, _physicsEngine->getNumSubsteps()); + } + + // remove from the physical simulation _incomingChanges.remove(motionState); _physicalObjects.remove(motionState); removeOwnershipData(motionState); _entitiesToRemoveFromPhysics.insert(entity); - if (entity->isMovingRelativeToParent()) { - _simpleKinematicEntities.insert(entity); + if (canBeKinematic && entity->isMovingRelativeToParent()) { + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr == _simpleKinematicEntities.end()) { + _simpleKinematicEntities.insert(entity); + } } } else { _incomingChanges.insert(motionState); } - } else if (entity->shouldBePhysical()) { + motionState->setRegion(region); + } else if (shouldBePhysical) { // The intent is for this object to be in the PhysicsEngine, but it has no MotionState yet. // Perhaps it's shape has changed and it can now be added? _entitiesToAddToPhysics.insert(entity); - _simpleKinematicEntities.remove(entity); // just in case it's non-physical-kinematic - } else if (entity->isMovingRelativeToParent()) { - _simpleKinematicEntities.insert(entity); + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr != _simpleKinematicEntities.end()) { + _simpleKinematicEntities.erase(itr); + } + } else if (canBeKinematic && entity->isMovingRelativeToParent()) { + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr == _simpleKinematicEntities.end()) { + _simpleKinematicEntities.insert(entity); + } } else { - _simpleKinematicEntities.remove(entity); // just in case it's non-physical-kinematic + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr != _simpleKinematicEntities.end()) { + _simpleKinematicEntities.erase(itr); + } } } @@ -187,18 +225,20 @@ const VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToRemoveFromPhys for (auto entity: _entitiesToRemoveFromPhysics) { EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); assert(motionState); + // TODO CLEan this, just a n extra check to avoid the crash that shouldn;t happen + if (motionState) { + _entitiesToAddToPhysics.remove(entity); + if (entity->isDead() && entity->getElement()) { + _deadEntities.insert(entity); + } - _entitiesToAddToPhysics.remove(entity); - if (entity->isDead() && entity->getElement()) { - _deadEntities.insert(entity); + _incomingChanges.remove(motionState); + removeOwnershipData(motionState); + _physicalObjects.remove(motionState); + + // remember this motionState and delete it later (after removing its RigidBody from the PhysicsEngine) + _objectsToDelete.push_back(motionState); } - - _incomingChanges.remove(motionState); - removeOwnershipData(motionState); - _physicalObjects.remove(motionState); - - // remember this motionState and delete it later (after removing its RigidBody from the PhysicsEngine) - _objectsToDelete.push_back(motionState); } _entitiesToRemoveFromPhysics.clear(); return _objectsToDelete; @@ -229,7 +269,10 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re // this entity should no longer be on the internal _entitiesToAddToPhysics entityItr = _entitiesToAddToPhysics.erase(entityItr); if (entity->isMovingRelativeToParent()) { - _simpleKinematicEntities.insert(entity); + SetOfEntities::iterator itr = _simpleKinematicEntities.find(entity); + if (itr == _simpleKinematicEntities.end()) { + _simpleKinematicEntities.insert(entity); + } } } else if (entity->isReadyToComputeShape()) { ShapeInfo shapeInfo; @@ -249,6 +292,9 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re _physicalObjects.insert(motionState); result.push_back(motionState); entityItr = _entitiesToAddToPhysics.erase(entityItr); + + // make sure the motionState's region is up-to-date before it is actually added to physics + motionState->setRegion(_space->getRegion(entity->getSpaceIndex())); } else { //qWarning() << "Failed to generate new shape for entity." << entity->getName(); ++entityItr; @@ -277,14 +323,32 @@ void PhysicalEntitySimulation::getObjectsToChange(VectorOfMotionStates& result) } void PhysicalEntitySimulation::handleDeactivatedMotionStates(const VectorOfMotionStates& motionStates) { + bool serverlessMode = getEntityTree()->isServerlessMode(); for (auto stateItr : motionStates) { ObjectMotionState* state = &(*stateItr); assert(state); if (state->getType() == MOTIONSTATE_TYPE_ENTITY) { EntityMotionState* entityState = static_cast(state); - entityState->handleDeactivation(); EntityItemPointer entity = entityState->getEntity(); _entitiesToSort.insert(entity); + if (!serverlessMode) { + if (entity->getClientOnly()) { + switch (entityState->getOwnershipState()) { + case EntityMotionState::OwnershipState::PendingBid: + _bids.removeFirst(entityState); + entityState->clearOwnershipState(); + break; + case EntityMotionState::OwnershipState::LocallyOwned: + _owned.removeFirst(entityState); + entityState->clearOwnershipState(); + break; + default: + break; + } + } else { + entityState->handleDeactivation(); + } + } } } } @@ -329,20 +393,18 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta void PhysicalEntitySimulation::addOwnershipBid(EntityMotionState* motionState) { if (getEntityTree()->isServerlessMode()) { - EntityItemPointer entity = motionState->getEntity(); - auto nodeList = DependencyManager::get(); - auto sessionID = nodeList->getSessionUUID(); - entity->setSimulationOwner(SimulationOwner(sessionID, SCRIPT_GRAB_SIMULATION_PRIORITY)); - _owned.push_back(motionState); - } else { - motionState->initForBid(); - motionState->sendBid(_entityPacketSender, _physicsEngine->getNumSubsteps()); - _bids.push_back(motionState); - _nextBidExpiry = glm::min(_nextBidExpiry, motionState->getNextBidExpiry()); + return; } + motionState->initForBid(); + motionState->sendBid(_entityPacketSender, _physicsEngine->getNumSubsteps()); + _bids.push_back(motionState); + _nextBidExpiry = glm::min(_nextBidExpiry, motionState->getNextBidExpiry()); } void PhysicalEntitySimulation::addOwnership(EntityMotionState* motionState) { + if (getEntityTree()->isServerlessMode()) { + return; + } motionState->initForOwned(); _owned.push_back(motionState); } @@ -383,6 +445,9 @@ void PhysicalEntitySimulation::sendOwnershipBids(uint32_t numSubsteps) { } void PhysicalEntitySimulation::sendOwnedUpdates(uint32_t numSubsteps) { + if (getEntityTree()->isServerlessMode()) { + return; + } PROFILE_RANGE_EX(simulation_physics, "Update", 0x00000000, (uint64_t)_owned.size()); uint32_t i = 0; while (i < _owned.size()) { diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index 7b6fe221fb..fdf996df25 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -19,6 +19,7 @@ #include #include +#include #include "PhysicsEngine.h" #include "EntityMotionState.h" @@ -36,6 +37,14 @@ public: } pop_back(); } + void removeFirst(EntityMotionState* state) { + for (uint32_t i = 0; i < size(); ++i) { + if ((*this)[i] == state) { + remove(i); + break; + } + } + } }; class PhysicalEntitySimulation : public EntitySimulation { @@ -45,6 +54,7 @@ public: ~PhysicalEntitySimulation(); void init(EntityTreePointer tree, PhysicsEnginePointer engine, EntityEditPacketSender* packetSender); + void setWorkloadSpace(const workload::SpacePointer space) { _space = space; } virtual void addDynamic(EntityDynamicPointer dynamic) override; virtual void applyDynamicChanges() override; @@ -102,6 +112,7 @@ private: VectorOfEntityMotionStates _owned; VectorOfEntityMotionStates _bids; + workload::SpacePointer _space; uint64_t _nextBidExpiry; uint32_t _lastStepSendPackets { 0 }; }; diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 21b5b38b13..66a4edb486 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -105,6 +105,10 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) { } case MOTION_TYPE_DYNAMIC: { mass = motionState->getMass(); + const float MIN_DYNAMIC_MASS = 0.01f; + if (mass != mass || mass < MIN_DYNAMIC_MASS) { + mass = MIN_DYNAMIC_MASS; + } btCollisionShape* shape = const_cast(motionState->getShape()); assert(shape); shape->calculateLocalInertia(mass, inertia); diff --git a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp index 07d5ceb9ac..17a52f7cd9 100644 --- a/libraries/physics/src/ThreadSafeDynamicsWorld.cpp +++ b/libraries/physics/src/ThreadSafeDynamicsWorld.cpp @@ -162,7 +162,13 @@ void ThreadSafeDynamicsWorld::saveKinematicState(btScalar timeStep) { for (int i=0;iisKinematicObject() && body->getActivationState() != ISLAND_SLEEPING) { - body->saveKinematicState(timeStep); + if (body->getMotionState()) { + btMotionState* motionState = body->getMotionState(); + ObjectMotionState* objectMotionState = static_cast(motionState); + objectMotionState->saveKinematicState(timeStep); + } else { + body->saveKinematicState(timeStep); + } } } } diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index e9c084e132..94ce16cf00 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -40,9 +40,8 @@ void PluginManager::setInputPluginSettingsPersister(const InputPluginSettingsPer _inputSettingsPersister = persister; } -PluginManager* PluginManager::getInstance() { - static PluginManager _manager; - return &_manager; +PluginManagerPointer PluginManager::getInstance() { + return DependencyManager::get(); } QString getPluginNameFromMetaData(QJsonObject object) { @@ -136,9 +135,6 @@ const LoaderList& getLoadedPlugins() { return loadedPlugins; } -PluginManager::PluginManager() { -} - const CodecPluginList& PluginManager::getCodecPlugins() { static CodecPluginList codecPlugins; static std::once_flag once; diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h index f16ad7d09f..65a4012aed 100644 --- a/libraries/plugins/src/plugins/PluginManager.h +++ b/libraries/plugins/src/plugins/PluginManager.h @@ -9,12 +9,19 @@ #include +#include + #include "Forward.h" -class PluginManager : public QObject { + +class PluginManager; +using PluginManagerPointer = QSharedPointer; + +class PluginManager : public QObject, public Dependency { + SINGLETON_DEPENDENCY + public: - static PluginManager* getInstance(); - PluginManager(); + static PluginManagerPointer getInstance(); const DisplayPluginList& getDisplayPlugins(); const InputPluginList& getInputPlugins(); @@ -39,6 +46,8 @@ public: void setInputPluginSettingsPersister(const InputPluginSettingsPersister& persister); private: + PluginManager() = default; + DisplayPluginProvider _displayPluginProvider { []()->DisplayPluginList { return {}; } }; InputPluginProvider _inputPluginProvider { []()->InputPluginList { return {}; } }; CodecPluginProvider _codecPluginProvider { []()->CodecPluginList { return {}; } }; diff --git a/libraries/pointers/src/Pick.h b/libraries/pointers/src/Pick.h index 53606b154f..dd59b50cc4 100644 --- a/libraries/pointers/src/Pick.h +++ b/libraries/pointers/src/Pick.h @@ -161,6 +161,7 @@ public: enum PickType { Ray = 0, Stylus, + Parabola, NUM_PICK_TYPES }; diff --git a/libraries/pointers/src/PickManager.cpp b/libraries/pointers/src/PickManager.cpp index ba8fa814f0..38e86572d5 100644 --- a/libraries/pointers/src/PickManager.cpp +++ b/libraries/pointers/src/PickManager.cpp @@ -100,6 +100,7 @@ void PickManager::update() { // and the rayPicks updae will ALWAYS update at least one ray even when there is no budget _stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], _nextPickToUpdate[PickQuery::Stylus], expiry, false); _rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], _nextPickToUpdate[PickQuery::Ray], expiry, shouldPickHUD); + _parabolaPickCacheOptimizer.update(cachedPicks[PickQuery::Parabola], _nextPickToUpdate[PickQuery::Parabola], expiry, shouldPickHUD); } bool PickManager::isLeftHand(unsigned int uid) { diff --git a/libraries/pointers/src/PickManager.h b/libraries/pointers/src/PickManager.h index 3b466be2bc..c726fd0668 100644 --- a/libraries/pointers/src/PickManager.h +++ b/libraries/pointers/src/PickManager.h @@ -59,14 +59,15 @@ protected: std::shared_ptr findPick(unsigned int uid) const; std::unordered_map>> _picks; - unsigned int _nextPickToUpdate[PickQuery::NUM_PICK_TYPES] { 0, 0 }; + unsigned int _nextPickToUpdate[PickQuery::NUM_PICK_TYPES] { 0, 0, 0 }; std::unordered_map _typeMap; unsigned int _nextPickID { INVALID_PICK_ID + 1 }; PickCacheOptimizer _rayPickCacheOptimizer; PickCacheOptimizer _stylusPickCacheOptimizer; + PickCacheOptimizer _parabolaPickCacheOptimizer; - static const unsigned int DEFAULT_PER_FRAME_TIME_BUDGET = 2 * USECS_PER_MSEC; + static const unsigned int DEFAULT_PER_FRAME_TIME_BUDGET = 3 * USECS_PER_MSEC; unsigned int _perFrameTimeBudget { DEFAULT_PER_FRAME_TIME_BUDGET }; }; diff --git a/libraries/procedural/src/procedural/ProceduralCommon.slf b/libraries/procedural/src/procedural/ProceduralCommon.slf index dcc34b5131..de226e6dae 100644 --- a/libraries/procedural/src/procedural/ProceduralCommon.slf +++ b/libraries/procedural/src/procedural/ProceduralCommon.slf @@ -8,292 +8,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// Shader includes portions of webgl-noise: -// Description : Array and textureless GLSL 2D/3D/4D simplex -// noise functions. -// Author : Ian McEwan, Ashima Arts. -// Maintainer : ijm -// Lastmod : 20110822 (ijm) -// License : Copyright (C) 2011 Ashima Arts. All rights reserved. -// Distributed under the MIT License. See LICENSE file. -// https://github.com/ashima/webgl-noise -// - <@include gpu/Transform.slh@> +<@include gpu/Noise.slh@> + <$declareStandardCameraTransform()$> -float mod289(float x) { - return x - floor(x * (1.0 / 289.0)) * 289.0; -} - -vec2 mod289(vec2 x) { - return x - floor(x * (1.0 / 289.0)) * 289.0; -} - -vec3 mod289(vec3 x) { - return x - floor(x * (1.0 / 289.0)) * 289.0; -} - -vec4 mod289(vec4 x) { - return x - floor(x * (1.0 / 289.0)) * 289.0; -} - -float permute(float x) { - return mod289(((x*34.0)+1.0)*x); -} - -vec3 permute(vec3 x) { - return mod289(((x*34.0)+1.0)*x); -} - -vec4 permute(vec4 x) { - return mod289(((x*34.0)+1.0)*x); -} - -float taylorInvSqrt(float r) { - return 1.79284291400159 - 0.85373472095314 * r; -} - -vec4 taylorInvSqrt(vec4 r) { - return 1.79284291400159 - 0.85373472095314 * r; -} - -vec4 grad4(float j, vec4 ip) { - const vec4 ones = vec4(1.0, 1.0, 1.0, -1.0); - vec4 p, s; - - p.xyz = floor(fract(vec3(j) * ip.xyz) * 7.0) * ip.z - 1.0; - p.w = 1.5 - dot(abs(p.xyz), ones.xyz); - s = vec4(lessThan(p, vec4(0.0))); - p.xyz = p.xyz + (s.xyz * 2.0 - 1.0) * s.www; - - return p; -} - -// (sqrt(5) - 1)/4 = F4, used once below -#define F4 0.309016994374947451 - -float snoise(vec4 v) { - const vec4 C = vec4(0.138196601125011, // (5 - sqrt(5))/20 G4 - 0.276393202250021, // 2 * G4 - 0.414589803375032, // 3 * G4 - -0.447213595499958); // -1 + 4 * G4 - - // First corner - vec4 i = floor(v + dot(v, vec4(F4))); - vec4 x0 = v - i + dot(i, C.xxxx); - - // Other corners - - // Rank sorting originally contributed by Bill Licea-Kane, AMD (formerly ATI) - vec4 i0; - vec3 isX = step(x0.yzw, x0.xxx); - vec3 isYZ = step(x0.zww, x0.yyz); - i0.x = isX.x + isX.y + isX.z; - i0.yzw = 1.0 - isX; - i0.y += isYZ.x + isYZ.y; - i0.zw += 1.0 - isYZ.xy; - i0.z += isYZ.z; - i0.w += 1.0 - isYZ.z; - - // i0 now contains the unique values 0,1,2,3 in each channel - vec4 i3 = clamp(i0, 0.0, 1.0); - vec4 i2 = clamp(i0 - 1.0, 0.0, 1.0); - vec4 i1 = clamp(i0 - 2.0, 0.0, 1.0); - - vec4 x1 = x0 - i1 + C.xxxx; - vec4 x2 = x0 - i2 + C.yyyy; - vec4 x3 = x0 - i3 + C.zzzz; - vec4 x4 = x0 + C.wwww; - - // Permutations - i = mod289(i); - float j0 = permute(permute(permute(permute(i.w) + i.z) + i.y) + i.x); - vec4 j1 = permute( - permute( - permute( - permute(i.w + vec4(i1.w, i2.w, i3.w, 1.0)) + i.z - + vec4(i1.z, i2.z, i3.z, 1.0)) + i.y - + vec4(i1.y, i2.y, i3.y, 1.0)) + i.x - + vec4(i1.x, i2.x, i3.x, 1.0)); - - // Gradients: 7x7x6 points over a cube, mapped onto a 4-cross polytope - // 7*7*6 = 294, which is close to the ring size 17*17 = 289. - vec4 ip = vec4(1.0 / 294.0, 1.0 / 49.0, 1.0 / 7.0, 0.0); - - vec4 p0 = grad4(j0, ip); - vec4 p1 = grad4(j1.x, ip); - vec4 p2 = grad4(j1.y, ip); - vec4 p3 = grad4(j1.z, ip); - vec4 p4 = grad4(j1.w, ip); - - // Normalise gradients - vec4 norm = taylorInvSqrt( - vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); - p0 *= norm.x; - p1 *= norm.y; - p2 *= norm.z; - p3 *= norm.w; - p4 *= taylorInvSqrt(dot(p4, p4)); - - // Mix contributions from the five corners - vec3 m0 = max(0.6 - vec3(dot(x0, x0), dot(x1, x1), dot(x2, x2)), 0.0); - vec2 m1 = max(0.6 - vec2(dot(x3, x3), dot(x4, x4)), 0.0); - m0 = m0 * m0; - m1 = m1 * m1; - return 49.0 - * (dot(m0 * m0, vec3(dot(p0, x0), dot(p1, x1), dot(p2, x2))) - + dot(m1 * m1, vec2(dot(p3, x3), dot(p4, x4)))); - -} - -float snoise(vec3 v) { - const vec2 C = vec2(1.0 / 6.0, 1.0 / 3.0); - const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); - - // First corner - vec3 i = floor(v + dot(v, C.yyy)); - vec3 x0 = v - i + dot(i, C.xxx); - - // Other corners - vec3 g = step(x0.yzx, x0.xyz); - vec3 l = 1.0 - g; - vec3 i1 = min(g.xyz, l.zxy); - vec3 i2 = max(g.xyz, l.zxy); - - vec3 x1 = x0 - i1 + C.xxx; - vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y - vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y - - // Permutations - i = mod289(i); - vec4 p = permute( - permute( - permute(i.z + vec4(0.0, i1.z, i2.z, 1.0)) + i.y - + vec4(0.0, i1.y, i2.y, 1.0)) + i.x - + vec4(0.0, i1.x, i2.x, 1.0)); - - // Gradients: 7x7 points over a square, mapped onto an octahedron. - // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) - float n_ = 0.142857142857; // 1.0/7.0 - vec3 ns = n_ * D.wyz - D.xzx; - - vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7) - - vec4 x_ = floor(j * ns.z); - vec4 y_ = floor(j - 7.0 * x_); // mod(j,N) - - vec4 x = x_ * ns.x + ns.yyyy; - vec4 y = y_ * ns.x + ns.yyyy; - vec4 h = 1.0 - abs(x) - abs(y); - - vec4 b0 = vec4(x.xy, y.xy); - vec4 b1 = vec4(x.zw, y.zw); - - //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; - //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; - vec4 s0 = floor(b0) * 2.0 + 1.0; - vec4 s1 = floor(b1) * 2.0 + 1.0; - vec4 sh = -step(h, vec4(0.0)); - - vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy; - vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww; - - vec3 p0 = vec3(a0.xy, h.x); - vec3 p1 = vec3(a0.zw, h.y); - vec3 p2 = vec3(a1.xy, h.z); - vec3 p3 = vec3(a1.zw, h.w); - - //Normalise gradients - vec4 norm = taylorInvSqrt( - vec4(dot(p0, p0), dot(p1, p1), dot(p2, p2), dot(p3, p3))); - p0 *= norm.x; - p1 *= norm.y; - p2 *= norm.z; - p3 *= norm.w; - - // Mix final noise value - vec4 m = max(0.6 - vec4(dot(x0, x0), dot(x1, x1), dot(x2, x2), dot(x3, x3)), - 0.0); - m = m * m; - return 42.0 - * dot(m * m, vec4(dot(p0, x0), dot(p1, x1), dot(p2, x2), dot(p3, x3))); -} - -float snoise(vec2 v) { - const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0 - 0.366025403784439, // 0.5*(sqrt(3.0)-1.0) - -0.577350269189626, // -1.0 + 2.0 * C.x - 0.024390243902439); // 1.0 / 41.0 - // First corner - vec2 i = floor(v + dot(v, C.yy)); - vec2 x0 = v - i + dot(i, C.xx); - - // Other corners - vec2 i1; - i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); - vec4 x12 = x0.xyxy + C.xxzz; - x12.xy -= i1; - - // Permutations - i = mod289(i); // Avoid truncation effects in permutation - vec3 p = permute( - permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0)); - - vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), - 0.0); - m = m * m; - m = m * m; - - // Gradients: 41 points uniformly over a line, mapped onto a diamond. - // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) - - vec3 x = 2.0 * fract(p * C.www) - 1.0; - vec3 h = abs(x) - 0.5; - vec3 ox = floor(x + 0.5); - vec3 a0 = x - ox; - - // Normalise gradients implicitly by scaling m - // Approximation of: m *= inversesqrt( a0*a0 + h*h ); - m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h); - - // Compute final noise value at P - vec3 g; - g.x = a0.x * x0.x + h.x * x0.y; - g.yz = a0.yz * x12.xz + h.yz * x12.yw; - return 130.0 * dot(m, g); -} - -// https://www.shadertoy.com/view/lsfGRr -float hifi_hash(float n) { - return fract(sin(n) * 43758.5453); -} - -float hifi_noise(in vec2 x) { - vec2 p = floor(x); - vec2 f = fract(x); - - f = f * f * (3.0 - 2.0 * f); - - float n = p.x + p.y * 57.0; - - return mix(mix(hifi_hash(n + 0.0), hifi_hash(n + 1.0), f.x), - mix(hifi_hash(n + 57.0), hifi_hash(n + 58.0), f.x), f.y); -} - -// https://www.shadertoy.com/view/MdX3Rr -// https://en.wikipedia.org/wiki/Fractional_Brownian_motion -float hifi_fbm(in vec2 p) { - const mat2 m2 = mat2(0.8, -0.6, 0.6, 0.8); - float f = 0.0; - f += 0.5000 * hifi_noise(p); p = m2 * p * 2.02; - f += 0.2500 * hifi_noise(p); p = m2 * p * 2.03; - f += 0.1250 * hifi_noise(p); p = m2 * p * 2.01; - f += 0.0625 * hifi_noise(p); - - return f / 0.9375; -} - #define PROCEDURAL 1 //PROCEDURAL_VERSION diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index 688f3fdb5f..ea6f1ce324 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -66,7 +66,7 @@ OffscreenSurface::OffscreenSurface() } OffscreenSurface::~OffscreenSurface() { - delete _sharedObject; + _sharedObject->deleteLater(); } bool OffscreenSurface::fetchTexture(TextureAndFence& textureAndFence) { @@ -286,6 +286,13 @@ void OffscreenSurface::loadInternal(const QUrl& qmlSource, if (QThread::currentThread() != thread()) { qFatal("Called load on a non-surface thread"); } + + // For desktop toolbar mode window: stop script when window is closed. + if (qmlSource.isEmpty()) { + getSurfaceContext()->engine()->quit(); + return; + } + // Synchronous loading may take a while; restart the deadlock timer QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection); @@ -386,9 +393,6 @@ void OffscreenSurface::finishQmlLoad(QQmlComponent* qmlComponent, _sharedObject->setRootItem(newItem); } - qmlComponent->completeCreate(); - qmlComponent->deleteLater(); - onItemCreated(qmlContext, newItem); if (!rootCreated) { @@ -398,6 +402,8 @@ void OffscreenSurface::finishQmlLoad(QQmlComponent* qmlComponent, // Call this callback after rootitem is set, otherwise VrMenu wont work callback(qmlContext, newItem); } + qmlComponent->completeCreate(); + qmlComponent->deleteLater(); } QQmlContext* OffscreenSurface::contextForUrl(const QUrl& qmlSource, QQuickItem* parent, bool forceNewContext) { diff --git a/libraries/recording/src/recording/impl/PointerClip.cpp b/libraries/recording/src/recording/impl/PointerClip.cpp index bd632a3f4a..bb8d6cfb33 100644 --- a/libraries/recording/src/recording/impl/PointerClip.cpp +++ b/libraries/recording/src/recording/impl/PointerClip.cpp @@ -61,7 +61,6 @@ PointerFrameHeaderList parseFrameHeaders(uchar* const start, const size_t& size) current += sizeof(FrameSize); header.fileOffset = current - start; if (end - current < header.size) { - current = end; break; } current += header.size; diff --git a/libraries/render-utils/src/BloomApply.shared.slh b/libraries/render-utils/src/BloomApply.shared.slh new file mode 100644 index 0000000000..61f5a983b3 --- /dev/null +++ b/libraries/render-utils/src/BloomApply.shared.slh @@ -0,0 +1,16 @@ +// glsl / C++ compatible source as interface for BloomApply +#ifdef __cplusplus +# define BA_VEC3 glm::vec3 +#else +# define BA_VEC3 vec3 +#endif + +struct Parameters +{ + BA_VEC3 _intensities; +}; + + // <@if 1@> + // Trigger Scribe include + // <@endif@> +// diff --git a/libraries/render-utils/src/BloomApply.slf b/libraries/render-utils/src/BloomApply.slf index 961438888e..28415643a0 100644 --- a/libraries/render-utils/src/BloomApply.slf +++ b/libraries/render-utils/src/BloomApply.slf @@ -9,11 +9,15 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include BloomApply.shared.slh@> uniform sampler2D blurMap0; uniform sampler2D blurMap1; uniform sampler2D blurMap2; -uniform vec3 intensity; + +layout(std140) uniform parametersBuffer { + Parameters parameters; +}; in vec2 varTexCoord0; out vec4 outFragColor; @@ -23,5 +27,5 @@ void main(void) { vec4 blur1 = texture(blurMap1, varTexCoord0); vec4 blur2 = texture(blurMap2, varTexCoord0); - outFragColor = vec4(blur0.rgb*intensity.x + blur1.rgb*intensity.y + blur2.rgb*intensity.z, 1.0f); + outFragColor = vec4(blur0.rgb*parameters._intensities.x + blur1.rgb*parameters._intensities.y + blur2.rgb*parameters._intensities.z, 1.0f); } diff --git a/libraries/render-utils/src/BloomEffect.cpp b/libraries/render-utils/src/BloomEffect.cpp index ee06e17578..0e95655370 100644 --- a/libraries/render-utils/src/BloomEffect.cpp +++ b/libraries/render-utils/src/BloomEffect.cpp @@ -21,13 +21,15 @@ #define BLOOM_BLUR_LEVEL_COUNT 3 -BloomThreshold::BloomThreshold(unsigned int downsamplingFactor) : - _downsamplingFactor(downsamplingFactor) { +BloomThreshold::BloomThreshold(unsigned int downsamplingFactor) { assert(downsamplingFactor > 0); + _parameters.edit()._sampleCount = downsamplingFactor; } void BloomThreshold::configure(const Config& config) { - _threshold = config.threshold; + if (_parameters.get()._threshold != config.threshold) { + _parameters.edit()._threshold = config.threshold; + } } void BloomThreshold::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { @@ -43,10 +45,11 @@ void BloomThreshold::run(const render::RenderContextPointer& renderContext, cons auto inputBuffer = inputFrameBuffer->getRenderBuffer(0); auto bufferSize = gpu::Vec2u(inputBuffer->getDimensions()); + const auto downSamplingFactor = _parameters.get()._sampleCount; // Downsample resolution - bufferSize.x /= _downsamplingFactor; - bufferSize.y /= _downsamplingFactor; + bufferSize.x /= downSamplingFactor; + bufferSize.y /= downSamplingFactor; if (!_outputBuffer || _outputBuffer->getSize() != bufferSize) { auto colorTexture = gpu::TexturePointer(gpu::Texture::createRenderBuffer(inputBuffer->getTexelFormat(), bufferSize.x, bufferSize.y, @@ -54,10 +57,12 @@ void BloomThreshold::run(const render::RenderContextPointer& renderContext, cons _outputBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("BloomThreshold")); _outputBuffer->setRenderBuffer(0, colorTexture); + + _parameters.edit()._deltaUV = { 1.0f / bufferSize.x, 1.0f / bufferSize.y }; } static const int COLOR_MAP_SLOT = 0; - static const int THRESHOLD_SLOT = 1; + static const int PARAMETERS_SLOT = 1; if (!_pipeline) { auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); @@ -66,7 +71,7 @@ void BloomThreshold::run(const render::RenderContextPointer& renderContext, cons gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding("colorMap", COLOR_MAP_SLOT)); - slotBindings.insert(gpu::Shader::Binding("threshold", THRESHOLD_SLOT)); + slotBindings.insert(gpu::Shader::Binding("parametersBuffer", PARAMETERS_SLOT)); gpu::Shader::makeProgram(*program, slotBindings); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); @@ -86,21 +91,26 @@ void BloomThreshold::run(const render::RenderContextPointer& renderContext, cons batch.setFramebuffer(_outputBuffer); batch.setResourceTexture(COLOR_MAP_SLOT, inputBuffer); - batch._glUniform1f(THRESHOLD_SLOT, _threshold); + batch.setUniformBuffer(PARAMETERS_SLOT, _parameters); batch.draw(gpu::TRIANGLE_STRIP, 4); }); outputs = _outputBuffer; } -BloomApply::BloomApply() : _intensities{ 1.0f, 1.0f, 1.0f } { +BloomApply::BloomApply() { } void BloomApply::configure(const Config& config) { - _intensities.x = config.intensity / 3.0f; - _intensities.y = _intensities.x; - _intensities.z = _intensities.x; + const auto newIntensity = config.intensity / 3.0f; + + if (_parameters.get()._intensities.x != newIntensity) { + auto& parameters = _parameters.edit(); + parameters._intensities.x = newIntensity; + parameters._intensities.y = newIntensity; + parameters._intensities.z = newIntensity; + } } void BloomApply::run(const render::RenderContextPointer& renderContext, const Inputs& inputs) { @@ -111,7 +121,7 @@ void BloomApply::run(const render::RenderContextPointer& renderContext, const In static const auto BLUR0_SLOT = 0; static const auto BLUR1_SLOT = 1; static const auto BLUR2_SLOT = 2; - static const auto INTENSITY_SLOT = 3; + static const auto PARAMETERS_SLOT = 0; if (!_pipeline) { auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); @@ -122,7 +132,7 @@ void BloomApply::run(const render::RenderContextPointer& renderContext, const In slotBindings.insert(gpu::Shader::Binding("blurMap0", BLUR0_SLOT)); slotBindings.insert(gpu::Shader::Binding("blurMap1", BLUR1_SLOT)); slotBindings.insert(gpu::Shader::Binding("blurMap2", BLUR2_SLOT)); - slotBindings.insert(gpu::Shader::Binding("intensity", INTENSITY_SLOT)); + slotBindings.insert(gpu::Shader::Binding("parametersBuffer", PARAMETERS_SLOT)); gpu::Shader::makeProgram(*program, slotBindings); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); @@ -151,7 +161,7 @@ void BloomApply::run(const render::RenderContextPointer& renderContext, const In batch.setResourceTexture(BLUR0_SLOT, blur0FB->getRenderBuffer(0)); batch.setResourceTexture(BLUR1_SLOT, blur1FB->getRenderBuffer(0)); batch.setResourceTexture(BLUR2_SLOT, blur2FB->getRenderBuffer(0)); - batch._glUniform3f(INTENSITY_SLOT, _intensities.x, _intensities.y, _intensities.z); + batch.setUniformBuffer(PARAMETERS_SLOT, _parameters); batch.draw(gpu::TRIANGLE_STRIP, 4); }); } diff --git a/libraries/render-utils/src/BloomEffect.h b/libraries/render-utils/src/BloomEffect.h index 2ff6bc35a7..04cb4a9474 100644 --- a/libraries/render-utils/src/BloomEffect.h +++ b/libraries/render-utils/src/BloomEffect.h @@ -61,10 +61,11 @@ public: private: +#include "BloomThreshold.shared.slh" + gpu::FramebufferPointer _outputBuffer; gpu::PipelinePointer _pipeline; - float _threshold; - unsigned int _downsamplingFactor; + gpu::StructBuffer _parameters; }; @@ -95,8 +96,10 @@ public: private: +#include "BloomApply.shared.slh" + gpu::PipelinePointer _pipeline; - glm::vec3 _intensities; + gpu::StructBuffer _parameters; }; class BloomDraw { diff --git a/libraries/render-utils/src/BloomThreshold.shared.slh b/libraries/render-utils/src/BloomThreshold.shared.slh new file mode 100644 index 0000000000..8aaf8ec311 --- /dev/null +++ b/libraries/render-utils/src/BloomThreshold.shared.slh @@ -0,0 +1,18 @@ +// glsl / C++ compatible source as interface for BloomThreshold +#ifdef __cplusplus +# define BT_VEC2 glm::vec2 +#else +# define BT_VEC2 vec2 +#endif + +struct Parameters +{ + BT_VEC2 _deltaUV; + float _threshold; + int _sampleCount; +}; + + // <@if 1@> + // Trigger Scribe include + // <@endif@> +// diff --git a/libraries/render-utils/src/BloomThreshold.slf b/libraries/render-utils/src/BloomThreshold.slf index e4b96618df..6eb75fba6e 100644 --- a/libraries/render-utils/src/BloomThreshold.slf +++ b/libraries/render-utils/src/BloomThreshold.slf @@ -9,37 +9,35 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include BloomThreshold.shared.slh@> uniform sampler2D colorMap; -uniform float threshold; +layout(std140) uniform parametersBuffer { + Parameters parameters; +}; in vec2 varTexCoord0; out vec4 outFragColor; -#define DOWNSAMPLING_FACTOR 4 -#define SAMPLE_COUNT (DOWNSAMPLING_FACTOR/2) - void main(void) { - vec2 deltaX = dFdx(varTexCoord0) / SAMPLE_COUNT; - vec2 deltaY = dFdy(varTexCoord0) / SAMPLE_COUNT; vec2 startUv = varTexCoord0; vec4 maskedColor = vec4(0,0,0,0); - for (int y=0 ; ygetMeshes(); @@ -94,15 +94,10 @@ void CauterizedModel::createVisibleRenderItemSet() { } } } else { - Model::createVisibleRenderItemSet(); + Model::createRenderItemSet(); } } -void CauterizedModel::createCollisionRenderItemSet() { - // Temporary HACK: use base class method for now - Model::createCollisionRenderItemSet(); -} - void CauterizedModel::updateClusterMatrices() { PerformanceTimer perfTimer("CauterizedModel::updateClusterMatrices"); @@ -186,12 +181,6 @@ void CauterizedModel::updateRenderItems() { if (!_addedToScene) { return; } - - glm::vec3 scale = getScale(); - if (_collisionGeometry) { - // _collisionGeometry is already scaled - scale = glm::vec3(1.0f); - } _needsUpdateClusterMatrices = true; _renderItemsNeedUpdate = false; diff --git a/libraries/render-utils/src/CauterizedModel.h b/libraries/render-utils/src/CauterizedModel.h index d16c928ba6..36a96fb006 100644 --- a/libraries/render-utils/src/CauterizedModel.h +++ b/libraries/render-utils/src/CauterizedModel.h @@ -31,9 +31,8 @@ public: void deleteGeometry() override; bool updateGeometry() override; - void createVisibleRenderItemSet() override; - void createCollisionRenderItemSet() override; - + void createRenderItemSet() override; + virtual void updateClusterMatrices() override; void updateRenderItems() override; diff --git a/libraries/render-utils/src/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index c17044be6d..8575df399e 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.cpp +++ b/libraries/render-utils/src/DebugDeferredBuffer.cpp @@ -60,7 +60,8 @@ enum TextureSlot { enum ParamSlot { CameraCorrection = 0, DeferredFrameTransform, - ShadowTransform + ShadowTransform, + DebugParametersBuffer }; static const std::string DEFAULT_ALBEDO_SHADER { @@ -139,12 +140,11 @@ static const std::string DEFAULT_LIGHTING_SHADER { " }" }; -static const std::string DEFAULT_SHADOW_SHADER{ - "uniform sampler2DShadow shadowMap;" +static const std::string DEFAULT_SHADOW_DEPTH_SHADER{ "vec4 getFragmentColor() {" " for (int i = 255; i >= 0; --i) {" " float depth = i / 255.0;" - " if (texture(shadowMap, vec3(uv, depth)) > 0.5) {" + " if (texture(shadowMaps, vec4(uv, parameters._shadowCascadeIndex, depth)) > 0.5) {" " return vec4(vec3(depth), 1.0);" " }" " }" @@ -323,7 +323,7 @@ std::string DebugDeferredBuffer::getShaderSourceCode(Mode mode, std::string cust case ShadowCascade1Mode: case ShadowCascade2Mode: case ShadowCascade3Mode: - return DEFAULT_SHADOW_SHADER; + return DEFAULT_SHADOW_DEPTH_SHADER; case ShadowCascadeIndicesMode: return DEFAULT_SHADOW_CASCADE_SHADER; case LinearDepthMode: @@ -396,6 +396,7 @@ const gpu::PipelinePointer& DebugDeferredBuffer::getPipeline(Mode mode, std::str slotBindings.insert(gpu::Shader::Binding("cameraCorrectionBuffer", CameraCorrection)); slotBindings.insert(gpu::Shader::Binding("deferredFrameTransformBuffer", DeferredFrameTransform)); slotBindings.insert(gpu::Shader::Binding("shadowTransformBuffer", ShadowTransform)); + slotBindings.insert(gpu::Shader::Binding("parametersBuffer", DebugParametersBuffer)); slotBindings.insert(gpu::Shader::Binding("albedoMap", Albedo)); slotBindings.insert(gpu::Shader::Binding("normalMap", Normal)); @@ -403,7 +404,7 @@ const gpu::PipelinePointer& DebugDeferredBuffer::getPipeline(Mode mode, std::str slotBindings.insert(gpu::Shader::Binding("depthMap", Depth)); slotBindings.insert(gpu::Shader::Binding("obscuranceMap", AmbientOcclusion)); slotBindings.insert(gpu::Shader::Binding("lightingMap", Lighting)); - slotBindings.insert(gpu::Shader::Binding("shadowMap", Shadow)); + slotBindings.insert(gpu::Shader::Binding("shadowMaps", Shadow)); slotBindings.insert(gpu::Shader::Binding("linearDepthMap", LinearDepth)); slotBindings.insert(gpu::Shader::Binding("halfLinearDepthMap", HalfLinearDepth)); slotBindings.insert(gpu::Shader::Binding("halfNormalMap", HalfNormal)); @@ -432,8 +433,11 @@ const gpu::PipelinePointer& DebugDeferredBuffer::getPipeline(Mode mode, std::str } void DebugDeferredBuffer::configure(const Config& config) { + auto& parameters = _parameters.edit(); + _mode = (Mode)config.mode; _size = config.size; + parameters._shadowCascadeIndex = glm::clamp(_mode - Mode::ShadowCascade0Mode, 0, (int)SHADOW_CASCADE_MAX_COUNT - 1); } void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const Inputs& inputs) { @@ -483,14 +487,15 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I batch.setResourceTexture(Velocity, velocityFramebuffer->getVelocityTexture()); } + batch.setUniformBuffer(DebugParametersBuffer, _parameters); + auto lightStage = renderContext->_scene->getStage(); assert(lightStage); assert(lightStage->getNumLights() > 0); auto lightAndShadow = lightStage->getCurrentKeyLightAndShadow(); const auto& globalShadow = lightAndShadow.second; if (globalShadow) { - const auto cascadeIndex = glm::clamp(_mode - Mode::ShadowCascade0Mode, 0, (int)globalShadow->getCascadeCount() - 1); - batch.setResourceTexture(Shadow, globalShadow->getCascade(cascadeIndex).map); + batch.setResourceTexture(Shadow, globalShadow->map); batch.setUniformBuffer(ShadowTransform, globalShadow->getBuffer()); batch.setUniformBuffer(DeferredFrameTransform, frameTransform->getFrameTransformBuffer()); } diff --git a/libraries/render-utils/src/DebugDeferredBuffer.h b/libraries/render-utils/src/DebugDeferredBuffer.h index 5384a77b76..9daa8fd530 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.h +++ b/libraries/render-utils/src/DebugDeferredBuffer.h @@ -30,7 +30,7 @@ public: DebugDeferredBufferConfig() : render::Job::Config(false) {} void setMode(int newMode); - + int mode{ 0 }; glm::vec4 size{ 0.0f, -1.0f, 1.0f, 1.0f }; signals: @@ -39,20 +39,26 @@ signals: class DebugDeferredBuffer { public: - using Inputs = render::VaryingSet6; + using Inputs = render::VaryingSet6; using Config = DebugDeferredBufferConfig; using JobModel = render::Job::ModelI; - + DebugDeferredBuffer(); ~DebugDeferredBuffer(); void configure(const Config& config); void run(const render::RenderContextPointer& renderContext, const Inputs& inputs); - + protected: friend class DebugDeferredBufferConfig; - enum Mode : uint8_t { + enum Mode : uint8_t + { // Use Mode suffix to avoid collisions Off = 0, DepthMode, @@ -83,7 +89,7 @@ protected: AmbientOcclusionMode, AmbientOcclusionBlurredMode, VelocityMode, - CustomMode, // Needs to stay last + CustomMode, // Needs to stay last NumModes, }; @@ -92,20 +98,25 @@ private: Mode _mode{ Off }; glm::vec4 _size; +#include "debug_deferred_buffer_shared.slh" + + using ParametersBuffer = gpu::StructBuffer; + struct CustomPipeline { gpu::PipelinePointer pipeline; mutable QFileInfo info; }; using StandardPipelines = std::array; using CustomPipelines = std::unordered_map; - + bool pipelineNeedsUpdate(Mode mode, std::string customFile = std::string()) const; const gpu::PipelinePointer& getPipeline(Mode mode, std::string customFile = std::string()); std::string getShaderSourceCode(Mode mode, std::string customFile = std::string()); - + + ParametersBuffer _parameters; StandardPipelines _pipelines; CustomPipelines _customPipelines; - int _geometryId { 0 }; + int _geometryId{ 0 }; }; -#endif // hifi_DebugDeferredBuffer_h \ No newline at end of file +#endif // hifi_DebugDeferredBuffer_h \ No newline at end of file diff --git a/libraries/render-utils/src/DeferredGlobalLight.slh b/libraries/render-utils/src/DeferredGlobalLight.slh index c4894200a3..03ec18c321 100644 --- a/libraries/render-utils/src/DeferredGlobalLight.slh +++ b/libraries/render-utils/src/DeferredGlobalLight.slh @@ -198,7 +198,7 @@ vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, fl vec3 directionalSpecular; evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surfaceWS, metallic, fresnel, albedo, shadowAttenuation); color += directionalDiffuse; - color += (ambientSpecular + directionalSpecular) / opacity; + color += evalSpecularWithOpacity(ambientSpecular + directionalSpecular, opacity); return color; } @@ -231,19 +231,18 @@ vec3 evalGlobalLightingAlphaBlendedWithHaze( vec3 directionalSpecular; evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surfaceWS, metallic, fresnel, albedo, shadowAttenuation); color += directionalDiffuse; - color += (ambientSpecular + directionalSpecular) / opacity; + color += evalSpecularWithOpacity(ambientSpecular + directionalSpecular, opacity); // Haze if ((isHazeEnabled() > 0.0) && (hazeParams.hazeMode & HAZE_MODE_IS_ACTIVE) == HAZE_MODE_IS_ACTIVE) { - vec4 colorV4 = computeHazeColor( - vec4(color, 1.0), // fragment original color + vec4 hazeColor = computeHazeColor( positionES, // fragment position in eye coordinates fragPositionWS, // fragment position in world coordinates invViewMat[3].xyz, // eye position in world coordinates lightDirection // keylight direction vector in world coordinates ); - color = colorV4.rgb; + color = mix(color.rgb, hazeColor.rgb, hazeColor.a); } return color; @@ -269,19 +268,18 @@ vec3 evalGlobalLightingAlphaBlendedWithHaze( evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surface, metallic, fresnel, albedo, shadowAttenuation); color += ambientDiffuse + directionalDiffuse; - color += (ambientSpecular + directionalSpecular) / opacity; + color += evalSpecularWithOpacity(ambientSpecular + directionalSpecular, opacity); // Haze if ((isHazeEnabled() > 0.0) && (hazeParams.hazeMode & HAZE_MODE_IS_ACTIVE) == HAZE_MODE_IS_ACTIVE) { - vec4 colorV4 = computeHazeColor( - vec4(color, 1.0), // fragment original color + vec4 hazeColor = computeHazeColor( positionES, // fragment position in eye coordinates positionWS, // fragment position in world coordinates invViewMat[3].xyz, // eye position in world coordinates lightDirection // keylight direction vector ); - color = colorV4.rgb; + color = mix(color.rgb, hazeColor.rgb, hazeColor.a); } return color; diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index a98e0403fa..62d8dffe3a 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -68,7 +68,7 @@ enum DeferredShader_MapSlot { SCATTERING_SPECULAR_UNIT = 9, SKYBOX_MAP_UNIT = render::ShapePipeline::Slot::LIGHT_AMBIENT_MAP, // unit = 10 SHADOW_MAP_UNIT = 11, - nextAvailableUnit = SHADOW_MAP_UNIT + SHADOW_CASCADE_MAX_COUNT + nextAvailableUnit = SHADOW_MAP_UNIT }; enum DeferredShader_BufferSlot { DEFERRED_FRAME_TRANSFORM_BUFFER_SLOT = 0, @@ -393,34 +393,42 @@ graphics::MeshPointer DeferredLightingEffect::getSpotLightMesh() { return _spotLightMesh; } -void PreparePrimaryFramebuffer::run(const RenderContextPointer& renderContext, gpu::FramebufferPointer& primaryFramebuffer) { +gpu::FramebufferPointer PreparePrimaryFramebuffer::createFramebuffer(const char* name, const glm::uvec2& frameSize) { + gpu::FramebufferPointer framebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(name)); + auto colorFormat = gpu::Element::COLOR_SRGBA_32; + + auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR); + auto primaryColorTexture = gpu::Texture::createRenderBuffer(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler); + + framebuffer->setRenderBuffer(0, primaryColorTexture); + + auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format + auto primaryDepthTexture = gpu::Texture::createRenderBuffer(depthFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler); + + framebuffer->setDepthStencilBuffer(primaryDepthTexture, depthFormat); + + return framebuffer; +} + +void PreparePrimaryFramebuffer::configure(const Config& config) { + _resolutionScale = config.resolutionScale; +} + +void PreparePrimaryFramebuffer::run(const RenderContextPointer& renderContext, Output& primaryFramebuffer) { glm::uvec2 frameSize(renderContext->args->_viewport.z, renderContext->args->_viewport.w); + glm::uvec2 scaledFrameSize(glm::vec2(frameSize) * _resolutionScale); // Resizing framebuffers instead of re-building them seems to cause issues with threaded // rendering - if (_primaryFramebuffer && _primaryFramebuffer->getSize() != frameSize) { - _primaryFramebuffer.reset(); + if (!_primaryFramebuffer || _primaryFramebuffer->getSize() != scaledFrameSize) { + _primaryFramebuffer = createFramebuffer("deferredPrimary", scaledFrameSize); } - if (!_primaryFramebuffer) { - _primaryFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("deferredPrimary")); - auto colorFormat = gpu::Element::COLOR_SRGBA_32; - - auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); - auto primaryColorTexture = gpu::Texture::createRenderBuffer(colorFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler); - - - _primaryFramebuffer->setRenderBuffer(0, primaryColorTexture); - - - auto depthFormat = gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::DEPTH_STENCIL); // Depth24_Stencil8 texel format - auto primaryDepthTexture = gpu::Texture::createRenderBuffer(depthFormat, frameSize.x, frameSize.y, gpu::Texture::SINGLE_MIP, defaultSampler); - - _primaryFramebuffer->setDepthStencilBuffer(primaryDepthTexture, depthFormat); - } - - primaryFramebuffer = _primaryFramebuffer; + + // Set viewport for the rest of the scaled passes + renderContext->args->_viewport.z = scaledFrameSize.x; + renderContext->args->_viewport.w = scaledFrameSize.y; } void PrepareDeferred::run(const RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { @@ -472,7 +480,8 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, const graphics::HazePointer& haze, const SurfaceGeometryFramebufferPointer& surfaceGeometryFramebuffer, const AmbientOcclusionFramebufferPointer& ambientOcclusionFramebuffer, - const SubsurfaceScatteringResourcePointer& subsurfaceScatteringResource) { + const SubsurfaceScatteringResourcePointer& subsurfaceScatteringResource, + bool renderShadows) { auto args = renderContext->args; auto& batch = (*args->_batch); @@ -533,9 +542,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, // Bind the shadow buffers if (globalShadow) { - for (unsigned int i = 0; i < globalShadow->getCascadeCount(); i++) { - batch.setResourceTexture(SHADOW_MAP_UNIT+i, globalShadow->getCascade(i).map); - } + batch.setResourceTexture(SHADOW_MAP_UNIT, globalShadow->map); } auto program = deferredLightingEffect->_directionalSkyboxLight; @@ -554,7 +561,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, // Check if keylight casts shadows bool keyLightCastShadows { false }; - if (lightStage && lightStage->_currentFrame._sunLights.size()) { + if (renderShadows && lightStage && lightStage->_currentFrame._sunLights.size()) { graphics::LightPointer keyLight = lightStage->getLight(lightStage->_currentFrame._sunLights.front()); if (keyLight) { keyLightCastShadows = keyLight->getCastShadows(); @@ -711,11 +718,6 @@ void RenderDeferredCleanup::run(const render::RenderContextPointer& renderContex } } -RenderDeferred::RenderDeferred() { - -} - - void RenderDeferred::configure(const Config& config) { } @@ -742,7 +744,7 @@ void RenderDeferred::run(const RenderContextPointer& renderContext, const Inputs args->_batch = &batch; _gpuTimer->begin(batch); - setupJob.run(renderContext, deferredTransform, deferredFramebuffer, lightingModel, haze, surfaceGeometryFramebuffer, ssaoFramebuffer, subsurfaceScatteringResource); + setupJob.run(renderContext, deferredTransform, deferredFramebuffer, lightingModel, haze, surfaceGeometryFramebuffer, ssaoFramebuffer, subsurfaceScatteringResource, _renderShadows); lightsJob.run(renderContext, deferredTransform, deferredFramebuffer, lightingModel, surfaceGeometryFramebuffer, lightClusters); diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 3b77b8137e..5da2eb22f7 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -93,13 +93,34 @@ private: friend class RenderDeferredCleanup; }; +class PreparePrimaryFramebufferConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(float resolutionScale MEMBER resolutionScale NOTIFY dirty) +public: + + float resolutionScale{ 1.0f }; + +signals: + void dirty(); +}; + class PreparePrimaryFramebuffer { public: - using JobModel = render::Job::ModelO; - void run(const render::RenderContextPointer& renderContext, gpu::FramebufferPointer& primaryFramebuffer); + using Output = gpu::FramebufferPointer; + using Config = PreparePrimaryFramebufferConfig; + using JobModel = render::Job::ModelO; + + PreparePrimaryFramebuffer(float resolutionScale = 1.0f) : _resolutionScale{resolutionScale} {} + void configure(const Config& config); + void run(const render::RenderContextPointer& renderContext, Output& primaryFramebuffer); gpu::FramebufferPointer _primaryFramebuffer; + float _resolutionScale{ 1.0f }; + +private: + + static gpu::FramebufferPointer createFramebuffer(const char* name, const glm::uvec2& size); }; class PrepareDeferred { @@ -127,7 +148,8 @@ public: const graphics::HazePointer& haze, const SurfaceGeometryFramebufferPointer& surfaceGeometryFramebuffer, const AmbientOcclusionFramebufferPointer& ambientOcclusionFramebuffer, - const SubsurfaceScatteringResourcePointer& subsurfaceScatteringResource); + const SubsurfaceScatteringResourcePointer& subsurfaceScatteringResource, + bool renderShadows); }; class RenderDeferredLocals { @@ -166,7 +188,8 @@ public: using Config = RenderDeferredConfig; using JobModel = render::Job::ModelI; - RenderDeferred(); + RenderDeferred() {} + RenderDeferred(bool renderShadows) : _renderShadows(renderShadows) {} void configure(const Config& config); @@ -178,6 +201,9 @@ public: protected: gpu::RangeTimerPointer _gpuTimer; + +private: + bool _renderShadows { false }; }; class DefaultLightingSetup { diff --git a/libraries/render-utils/src/DrawHaze.cpp b/libraries/render-utils/src/DrawHaze.cpp index e6337d7099..94bac4e3ac 100644 --- a/libraries/render-utils/src/DrawHaze.cpp +++ b/libraries/render-utils/src/DrawHaze.cpp @@ -107,11 +107,11 @@ void MakeHaze::run(const render::RenderContextPointer& renderContext, graphics:: haze = _haze; } +// Buffer slots const int HazeEffect_ParamsSlot = 0; const int HazeEffect_TransformBufferSlot = 1; -const int HazeEffect_ColorMapSlot = 2; -const int HazeEffect_LinearDepthMapSlot = 3; -const int HazeEffect_LightingMapSlot = 4; +// Texture slots +const int HazeEffect_LinearDepthMapSlot = 0; void DrawHaze::configure(const Config& config) { } @@ -122,11 +122,10 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu return; } - const auto inputBuffer = inputs.get1()->getRenderBuffer(0); + const auto outputBuffer = inputs.get1(); const auto framebuffer = inputs.get2(); const auto transformBuffer = inputs.get3(); - - auto outputBuffer = inputs.get4(); + const auto lightingModel = inputs.get4(); auto depthBuffer = framebuffer->getLinearDepthTexture(); @@ -139,6 +138,10 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + 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); + // Mask out haze on the tablet PrepareStencil::testMask(*state); @@ -148,15 +151,15 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu gpu::Shader::BindingSet slotBindings; slotBindings.insert(gpu::Shader::Binding(std::string("hazeBuffer"), HazeEffect_ParamsSlot)); slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), HazeEffect_TransformBufferSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("colorMap"), HazeEffect_ColorMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("lightingModelBuffer"), render::ShapePipeline::Slot::LIGHTING_MODEL)); slotBindings.insert(gpu::Shader::Binding(std::string("linearDepthMap"), HazeEffect_LinearDepthMapSlot)); - slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), HazeEffect_LightingMapSlot)); + slotBindings.insert(gpu::Shader::Binding(std::string("keyLightBuffer"), render::ShapePipeline::Slot::KEY_LIGHT)); gpu::Shader::makeProgram(*program, slotBindings); }); }); } - auto sourceFramebufferSize = glm::ivec2(inputBuffer->getDimensions()); + auto outputFramebufferSize = glm::ivec2(outputBuffer->getSize()); gpu::doInBatch("DrawHaze::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); @@ -165,7 +168,7 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu batch.setViewportTransform(args->_viewport); batch.setProjectionTransform(glm::mat4()); batch.resetViewTransform(); - batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(sourceFramebufferSize, args->_viewport)); + batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(outputFramebufferSize, args->_viewport)); batch.setPipeline(_hazePipeline); @@ -181,17 +184,17 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu } batch.setUniformBuffer(HazeEffect_TransformBufferSlot, transformBuffer->getFrameTransformBuffer()); + batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer()); auto lightStage = args->_scene->getStage(); if (lightStage) { graphics::LightPointer keyLight; keyLight = lightStage->getCurrentKeyLight(); if (keyLight) { - batch.setUniformBuffer(HazeEffect_LightingMapSlot, keyLight->getLightSchemaBuffer()); + batch.setUniformBuffer(render::ShapePipeline::Slot::KEY_LIGHT, keyLight->getLightSchemaBuffer()); } } - batch.setResourceTexture(HazeEffect_ColorMapSlot, inputBuffer); batch.setResourceTexture(HazeEffect_LinearDepthMapSlot, depthBuffer); batch.draw(gpu::TRIANGLE_STRIP, 4); diff --git a/libraries/render-utils/src/DrawHaze.h b/libraries/render-utils/src/DrawHaze.h index e7d4e15d77..e30ce26dd4 100644 --- a/libraries/render-utils/src/DrawHaze.h +++ b/libraries/render-utils/src/DrawHaze.h @@ -22,6 +22,7 @@ #include #include "SurfaceGeometryPass.h" +#include "LightingModel.h" using LinearDepthFramebufferPointer = std::shared_ptr; @@ -159,7 +160,7 @@ public: class DrawHaze { public: - using Inputs = render::VaryingSet5; + using Inputs = render::VaryingSet5; using Config = HazeConfig; using JobModel = render::Job::ModelI; diff --git a/libraries/render-utils/src/Fade.slh b/libraries/render-utils/src/Fade.slh index a06c8c869e..a4e8fdf1f4 100644 --- a/libraries/render-utils/src/Fade.slh +++ b/libraries/render-utils/src/Fade.slh @@ -15,19 +15,20 @@ #define CATEGORY_COUNT 5 <@include Fade_shared.slh@> +<@include FadeObjectParams.shared.slh@> layout(std140) uniform fadeParametersBuffer { FadeParameters fadeParameters[CATEGORY_COUNT]; }; uniform sampler2D fadeMaskMap; -struct FadeObjectParams { - int category; - float threshold; - vec3 noiseOffset; - vec3 baseOffset; - vec3 baseInvSize; -}; +vec3 getNoiseInverseSize(int category) { + return fadeParameters[category]._noiseInvSizeAndLevel.xyz; +} + +float getNoiseLevel(int category) { + return fadeParameters[category]._noiseInvSizeAndLevel.w; +} vec2 hash2D(vec3 position) { return position.xy* vec2(0.1677, 0.221765) + position.z*0.561; @@ -40,7 +41,7 @@ float noise3D(vec3 position) { float evalFadeNoiseGradient(FadeObjectParams params, vec3 position) { // Do tri-linear interpolation - vec3 noisePosition = position * fadeParameters[params.category]._noiseInvSizeAndLevel.xyz + params.noiseOffset; + vec3 noisePosition = position * getNoiseInverseSize(params.category) + params.noiseOffset.xyz; vec3 noisePositionFloored = floor(noisePosition); vec3 noisePositionFraction = fract(noisePosition); @@ -61,11 +62,11 @@ float evalFadeNoiseGradient(FadeObjectParams params, vec3 position) { float noise = mix(maskY.x, maskY.y, noisePositionFraction.y); noise -= 0.5; // Center on value 0 - return noise * fadeParameters[params.category]._noiseInvSizeAndLevel.w; + return noise * getNoiseLevel(params.category); } float evalFadeBaseGradient(FadeObjectParams params, vec3 position) { - float gradient = length((position - params.baseOffset) * params.baseInvSize.xyz); + float gradient = length((position - params.baseOffset.xyz) * params.baseInvSize.xyz); gradient = gradient-0.5; // Center on value 0.5 gradient *= fadeParameters[params.category]._baseLevel; return gradient; @@ -112,20 +113,14 @@ void applyFade(FadeObjectParams params, vec3 position, out vec3 emissive) { <@func declareFadeFragmentUniform()@> -uniform int fadeCategory; -uniform vec3 fadeNoiseOffset; -uniform vec3 fadeBaseOffset; -uniform vec3 fadeBaseInvSize; -uniform float fadeThreshold; +layout(std140) uniform fadeObjectParametersBuffer { + FadeObjectParams fadeObjectParams; +}; <@endfunc@> <@func fetchFadeObjectParams(fadeParams)@> - <$fadeParams$>.category = fadeCategory; - <$fadeParams$>.threshold = fadeThreshold; - <$fadeParams$>.noiseOffset = fadeNoiseOffset; - <$fadeParams$>.baseOffset = fadeBaseOffset; - <$fadeParams$>.baseInvSize = fadeBaseInvSize; + <$fadeParams$> = fadeObjectParams; <@endfunc@> <@func declareFadeFragmentVertexInput()@> @@ -139,9 +134,9 @@ in vec4 _fadeData3; <@func fetchFadeObjectParamsInstanced(fadeParams)@> <$fadeParams$>.category = int(_fadeData1.w); <$fadeParams$>.threshold = _fadeData2.w; - <$fadeParams$>.noiseOffset = _fadeData1.xyz; - <$fadeParams$>.baseOffset = _fadeData2.xyz; - <$fadeParams$>.baseInvSize = _fadeData3.xyz; + <$fadeParams$>.noiseOffset = _fadeData1; + <$fadeParams$>.baseOffset = _fadeData2; + <$fadeParams$>.baseInvSize = _fadeData3; <@endfunc@> <@func declareFadeFragment()@> diff --git a/libraries/render-utils/src/FadeEffect.cpp b/libraries/render-utils/src/FadeEffect.cpp index c09aa99988..12531d4c9d 100644 --- a/libraries/render-utils/src/FadeEffect.cpp +++ b/libraries/render-utils/src/FadeEffect.cpp @@ -13,6 +13,8 @@ #include "render/TransitionStage.h" +#include "FadeObjectParams.shared.slh" + #include FadeEffect::FadeEffect() { @@ -31,15 +33,8 @@ void FadeEffect::build(render::Task::TaskConcept& task, const task::Varying& edi render::ShapePipeline::BatchSetter FadeEffect::getBatchSetter() const { return [this](const render::ShapePipeline& shapePipeline, gpu::Batch& batch, render::Args*) { - auto program = shapePipeline.pipeline->getProgram(); - auto maskMapLocation = program->getTextures().findLocation("fadeMaskMap"); - auto bufferLocation = program->getUniformBuffers().findLocation("fadeParametersBuffer"); - if (maskMapLocation != -1) { - batch.setResourceTexture(maskMapLocation, _maskMap); - } - if (bufferLocation != -1) { - batch.setUniformBuffer(bufferLocation, _configurations); - } + batch.setResourceTexture(render::ShapePipeline::Slot::FADE_MASK, _maskMap); + batch.setUniformBuffer(render::ShapePipeline::Slot::FADE_PARAMETERS, _configurations); }; } @@ -50,23 +45,29 @@ render::ShapePipeline::ItemSetter FadeEffect::getItemUniformSetter() const { auto batch = args->_batch; auto transitionStage = scene->getStage(render::TransitionStage::getName()); auto& transitionState = transitionStage->getTransition(item.getTransitionId()); - auto program = shapePipeline.pipeline->getProgram(); - auto& uniforms = program->getUniforms(); - auto fadeNoiseOffsetLocation = uniforms.findLocation("fadeNoiseOffset"); - auto fadeBaseOffsetLocation = uniforms.findLocation("fadeBaseOffset"); - auto fadeBaseInvSizeLocation = uniforms.findLocation("fadeBaseInvSize"); - auto fadeThresholdLocation = uniforms.findLocation("fadeThreshold"); - auto fadeCategoryLocation = uniforms.findLocation("fadeCategory"); - if (fadeNoiseOffsetLocation >= 0 || fadeBaseInvSizeLocation >= 0 || fadeBaseOffsetLocation >= 0 || fadeThresholdLocation >= 0 || fadeCategoryLocation >= 0) { - const auto fadeCategory = FadeJob::transitionToCategory[transitionState.eventType]; - - batch->_glUniform1i(fadeCategoryLocation, fadeCategory); - batch->_glUniform1f(fadeThresholdLocation, transitionState.threshold); - batch->_glUniform3f(fadeNoiseOffsetLocation, transitionState.noiseOffset.x, transitionState.noiseOffset.y, transitionState.noiseOffset.z); - batch->_glUniform3f(fadeBaseOffsetLocation, transitionState.baseOffset.x, transitionState.baseOffset.y, transitionState.baseOffset.z); - batch->_glUniform3f(fadeBaseInvSizeLocation, transitionState.baseInvSize.x, transitionState.baseInvSize.y, transitionState.baseInvSize.z); + if (transitionState.paramsBuffer._size != sizeof(gpu::StructBuffer)) { + static_assert(sizeof(transitionState.paramsBuffer) == sizeof(gpu::StructBuffer), "Assuming gpu::StructBuffer is a helper class for gpu::BufferView"); + transitionState.paramsBuffer = gpu::StructBuffer(); } + + const auto fadeCategory = FadeJob::transitionToCategory[transitionState.eventType]; + auto& paramsConst = static_cast&>(transitionState.paramsBuffer).get(); + + if (paramsConst.category != fadeCategory + || paramsConst.threshold != transitionState.threshold + || glm::vec3(paramsConst.baseOffset) != transitionState.baseOffset + || glm::vec3(paramsConst.noiseOffset) != transitionState.noiseOffset + || glm::vec3(paramsConst.baseInvSize) != transitionState.baseInvSize) { + auto& params = static_cast&>(transitionState.paramsBuffer).edit(); + + params.category = fadeCategory; + params.threshold = transitionState.threshold; + params.baseInvSize = glm::vec4(transitionState.baseInvSize, 0.0f); + params.noiseOffset = glm::vec4(transitionState.noiseOffset, 0.0f); + params.baseOffset = glm::vec4(transitionState.baseOffset, 0.0f); + } + batch->setUniformBuffer(render::ShapePipeline::Slot::FADE_OBJECT_PARAMETERS, transitionState.paramsBuffer); } }; } diff --git a/libraries/render-utils/src/FadeObjectParams.shared.slh b/libraries/render-utils/src/FadeObjectParams.shared.slh new file mode 100644 index 0000000000..e97acaf0b0 --- /dev/null +++ b/libraries/render-utils/src/FadeObjectParams.shared.slh @@ -0,0 +1,25 @@ +// glsl / C++ compatible source as interface for FadeObjectParams +#ifdef __cplusplus +# define FOP_VEC4 glm::vec4 +# define FOP_VEC2 glm::vec2 +# define FOP_FLOAT32 glm::float32 +# define FOP_INT32 glm::int32 +#else +# define FOP_VEC4 vec4 +# define FOP_VEC2 vec2 +# define FOP_FLOAT32 float +# define FOP_INT32 int +#endif + +struct FadeObjectParams { + FOP_VEC4 noiseOffset; + FOP_VEC4 baseOffset; + FOP_VEC4 baseInvSize; + FOP_INT32 category; + FOP_FLOAT32 threshold; +}; + + // <@if 1@> + // Trigger Scribe include + // <@endif@> +// diff --git a/libraries/render-utils/src/ForwardGlobalLight.slh b/libraries/render-utils/src/ForwardGlobalLight.slh index 84999f347b..cf5f070c55 100644 --- a/libraries/render-utils/src/ForwardGlobalLight.slh +++ b/libraries/render-utils/src/ForwardGlobalLight.slh @@ -197,7 +197,7 @@ vec3 evalGlobalLightingAlphaBlended(mat4 invViewMat, float shadowAttenuation, fl vec3 directionalSpecular; evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surfaceWS, metallic, fresnel, albedo, shadowAttenuation); color += directionalDiffuse; - color += (ambientSpecular + directionalSpecular) / opacity; + color += evalSpecularWithOpacity(ambientSpecular + directionalSpecular, opacity); return color; } @@ -223,20 +223,19 @@ vec3 evalGlobalLightingAlphaBlendedWithHaze( vec3 directionalSpecular; evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surfaceWS, metallic, fresnel, albedo, shadowAttenuation); color += directionalDiffuse; - color += (ambientSpecular + directionalSpecular) / opacity; + color += evalSpecularWithOpacity(ambientSpecular + directionalSpecular, opacity); // Haze // FIXME - temporarily removed until we support it for forward... /* if ((hazeParams.hazeMode & HAZE_MODE_IS_ACTIVE) == HAZE_MODE_IS_ACTIVE) { - vec4 colorV4 = computeHazeColor( - vec4(color, 1.0), // fragment original color + vec4 hazeColor = computeHazeColor( positionES, // fragment position in eye coordinates fragPositionWS, // fragment position in world coordinates invViewMat[3].xyz, // eye position in world coordinates lightDirection // keylight direction vector ); - color = colorV4.rgb; + color = mix(color.rgb, hazeColor.rgb, hazeColor.a); }*/ return color; diff --git a/libraries/render-utils/src/Haze.slf b/libraries/render-utils/src/Haze.slf index 6b45a72768..93b66d99ed 100644 --- a/libraries/render-utils/src/Haze.slf +++ b/libraries/render-utils/src/Haze.slf @@ -22,7 +22,6 @@ <@include Haze.slh@> -uniform sampler2D colorMap; uniform sampler2D linearDepthMap; vec4 unpackPositionFromZeye(vec2 texcoord) { @@ -46,7 +45,6 @@ void main(void) { discard; } - vec4 fragColor = texture(colorMap, varTexCoord0); vec4 fragPositionES = unpackPositionFromZeye(varTexCoord0); mat4 viewInverse = getViewInverse(); @@ -56,5 +54,8 @@ void main(void) { Light light = getKeyLight(); vec3 lightDirectionWS = getLightDirection(light); - outFragColor = computeHazeColor(fragColor, fragPositionES.xyz, fragPositionWS.xyz, eyePositionWS.xyz, lightDirectionWS); + outFragColor = computeHazeColor(fragPositionES.xyz, fragPositionWS.xyz, eyePositionWS.xyz, lightDirectionWS); + if (outFragColor.a < 1e-4) { + discard; + } } diff --git a/libraries/render-utils/src/Haze.slh b/libraries/render-utils/src/Haze.slh index ab973ba752..7854ad08ca 100644 --- a/libraries/render-utils/src/Haze.slh +++ b/libraries/render-utils/src/Haze.slh @@ -92,22 +92,21 @@ vec3 computeHazeColorKeyLightAttenuation(vec3 color, vec3 lightDirectionWS, vec3 } // Input: -// fragColor - fragment original color // fragPositionES - fragment position in eye coordinates // fragPositionWS - fragment position in world coordinates // eyePositionWS - eye position in world coordinates // Output: -// fragment colour after haze effect +// haze colour and alpha contains haze blend factor // // General algorithm taken from http://www.iquilezles.org/www/articles/fog/fog.htm, with permission // -vec4 computeHazeColor(vec4 fragColor, vec3 fragPositionES, vec3 fragPositionWS, vec3 eyePositionWS, vec3 lightDirectionWS) { +vec4 computeHazeColor(vec3 fragPositionES, vec3 fragPositionWS, vec3 eyePositionWS, vec3 lightDirectionWS) { // Distance to fragment float distance = length(fragPositionES); float eyeWorldHeight = eyePositionWS.y; // Convert haze colour from uniform into a vec4 - vec4 hazeColor = vec4(hazeParams.hazeColor, 1.0); + vec4 hazeColor = vec4(hazeParams.hazeColor, 1.0); // Use the haze colour for the glare colour, if blend is not enabled vec4 blendedHazeColor; @@ -149,13 +148,13 @@ vec4 computeHazeColor(vec4 fragColor, vec3 fragPositionES, vec3 fragPositionWS, vec3 hazeAmount = 1.0 - exp(-hazeIntegral); // Compute color after haze effect - potentialFragColor = mix(fragColor, vec4(1.0, 1.0, 1.0, 1.0), vec4(hazeAmount, 1.0)); + potentialFragColor = vec4(1.0, 1.0, 1.0, hazeAmount); } else if ((hazeParams.hazeMode & HAZE_MODE_IS_ALTITUDE_BASED) != HAZE_MODE_IS_ALTITUDE_BASED) { // Haze is based only on range float hazeAmount = 1.0 - exp(-distance * hazeParams.hazeRangeFactor); // Compute color after haze effect - potentialFragColor = mix(fragColor, blendedHazeColor, hazeAmount); + potentialFragColor = vec4(blendedHazeColor.rgb, hazeAmount); } else { // Haze is based on both range and altitude // Taken from www.crytek.com/download/GDC2007_RealtimeAtmoFxInGamesRev.ppt @@ -181,16 +180,14 @@ vec4 computeHazeColor(vec4 fragColor, vec3 fragPositionES, vec3 fragPositionWS, float hazeAmount = 1.0 - exp(-hazeIntegral); // Compute color after haze effect - potentialFragColor = mix(fragColor, blendedHazeColor, hazeAmount); + potentialFragColor = vec4(blendedHazeColor.rgb, hazeAmount); } // Mix with background at far range const float BLEND_DISTANCE = 27000.0f; - vec4 outFragColor; + vec4 outFragColor = potentialFragColor; if (distance > BLEND_DISTANCE) { - outFragColor = mix(potentialFragColor, fragColor, hazeParams.backgroundBlend); - } else { - outFragColor = potentialFragColor; + outFragColor.a *= hazeParams.backgroundBlend; } return outFragColor; diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index 3905d3a54c..6c8a90da81 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -117,6 +117,9 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c assert(renderContext->args->hasViewFrustum()); auto& inShapes = inputs.get0(); + const int BOUNDS_SLOT = 0; + const int PARAMETERS_SLOT = 1; + if (!_stencilMaskPipeline || !_stencilMaskFillPipeline) { gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(true, false, gpu::LESS_EQUAL); @@ -135,6 +138,8 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; + slotBindings.insert(gpu::Shader::Binding(std::string("ssbo0Buffer"), BOUNDS_SLOT)); + slotBindings.insert(gpu::Shader::Binding(std::string("parametersBuffer"), PARAMETERS_SLOT)); gpu::Shader::makeProgram(*program, slotBindings); _stencilMaskPipeline = gpu::Pipeline::create(program, state); @@ -214,6 +219,15 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c _boundsBuffer->setData(itemBounds.size() * sizeof(render::ItemBound), (const gpu::Byte*) itemBounds.data()); + const auto securityMargin = 2.0f; + const float blurPixelWidth = 2.0f * securityMargin * HighlightSharedParameters::getBlurPixelWidth(highlight._style, args->_viewport.w); + const auto framebufferSize = ressources->getSourceFrameSize(); + const glm::vec2 highlightWidth = { blurPixelWidth / framebufferSize.x, blurPixelWidth / framebufferSize.y }; + + if (highlightWidth != _outlineWidth.get()) { + _outlineWidth.edit() = highlightWidth; + } + gpu::doInBatch("DrawHighlightMask::run::end", args->_context, [&](gpu::Batch& batch) { // Setup camera, projection and viewport for all items batch.setViewportTransform(args->_viewport); @@ -221,15 +235,10 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c batch.setViewTransform(viewMat); // Draw stencil mask with object bounding boxes - const auto highlightWidthLoc = _stencilMaskPipeline->getProgram()->getUniforms().findLocation("outlineWidth"); - const auto securityMargin = 2.0f; - const float blurPixelWidth = 2.0f * securityMargin * HighlightSharedParameters::getBlurPixelWidth(highlight._style, args->_viewport.w); - const auto framebufferSize = ressources->getSourceFrameSize(); - auto stencilPipeline = highlight._style.isFilled() ? _stencilMaskFillPipeline : _stencilMaskPipeline; batch.setPipeline(stencilPipeline); - batch.setResourceBuffer(0, _boundsBuffer); - batch._glUniform2f(highlightWidthLoc, blurPixelWidth / framebufferSize.x, blurPixelWidth / framebufferSize.y); + batch.setResourceBuffer(BOUNDS_SLOT, _boundsBuffer); + batch.setUniformBuffer(PARAMETERS_SLOT, _outlineWidth); static const int NUM_VERTICES_PER_CUBE = 36; batch.draw(gpu::TRIANGLES, NUM_VERTICES_PER_CUBE * (gpu::uint32) itemBounds.size(), 0); }); @@ -286,6 +295,7 @@ void DrawHighlight::run(const render::RenderContextPointer& renderContext, const shaderParameters._size.y = size; } + auto primaryFramebuffer = inputs.get4(); gpu::doInBatch("DrawHighlight::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(destinationFrameBuffer); @@ -301,6 +311,9 @@ void DrawHighlight::run(const render::RenderContextPointer& renderContext, const batch.setResourceTexture(SCENE_DEPTH_MAP_SLOT, sceneDepthBuffer->getPrimaryDepthTexture()); batch.setResourceTexture(HIGHLIGHTED_DEPTH_MAP_SLOT, highlightedDepthTexture); batch.draw(gpu::TRIANGLE_STRIP, 4); + + // Reset the framebuffer for overlay drawing + batch.setFramebuffer(primaryFramebuffer); }); } } @@ -360,6 +373,7 @@ void DebugHighlight::run(const render::RenderContextPointer& renderContext, cons RenderArgs* args = renderContext->args; const auto jitter = input.get2(); + auto primaryFramebuffer = input.get3(); gpu::doInBatch("DebugHighlight::run", args->_context, [&](gpu::Batch& batch) { batch.setViewportTransform(args->_viewport); batch.setFramebuffer(highlightRessources->getColorFramebuffer()); @@ -384,6 +398,9 @@ void DebugHighlight::run(const render::RenderContextPointer& renderContext, cons geometryBuffer->renderQuad(batch, bottomLeft, topRight, color, _geometryDepthId); batch.setResourceTexture(0, nullptr); + + // Reset the framebuffer for overlay drawing + batch.setFramebuffer(primaryFramebuffer); }); } } @@ -532,12 +549,12 @@ void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, ren stream << "HighlightEffect" << i; name = stream.str(); } - const auto drawHighlightInputs = DrawHighlight::Inputs(deferredFrameTransform, highlightRessources, sceneFrameBuffer, highlightedRect).asVarying(); + const auto drawHighlightInputs = DrawHighlight::Inputs(deferredFrameTransform, highlightRessources, sceneFrameBuffer, highlightedRect, primaryFramebuffer).asVarying(); task.addJob(name, drawHighlightInputs, i, sharedParameters); } // Debug highlight - const auto debugInputs = DebugHighlight::Inputs(highlightRessources, const_cast(highlight0Rect), jitter).asVarying(); + const auto debugInputs = DebugHighlight::Inputs(highlightRessources, const_cast(highlight0Rect), jitter, primaryFramebuffer).asVarying(); task.addJob("HighlightDebug", debugInputs); } diff --git a/libraries/render-utils/src/HighlightEffect.h b/libraries/render-utils/src/HighlightEffect.h index 7c1db795fb..eee1c29cb7 100644 --- a/libraries/render-utils/src/HighlightEffect.h +++ b/libraries/render-utils/src/HighlightEffect.h @@ -127,6 +127,7 @@ protected: render::ShapePlumberPointer _shapePlumber; HighlightSharedParametersPointer _sharedParameters; gpu::BufferPointer _boundsBuffer; + gpu::StructBuffer _outlineWidth; static gpu::PipelinePointer _stencilMaskPipeline; static gpu::PipelinePointer _stencilMaskFillPipeline; @@ -135,7 +136,7 @@ protected: class DrawHighlight { public: - using Inputs = render::VaryingSet4; + using Inputs = render::VaryingSet5; using Config = render::Job::Config; using JobModel = render::Job::ModelI; @@ -182,7 +183,7 @@ signals: class DebugHighlight { public: - using Inputs = render::VaryingSet3; + using Inputs = render::VaryingSet4; using Config = DebugHighlightConfig; using JobModel = render::Job::ModelI; diff --git a/libraries/render-utils/src/Highlight_aabox.slv b/libraries/render-utils/src/Highlight_aabox.slv index 4927db9610..2a87e00f94 100644 --- a/libraries/render-utils/src/Highlight_aabox.slv +++ b/libraries/render-utils/src/Highlight_aabox.slv @@ -40,7 +40,9 @@ ItemBound getItemBound(int i) { } #endif -uniform vec2 outlineWidth; +uniform parametersBuffer { + vec2 outlineWidth; +}; void main(void) { const vec3 UNIT_BOX_VERTICES[8] = vec3[8]( diff --git a/libraries/render-utils/src/LightLocal.slh b/libraries/render-utils/src/LightLocal.slh index 515a065d2e..06f8871e24 100644 --- a/libraries/render-utils/src/LightLocal.slh +++ b/libraries/render-utils/src/LightLocal.slh @@ -143,6 +143,6 @@ vec4 evalLocalLighting(ivec3 cluster, int numLights, vec3 fragWorldPos, SurfaceD fragSpecular *= isSpecularEnabled(); fragColor.rgb += fragDiffuse; - fragColor.rgb += fragSpecular / opacity; + fragColor.rgb += evalSpecularWithOpacity(fragSpecular, opacity); return fragColor; } \ No newline at end of file diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index ceac4ae3c8..369c62c197 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -74,8 +74,6 @@ LightStage::Shadow::Cascade::Cascade() : _frustum{ std::make_shared() }, _minDistance{ 0.0f }, _maxDistance{ 20.0f } { - framebuffer = gpu::FramebufferPointer(gpu::Framebuffer::createShadowmap(MAP_SIZE)); - map = framebuffer->getDepthStencilBuffer(); } const glm::mat4& LightStage::Shadow::Cascade::getView() const { @@ -127,8 +125,29 @@ LightStage::Shadow::Shadow(graphics::LightPointer light, float maxDistance, unsi Schema schema; schema.cascadeCount = cascadeCount; _schemaBuffer = std::make_shared(sizeof(Schema), (const gpu::Byte*) &schema); + + // Create shadow cascade texture array + auto depthFormat = gpu::Element(gpu::SCALAR, gpu::FLOAT, gpu::DEPTH); // Depth32 texel format + map = gpu::TexturePointer(gpu::Texture::createRenderBufferArray(depthFormat, MAP_SIZE, MAP_SIZE, cascadeCount)); + gpu::Sampler::Desc samplerDesc; + samplerDesc._borderColor = glm::vec4(1.0f); + samplerDesc._wrapModeU = gpu::Sampler::WRAP_BORDER; + samplerDesc._wrapModeV = gpu::Sampler::WRAP_BORDER; + samplerDesc._filter = gpu::Sampler::FILTER_MIN_MAG_LINEAR; + samplerDesc._comparisonFunc = gpu::LESS; + + map->setSampler(gpu::Sampler(samplerDesc)); + _cascades.resize(cascadeCount); + for (uint cascadeIndex=0; cascadeIndex < cascadeCount; cascadeIndex++) { + auto& cascade = _cascades[cascadeIndex]; + std::string name = "Shadowmap Cascade "; + name += '0' + cascadeIndex; + cascade.framebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create(name)); + cascade.framebuffer->setDepthBuffer(map, depthFormat, cascadeIndex); + } + setMaxDistance(maxDistance); } diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index 9812426fa6..b8a49d81bb 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -53,7 +53,6 @@ public: Cascade(); gpu::FramebufferPointer framebuffer; - gpu::TexturePointer map; const std::shared_ptr& getFrustum() const { return _frustum; } @@ -93,6 +92,8 @@ public: const graphics::LightPointer& getLight() const { return _light; } + gpu::TexturePointer map; + protected: #include "Shadows_shared.slh" diff --git a/libraries/render-utils/src/LightingModel.slh b/libraries/render-utils/src/LightingModel.slh index 3b1c94f949..fe2d684c32 100644 --- a/libraries/render-utils/src/LightingModel.slh +++ b/libraries/render-utils/src/LightingModel.slh @@ -314,6 +314,9 @@ void evalFragShadingGloss(out vec3 diffuse, out vec3 specular, specular = shading.xyz; } +vec3 evalSpecularWithOpacity(vec3 specular, float opacity) { + return specular / opacity; +} <@if not GETFRESNEL0@> <@def GETFRESNEL0@> diff --git a/libraries/render-utils/src/MaterialTextures.slh b/libraries/render-utils/src/MaterialTextures.slh index 4bcd74eefe..0b83fd1334 100644 --- a/libraries/render-utils/src/MaterialTextures.slh +++ b/libraries/render-utils/src/MaterialTextures.slh @@ -267,7 +267,7 @@ vec3 fetchLightmapMap(vec2 uv) { <@func discardTransparent(opacity)@> { - if (<$opacity$> < 1.0) { + if (<$opacity$> < 1e-6) { discard; } } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 9bf6c31784..ba0d714f7a 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -47,51 +47,9 @@ int vec3VectorTypeId = qRegisterMetaType >(); float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f; #define HTTP_INVALID_COM "http://invalid.com" -const int NUM_COLLISION_HULL_COLORS = 24; -std::vector _collisionMaterials; - -void initCollisionMaterials() { - // generates bright colors in red, green, blue, yellow, magenta, and cyan spectrums - // (no browns, greys, or dark shades) - float component[NUM_COLLISION_HULL_COLORS] = { - 0.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 0.0f, - 0.2f, 0.4f, 0.6f, 0.8f, - 1.0f, 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 1.0f, - 0.8f, 0.6f, 0.4f, 0.2f - }; - _collisionMaterials.reserve(NUM_COLLISION_HULL_COLORS); - - // each component gets the same cuve - // but offset by a multiple of one third the full width - int numComponents = 3; - int sectionWidth = NUM_COLLISION_HULL_COLORS / numComponents; - int greenPhase = sectionWidth; - int bluePhase = 2 * sectionWidth; - - // we stride through the colors to scatter adjacent shades - // so they don't tend to group together for large models - for (int i = 0; i < sectionWidth; ++i) { - for (int j = 0; j < numComponents; ++j) { - graphics::MaterialPointer material; - material = std::make_shared(); - int index = j * sectionWidth + i; - float red = component[index % NUM_COLLISION_HULL_COLORS]; - float green = component[(index + greenPhase) % NUM_COLLISION_HULL_COLORS]; - float blue = component[(index + bluePhase) % NUM_COLLISION_HULL_COLORS]; - material->setAlbedo(glm::vec3(red, green, blue)); - material->setMetallic(0.02f); - material->setRoughness(0.5f); - _collisionMaterials.push_back(material); - } - } -} - Model::Model(QObject* parent, SpatiallyNestable* spatiallyNestableOverride) : QObject(parent), _renderGeometry(), - _collisionGeometry(), _renderWatcher(_renderGeometry), _spatiallyNestableOverride(spatiallyNestableOverride), _translation(0.0f), @@ -310,16 +268,6 @@ void Model::updateRenderItems() { }); } - Transform collisionMeshOffset; - collisionMeshOffset.setIdentity(); - foreach(auto itemID, self->_collisionRenderItemsMap.keys()) { - transaction.updateItem(itemID, [renderItemKeyGlobalFlags, modelTransform, collisionMeshOffset](MeshPartPayload& data) { - // update the model transform for this render item. - data.updateKey(renderItemKeyGlobalFlags); - data.updateTransform(modelTransform, collisionMeshOffset); - }); - } - AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction); }); } @@ -405,31 +353,27 @@ void Model::initJointStates() { bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool pickAgainstTriangles, bool allowBackface) { - bool intersectedSomething = false; - // if we aren't active, we can't ray pick yet... + // if we aren't active, we can't pick yet... if (!isActive()) { return intersectedSomething; } // extents is the entity relative, scaled, centered extents of the entity - glm::vec3 position = _translation; - glm::mat4 rotation = glm::mat4_cast(_rotation); - glm::mat4 translation = glm::translate(position); - glm::mat4 modelToWorldMatrix = translation * rotation; + glm::mat4 modelToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation); glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix); Extents modelExtents = getMeshExtents(); // NOTE: unrotated glm::vec3 dimensions = modelExtents.maximum - modelExtents.minimum; - glm::vec3 corner = -(dimensions * _registrationPoint); // since we're going to do the ray picking in the model frame of reference + glm::vec3 corner = -(dimensions * _registrationPoint); // since we're going to do the picking in the model frame of reference AABox modelFrameBox(corner, dimensions); glm::vec3 modelFrameOrigin = glm::vec3(worldToModelMatrix * glm::vec4(origin, 1.0f)); glm::vec3 modelFrameDirection = glm::vec3(worldToModelMatrix * glm::vec4(direction, 0.0f)); - // we can use the AABox's ray intersection by mapping our origin and direction into the model frame + // we can use the AABox's intersection by mapping our origin and direction into the model frame // and testing intersection there. if (modelFrameBox.findRayIntersection(modelFrameOrigin, modelFrameDirection, distance, face, surfaceNormal)) { QMutexLocker locker(&_mutex); @@ -447,32 +391,39 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g } glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); - glm::mat4 meshToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation) * meshToModelMatrix; + glm::mat4 meshToWorldMatrix = modelToWorldMatrix * meshToModelMatrix; glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix); glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f)); glm::vec3 meshFrameDirection = glm::vec3(worldToMeshMatrix * glm::vec4(direction, 0.0f)); - for (auto& triangleSet : _modelSpaceMeshTriangleSets) { - float triangleSetDistance = 0.0f; - BoxFace triangleSetFace; - Triangle triangleSetTriangle; - if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) { + int shapeID = 0; + for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) { + int partIndex = 0; + for (auto &partTriangleSet : meshTriangleSets) { + float triangleSetDistance; + BoxFace triangleSetFace; + Triangle triangleSetTriangle; + if (partTriangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) { + glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance); + glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f)); + float worldDistance = glm::distance(origin, worldIntersectionPoint); - glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance); - glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f)); - float worldDistance = glm::distance(origin, worldIntersectionPoint); - - if (worldDistance < bestDistance) { - bestDistance = worldDistance; - intersectedSomething = true; - face = triangleSetFace; - bestModelTriangle = triangleSetTriangle; - bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix; - extraInfo["worldIntersectionPoint"] = vec3toVariant(worldIntersectionPoint); - extraInfo["meshIntersectionPoint"] = vec3toVariant(meshIntersectionPoint); - bestSubMeshIndex = subMeshIndex; + if (worldDistance < bestDistance) { + bestDistance = worldDistance; + intersectedSomething = true; + face = triangleSetFace; + bestModelTriangle = triangleSetTriangle; + bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix; + extraInfo["worldIntersectionPoint"] = vec3toVariant(worldIntersectionPoint); + extraInfo["meshIntersectionPoint"] = vec3toVariant(meshIntersectionPoint); + extraInfo["partIndex"] = partIndex; + extraInfo["shapeID"] = shapeID; + bestSubMeshIndex = subMeshIndex; + } } + partIndex++; + shapeID++; } subMeshIndex++; } @@ -501,6 +452,111 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g return intersectedSomething; } +bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, + bool pickAgainstTriangles, bool allowBackface) { + bool intersectedSomething = false; + + // if we aren't active, we can't pick yet... + if (!isActive()) { + return intersectedSomething; + } + + // extents is the entity relative, scaled, centered extents of the entity + glm::mat4 modelToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation); + glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix); + + Extents modelExtents = getMeshExtents(); // NOTE: unrotated + + glm::vec3 dimensions = modelExtents.maximum - modelExtents.minimum; + glm::vec3 corner = -(dimensions * _registrationPoint); // since we're going to do the picking in the model frame of reference + AABox modelFrameBox(corner, dimensions); + + glm::vec3 modelFrameOrigin = glm::vec3(worldToModelMatrix * glm::vec4(origin, 1.0f)); + glm::vec3 modelFrameVelocity = glm::vec3(worldToModelMatrix * glm::vec4(velocity, 0.0f)); + glm::vec3 modelFrameAcceleration = glm::vec3(worldToModelMatrix * glm::vec4(acceleration, 0.0f)); + + // we can use the AABox's intersection by mapping our origin and direction into the model frame + // and testing intersection there. + if (modelFrameBox.findParabolaIntersection(modelFrameOrigin, modelFrameVelocity, modelFrameAcceleration, parabolicDistance, face, surfaceNormal)) { + QMutexLocker locker(&_mutex); + + float bestDistance = FLT_MAX; + Triangle bestModelTriangle; + Triangle bestWorldTriangle; + int bestSubMeshIndex = 0; + + int subMeshIndex = 0; + const FBXGeometry& geometry = getFBXGeometry(); + + if (!_triangleSetsValid) { + calculateTriangleSets(geometry); + } + + glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); + glm::mat4 meshToWorldMatrix = modelToWorldMatrix * meshToModelMatrix; + glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix); + + glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f)); + glm::vec3 meshFrameVelocity = glm::vec3(worldToMeshMatrix * glm::vec4(velocity, 0.0f)); + glm::vec3 meshFrameAcceleration = glm::vec3(worldToMeshMatrix * glm::vec4(acceleration, 0.0f)); + + int shapeID = 0; + for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) { + int partIndex = 0; + for (auto &partTriangleSet : meshTriangleSets) { + float triangleSetDistance; + BoxFace triangleSetFace; + Triangle triangleSetTriangle; + if (partTriangleSet.findParabolaIntersection(meshFrameOrigin, meshFrameVelocity, meshFrameAcceleration, + triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) { + if (triangleSetDistance < bestDistance) { + bestDistance = triangleSetDistance; + intersectedSomething = true; + face = triangleSetFace; + bestModelTriangle = triangleSetTriangle; + bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix; + glm::vec3 meshIntersectionPoint = meshFrameOrigin + meshFrameVelocity * triangleSetDistance + + 0.5f * meshFrameAcceleration * triangleSetDistance * triangleSetDistance; + glm::vec3 worldIntersectionPoint = origin + velocity * triangleSetDistance + + 0.5f * acceleration * triangleSetDistance * triangleSetDistance; + extraInfo["worldIntersectionPoint"] = vec3toVariant(worldIntersectionPoint); + extraInfo["meshIntersectionPoint"] = vec3toVariant(meshIntersectionPoint); + extraInfo["partIndex"] = partIndex; + extraInfo["shapeID"] = shapeID; + bestSubMeshIndex = subMeshIndex; + } + } + partIndex++; + shapeID++; + } + subMeshIndex++; + } + + if (intersectedSomething) { + parabolicDistance = bestDistance; + surfaceNormal = bestWorldTriangle.getNormal(); + if (pickAgainstTriangles) { + extraInfo["subMeshIndex"] = bestSubMeshIndex; + extraInfo["subMeshName"] = geometry.getModelNameOfMesh(bestSubMeshIndex); + extraInfo["subMeshTriangleWorld"] = QVariantMap{ + { "v0", vec3toVariant(bestWorldTriangle.v0) }, + { "v1", vec3toVariant(bestWorldTriangle.v1) }, + { "v2", vec3toVariant(bestWorldTriangle.v2) }, + }; + extraInfo["subMeshNormal"] = vec3toVariant(bestModelTriangle.getNormal()); + extraInfo["subMeshTriangle"] = QVariantMap{ + { "v0", vec3toVariant(bestModelTriangle.v0) }, + { "v1", vec3toVariant(bestModelTriangle.v1) }, + { "v2", vec3toVariant(bestModelTriangle.v2) }, + }; + } + } + } + + return intersectedSomething; +} + bool Model::convexHullContains(glm::vec3 point) { // if we aren't active, we can't compute that yet... if (!isActive()) { @@ -537,12 +593,14 @@ bool Model::convexHullContains(glm::vec3 point) { glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix); glm::vec3 meshFramePoint = glm::vec3(worldToMeshMatrix * glm::vec4(point, 1.0f)); - for (const auto& triangleSet : _modelSpaceMeshTriangleSets) { - const AABox& box = triangleSet.getBounds(); - if (box.contains(meshFramePoint)) { - if (triangleSet.convexHullContains(meshFramePoint)) { - // It's inside this mesh, return true. - return true; + for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) { + for (auto &partTriangleSet : meshTriangleSets) { + const AABox& box = partTriangleSet.getBounds(); + if (box.contains(meshFramePoint)) { + if (partTriangleSet.convexHullContains(meshFramePoint)) { + // It's inside this mesh, return true. + return true; + } } } } @@ -636,7 +694,7 @@ bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointe } scene->enqueueTransaction(transaction); } - // update triangles for ray picking + // update triangles for picking { FBXGeometry geometry; for (const auto& newMesh : meshes) { @@ -705,9 +763,15 @@ void Model::calculateTriangleSets(const FBXGeometry& geometry) { for (int i = 0; i < numberOfMeshes; i++) { const FBXMesh& mesh = geometry.meshes.at(i); - for (int j = 0; j < mesh.parts.size(); j++) { + const int numberOfParts = mesh.parts.size(); + auto& meshTriangleSets = _modelSpaceMeshTriangleSets[i]; + meshTriangleSets.resize(numberOfParts); + + for (int j = 0; j < numberOfParts; j++) { const FBXMeshPart& part = mesh.parts.at(j); + auto& partTriangleSet = meshTriangleSets[j]; + const int INDICES_PER_TRIANGLE = 3; const int INDICES_PER_QUAD = 4; const int TRIANGLES_PER_QUAD = 2; @@ -716,7 +780,7 @@ void Model::calculateTriangleSets(const FBXGeometry& geometry) { int numberOfQuads = part.quadIndices.size() / INDICES_PER_QUAD; int numberOfTris = part.triangleIndices.size() / INDICES_PER_TRIANGLE; int totalTriangles = (numberOfQuads * TRIANGLES_PER_QUAD) + numberOfTris; - _modelSpaceMeshTriangleSets[i].reserve(totalTriangles); + partTriangleSet.reserve(totalTriangles); auto meshTransform = geometry.offset * mesh.modelTransform; @@ -738,8 +802,8 @@ void Model::calculateTriangleSets(const FBXGeometry& geometry) { Triangle tri1 = { v0, v1, v3 }; Triangle tri2 = { v1, v2, v3 }; - _modelSpaceMeshTriangleSets[i].insert(tri1); - _modelSpaceMeshTriangleSets[i].insert(tri2); + partTriangleSet.insert(tri1); + partTriangleSet.insert(tri2); } } @@ -758,7 +822,7 @@ void Model::calculateTriangleSets(const FBXGeometry& geometry) { glm::vec3 v2 = glm::vec3(meshTransform * glm::vec4(mesh.vertices[i2], 1.0f)); Triangle tri = { v0, v1, v2 }; - _modelSpaceMeshTriangleSets[i].insert(tri); + partTriangleSet.insert(tri); } } } @@ -777,11 +841,6 @@ void Model::updateRenderItemsKey(const render::ScenePointer& scene) { data.updateKey(renderItemsKey); }); } - foreach(auto item, _collisionRenderItemsMap.keys()) { - transaction.updateItem(item, [renderItemsKey](ModelMeshPartPayload& data) { - data.updateKey(renderItemsKey); - }); - } scene->enqueueTransaction(transaction); } @@ -862,49 +921,37 @@ const render::ItemKey Model::getRenderItemKeyGlobalFlags() const { bool Model::addToScene(const render::ScenePointer& scene, render::Transaction& transaction, render::Item::Status::Getters& statusGetters) { - bool readyToRender = _collisionGeometry || isLoaded(); - if (!_addedToScene && readyToRender) { - createRenderItemSet(); + if (!_addedToScene && isLoaded()) { + updateClusterMatrices(); + if (_modelMeshRenderItems.empty()) { + createRenderItemSet(); + } } bool somethingAdded = false; - if (_collisionGeometry) { - if (_collisionRenderItemsMap.empty()) { - foreach (auto renderItem, _collisionRenderItems) { - auto item = scene->allocateID(); - auto renderPayload = std::make_shared(renderItem); - if (_collisionRenderItems.empty() && statusGetters.size()) { - renderPayload->addStatusGetters(statusGetters); - } - transaction.resetItem(item, renderPayload); - _collisionRenderItemsMap.insert(item, renderPayload); + + if (_modelMeshRenderItemsMap.empty()) { + + bool hasTransparent = false; + size_t verticesCount = 0; + foreach(auto renderItem, _modelMeshRenderItems) { + auto item = scene->allocateID(); + auto renderPayload = std::make_shared(renderItem); + if (_modelMeshRenderItemsMap.empty() && statusGetters.size()) { + renderPayload->addStatusGetters(statusGetters); } - somethingAdded = !_collisionRenderItemsMap.empty(); + transaction.resetItem(item, renderPayload); + + hasTransparent = hasTransparent || renderItem.get()->getShapeKey().isTranslucent(); + verticesCount += renderItem.get()->getVerticesCount(); + _modelMeshRenderItemsMap.insert(item, renderPayload); + _modelMeshRenderItemIDs.emplace_back(item); } - } else { - if (_modelMeshRenderItemsMap.empty()) { + somethingAdded = !_modelMeshRenderItemsMap.empty(); - bool hasTransparent = false; - size_t verticesCount = 0; - foreach(auto renderItem, _modelMeshRenderItems) { - auto item = scene->allocateID(); - auto renderPayload = std::make_shared(renderItem); - if (_modelMeshRenderItemsMap.empty() && statusGetters.size()) { - renderPayload->addStatusGetters(statusGetters); - } - transaction.resetItem(item, renderPayload); - - hasTransparent = hasTransparent || renderItem.get()->getShapeKey().isTranslucent(); - verticesCount += renderItem.get()->getVerticesCount(); - _modelMeshRenderItemsMap.insert(item, renderPayload); - _modelMeshRenderItemIDs.emplace_back(item); - } - somethingAdded = !_modelMeshRenderItemsMap.empty(); - - _renderInfoVertexCount = verticesCount; - _renderInfoDrawCalls = _modelMeshRenderItemsMap.count(); - _renderInfoHasTransparent = hasTransparent; - } + _renderInfoVertexCount = verticesCount; + _renderInfoDrawCalls = _modelMeshRenderItemsMap.count(); + _renderInfoHasTransparent = hasTransparent; } if (somethingAdded) { @@ -926,11 +973,6 @@ void Model::removeFromScene(const render::ScenePointer& scene, render::Transacti _modelMeshMaterialNames.clear(); _modelMeshRenderItemShapes.clear(); - foreach(auto item, _collisionRenderItemsMap.keys()) { - transaction.removeItem(item); - } - _collisionRenderItems.clear(); - _collisionRenderItemsMap.clear(); _addedToScene = false; _renderInfoVertexCount = 0; @@ -950,56 +992,58 @@ void Model::renderDebugMeshBoxes(gpu::Batch& batch) { DependencyManager::get()->bindSimpleProgram(batch, false, false, false, true, true); - for (const auto& triangleSet : _modelSpaceMeshTriangleSets) { - auto box = triangleSet.getBounds(); + for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) { + for (auto &partTriangleSet : meshTriangleSets) { + auto box = partTriangleSet.getBounds(); - if (_debugMeshBoxesID == GeometryCache::UNKNOWN_ID) { - _debugMeshBoxesID = DependencyManager::get()->allocateID(); + if (_debugMeshBoxesID == GeometryCache::UNKNOWN_ID) { + _debugMeshBoxesID = DependencyManager::get()->allocateID(); + } + QVector points; + + glm::vec3 brn = box.getCorner(); + glm::vec3 bln = brn + glm::vec3(box.getDimensions().x, 0, 0); + glm::vec3 brf = brn + glm::vec3(0, 0, box.getDimensions().z); + glm::vec3 blf = brn + glm::vec3(box.getDimensions().x, 0, box.getDimensions().z); + + glm::vec3 trn = brn + glm::vec3(0, box.getDimensions().y, 0); + glm::vec3 tln = bln + glm::vec3(0, box.getDimensions().y, 0); + glm::vec3 trf = brf + glm::vec3(0, box.getDimensions().y, 0); + glm::vec3 tlf = blf + glm::vec3(0, box.getDimensions().y, 0); + + points << brn << bln; + points << brf << blf; + points << brn << brf; + points << bln << blf; + + points << trn << tln; + points << trf << tlf; + points << trn << trf; + points << tln << tlf; + + points << brn << trn; + points << brf << trf; + points << bln << tln; + points << blf << tlf; + + glm::vec4 color[] = { + { 0.0f, 1.0f, 0.0f, 1.0f }, // green + { 1.0f, 0.0f, 0.0f, 1.0f }, // red + { 0.0f, 0.0f, 1.0f, 1.0f }, // blue + { 1.0f, 0.0f, 1.0f, 1.0f }, // purple + { 1.0f, 1.0f, 0.0f, 1.0f }, // yellow + { 0.0f, 1.0f, 1.0f, 1.0f }, // cyan + { 1.0f, 1.0f, 1.0f, 1.0f }, // white + { 0.0f, 0.5f, 0.0f, 1.0f }, + { 0.0f, 0.0f, 0.5f, 1.0f }, + { 0.5f, 0.0f, 0.5f, 1.0f }, + { 0.5f, 0.5f, 0.0f, 1.0f }, + { 0.0f, 0.5f, 0.5f, 1.0f } }; + + DependencyManager::get()->updateVertices(_debugMeshBoxesID, points, color[colorNdx]); + DependencyManager::get()->renderVertices(batch, gpu::LINES, _debugMeshBoxesID); + colorNdx++; } - QVector points; - - glm::vec3 brn = box.getCorner(); - glm::vec3 bln = brn + glm::vec3(box.getDimensions().x, 0, 0); - glm::vec3 brf = brn + glm::vec3(0, 0, box.getDimensions().z); - glm::vec3 blf = brn + glm::vec3(box.getDimensions().x, 0, box.getDimensions().z); - - glm::vec3 trn = brn + glm::vec3(0, box.getDimensions().y, 0); - glm::vec3 tln = bln + glm::vec3(0, box.getDimensions().y, 0); - glm::vec3 trf = brf + glm::vec3(0, box.getDimensions().y, 0); - glm::vec3 tlf = blf + glm::vec3(0, box.getDimensions().y, 0); - - points << brn << bln; - points << brf << blf; - points << brn << brf; - points << bln << blf; - - points << trn << tln; - points << trf << tlf; - points << trn << trf; - points << tln << tlf; - - points << brn << trn; - points << brf << trf; - points << bln << tln; - points << blf << tlf; - - glm::vec4 color[] = { - { 0.0f, 1.0f, 0.0f, 1.0f }, // green - { 1.0f, 0.0f, 0.0f, 1.0f }, // red - { 0.0f, 0.0f, 1.0f, 1.0f }, // blue - { 1.0f, 0.0f, 1.0f, 1.0f }, // purple - { 1.0f, 1.0f, 0.0f, 1.0f }, // yellow - { 0.0f, 1.0f, 1.0f, 1.0f }, // cyan - { 1.0f, 1.0f, 1.0f, 1.0f }, // white - { 0.0f, 0.5f, 0.0f, 1.0f }, - { 0.0f, 0.0f, 0.5f, 1.0f }, - { 0.5f, 0.0f, 0.5f, 1.0f }, - { 0.5f, 0.5f, 0.0f, 1.0f }, - { 0.0f, 0.5f, 0.5f, 1.0f } }; - - DependencyManager::get()->updateVertices(_debugMeshBoxesID, points, color[colorNdx]); - DependencyManager::get()->renderVertices(batch, gpu::LINES, _debugMeshBoxesID); - colorNdx++; } _mutex.unlock(); } @@ -1504,7 +1548,6 @@ void Model::deleteGeometry() { _rig.destroyAnimGraph(); _blendedBlendshapeCoefficients.clear(); _renderGeometry.reset(); - _collisionGeometry.reset(); } void Model::overrideModelTransformAndOffset(const Transform& transform, const glm::vec3& offset) { @@ -1533,19 +1576,6 @@ const render::ItemIDs& Model::fetchRenderItemIDs() const { } void Model::createRenderItemSet() { - updateClusterMatrices(); - if (_collisionGeometry) { - if (_collisionRenderItems.empty()) { - createCollisionRenderItemSet(); - } - } else { - if (_modelMeshRenderItems.empty()) { - createVisibleRenderItemSet(); - } - } -}; - -void Model::createVisibleRenderItemSet() { assert(isLoaded()); const auto& meshes = _renderGeometry->getMeshes(); @@ -1591,41 +1621,6 @@ void Model::createVisibleRenderItemSet() { } } -void Model::createCollisionRenderItemSet() { - assert((bool)_collisionGeometry); - if (_collisionMaterials.empty()) { - initCollisionMaterials(); - } - - const auto& meshes = _collisionGeometry->getMeshes(); - - // We should not have any existing renderItems if we enter this section of code - Q_ASSERT(_collisionRenderItems.isEmpty()); - - Transform identity; - identity.setIdentity(); - Transform offset; - offset.postTranslate(_offset); - - // Run through all of the meshes, and place them into their segregated, but unsorted buckets - uint32_t numMeshes = (uint32_t)meshes.size(); - for (uint32_t i = 0; i < numMeshes; i++) { - const auto& mesh = meshes.at(i); - if (!mesh) { - continue; - } - - // Create the render payloads - int numParts = (int)mesh->getNumParts(); - for (int partIndex = 0; partIndex < numParts; partIndex++) { - graphics::MaterialPointer& material = _collisionMaterials[partIndex % NUM_COLLISION_HULL_COLORS]; - auto payload = std::make_shared(mesh, partIndex, material); - payload->updateTransform(identity, offset); - _collisionRenderItems << payload; - } - } -} - bool Model::isRenderable() const { return !_meshStates.empty() || (isLoaded() && _renderGeometry->getMeshes().empty()); } @@ -1680,13 +1675,12 @@ void Model::removeMaterial(graphics::MaterialPointer material, const std::string for (auto shapeID : shapeIDs) { if (shapeID < _modelMeshRenderItemIDs.size()) { auto itemID = _modelMeshRenderItemIDs[shapeID]; - bool visible = isVisible(); auto renderItemsKey = _renderItemKeyGlobalFlags; bool wireframe = isWireframe(); auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex; bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex); bool useDualQuaternionSkinning = _useDualQuaternionSkinning; - transaction.updateItem(itemID, [material, visible, renderItemsKey, + transaction.updateItem(itemID, [material, renderItemsKey, invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning](ModelMeshPartPayload& data) { data.removeMaterial(material); // if the material changed, we might need to update our item key or shape key @@ -1709,15 +1703,6 @@ public: } }; -void Model::setCollisionMesh(graphics::MeshPointer mesh) { - if (mesh) { - _collisionGeometry = std::make_shared(mesh); - } else { - _collisionGeometry.reset(); - } - _needsFixupInScene = true; -} - ModelBlender::ModelBlender() : _pendingBlenders(0) { } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 67e6d178ea..627e5fddab 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -153,8 +153,6 @@ public: /// Returns a reference to the shared geometry. const Geometry::Pointer& getGeometry() const { return _renderGeometry; } - /// Returns a reference to the shared collision geometry. - const Geometry::Pointer& getCollisionGeometry() const { return _collisionGeometry; } const QVariantMap getTextures() const { assert(isLoaded()); return _renderGeometry->getTextures(); } Q_INVOKABLE virtual void setTextures(const QVariantMap& textures); @@ -180,6 +178,9 @@ public: bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false); + bool findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false); void setOffset(const glm::vec3& offset); const glm::vec3& getOffset() const { return _offset; } @@ -260,7 +261,6 @@ public: // returns 'true' if needs fullUpdate after geometry change virtual bool updateGeometry(); - void setCollisionMesh(graphics::MeshPointer mesh); void setLoadingPriority(float priority) { _loadingPriority = priority; } @@ -362,7 +362,6 @@ protected: bool getJointPosition(int jointIndex, glm::vec3& position) const; Geometry::Pointer _renderGeometry; // only ever set by its watcher - Geometry::Pointer _collisionGeometry; GeometryResourceWatcher _renderWatcher; @@ -427,12 +426,9 @@ protected: bool _overrideModelTransform { false }; bool _triangleSetsValid { false }; void calculateTriangleSets(const FBXGeometry& geometry); - QVector _modelSpaceMeshTriangleSets; // model space triangles for all sub meshes + std::vector> _modelSpaceMeshTriangleSets; // model space triangles for all sub meshes - - void createRenderItemSet(); - virtual void createVisibleRenderItemSet(); - virtual void createCollisionRenderItemSet(); + virtual void createRenderItemSet(); bool _isWireframe; bool _useDualQuaternionSkinning { false }; @@ -443,9 +439,6 @@ protected: static AbstractViewStateInterface* _viewState; - QVector> _collisionRenderItems; - QMap _collisionRenderItemsMap; - QVector> _modelMeshRenderItems; QMap _modelMeshRenderItemsMap; render::ItemIDs _modelMeshRenderItemIDs; diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp index 293393550b..c2181b7613 100644 --- a/libraries/render-utils/src/RenderCommonTask.cpp +++ b/libraries/render-utils/src/RenderCommonTask.cpp @@ -14,7 +14,7 @@ #include "RenderUtilsLogging.h" using namespace render; -extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest = false); +extern void initForwardPipelines(ShapePlumber& plumber); void BeginGPURangeTimer::run(const render::RenderContextPointer& renderContext, gpu::RangeTimerPointer& timer) { timer = _gpuTimer; @@ -35,7 +35,7 @@ void EndGPURangeTimer::run(const render::RenderContextPointer& renderContext, co DrawOverlay3D::DrawOverlay3D(bool opaque) : _shapePlumber(std::make_shared()), _opaquePass(opaque) { - initOverlay3DPipelines(*_shapePlumber); + initForwardPipelines(*_shapePlumber); } void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs& inputs) { @@ -51,19 +51,19 @@ void DrawOverlay3D::run(const RenderContextPointer& renderContext, const Inputs& config->setNumDrawn((int)inItems.size()); emit config->numDrawnChanged(); + RenderArgs* args = renderContext->args; + + // Clear the framebuffer without stereo + // Needs to be distinct from the other batch because using the clear call + // while stereo is enabled triggers a warning + if (_opaquePass) { + gpu::doInBatch("DrawOverlay3D::run::clear", args->_context, [&](gpu::Batch& batch) { + batch.enableStereo(false); + batch.clearFramebuffer(gpu::Framebuffer::BUFFER_DEPTH, glm::vec4(), 1.f, 0, false); + }); + } + if (!inItems.empty()) { - RenderArgs* args = renderContext->args; - - // Clear the framebuffer without stereo - // Needs to be distinct from the other batch because using the clear call - // while stereo is enabled triggers a warning - if (_opaquePass) { - gpu::doInBatch("DrawOverlay3D::run::clear", args->_context, [&](gpu::Batch& batch){ - batch.enableStereo(false); - batch.clearFramebuffer(gpu::Framebuffer::BUFFER_DEPTH, glm::vec4(), 1.f, 0, false); - }); - } - // Render the items gpu::doInBatch("DrawOverlay3D::main", args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; diff --git a/libraries/render-utils/src/RenderCommonTask.h b/libraries/render-utils/src/RenderCommonTask.h index 79599d3ab7..65f8cdfbfc 100644 --- a/libraries/render-utils/src/RenderCommonTask.h +++ b/libraries/render-utils/src/RenderCommonTask.h @@ -61,7 +61,6 @@ protected: class DrawOverlay3D { public: using Inputs = render::VaryingSet3; - using Config = DrawOverlay3DConfig; using JobModel = render::Job::ModelI; @@ -73,7 +72,7 @@ public: protected: render::ShapePlumberPointer _shapePlumber; int _maxDrawn; // initialized by Config - bool _opaquePass{ true }; + bool _opaquePass { true }; }; class CompositeHUD { diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 47cab54d09..0b05977265 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "RenderHifi.h" #include "RenderCommonTask.h" @@ -53,20 +54,25 @@ #include using namespace render; -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() { } -void RenderDeferredTask::configure(const Config& config) -{ +void RenderDeferredTask::configure(const Config& config) { + // Propagate resolution scale to sub jobs who need it + auto preparePrimaryBufferConfig = config.getConfig("PreparePrimaryBuffer"); + auto upsamplePrimaryBufferConfig = config.getConfig("PrimaryBufferUpscale"); + assert(preparePrimaryBufferConfig); + assert(upsamplePrimaryBufferConfig); + preparePrimaryBufferConfig->setProperty("resolutionScale", config.resolutionScale); + upsamplePrimaryBufferConfig->setProperty("factor", 1.0f / config.resolutionScale); } const render::Varying RenderDeferredTask::addSelectItemJobs(JobModel& task, const char* selectionName, - const render::Varying& metas, - const render::Varying& opaques, + const render::Varying& metas, + const render::Varying& opaques, const render::Varying& transparents) { const auto selectMetaInput = SelectItems::Inputs(metas, Varying(), std::string()).asVarying(); const auto selectedMetas = task.addJob("MetaSelection", selectMetaInput, selectionName); @@ -76,7 +82,7 @@ const render::Varying RenderDeferredTask::addSelectItemJobs(JobModel& task, cons return task.addJob("TransparentSelection", selectItemInput, selectionName); } -void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { +void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output, bool renderShadows) { const auto& items = input.get(); auto fadeEffect = DependencyManager::get(); @@ -98,23 +104,22 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto jitter = task.addJob("JitterCam"); - // Prepare deferred, generate the shared Deferred Frame Transform + // GPU jobs: Start preparing the primary, deferred and lighting buffer + const auto scaledPrimaryFramebuffer = task.addJob("PreparePrimaryBuffer"); + + // Prepare deferred, generate the shared Deferred Frame Transform. Only valid with the scaled frame buffer const auto deferredFrameTransform = task.addJob("DeferredFrameTransform", jitter); const auto lightingModel = task.addJob("LightingModel"); - - - // GPU jobs: Start preparing the primary, deferred and lighting buffer - const auto primaryFramebuffer = task.addJob("PreparePrimaryBuffer"); const auto opaqueRangeTimer = task.addJob("BeginOpaqueRangeTimer", "DrawOpaques"); - const auto prepareDeferredInputs = PrepareDeferred::Inputs(primaryFramebuffer, lightingModel).asVarying(); + const auto prepareDeferredInputs = PrepareDeferred::Inputs(scaledPrimaryFramebuffer, lightingModel).asVarying(); const auto prepareDeferredOutputs = task.addJob("PrepareDeferred", prepareDeferredInputs); const auto deferredFramebuffer = prepareDeferredOutputs.getN(0); const auto lightingFramebuffer = prepareDeferredOutputs.getN(1); // draw a stencil mask in hidden regions of the framebuffer. - task.addJob("PrepareStencil", primaryFramebuffer); + task.addJob("PrepareStencil", scaledPrimaryFramebuffer); // Render opaque objects in DeferredBuffer const auto opaqueInputs = DrawStateSortDeferred::Inputs(opaques, lightingModel, jitter).asVarying(); @@ -169,13 +174,13 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto deferredLightingInputs = RenderDeferred::Inputs(deferredFrameTransform, deferredFramebuffer, lightingModel, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, scatteringResource, lightClusters, hazeModel).asVarying(); - task.addJob("RenderDeferred", deferredLightingInputs); + task.addJob("RenderDeferred", deferredLightingInputs, renderShadows); // Similar to light stage, background stage has been filled by several potential render items and resolved for the frame in this job task.addJob("DrawBackgroundDeferred", lightingModel); - const auto drawHazeInputs = render::Varying(DrawHaze::Inputs(hazeModel, lightingFramebuffer, linearDepthTarget, deferredFrameTransform, lightingFramebuffer)); + const auto drawHazeInputs = render::Varying(DrawHaze::Inputs(hazeModel, lightingFramebuffer, linearDepthTarget, deferredFrameTransform, lightingModel)); task.addJob("DrawHazeDeferred", drawHazeInputs); // Render transparent objects forward in LightingBuffer @@ -224,7 +229,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("Bloom", bloomInputs); // Lighting Buffer ready for tone mapping - const auto toneMappingInputs = ToneMappingDeferred::Inputs(lightingFramebuffer, primaryFramebuffer).asVarying(); + const auto toneMappingInputs = ToneMappingDeferred::Inputs(lightingFramebuffer, scaledPrimaryFramebuffer).asVarying(); task.addJob("ToneMapping", toneMappingInputs); { // Debug the bounds of the rendered items, still look at the zbuffer @@ -285,6 +290,9 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob("DrawZoneStack", deferredFrameTransform); } + // Upscale to finale resolution + const auto primaryFramebuffer = task.addJob("PrimaryBufferUpscale", scaledPrimaryFramebuffer); + // Composite the HUD and HUD overlays task.addJob("HUD"); diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index 9917058790..1ce1682cf1 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -105,11 +105,13 @@ class RenderDeferredTaskConfig : public render::Task::Config { Q_OBJECT Q_PROPERTY(float fadeScale MEMBER fadeScale NOTIFY dirty) Q_PROPERTY(float fadeDuration MEMBER fadeDuration NOTIFY dirty) + Q_PROPERTY(float resolutionScale MEMBER resolutionScale NOTIFY dirty) Q_PROPERTY(bool debugFade MEMBER debugFade NOTIFY dirty) Q_PROPERTY(float debugFadePercent MEMBER debugFadePercent NOTIFY dirty) public: float fadeScale{ 0.5f }; float fadeDuration{ 3.0f }; + float resolutionScale{ 1.f }; float debugFadePercent{ 0.f }; bool debugFade{ false }; @@ -126,7 +128,7 @@ public: RenderDeferredTask(); void configure(const Config& config); - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, bool renderShadows); private: static const render::Varying addSelectItemJobs(JobModel& task, diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index 09a2afb711..d2933627f4 100755 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -37,10 +37,7 @@ #include "nop_frag.h" using namespace render; -extern void initForwardPipelines(ShapePlumber& plumber, - const render::ShapePipeline::BatchSetter& batchSetter, - const render::ShapePipeline::ItemSetter& itemSetter); -extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest = false); +extern void initForwardPipelines(ShapePlumber& plumber); void RenderForwardTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { auto items = input.get(); @@ -48,8 +45,7 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend // Prepare the ShapePipelines ShapePlumberPointer shapePlumber = std::make_shared(); - initForwardPipelines(*shapePlumber, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter()); - initOverlay3DPipelines(*shapePlumber); + initForwardPipelines(*shapePlumber); // Extract opaques / transparents / lights / metas / overlays / background const auto& opaques = items.get0()[RenderFetchCullSortTask::OPAQUE_SHAPE]; diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index b02266e67b..6a653bb192 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -80,16 +80,6 @@ #include "model_translucent_unlit_fade_frag.h" #include "model_translucent_normal_map_fade_frag.h" -#include "overlay3D_vert.h" -#include "overlay3D_frag.h" -#include "overlay3D_model_frag.h" -#include "overlay3D_model_translucent_frag.h" -#include "overlay3D_translucent_frag.h" -#include "overlay3D_unlit_frag.h" -#include "overlay3D_translucent_unlit_frag.h" -#include "overlay3D_model_unlit_frag.h" -#include "overlay3D_model_translucent_unlit_frag.h" - #include "model_shadow_vert.h" #include "skin_model_shadow_vert.h" #include "skin_model_shadow_dq_vert.h" @@ -104,12 +94,16 @@ #include "model_shadow_fade_frag.h" #include "skin_model_shadow_fade_frag.h" +#include "simple_vert.h" +#include "forward_simple_textured_frag.h" +#include "forward_simple_textured_transparent_frag.h" +#include "forward_simple_textured_unlit_frag.h" + using namespace render; using namespace std::placeholders; -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, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); +void initForwardPipelines(ShapePlumber& plumber); void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state); void addPlumberPipeline(ShapePlumber& plumber, @@ -120,71 +114,6 @@ void batchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderArgs* a void lightBatchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderArgs* args); static bool forceLightBatchSetter{ false }; -void initOverlay3DPipelines(ShapePlumber& plumber, bool depthTest) { - auto vertex = overlay3D_vert::getShader(); - auto vertexModel = model_vert::getShader(); - auto pixel = overlay3D_frag::getShader(); - auto pixelTranslucent = overlay3D_translucent_frag::getShader(); - auto pixelUnlit = overlay3D_unlit_frag::getShader(); - auto pixelTranslucentUnlit = overlay3D_translucent_unlit_frag::getShader(); - auto pixelModel = overlay3D_model_frag::getShader(); - auto pixelModelTranslucent = overlay3D_model_translucent_frag::getShader(); - auto pixelModelUnlit = overlay3D_model_unlit_frag::getShader(); - auto pixelModelTranslucentUnlit = overlay3D_model_translucent_unlit_frag::getShader(); - - auto opaqueProgram = gpu::Shader::createProgram(vertex, pixel); - auto translucentProgram = gpu::Shader::createProgram(vertex, pixelTranslucent); - auto unlitOpaqueProgram = gpu::Shader::createProgram(vertex, pixelUnlit); - auto unlitTranslucentProgram = gpu::Shader::createProgram(vertex, pixelTranslucentUnlit); - auto materialOpaqueProgram = gpu::Shader::createProgram(vertexModel, pixelModel); - auto materialTranslucentProgram = gpu::Shader::createProgram(vertexModel, pixelModelTranslucent); - auto materialUnlitOpaqueProgram = gpu::Shader::createProgram(vertexModel, pixelModel); - auto materialUnlitTranslucentProgram = gpu::Shader::createProgram(vertexModel, pixelModelTranslucent); - - for (int i = 0; i < 8; i++) { - bool isCulled = (i & 1); - bool isBiased = (i & 2); - bool isOpaque = (i & 4); - - auto state = std::make_shared(); - 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); - state->setDepthBiasSlopeScale(1.0f); - } - if (isOpaque) { - // Soft edges - state->setBlendFunction(true, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); - } else { - 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); - } - - ShapeKey::Filter::Builder builder; - - isCulled ? builder.withCullFace() : builder.withoutCullFace(); - isBiased ? builder.withDepthBias() : builder.withoutDepthBias(); - isOpaque ? builder.withOpaque() : builder.withTranslucent(); - - auto simpleProgram = isOpaque ? opaqueProgram : translucentProgram; - auto unlitProgram = isOpaque ? unlitOpaqueProgram : unlitTranslucentProgram; - auto materialProgram = isOpaque ? materialOpaqueProgram : materialTranslucentProgram; - auto materialUnlitProgram = isOpaque ? materialUnlitOpaqueProgram : materialUnlitTranslucentProgram; - - plumber.addPipeline(builder.withMaterial().build().key(), materialProgram, state, &lightBatchSetter); - plumber.addPipeline(builder.withMaterial().withUnlit().build().key(), materialUnlitProgram, state, &batchSetter); - plumber.addPipeline(builder.withoutUnlit().withoutMaterial().build().key(), simpleProgram, state, &lightBatchSetter); - plumber.addPipeline(builder.withUnlit().withoutMaterial().build().key(), unlitProgram, state, &batchSetter); - } -} - void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter) { // Vertex shaders auto simpleVertex = simple_vert::getShader(); @@ -432,8 +361,9 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip skinModelShadowFadeDualQuatVertex, modelShadowFadePixel, batchSetter, itemSetter); } -void initForwardPipelines(ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter) { +void initForwardPipelines(ShapePlumber& plumber) { // Vertex shaders + auto simpleVertex = simple_vert::getShader(); auto modelVertex = model_vert::getShader(); auto modelNormalMapVertex = model_normal_map_vert::getShader(); auto skinModelVertex = skin_model_vert::getShader(); @@ -443,6 +373,10 @@ void initForwardPipelines(ShapePlumber& plumber, const render::ShapePipeline::Ba auto skinModelNormalMapDualQuatVertex = skin_model_normal_map_dq_vert::getShader(); // Pixel shaders + auto simplePixel = forward_simple_textured_frag::getShader(); + auto simpleTranslucentPixel = forward_simple_textured_transparent_frag::getShader(); + auto simpleUnlitPixel = forward_simple_textured_unlit_frag::getShader(); + auto simpleTranslucentUnlitPixel = simple_transparent_textured_unlit_frag::getShader(); auto modelPixel = forward_model_frag::getShader(); auto modelUnlitPixel = forward_model_unlit_frag::getShader(); auto modelNormalMapPixel = forward_model_normal_map_frag::getShader(); @@ -458,8 +392,15 @@ void initForwardPipelines(ShapePlumber& plumber, const render::ShapePipeline::Ba }; // Forward pipelines need the lightBatchSetter for opaques and transparents - // forceLightBatchSetter = true; - forceLightBatchSetter = false; + forceLightBatchSetter = true; + + // Simple Opaques + addPipeline(Key::Builder(), simpleVertex, simplePixel); + addPipeline(Key::Builder().withUnlit(), simpleVertex, simpleUnlitPixel); + + // Simple Translucents + addPipeline(Key::Builder().withTranslucent(), simpleVertex, simpleTranslucentPixel); + addPipeline(Key::Builder().withTranslucent().withUnlit(), simpleVertex, simpleTranslucentUnlitPixel); // Opaques addPipeline(Key::Builder().withMaterial(), modelVertex, modelPixel); @@ -501,7 +442,7 @@ void addPlumberPipeline(ShapePlumber& plumber, bool isWireframed = (i & 4); auto state = std::make_shared(); - PrepareStencil::testMaskDrawShape(*state); + key.isTranslucent() ? PrepareStencil::testMask(*state) : PrepareStencil::testMaskDrawShape(*state); // Depth test depends on transparency state->setDepthTest(true, !key.isTranslucent(), gpu::LESS_EQUAL); diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index fbb4bba263..91eb777199 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -227,7 +227,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende } const auto setupOutput = task.addJob("ShadowSetup"); - const auto queryResolution = setupOutput.getN(2); + const auto queryResolution = setupOutput.getN(1); // Fetch and cull the items from the scene static const auto shadowCasterReceiverFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask); @@ -248,10 +248,12 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende const auto sortedShapes = task.addJob("DepthSortShadow", sortedPipelines, true); render::Varying cascadeFrustums[SHADOW_CASCADE_MAX_COUNT] = { - ViewFrustumPointer(), - ViewFrustumPointer(), + ViewFrustumPointer() +#if SHADOW_CASCADE_MAX_COUNT>1 + ,ViewFrustumPointer(), ViewFrustumPointer(), ViewFrustumPointer() +#endif }; for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { @@ -293,13 +295,15 @@ RenderShadowSetup::RenderShadowSetup() : void RenderShadowSetup::configure(const Config& configuration) { setConstantBias(0, configuration.constantBias0); - setConstantBias(1, configuration.constantBias1); - setConstantBias(2, configuration.constantBias2); - setConstantBias(3, configuration.constantBias3); setSlopeBias(0, configuration.slopeBias0); +#if SHADOW_CASCADE_MAX_COUNT>1 + setConstantBias(1, configuration.constantBias1); setSlopeBias(1, configuration.slopeBias1); + setConstantBias(2, configuration.constantBias2); setSlopeBias(2, configuration.slopeBias2); + setConstantBias(3, configuration.constantBias3); setSlopeBias(3, configuration.slopeBias3); +#endif } void RenderShadowSetup::setConstantBias(int cascadeIndex, float value) { diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp index 82426a3a1f..6f6a87c222 100644 --- a/libraries/render-utils/src/RenderViewTask.cpp +++ b/libraries/render-utils/src/RenderViewTask.cpp @@ -27,7 +27,7 @@ void RenderViewTask::build(JobModel& task, const render::Varying& input, render: assert(items.canCast()); if (isDeferred) { - task.addJob("RenderDeferredTask", items); + task.addJob("RenderDeferredTask", items, true); } else { task.addJob("Forward", items); } diff --git a/libraries/render-utils/src/Shadow.slh b/libraries/render-utils/src/Shadow.slh index 36eb35c757..235ea519ab 100644 --- a/libraries/render-utils/src/Shadow.slh +++ b/libraries/render-utils/src/Shadow.slh @@ -17,11 +17,11 @@ #define SHADOW_SCREEN_SPACE_DITHER 1 // the shadow texture -uniform sampler2DShadow shadowMaps[SHADOW_CASCADE_MAX_COUNT]; +uniform sampler2DArrayShadow shadowMaps; // Sample the shadowMap with PCF (built-in) float fetchShadow(int cascadeIndex, vec3 shadowTexcoord) { - return texture(shadowMaps[cascadeIndex], shadowTexcoord); + return texture(shadowMaps, vec4(shadowTexcoord.xy, cascadeIndex, shadowTexcoord.z)); } vec2 PCFkernel[4] = vec2[4]( diff --git a/libraries/render-utils/src/debug_deferred_buffer.slf b/libraries/render-utils/src/debug_deferred_buffer.slf index fded04ca87..5f974acfeb 100644 --- a/libraries/render-utils/src/debug_deferred_buffer.slf +++ b/libraries/render-utils/src/debug_deferred_buffer.slf @@ -23,11 +23,18 @@ uniform sampler2D occlusionMap; uniform sampler2D occlusionBlurredMap; uniform sampler2D scatteringMap; uniform sampler2D velocityMap; +uniform sampler2DArrayShadow shadowMaps; <@include ShadowCore.slh@> <$declareDeferredCurvature()$> +<@include debug_deferred_buffer_shared.slh@> + +layout(std140) uniform parametersBuffer { + DebugParameters parameters; +}; + float curvatureAO(float k) { return 1.0f - (0.0022f * k * k) + (0.0776f * k) + 0.7369f; } diff --git a/libraries/render-utils/src/debug_deferred_buffer_shared.slh b/libraries/render-utils/src/debug_deferred_buffer_shared.slh new file mode 100644 index 0000000000..2d11a66d61 --- /dev/null +++ b/libraries/render-utils/src/debug_deferred_buffer_shared.slh @@ -0,0 +1,17 @@ +// glsl / C++ compatible source as interface for FadeEffect +#ifdef __cplusplus +# define INT32 glm::int32 +#else +# define INT32 int +#endif + +struct DebugParameters +{ + INT32 _shadowCascadeIndex; +}; + + // <@if 1@> + // Trigger Scribe include + // <@endif@> +// + diff --git a/libraries/render-utils/src/drawWorkloadProxy.slf b/libraries/render-utils/src/drawWorkloadProxy.slf new file mode 100644 index 0000000000..1304e68c7f --- /dev/null +++ b/libraries/render-utils/src/drawWorkloadProxy.slf @@ -0,0 +1,32 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// drawItemBounds.frag +// fragment shader +// +// Created by Sam Gateau on 6/29/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +<@include DeferredBufferWrite.slh@> +<@include gpu/Paint.slh@> + +in vec4 varColor; +in vec3 varTexcoord; + +void main(void) { + if (varColor.w > 0.0) { + float r = sqrt(dot(varTexcoord.xyz,varTexcoord.xyz)); + float a = paintStripe(r * varColor.w, 0.0, 1.0 / varColor.w, 0.05 / varColor.w); + if (a <= 0.1 || r > 1.1) { + discard; + } + } + + packDeferredFragmentUnlit( + vec3(0.0, 1.0, 0.0), + 1.0, + varColor.rgb); +} diff --git a/libraries/render-utils/src/drawWorkloadProxy.slv b/libraries/render-utils/src/drawWorkloadProxy.slv new file mode 100644 index 0000000000..64fb335fd6 --- /dev/null +++ b/libraries/render-utils/src/drawWorkloadProxy.slv @@ -0,0 +1,94 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// drawItemBounds.slv +// vertex shader +// +// Created by Sam Gateau on 6/29/2015. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +<@include gpu/Color.slh@> +<$declareColorWheel()$> + +uniform vec4 inColor; + + +struct WorkloadProxy { + vec4 sphere; + vec4 region; +}; + +#if defined(GPU_GL410) +uniform samplerBuffer workloadProxiesBuffer; +WorkloadProxy getWorkloadProxy(int i) { + int offset = 2 * i; + WorkloadProxy proxy; + proxy.sphere = texelFetch(workloadProxiesBuffer, offset); + proxy.region = texelFetch(workloadProxiesBuffer, offset + 1); + return proxy; +} +#else +layout(std140) buffer workloadProxiesBuffer { + WorkloadProxy _proxies[]; +}; +WorkloadProxy getWorkloadProxy(int i) { + WorkloadProxy proxy = _proxies[i]; + return proxy; +} +#endif + + + +out vec4 varColor; +out vec3 varTexcoord; + +void main(void) { + const vec4 UNIT_SPRITE[3] = vec4[3]( + vec4(-1.0, -1.0, 0.0, 1.0), + vec4(3.0, -1.0, 0.0, 1.0), + vec4(-1.0, 3.0, 0.0, 1.0) + ); + const int UNIT_SPRITE_INDICES[3] = int[3]( + 0, 1, 2 + ); + int proxyID = gl_VertexID / 3; + int vertexID = gl_VertexID - proxyID * 3; + + vec4 spriteVert = UNIT_SPRITE[UNIT_SPRITE_INDICES[vertexID]]; + + WorkloadProxy proxy = getWorkloadProxy(proxyID); + vec4 proxyPosWorld = vec4(proxy.sphere.xyz, 1.0); + + // standard transform, bring proxy in view space + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + vec4 proxyPosEye; + <$transformModelToEyePos(cam, obj, proxyPosWorld, proxyPosEye)$> + + // Define the billboarded space + vec3 dirZ = -normalize(proxyPosEye.xyz); + vec3 dirX = normalize(cross(vec3(0.0, 1.0, 0.0), dirZ)); + vec3 dirY = vec3(0.0, 1.0, 0.0); + + vec4 pos = vec4(proxyPosEye.xyz + proxy.sphere.w * ( dirX * spriteVert.x + dirY * spriteVert.y /* + dirZ * spriteVert.z*/), 1.0); + varTexcoord = spriteVert.xyz; + <$transformEyeToClipPos(cam, pos, gl_Position)$> + + // Convert region to color + int region = floatBitsToInt(proxy.region.x); + region = (0x000000FF & region); + + varColor = vec4(colorWheel(float(region) / 4.0), proxy.sphere.w); + + if (region == 4) { + gl_Position = vec4(0.0); + } +} diff --git a/libraries/render-utils/src/drawWorkloadView.slf b/libraries/render-utils/src/drawWorkloadView.slf new file mode 100644 index 0000000000..1304e68c7f --- /dev/null +++ b/libraries/render-utils/src/drawWorkloadView.slf @@ -0,0 +1,32 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// drawItemBounds.frag +// fragment shader +// +// Created by Sam Gateau on 6/29/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +<@include DeferredBufferWrite.slh@> +<@include gpu/Paint.slh@> + +in vec4 varColor; +in vec3 varTexcoord; + +void main(void) { + if (varColor.w > 0.0) { + float r = sqrt(dot(varTexcoord.xyz,varTexcoord.xyz)); + float a = paintStripe(r * varColor.w, 0.0, 1.0 / varColor.w, 0.05 / varColor.w); + if (a <= 0.1 || r > 1.1) { + discard; + } + } + + packDeferredFragmentUnlit( + vec3(0.0, 1.0, 0.0), + 1.0, + varColor.rgb); +} diff --git a/libraries/render-utils/src/drawWorkloadView.slv b/libraries/render-utils/src/drawWorkloadView.slv new file mode 100644 index 0000000000..f5497d250c --- /dev/null +++ b/libraries/render-utils/src/drawWorkloadView.slv @@ -0,0 +1,120 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// drawItemBounds.slv +// vertex shader +// +// Created by Sam Gateau on 6/29/2015. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +<@include gpu/Transform.slh@> +<$declareStandardTransform()$> + +<@include gpu/Color.slh@> +<$declareColorWheel()$> + +uniform vec4 inColor; + + +struct WorkloadView { + vec4 direction_far; + vec4 fov; + vec4 origin; + vec4 backFront[2]; + vec4 regions[3]; +}; + +#if defined(GPU_GL410) +uniform samplerBuffer workloadViewsBuffer; +WorkloadView getWorkloadView(int i) { + int offset = 2 * i; + WorkloadView view; + view.origin = texelFetch(workloadViewsBuffer, offset); + view.radiuses = texelFetch(workloadViewsBuffer, offset + 1); + return view; +} +#else +layout(std140) buffer workloadViewsBuffer { + WorkloadView _views[]; +}; +WorkloadView getWorkloadView(int i) { + WorkloadView view = _views[i]; + return view; +} +#endif + + + + + +out vec4 varColor; +out vec3 varTexcoord; + +const int NUM_VERTICES_PER_SEGMENT = 2; +const int NUM_SEGMENT_PER_VIEW_REGION = 65; +const int NUM_VERTICES_PER_VIEW_REGION = NUM_SEGMENT_PER_VIEW_REGION * NUM_VERTICES_PER_SEGMENT; +const int NUM_REGIONS_PER_VIEW = 3; +const int NUM_VERTICES_PER_VIEW = NUM_VERTICES_PER_VIEW_REGION * NUM_REGIONS_PER_VIEW; + + +layout(std140) uniform drawMeshBuffer { + vec4 verts[NUM_SEGMENT_PER_VIEW_REGION]; +}; + +void main(void) { + int viewID = gl_VertexID / NUM_VERTICES_PER_VIEW; + int viewVertexID = gl_VertexID - viewID * NUM_VERTICES_PER_VIEW; + + int regionID = viewVertexID / NUM_VERTICES_PER_VIEW_REGION; + int regionVertexID = viewVertexID - regionID * NUM_VERTICES_PER_VIEW_REGION; + + int segmentID = regionVertexID / NUM_VERTICES_PER_SEGMENT; + int segmentVertexID = regionVertexID - segmentID * NUM_VERTICES_PER_SEGMENT; + + vec4 segment = verts[segmentID]; + + vec4 spriteVert = vec4(segment.y, 0.0, segment.x, 1.0); + vec3 spriteTan = vec3(segment.x, 0.0, -segment.y); + + vec3 lateralDir = vec3(0.0, -1.0 + 2.0 * float(segmentVertexID), 0.0); + + WorkloadView view = getWorkloadView(viewID); + vec4 region = view.regions[regionID]; + vec4 proxyPosWorld = vec4(region.xyz, 1.0); + float regionRadius = region.w; + + // Define the sprite space + vec3 dirZ = -normalize(view.direction_far.xyz); + vec3 dirY = vec3(0.0, 1.0, 0.0); + vec3 dirX = normalize(cross(dirY, dirZ)); + dirY = normalize(cross(dirZ, dirX)); + + spriteVert.xyz *= regionRadius; + vec3 originSpaceVert = (dirX * spriteVert.x + dirY * spriteVert.y + dirZ * spriteVert.z); + vec4 pos = vec4(proxyPosWorld.xyz + originSpaceVert, 1.0); + + vec3 originSpaceTan = normalize(dirX * spriteTan.x + dirY * spriteTan.y + dirZ * spriteTan.z); + + // standard transform, bring pos in view space + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + vec4 posEye; + <$transformModelToEyePos(cam, obj, pos, posEye)$> + vec3 tanEye; + <$transformModelToEyeDir(cam, obj, originSpaceTan, tanEye)$> + + lateralDir = normalize(cross(vec3(0.0, 0.0, 1.0), normalize(tanEye))); + posEye.xyz += (0.05 * (regionID + 1)) * (-1.0 + 2.0 * float(segmentVertexID)) * lateralDir; + + <$transformEyeToClipPos(cam, posEye, gl_Position)$> + + varTexcoord = spriteVert.xyz; + + // Convert region to color + varColor = vec4(colorWheel(float(regionID) / 4.0), -1.0); +} diff --git a/libraries/render-utils/src/forward_model_translucent.slf b/libraries/render-utils/src/forward_model_translucent.slf index b8d43f15f1..70a3233737 100644 --- a/libraries/render-utils/src/forward_model_translucent.slf +++ b/libraries/render-utils/src/forward_model_translucent.slf @@ -41,6 +41,7 @@ void main(void) { float opacity = getMaterialOpacity(mat) * _alpha; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; + <$discardTransparent(opacity)$>; vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; diff --git a/libraries/render-utils/src/model_translucent.slf b/libraries/render-utils/src/model_translucent.slf index 71f76c8a8d..b808ca4bab 100644 --- a/libraries/render-utils/src/model_translucent.slf +++ b/libraries/render-utils/src/model_translucent.slf @@ -44,6 +44,7 @@ void main(void) { float opacity = getMaterialOpacity(mat) * _alpha; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; + <$discardTransparent(opacity)$>; vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; diff --git a/libraries/render-utils/src/model_translucent_fade.slf b/libraries/render-utils/src/model_translucent_fade.slf index 8b40186448..a93adee96b 100644 --- a/libraries/render-utils/src/model_translucent_fade.slf +++ b/libraries/render-utils/src/model_translucent_fade.slf @@ -46,6 +46,7 @@ void main(void) { float opacity = getMaterialOpacity(mat) * _alpha; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; + <$discardTransparent(opacity)$>; vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; diff --git a/libraries/render-utils/src/model_translucent_normal_map.slf b/libraries/render-utils/src/model_translucent_normal_map.slf index 320e883bb0..750149dc1b 100644 --- a/libraries/render-utils/src/model_translucent_normal_map.slf +++ b/libraries/render-utils/src/model_translucent_normal_map.slf @@ -45,6 +45,7 @@ void main(void) { float opacity = getMaterialOpacity(mat) * _alpha; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; + <$discardTransparent(opacity)$>; vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; diff --git a/libraries/render-utils/src/model_translucent_normal_map_fade.slf b/libraries/render-utils/src/model_translucent_normal_map_fade.slf index 0e114f7fdd..c7615626ce 100644 --- a/libraries/render-utils/src/model_translucent_normal_map_fade.slf +++ b/libraries/render-utils/src/model_translucent_normal_map_fade.slf @@ -54,6 +54,7 @@ void main(void) { float opacity = getMaterialOpacity(mat) * _alpha; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; + <$discardTransparent(opacity)$>; vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; diff --git a/libraries/render-utils/src/model_translucent_unlit.slf b/libraries/render-utils/src/model_translucent_unlit.slf index ebe9901616..e5507dd2e0 100644 --- a/libraries/render-utils/src/model_translucent_unlit.slf +++ b/libraries/render-utils/src/model_translucent_unlit.slf @@ -31,6 +31,7 @@ void main(void) { float opacity = getMaterialOpacity(mat) * _alpha; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; + <$discardTransparent(opacity)$>; vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; diff --git a/libraries/render-utils/src/model_translucent_unlit_fade.slf b/libraries/render-utils/src/model_translucent_unlit_fade.slf index 0f7c3366bb..016db4639f 100644 --- a/libraries/render-utils/src/model_translucent_unlit_fade.slf +++ b/libraries/render-utils/src/model_translucent_unlit_fade.slf @@ -41,6 +41,7 @@ void main(void) { float opacity = getMaterialOpacity(mat) * _alpha; <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; + <$discardTransparent(opacity)$>; vec3 albedo = getMaterialAlbedo(mat); <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; diff --git a/libraries/render-utils/src/overlay3D.slf b/libraries/render-utils/src/overlay3D.slf deleted file mode 100644 index 83cee88790..0000000000 --- a/libraries/render-utils/src/overlay3D.slf +++ /dev/null @@ -1,92 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// overlay3D.slf -// fragment shader -// -// Created by Sam Gateau on 6/16/15. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - - -<@include graphics/Light.slh@> -<$declareLightBuffer()$> -<$declareLightAmbientBuffer()$> - -<@include LightingModel.slh@> - -<@include LightDirectional.slh@> -<$declareLightingDirectional()$> - -<@include gpu/Transform.slh@> -<$declareStandardCameraTransform()$> - -vec4 evalGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 fresnel, float roughness, float opacity) { - - // Need the light now - Light light = getKeyLight(); - vec3 lightDirection = getLightDirection(light); - vec3 lightIrradiance = getLightIrradiance(light); - - LightAmbient ambient = getLightAmbient(); - - TransformCamera cam = getTransformCamera(); - vec3 fragEyeVectorView = normalize(-position); - vec3 fragEyeDir; - <$transformEyeToWorldDir(cam, fragEyeVectorView, fragEyeDir)$> - - SurfaceData surface = initSurfaceData(roughness, normal, fragEyeDir); - - vec3 color = opacity * albedo * getLightColor(light) * getLightAmbientIntensity(ambient); - - // Directional - vec3 directionalDiffuse; - vec3 directionalSpecular; - evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surface, metallic, fresnel, albedo, shadowAttenuation); - color += directionalDiffuse * isDiffuseEnabled() * isDirectionalEnabled(); - color += directionalSpecular * isSpecularEnabled() * isDirectionalEnabled(); - - return vec4(color, opacity); -} - -uniform sampler2D originalTexture; - -in vec2 _texCoord0; -in vec4 _positionES; -in vec3 _normalWS; -in vec3 _color; -in float _alpha; - -out vec4 _fragColor; - -void main(void) { - vec4 albedo = texture(originalTexture, _texCoord0); - - vec3 fragPosition = _positionES.xyz; - vec3 fragNormal = normalize(_normalWS); - vec3 fragAlbedo = albedo.rgb * _color; - float fragMetallic = 0.0; - vec3 fragSpecular = vec3(0.1); - float fragRoughness = 0.9; - float fragOpacity = albedo.a; - - if (fragOpacity <= 0.1) { - discard; - } - - vec4 color = evalGlobalColor(1.0, - fragPosition, - fragNormal, - fragAlbedo, - fragMetallic, - fragSpecular, - fragRoughness, - fragOpacity); - - - // Apply standard tone mapping - _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w); -} diff --git a/libraries/render-utils/src/overlay3D.slv b/libraries/render-utils/src/overlay3D.slv deleted file mode 100644 index 9e13fb3776..0000000000 --- a/libraries/render-utils/src/overlay3D.slv +++ /dev/null @@ -1,36 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// overlay3D.slv -// vertex shader -// -// Created by Sam Gateau on 6/16/15. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -<@include gpu/Inputs.slh@> -<@include gpu/Color.slh@> -<@include gpu/Transform.slh@> -<$declareStandardTransform()$> - -out vec3 _color; -out float _alpha; -out vec2 _texCoord0; -out vec4 _positionES; -out vec3 _normalWS; - -void main(void) { - _color = color_sRGBToLinear(inColor.xyz); - _alpha = inColor.w; - - _texCoord0 = inTexCoord0.st; - - // standard transform - TransformCamera cam = getTransformCamera(); - TransformObject obj = getTransformObject(); - <$transformModelToEyeAndClipPos(cam, obj, inPosition, _positionES, gl_Position)$> - <$transformModelToWorldDir(cam, obj, inNormal.xyz, _normalWS)$> -} diff --git a/libraries/render-utils/src/overlay3D_model.slf b/libraries/render-utils/src/overlay3D_model.slf deleted file mode 100644 index fd6470efb2..0000000000 --- a/libraries/render-utils/src/overlay3D_model.slf +++ /dev/null @@ -1,79 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// overlay3D.slf -// fragment shader -// -// Created by Sam Gateau on 6/16/15. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -<@include DeferredGlobalLight.slh@> -<$declareEvalSkyboxGlobalColor()$> - -<@include graphics/Material.slh@> - -<@include gpu/Transform.slh@> -<$declareStandardCameraTransform()$> - -<@include MaterialTextures.slh@> -<$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$> - -in vec2 _texCoord0; -in vec2 _texCoord1; -in vec4 _positionES; -in vec3 _normalWS; -in vec3 _color; -in float _alpha; - -out vec4 _fragColor; - -void main(void) { - Material mat = getMaterial(); - BITFIELD matKey = getMaterialKey(mat); - <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex)$> - <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$> - - float opacity = 1.0; - <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; - <$discardTransparent(opacity)$>; - - vec3 albedo = getMaterialAlbedo(mat); - <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; - - float metallic = getMaterialMetallic(mat); - vec3 fresnel = getFresnelF0(metallic, albedo); - - float roughness = getMaterialRoughness(mat); - <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; - - vec3 emissive = getMaterialEmissive(mat); - <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; - - - vec3 fragPosition = _positionES.xyz; - - TransformCamera cam = getTransformCamera(); - - vec4 color = vec4(evalSkyboxGlobalColor( - cam._viewInverse, - 1.0, - occlusionTex, - fragPosition, - normalize(_normalWS), - albedo, - fresnel, - metallic, - roughness), - opacity); - - // And emissive - color.rgb += emissive * isEmissiveEnabled(); - - // Apply standard tone mapping - _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w); -} \ No newline at end of file diff --git a/libraries/render-utils/src/overlay3D_model_translucent.slf b/libraries/render-utils/src/overlay3D_model_translucent.slf deleted file mode 100644 index 0c5b922d34..0000000000 --- a/libraries/render-utils/src/overlay3D_model_translucent.slf +++ /dev/null @@ -1,72 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// overlay3D_model_transparent.slf -// -// Created by Sam Gateau on 2/27/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 DeferredGlobalLight.slh@> -<$declareEvalGlobalLightingAlphaBlendedWithHaze()$> - -<@include graphics/Material.slh@> - -<@include gpu/Transform.slh@> -<$declareStandardCameraTransform()$> - -<@include MaterialTextures.slh@> -<$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$> - -in vec2 _texCoord0; -in vec2 _texCoord1; -in vec4 _positionES; -in vec3 _normalWS; -in vec3 _color; - -out vec4 _fragColor; - -void main(void) { - Material mat = getMaterial(); - BITFIELD matKey = getMaterialKey(mat); - <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex)$> - <$fetchMaterialTexturesCoord1(matKey, _texCoord1, occlusionTex)$> - - float opacity = 1.0; - <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; - - vec3 albedo = getMaterialAlbedo(mat); - <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; - - float metallic = getMaterialMetallic(mat); - vec3 fresnel = getFresnelF0(metallic, albedo); - - float roughness = getMaterialRoughness(mat); - <$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>; - - vec3 emissive = getMaterialEmissive(mat); - <$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>; - - vec3 fragPosition = _positionES.xyz; - TransformCamera cam = getTransformCamera(); - - vec4 color = vec4(evalGlobalLightingAlphaBlendedWithHaze( - cam._viewInverse, - 1.0, - occlusionTex, - fragPosition, - normalize(_normalWS), - albedo, - fresnel, - metallic, - emissive, - roughness, opacity), - opacity); - - // Apply standard tone mapping - _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w); -} \ No newline at end of file diff --git a/libraries/render-utils/src/overlay3D_model_translucent_unlit.slf b/libraries/render-utils/src/overlay3D_model_translucent_unlit.slf deleted file mode 100644 index b1822a645a..0000000000 --- a/libraries/render-utils/src/overlay3D_model_translucent_unlit.slf +++ /dev/null @@ -1,41 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// overlay3D-model_transparent_unlit.slf -// fragment shader -// -// Created by Sam Gateau on 2/28/2017. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -<@include LightingModel.slh@> -<@include graphics/Material.slh@> - -<@include MaterialTextures.slh@> -<$declareMaterialTextures(ALBEDO)$> - -in vec2 _texCoord0; -in vec3 _color; - -out vec4 _fragColor; - -void main(void) { - - Material mat = getMaterial(); - BITFIELD matKey = getMaterialKey(mat); - <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex)$> - - float opacity = 1.0; - <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; - - vec3 albedo = getMaterialAlbedo(mat); - <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; - - vec4 color = vec4(albedo * isUnlitEnabled(), opacity); - - _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w); -} \ No newline at end of file diff --git a/libraries/render-utils/src/overlay3D_model_unlit.slf b/libraries/render-utils/src/overlay3D_model_unlit.slf deleted file mode 100644 index cc16d0751d..0000000000 --- a/libraries/render-utils/src/overlay3D_model_unlit.slf +++ /dev/null @@ -1,42 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// overlay3D-model_unlit.slf -// fragment shader -// -// Created by Sam Gateau on 2/28/2017. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -<@include LightingModel.slh@> -<@include graphics/Material.slh@> - -<@include MaterialTextures.slh@> -<$declareMaterialTextures(ALBEDO)$> - -in vec2 _texCoord0; -in vec3 _color; - -out vec4 _fragColor; - -void main(void) { - - Material mat = getMaterial(); - BITFIELD matKey = getMaterialKey(mat); - <$fetchMaterialTexturesCoord0(matKey, _texCoord0, albedoTex)$> - - float opacity = 1.0; - <$evalMaterialOpacity(albedoTex.a, opacity, matKey, opacity)$>; - <$discardTransparent(opacity)$>; - - vec3 albedo = getMaterialAlbedo(mat); - <$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>; - albedo *= _color; - - vec4 color = vec4(albedo * isUnlitEnabled(), opacity); - - _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w); -} diff --git a/libraries/render-utils/src/overlay3D_translucent.slf b/libraries/render-utils/src/overlay3D_translucent.slf deleted file mode 100644 index b93550a63d..0000000000 --- a/libraries/render-utils/src/overlay3D_translucent.slf +++ /dev/null @@ -1,87 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// overlay3D_translucent.slf -// fragment shader -// -// Created by Sam Gateau on 6/16/15. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -<@include graphics/Light.slh@> -<$declareLightBuffer()$> -<$declareLightAmbientBuffer()$> - -<@include LightingModel.slh@> - -<@include LightDirectional.slh@> -<$declareLightingDirectional()$> - -<@include gpu/Transform.slh@> -<$declareStandardCameraTransform()$> - -vec4 evalGlobalColor(float shadowAttenuation, vec3 position, vec3 normal, vec3 albedo, float metallic, vec3 fresnel, float roughness, float opacity) { - - // Need the light now - Light light = getKeyLight(); - vec3 lightDirection = getLightDirection(light); - vec3 lightIrradiance = getLightIrradiance(light); - - LightAmbient ambient = getLightAmbient(); - - TransformCamera cam = getTransformCamera(); - vec3 fragEyeVectorView = normalize(-position); - vec3 fragEyeDir; - <$transformEyeToWorldDir(cam, fragEyeVectorView, fragEyeDir)$> - - SurfaceData surface = initSurfaceData(roughness, normal, fragEyeDir); - - vec3 color = opacity * albedo * getLightColor(light) * getLightAmbientIntensity(ambient); - - // Directional - vec3 directionalDiffuse; - vec3 directionalSpecular; - evalLightingDirectional(directionalDiffuse, directionalSpecular, lightDirection, lightIrradiance, surface, metallic, fresnel, albedo, shadowAttenuation); - color += directionalDiffuse; - color += directionalSpecular / opacity; - - return vec4(color, opacity); -} - -uniform sampler2D originalTexture; - -in vec2 _texCoord0; -in vec4 _positionES; -in vec3 _normalWS; -in vec3 _color; -in float _alpha; - -out vec4 _fragColor; - -void main(void) { - vec4 albedo = texture(originalTexture, _texCoord0); - - vec3 fragPosition = _positionES.xyz; - vec3 fragNormal = normalize(_normalWS); - vec3 fragAlbedo = albedo.rgb * _color; - float fragMetallic = 0.0; - vec3 fragSpecular = vec3(0.1); - float fragRoughness = 0.9; - float fragOpacity = albedo.a * _alpha; - - vec4 color = evalGlobalColor(1.0, - fragPosition, - fragNormal, - fragAlbedo, - fragMetallic, - fragSpecular, - fragRoughness, - fragOpacity); - - // Apply standard tone mapping - _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w); -} diff --git a/libraries/render-utils/src/overlay3D_translucent_unlit.slf b/libraries/render-utils/src/overlay3D_translucent_unlit.slf deleted file mode 100644 index 18a26d0790..0000000000 --- a/libraries/render-utils/src/overlay3D_translucent_unlit.slf +++ /dev/null @@ -1,27 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// overlay3D_translucent_unlit.frag -// fragment shader -// -// Created by Zach Pomerantz on 2/2/2016. -// Copyright 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 originalTexture; - -in vec2 _texCoord0; -in vec3 _color; -in float _alpha; - -out vec4 _fragColor; - -void main(void) { - vec4 albedo = texture(originalTexture, _texCoord0); - - _fragColor = vec4(albedo.rgb * _color, albedo.a * _alpha); -} diff --git a/libraries/render-utils/src/overlay3D_unlit.slf b/libraries/render-utils/src/overlay3D_unlit.slf deleted file mode 100644 index 42e51bdc25..0000000000 --- a/libraries/render-utils/src/overlay3D_unlit.slf +++ /dev/null @@ -1,32 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// overlay3D_unlit.frag -// fragment shader -// -// Created by Zach Pomerantz on 2/2/2016. -// Copyright 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 originalTexture; - -in vec2 _texCoord0; -in vec3 _color; - -out vec4 _fragColor; - -void main(void) { - vec4 albedo = texture(originalTexture, _texCoord0); - - if (albedo.a <= 0.1) { - discard; - } - vec4 color = vec4(albedo.rgb * _color, albedo.a); - - // Apply standard tone mapping - _fragColor = vec4(pow(color.xyz, vec3(1.0 / 2.2)), color.w); -} diff --git a/libraries/render-utils/src/parabola.slf b/libraries/render-utils/src/parabola.slf new file mode 100644 index 0000000000..ae7a44ddd1 --- /dev/null +++ b/libraries/render-utils/src/parabola.slf @@ -0,0 +1,18 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gondelman on 7/18/2018 +// Copyright 2018 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 +// + +in vec4 _color; + +out vec4 _fragColor; + +void main(void) { + _fragColor = _color; +} diff --git a/libraries/render-utils/src/parabola.slv b/libraries/render-utils/src/parabola.slv new file mode 100644 index 0000000000..c40fc89302 --- /dev/null +++ b/libraries/render-utils/src/parabola.slv @@ -0,0 +1,54 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gondelman on 7/18/2018 +// Copyright 2018 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 gpu/Transform.slh@> +<$declareStandardTransform()$> + +layout(std140) uniform parabolaData { + vec3 velocity; + float parabolicDistance; + vec3 acceleration; + float width; + vec4 color; + int numSections; + ivec3 spare; +}; + +out vec4 _color; + +void main(void) { + _color = color; + + float t = parabolicDistance * (floor(gl_VertexID / 2) / float(numSections)); + + vec4 pos = vec4(velocity * t + 0.5 * acceleration * t * t, 1); + const float EPSILON = 0.00001; + vec4 normal; + + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + if (dot(acceleration, acceleration) < EPSILON) { + // Handle case where acceleration == (0, 0, 0) + vec3 eyeUp = vec3(0, 1, 0); + vec3 worldUp; + <$transformEyeToWorldDir(cam, eyeUp, worldUp)$> + normal = vec4(normalize(cross(velocity, worldUp)), 0); + } else { + normal = vec4(normalize(cross(velocity, acceleration)), 0); + } + if (gl_VertexID % 2 == 0) { + pos += 0.5 * width * normal; + } else { + pos -= 0.5 * width * normal; + } + + <$transformModelToClipPos(cam, obj, pos, gl_Position)$> +} \ No newline at end of file diff --git a/libraries/render/src/render/CullTask.cpp b/libraries/render/src/render/CullTask.cpp index 3f55e6dedc..8cfe7683ce 100644 --- a/libraries/render/src/render/CullTask.cpp +++ b/libraries/render/src/render/CullTask.cpp @@ -370,10 +370,13 @@ void CullShapeBounds::run(const RenderContextPointer& renderContext, const Input const auto& inShapes = inputs.get0(); const auto& cullFilter = inputs.get1(); const auto& boundsFilter = inputs.get2(); - const auto& antiFrustum = inputs.get3(); + ViewFrustumPointer antiFrustum; auto& outShapes = outputs.edit0(); auto& outBounds = outputs.edit1(); + if (!inputs[3].isNull()) { + antiFrustum = inputs.get3(); + } outShapes.clear(); outBounds = AABox(); diff --git a/libraries/render/src/render/ResampleTask.cpp b/libraries/render/src/render/ResampleTask.cpp index 07f7367582..008234b437 100644 --- a/libraries/render/src/render/ResampleTask.cpp +++ b/libraries/render/src/render/ResampleTask.cpp @@ -81,3 +81,69 @@ void HalfDownsample::run(const RenderContextPointer& renderContext, const gpu::F batch.draw(gpu::TRIANGLE_STRIP, 4); }); } + +gpu::PipelinePointer Upsample::_pipeline; + +void Upsample::configure(const Config& config) { + _factor = config.factor; +} + +gpu::FramebufferPointer Upsample::getResampledFrameBuffer(const gpu::FramebufferPointer& sourceFramebuffer) { + if (_factor == 1.0f) { + return sourceFramebuffer; + } + + auto resampledFramebufferSize = glm::uvec2(glm::vec2(sourceFramebuffer->getSize()) * _factor); + + if (!_destinationFrameBuffer || resampledFramebufferSize != _destinationFrameBuffer->getSize()) { + _destinationFrameBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("UpsampledOutput")); + + auto sampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR); + auto target = gpu::Texture::createRenderBuffer(sourceFramebuffer->getRenderBuffer(0)->getTexelFormat(), resampledFramebufferSize.x, resampledFramebufferSize.y, gpu::Texture::SINGLE_MIP, sampler); + _destinationFrameBuffer->setRenderBuffer(0, target); + } + return _destinationFrameBuffer; +} + +void Upsample::run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& sourceFramebuffer, gpu::FramebufferPointer& resampledFrameBuffer) { + assert(renderContext->args); + assert(renderContext->args->hasViewFrustum()); + RenderArgs* args = renderContext->args; + + resampledFrameBuffer = getResampledFrameBuffer(sourceFramebuffer); + if (resampledFrameBuffer != sourceFramebuffer) { + if (!_pipeline) { + auto vs = gpu::StandardShaderLib::getDrawTransformUnitQuadVS(); + auto ps = gpu::StandardShaderLib::getDrawTextureOpaquePS(); + gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); + + gpu::Shader::BindingSet slotBindings; + gpu::Shader::makeProgram(*program, slotBindings); + + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setDepthTest(gpu::State::DepthTest(false, false)); + _pipeline = gpu::Pipeline::create(program, state); + } + + const auto bufferSize = resampledFrameBuffer->getSize(); + glm::ivec4 viewport{ 0, 0, bufferSize.x, bufferSize.y }; + + gpu::doInBatch("Upsample::run", args->_context, [&](gpu::Batch& batch) { + batch.enableStereo(false); + + batch.setFramebuffer(resampledFrameBuffer); + + batch.setViewportTransform(viewport); + batch.setProjectionTransform(glm::mat4()); + batch.resetViewTransform(); + batch.setPipeline(_pipeline); + + batch.setModelTransform(gpu::Framebuffer::evalSubregionTexcoordTransform(bufferSize, viewport)); + batch.setResourceTexture(0, sourceFramebuffer->getRenderBuffer(0)); + batch.draw(gpu::TRIANGLE_STRIP, 4); + }); + + // Set full final viewport + args->_viewport = viewport; + } +} diff --git a/libraries/render/src/render/ResampleTask.h b/libraries/render/src/render/ResampleTask.h index da2b7b3537..25f9c6a3e9 100644 --- a/libraries/render/src/render/ResampleTask.h +++ b/libraries/render/src/render/ResampleTask.h @@ -36,6 +36,37 @@ namespace render { gpu::FramebufferPointer getResampledFrameBuffer(const gpu::FramebufferPointer& sourceFramebuffer); }; + + class UpsampleConfig : public render::Job::Config { + Q_OBJECT + Q_PROPERTY(float factor MEMBER factor NOTIFY dirty) + public: + + float factor{ 1.0f }; + + signals: + void dirty(); + }; + + class Upsample { + public: + using Config = UpsampleConfig; + using JobModel = Job::ModelIO; + + Upsample(float factor = 2.0f) : _factor{ factor } {} + + void configure(const Config& config); + void run(const RenderContextPointer& renderContext, const gpu::FramebufferPointer& sourceFramebuffer, gpu::FramebufferPointer& resampledFrameBuffer); + + protected: + + static gpu::PipelinePointer _pipeline; + + gpu::FramebufferPointer _destinationFrameBuffer; + float _factor{ 2.0f }; + + gpu::FramebufferPointer getResampledFrameBuffer(const gpu::FramebufferPointer& sourceFramebuffer); + }; } #endif // hifi_render_ResampleTask_h diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index 1ce58c49ae..8cd04f8067 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -95,6 +95,7 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p slotBindings.insert(gpu::Shader::Binding(std::string("skyboxMap"), Slot::MAP::LIGHT_AMBIENT_MAP)); slotBindings.insert(gpu::Shader::Binding(std::string("fadeMaskMap"), Slot::MAP::FADE_MASK)); slotBindings.insert(gpu::Shader::Binding(std::string("fadeParametersBuffer"), Slot::BUFFER::FADE_PARAMETERS)); + slotBindings.insert(gpu::Shader::Binding(std::string("fadeObjectParametersBuffer"), Slot::BUFFER::FADE_OBJECT_PARAMETERS)); slotBindings.insert(gpu::Shader::Binding(std::string("hazeBuffer"), Slot::BUFFER::HAZE_MODEL)); if (key.isTranslucent()) { @@ -124,6 +125,7 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p locations->lightAmbientMapUnit = program->getTextures().findLocation("skyboxMap"); locations->fadeMaskTextureUnit = program->getTextures().findLocation("fadeMaskMap"); locations->fadeParameterBufferUnit = program->getUniformBuffers().findLocation("fadeParametersBuffer"); + locations->fadeObjectParameterBufferUnit = program->getUniformBuffers().findLocation("fadeObjectParametersBuffer"); locations->hazeParameterBufferUnit = program->getUniformBuffers().findLocation("hazeBuffer"); if (key.isTranslucent()) { locations->lightClusterGridBufferUnit = program->getUniformBuffers().findLocation("clusterGridBuffer"); diff --git a/libraries/render/src/render/ShapePipeline.h b/libraries/render/src/render/ShapePipeline.h index 7d87d98deb..10f1b757cc 100644 --- a/libraries/render/src/render/ShapePipeline.h +++ b/libraries/render/src/render/ShapePipeline.h @@ -240,6 +240,7 @@ public: LIGHT_AMBIENT_BUFFER, HAZE_MODEL, FADE_PARAMETERS, + FADE_OBJECT_PARAMETERS, LIGHT_CLUSTER_GRID_FRUSTUM_GRID_SLOT, LIGHT_CLUSTER_GRID_CLUSTER_GRID_SLOT, LIGHT_CLUSTER_GRID_CLUSTER_CONTENT_SLOT, @@ -254,9 +255,9 @@ public: ROUGHNESS, OCCLUSION, SCATTERING, - FADE_MASK, LIGHT_AMBIENT_MAP = 10, + FADE_MASK, }; }; @@ -278,6 +279,7 @@ public: int lightAmbientMapUnit; int fadeMaskTextureUnit; int fadeParameterBufferUnit; + int fadeObjectParameterBufferUnit; int hazeParameterBufferUnit; int lightClusterGridBufferUnit; int lightClusterContentBufferUnit; diff --git a/libraries/render/src/render/Transition.h b/libraries/render/src/render/Transition.h index 622e6f69ce..30bda8aa2a 100644 --- a/libraries/render/src/render/Transition.h +++ b/libraries/render/src/render/Transition.h @@ -42,6 +42,8 @@ namespace render { glm::vec3 baseInvSize{ 1.f, 1.f, 1.f }; float threshold{ 0.f }; uint8_t isFinished{ 0 }; + + mutable gpu::BufferView paramsBuffer; }; typedef std::shared_ptr TransitionPointer; diff --git a/libraries/script-engine/src/AudioScriptingInterface.cpp b/libraries/script-engine/src/AudioScriptingInterface.cpp index f248c20d41..72918e33f6 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.cpp +++ b/libraries/script-engine/src/AudioScriptingInterface.cpp @@ -23,6 +23,21 @@ void registerAudioMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, soundSharedPointerToScriptValue, soundSharedPointerFromScriptValue); } + +void AudioScriptingInterface::setLocalAudioInterface(AbstractAudioInterface* audioInterface) { + if (_localAudioInterface) { + disconnect(_localAudioInterface, &AbstractAudioInterface::isStereoInputChanged, + this, &AudioScriptingInterface::isStereoInputChanged); + } + + _localAudioInterface = audioInterface; + + if (_localAudioInterface) { + connect(_localAudioInterface, &AbstractAudioInterface::isStereoInputChanged, + this, &AudioScriptingInterface::isStereoInputChanged); + } +} + ScriptAudioInjector* AudioScriptingInterface::playSystemSound(SharedSoundPointer sound, const QVector3D& position) { AudioInjectorOptions options; options.position = glm::vec3(position.x(), position.y(), position.z()); @@ -60,12 +75,10 @@ ScriptAudioInjector* AudioScriptingInterface::playSound(SharedSoundPointer sound } } -bool AudioScriptingInterface::setStereoInput(bool stereo) { - bool stereoInputChanged = false; +void AudioScriptingInterface::setStereoInput(bool stereo) { if (_localAudioInterface) { - stereoInputChanged = _localAudioInterface->setIsStereoInput(stereo); + QMetaObject::invokeMethod(_localAudioInterface, "setIsStereoInput", Q_ARG(bool, stereo)); } - return stereoInputChanged; } bool AudioScriptingInterface::isStereoInput() { diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index 36fe29243d..20ca977da1 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -23,9 +23,11 @@ class AudioScriptingInterface : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY + Q_PROPERTY(bool isStereoInput READ isStereoInput WRITE setStereoInput NOTIFY isStereoInputChanged) + public: virtual ~AudioScriptingInterface() {} - void setLocalAudioInterface(AbstractAudioInterface* audioInterface) { _localAudioInterface = audioInterface; } + void setLocalAudioInterface(AbstractAudioInterface* audioInterface); protected: AudioScriptingInterface() {} @@ -52,9 +54,8 @@ protected: /**jsdoc * @function Audio.setStereoInput * @param {boolean} stereo - * @returns {boolean} */ - Q_INVOKABLE bool setStereoInput(bool stereo); + Q_INVOKABLE void setStereoInput(bool stereo); /**jsdoc * @function Audio.isStereoInput @@ -114,6 +115,13 @@ signals: */ void inputReceived(const QByteArray& inputSamples); + /**jsdoc + * @function Audio.isStereoInputChanged + * @param {boolean} isStereo + * @returns {Signal} + */ + void isStereoInputChanged(bool isStereo); + private: AbstractAudioInterface* _localAudioInterface { nullptr }; }; diff --git a/libraries/script-engine/src/RecordingScriptingInterface.cpp b/libraries/script-engine/src/RecordingScriptingInterface.cpp index 55895e31a4..cc2edfcca7 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.cpp +++ b/libraries/script-engine/src/RecordingScriptingInterface.cpp @@ -59,7 +59,7 @@ void RecordingScriptingInterface::playClip(NetworkClipLoaderPointer clipLoader, if (callback.isFunction()) { QScriptValueList args { true, url }; - callback.call(_scriptEngine->globalObject(), args); + callback.call(QScriptValue(), args); } } @@ -78,7 +78,7 @@ void RecordingScriptingInterface::loadRecording(const QString& url, QScriptValue auto weakClipLoader = clipLoader.toWeakRef(); // when clip loaded, call the callback with the URL and success boolean - connect(clipLoader.data(), &recording::NetworkClipLoader::clipLoaded, this, + connect(clipLoader.data(), &recording::NetworkClipLoader::clipLoaded, callback.engine(), [this, weakClipLoader, url, callback]() mutable { if (auto clipLoader = weakClipLoader.toStrongRef()) { @@ -92,12 +92,12 @@ void RecordingScriptingInterface::loadRecording(const QString& url, QScriptValue }); // when clip load fails, call the callback with the URL and failure boolean - connect(clipLoader.data(), &recording::NetworkClipLoader::failed, this, [this, weakClipLoader, url, callback](QNetworkReply::NetworkError error) mutable { + connect(clipLoader.data(), &recording::NetworkClipLoader::failed, callback.engine(), [this, weakClipLoader, url, callback](QNetworkReply::NetworkError error) mutable { qCDebug(scriptengine) << "Failed to load recording from" << url; if (callback.isFunction()) { QScriptValueList args { false, url }; - callback.call(_scriptEngine->currentContext()->thisObject(), args); + callback.call(QScriptValue(), args); } if (auto clipLoader = weakClipLoader.toStrongRef()) { diff --git a/libraries/script-engine/src/RecordingScriptingInterface.h b/libraries/script-engine/src/RecordingScriptingInterface.h index 29d9b31049..c4d576351f 100644 --- a/libraries/script-engine/src/RecordingScriptingInterface.h +++ b/libraries/script-engine/src/RecordingScriptingInterface.h @@ -36,8 +36,6 @@ class RecordingScriptingInterface : public QObject, public Dependency { public: RecordingScriptingInterface(); - void setScriptEngine(QSharedPointer scriptEngine) { _scriptEngine = scriptEngine; } - public slots: /**jsdoc @@ -246,7 +244,6 @@ protected: Flag _useSkeletonModel { false }; recording::ClipPointer _lastClip; - QSharedPointer _scriptEngine; QSet _clipLoaders; private: diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index f8c99b192f..99c02ba1f6 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -1639,22 +1639,24 @@ QVariantMap ScriptEngine::fetchModuleSource(const QString& modulePath, const boo loader->start(MAX_RETRIES); if (!loader->isFinished()) { - QTimer monitor; - QEventLoop loop; - QObject::connect(loader, &BatchLoader::finished, this, [&monitor, &loop]{ - monitor.stop(); - loop.quit(); + // This lambda can get called AFTER this local scope has completed. + // This is why we pass smart ptrs to the lambda instead of references to local variables. + auto monitor = std::make_shared(); + auto loop = std::make_shared(); + QObject::connect(loader, &BatchLoader::finished, this, [monitor, loop] { + monitor->stop(); + loop->quit(); }); // this helps detect the case where stop() is invoked during the download // but not seen in time to abort processing in onload()... - connect(&monitor, &QTimer::timeout, this, [this, &loop]{ + connect(monitor.get(), &QTimer::timeout, this, [this, loop] { if (isStopping()) { - loop.exit(-1); + loop->exit(-1); } }); - monitor.start(500); - loop.exec(); + monitor->start(500); + loop->exec(); } loader->deleteLater(); return req; diff --git a/libraries/script-engine/src/ScriptEngines.cpp b/libraries/script-engine/src/ScriptEngines.cpp index ad6e1debe9..d385dcca84 100644 --- a/libraries/script-engine/src/ScriptEngines.cpp +++ b/libraries/script-engine/src/ScriptEngines.cpp @@ -191,6 +191,7 @@ void ScriptEngines::shutdownScripting() { // Gracefully stop the engine's scripting thread scriptEngine->stop(); + removeScriptEngine(scriptEngine); // We need to wait for the engine to be done running before we proceed, because we don't // want any of the scripts final "scriptEnding()" or pending "update()" methods from accessing @@ -394,6 +395,7 @@ void ScriptEngines::stopAllScripts(bool restart) { // stop all scripts qCDebug(scriptengine) << "stopping script..." << it.key(); scriptEngine->stop(); + removeScriptEngine(scriptEngine); } // wait for engines to stop (ie: providing time for .scriptEnding cleanup handlers to run) before // triggering reload of any Client scripts / Entity scripts @@ -432,12 +434,16 @@ bool ScriptEngines::stopScript(const QString& rawScriptURL, bool restart) { ScriptEngine::Type type = scriptEngine->getType(); auto scriptCache = DependencyManager::get(); scriptCache->deleteScript(scriptURL); - connect(scriptEngine.data(), &ScriptEngine::finished, - this, [this, isUserLoaded, type](QString scriptName, ScriptEnginePointer engine) { - reloadScript(scriptName, isUserLoaded)->setType(type); - }); + + if (!scriptEngine->isStopping()) { + connect(scriptEngine.data(), &ScriptEngine::finished, + this, [this, isUserLoaded, type](QString scriptName, ScriptEnginePointer engine) { + reloadScript(scriptName, isUserLoaded)->setType(type); + }); + } } scriptEngine->stop(); + removeScriptEngine(scriptEngine); stoppedScript = true; qCDebug(scriptengine) << "stopping script..." << scriptURL; } @@ -594,7 +600,7 @@ void ScriptEngines::onScriptFinished(const QString& rawScriptURL, ScriptEnginePo } } - if (removed) { + if (removed && !_isReloading) { // Update settings with removed script saveScripts(); emit scriptCountChanged(); diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index cbf3c1b785..994df551fe 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -109,19 +109,12 @@ glm::vec3 AABox::getNearestVertex(const glm::vec3& normal) const { return result; } -// determines whether a value is within the extents -static bool isWithin(float value, float corner, float size) { - return value >= corner && value <= corner + size; -} - bool AABox::contains(const Triangle& triangle) const { return contains(triangle.v0) && contains(triangle.v1) && contains(triangle.v2); } bool AABox::contains(const glm::vec3& point) const { - return isWithin(point.x, _corner.x, _scale.x) && - isWithin(point.y, _corner.y, _scale.y) && - isWithin(point.z, _corner.z, _scale.z); + return aaBoxContains(point, _corner, _scale); } bool AABox::contains(const AABox& otherBox) const { @@ -175,30 +168,6 @@ bool AABox::expandedContains(const glm::vec3& point, float expansion) const { isWithinExpanded(point.z, _corner.z, _scale.z, expansion); } -// finds the intersection between a ray and the facing plane on one axis -static bool findIntersection(float origin, float direction, float corner, float size, float& distance) { - if (direction > EPSILON) { - distance = (corner - origin) / direction; - return true; - } else if (direction < -EPSILON) { - distance = (corner + size - origin) / direction; - return true; - } - return false; -} - -// finds the intersection between a ray and the inside facing plane on one axis -static bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance) { - if (direction > EPSILON) { - distance = -1.0f * (origin - (corner + size)) / direction; - return true; - } else if (direction < -EPSILON) { - distance = -1.0f * (origin - corner) / direction; - return true; - } - return false; -} - bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const { // handle the trivial cases where the expanded box contains the start or end if (expandedContains(start, expansion) || expandedContains(end, expansion)) { @@ -225,66 +194,12 @@ bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& e bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) const { - // handle the trivial case where the box contains the origin - if (contains(origin)) { - // We still want to calculate the distance from the origin to the inside out plane - float axisDistance; - if ((findInsideOutIntersection(origin.x, direction.x, _corner.x, _scale.x, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) { - distance = axisDistance; - face = direction.x > 0 ? MAX_X_FACE : MIN_X_FACE; - surfaceNormal = glm::vec3(direction.x > 0 ? 1.0f : -1.0f, 0.0f, 0.0f); - return true; - } - if ((findInsideOutIntersection(origin.y, direction.y, _corner.y, _scale.y, axisDistance) && axisDistance >= 0 && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) { - distance = axisDistance; - face = direction.y > 0 ? MAX_Y_FACE : MIN_Y_FACE; - surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? 1.0f : -1.0f, 0.0f); - return true; - } - if ((findInsideOutIntersection(origin.z, direction.z, _corner.z, _scale.z, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x))) { - distance = axisDistance; - face = direction.z > 0 ? MAX_Z_FACE : MIN_Z_FACE; - surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? 1.0f : -1.0f); - return true; - } - // This case is unexpected, but mimics the previous behavior for inside out intersections - distance = 0; - return true; - } + return findRayAABoxIntersection(origin, direction, _corner, _scale, distance, face, surfaceNormal); +} - // check each axis - float axisDistance; - if ((findIntersection(origin.x, direction.x, _corner.x, _scale.x, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) { - distance = axisDistance; - face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE; - surfaceNormal = glm::vec3(direction.x > 0 ? -1.0f : 1.0f, 0.0f, 0.0f); - return true; - } - if ((findIntersection(origin.y, direction.y, _corner.y, _scale.y, axisDistance) && axisDistance >= 0 && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) { - distance = axisDistance; - face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE; - surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? -1.0f : 1.0f, 0.0f); - return true; - } - if ((findIntersection(origin.z, direction.z, _corner.z, _scale.z, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x))) { - distance = axisDistance; - face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE; - surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? -1.0f : 1.0f); - return true; - } - return false; +bool AABox::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const { + return findParabolaAABoxIntersection(origin, velocity, acceleration, _corner, _scale, parabolicDistance, face, surfaceNormal); } bool AABox::rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& direction) const { @@ -296,6 +211,29 @@ bool AABox::rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& dire || (glm::abs(distance) > 0.0f && glm::distance2(distance * direction, localCenter) < radiusSquared)); } +bool AABox::parabolaPlaneIntersectsBoundingSphere(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec3& normal) const { + glm::vec3 localCenter = calcCenter() - origin; + const float ONE_OVER_TWO_SQUARED = 0.25f; + float radiusSquared = ONE_OVER_TWO_SQUARED * glm::length2(_scale); + + // origin is inside the sphere + if (glm::length2(localCenter) < radiusSquared) { + return true; + } + + if (glm::length2(acceleration) < EPSILON) { + // Handle the degenerate case where acceleration == (0, 0, 0) + return rayHitsBoundingSphere(origin, glm::normalize(velocity)); + } else { + // Project vector from plane to sphere center onto the normal + float distance = glm::dot(localCenter, normal); + if (distance * distance < radiusSquared) { + return true; + } + } + return false; +} + bool AABox::touchesSphere(const glm::vec3& center, float radius) const { // Avro's algorithm from this paper: http://www.mrtc.mdh.se/projects/3Dgraphics/paperF.pdf glm::vec3 e = glm::max(_corner - center, Vectors::ZERO) + glm::max(center - _corner - _scale, Vectors::ZERO); diff --git a/libraries/shared/src/AABox.h b/libraries/shared/src/AABox.h index cf79cf9d04..fbc90cff47 100644 --- a/libraries/shared/src/AABox.h +++ b/libraries/shared/src/AABox.h @@ -70,8 +70,11 @@ public: bool expandedContains(const glm::vec3& point, float expansion) const; bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const; bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, - BoxFace& face, glm::vec3& surfaceNormal) const; + BoxFace& face, glm::vec3& surfaceNormal) const; + bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const; bool rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& direction) const; + bool parabolaPlaneIntersectsBoundingSphere(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec3& normal) const; bool touchesSphere(const glm::vec3& center, float radius) const; // fast but may generate false positives bool touchesAAEllipsoid(const glm::vec3& center, const glm::vec3& radials) const; bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const; @@ -136,6 +139,9 @@ private: static BoxFace getOppositeFace(BoxFace face); + void checkPossibleParabolicIntersection(float t, int i, float& minDistance, + const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, bool& hit) const; + glm::vec3 _corner; glm::vec3 _scale; }; diff --git a/libraries/shared/src/AACube.cpp b/libraries/shared/src/AACube.cpp index 7dd2f8cb5b..dc1003215d 100644 --- a/libraries/shared/src/AACube.cpp +++ b/libraries/shared/src/AACube.cpp @@ -110,15 +110,8 @@ glm::vec3 AACube::getNearestVertex(const glm::vec3& normal) const { return result; } -// determines whether a value is within the extents -static bool isWithin(float value, float corner, float size) { - return value >= corner && value <= corner + size; -} - bool AACube::contains(const glm::vec3& point) const { - return isWithin(point.x, _corner.x, _scale) && - isWithin(point.y, _corner.y, _scale) && - isWithin(point.z, _corner.z, _scale); + return aaBoxContains(point, _corner, glm::vec3(_scale)); } bool AACube::contains(const AACube& otherCube) const { @@ -170,30 +163,6 @@ bool AACube::expandedContains(const glm::vec3& point, float expansion) const { isWithinExpanded(point.z, _corner.z, _scale, expansion); } -// finds the intersection between a ray and the facing plane on one axis -static bool findIntersection(float origin, float direction, float corner, float size, float& distance) { - if (direction > EPSILON) { - distance = (corner - origin) / direction; - return true; - } else if (direction < -EPSILON) { - distance = (corner + size - origin) / direction; - return true; - } - return false; -} - -// finds the intersection between a ray and the inside facing plane on one axis -static bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance) { - if (direction > EPSILON) { - distance = -1.0f * (origin - (corner + size)) / direction; - return true; - } else if (direction < -EPSILON) { - distance = -1.0f * (origin - corner) / direction; - return true; - } - return false; -} - bool AACube::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const { // handle the trivial cases where the expanded box contains the start or end if (expandedContains(start, expansion) || expandedContains(end, expansion)) { @@ -220,67 +189,12 @@ bool AACube::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& bool AACube::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) const { - // handle the trivial case where the box contains the origin - if (contains(origin)) { + return findRayAABoxIntersection(origin, direction, _corner, glm::vec3(_scale), distance, face, surfaceNormal); +} - // We still want to calculate the distance from the origin to the inside out plane - float axisDistance; - if ((findInsideOutIntersection(origin.x, direction.x, _corner.x, _scale, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) { - distance = axisDistance; - face = direction.x > 0 ? MAX_X_FACE : MIN_X_FACE; - surfaceNormal = glm::vec3(direction.x > 0 ? 1.0f : -1.0f, 0.0f, 0.0f); - return true; - } - if ((findInsideOutIntersection(origin.y, direction.y, _corner.y, _scale, axisDistance) && axisDistance >= 0 && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) { - distance = axisDistance; - face = direction.y > 0 ? MAX_Y_FACE : MIN_Y_FACE; - surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? 1.0f : -1.0f, 0.0f); - return true; - } - if ((findInsideOutIntersection(origin.z, direction.z, _corner.z, _scale, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale))) { - distance = axisDistance; - face = direction.z > 0 ? MAX_Z_FACE : MIN_Z_FACE; - surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? 1.0f : -1.0f); - return true; - } - // This case is unexpected, but mimics the previous behavior for inside out intersections - distance = 0; - return true; - } - - // check each axis - float axisDistance; - if ((findIntersection(origin.x, direction.x, _corner.x, _scale, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) { - distance = axisDistance; - face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE; - surfaceNormal = glm::vec3(direction.x > 0 ? -1.0f : 1.0f, 0.0f, 0.0f); - return true; - } - if ((findIntersection(origin.y, direction.y, _corner.y, _scale, axisDistance) && axisDistance >= 0 && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale) && - isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) { - distance = axisDistance; - face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE; - surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? -1.0f : 1.0f, 0.0f); - return true; - } - if ((findIntersection(origin.z, direction.z, _corner.z, _scale, axisDistance) && axisDistance >= 0 && - isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) && - isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale))) { - distance = axisDistance; - face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE; - surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? -1.0f : 1.0f); - return true; - } - return false; +bool AACube::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const { + return findParabolaAABoxIntersection(origin, velocity, acceleration, _corner, glm::vec3(_scale), parabolicDistance, face, surfaceNormal); } bool AACube::touchesSphere(const glm::vec3& center, float radius) const { diff --git a/libraries/shared/src/AACube.h b/libraries/shared/src/AACube.h index 87a38cb304..72aed31999 100644 --- a/libraries/shared/src/AACube.h +++ b/libraries/shared/src/AACube.h @@ -58,6 +58,8 @@ public: bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const; bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) const; + bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const; bool touchesSphere(const glm::vec3& center, float radius) const; bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const; bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const; diff --git a/libraries/shared/src/ApplicationVersion.cpp b/libraries/shared/src/ApplicationVersion.cpp new file mode 100644 index 0000000000..5c2d5ad11c --- /dev/null +++ b/libraries/shared/src/ApplicationVersion.cpp @@ -0,0 +1,94 @@ +// +// ApplicationVersion.cpp +// libraries/shared/src +// +// Created by Stephen Birarda on 6/8/18. +// Copyright 2018 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 "ApplicationVersion.h" + +#include + +#include +#include +#include + +ApplicationVersion::ApplicationVersion(const QString& versionString) : + versionString(versionString) +{ + // attempt to regex out a semantic version from the string + // handling both x.y.z and x.y formats + QRegExp semanticRegex("([\\d]+)\\.([\\d]+)(?:\\.([\\d]+))?"); + + int pos = semanticRegex.indexIn(versionString); + if (pos != -1) { + isSemantic = true; + auto captures = semanticRegex.capturedTexts(); + + major = captures[1].toInt(); + minor = captures[2].toInt(); + + if (captures.length() > 3) { + patch = captures[3].toInt(); + } else { + // the patch is implictly 0 if it was not included + patch = 0; + } + } else { + // if we didn't have a sematic style, we assume that we just have a build number + build = versionString.toInt(); + } +} + +bool ApplicationVersion::operator==(const ApplicationVersion& other) const { + if (isSemantic && other.isSemantic) { + return major == other.major && minor == other.minor && patch == other.patch; + } else if (!isSemantic && !other.isSemantic) { + return build == other.build; + } else { + assert(isSemantic == other.isSemantic); + return false; + } +} + +bool ApplicationVersion::operator<(const ApplicationVersion& other) const { + if (isSemantic && other.isSemantic) { + if (major == other.major) { + if (minor == other.minor) { + return patch < other.patch; + } else { + return minor < other.minor; + } + } else { + return major < other.major; + } + } else if (!isSemantic && !other.isSemantic) { + return build < other.build; + } else { + assert(isSemantic == other.isSemantic); + return false; + } +} + +bool ApplicationVersion::operator>(const ApplicationVersion& other) const { + if (isSemantic && other.isSemantic) { + if (major == other.major) { + if (minor == other.minor) { + return patch > other.patch; + } else { + return minor > other.minor; + } + } else { + return major > other.major; + } + } else if (!isSemantic && !other.isSemantic) { + return build > other.build; + } else { + assert(isSemantic == other.isSemantic); + return false; + } +} diff --git a/libraries/shared/src/ApplicationVersion.h b/libraries/shared/src/ApplicationVersion.h new file mode 100644 index 0000000000..5cb0a09a8d --- /dev/null +++ b/libraries/shared/src/ApplicationVersion.h @@ -0,0 +1,41 @@ +// +// ApplicationVersion.h +// libraries/shared/src +// +// Created by Stephen Birarda on 6/8/18. +// Copyright 2018 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_ApplicationVersion_h +#define hifi_ApplicationVersion_h + +#include + +class ApplicationVersion { +public: + ApplicationVersion(const QString& versionString); + + bool operator==(const ApplicationVersion& other) const; + bool operator!=(const ApplicationVersion& other) const { return !(*this == other); } + + bool operator <(const ApplicationVersion& other) const; + bool operator >(const ApplicationVersion& other) const; + + bool operator >=(const ApplicationVersion& other) const { return (*this == other) || (*this > other); } + bool operator <=(const ApplicationVersion& other) const { return (*this == other) || (*this < other); } + + int major = -1; + int minor = -1; + int patch = -1; + + int build = -1; + + bool isSemantic { false }; + + QString versionString; +}; + +#endif // hifi_ApplicationVersion_h diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index e90e25d5b0..d9b26927e2 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -23,7 +23,18 @@ const float DEFAULT_AVATAR_EYE_HEIGHT = DEFAULT_AVATAR_HEIGHT - DEFAULT_AVATAR_E const float DEFAULT_AVATAR_SUPPORT_BASE_LEFT = -0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_RIGHT = 0.25f; const float DEFAULT_AVATAR_SUPPORT_BASE_FRONT = -0.20f; -const float DEFAULT_AVATAR_SUPPORT_BASE_BACK = 0.10f; +const float DEFAULT_AVATAR_SUPPORT_BASE_BACK = 0.12f; +const float DEFAULT_AVATAR_LATERAL_STEPPING_THRESHOLD = 0.10f; +const float DEFAULT_AVATAR_ANTERIOR_STEPPING_THRESHOLD = 0.04f; +const float DEFAULT_AVATAR_POSTERIOR_STEPPING_THRESHOLD = 0.05f; +const float DEFAULT_AVATAR_HEAD_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 0.3f; +const float DEFAULT_AVATAR_MODE_HEIGHT_STEPPING_THRESHOLD = -0.02f; +const float DEFAULT_HANDS_VELOCITY_DIRECTION_STEPPING_THRESHOLD = 0.4f; +const float DEFAULT_HANDS_ANGULAR_VELOCITY_STEPPING_THRESHOLD = 3.3f; +const float DEFAULT_HEAD_VELOCITY_STEPPING_THRESHOLD = 0.18f; +const float DEFAULT_HEAD_PITCH_STEPPING_TOLERANCE = 7.0f; +const float DEFAULT_HEAD_ROLL_STEPPING_TOLERANCE = 7.0f; +const float DEFAULT_AVATAR_SPINE_STRETCH_LIMIT = 0.07f; const float DEFAULT_AVATAR_FORWARD_DAMPENING_FACTOR = 0.5f; const float DEFAULT_AVATAR_LATERAL_DAMPENING_FACTOR = 2.0f; const float DEFAULT_AVATAR_HIPS_MASS = 40.0f; @@ -69,6 +80,7 @@ static const float MIN_AVATAR_SCALE = 0.005f; static const float MAX_AVATAR_HEIGHT = 1000.0f * DEFAULT_AVATAR_HEIGHT; // meters static const float MIN_AVATAR_HEIGHT = 0.005f * DEFAULT_AVATAR_HEIGHT; // meters +static const float MIN_AVATAR_RADIUS = 0.5f * MIN_AVATAR_HEIGHT; static const float AVATAR_WALK_SPEED_SCALAR = 1.0f; static const float AVATAR_SPRINT_SPEED_SCALAR = 3.0f; diff --git a/libraries/shared/src/BoxBase.cpp b/libraries/shared/src/BoxBase.cpp new file mode 100644 index 0000000000..0b790dc2b0 --- /dev/null +++ b/libraries/shared/src/BoxBase.cpp @@ -0,0 +1,46 @@ +// +// Created by Sam Gondelman on 7/20/18 +// Copyright 2018 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 "BoxBase.h" + +QString boxFaceToString(BoxFace face) { + switch (face) { + case MIN_X_FACE: + return "MIN_X_FACE"; + case MAX_X_FACE: + return "MAX_X_FACE"; + case MIN_Y_FACE: + return "MIN_Y_FACE"; + case MAX_Y_FACE: + return "MAX_Y_FACE"; + case MIN_Z_FACE: + return "MIN_Z_FACE"; + case MAX_Z_FACE: + return "MAX_Z_FACE"; + default: + return "UNKNOWN_FACE"; + } +} + +BoxFace boxFaceFromString(const QString& face) { + if (face == "MIN_X_FACE") { + return MIN_X_FACE; + } else if (face == "MAX_X_FACE") { + return MAX_X_FACE; + } else if (face == "MIN_Y_FACE") { + return MIN_Y_FACE; + } else if (face == "MAX_Y_FACE") { + return MAX_Y_FACE; + } else if (face == "MIN_Z_FACE") { + return MIN_Z_FACE; + } else if (face == "MAX_Z_FACE") { + return MAX_Z_FACE; + } else { + return UNKNOWN_FACE; + } +} \ No newline at end of file diff --git a/libraries/shared/src/BoxBase.h b/libraries/shared/src/BoxBase.h index 7f1dd4d34c..9bc2115d9e 100644 --- a/libraries/shared/src/BoxBase.h +++ b/libraries/shared/src/BoxBase.h @@ -16,7 +16,26 @@ #define hifi_BoxBase_h #include +#include +/**jsdoc +*

A BoxFace specifies the face of an axis-aligned (AA) box. +* +* +* +* +* +* +* +* +* +* +* +* +* +*
ValueDescription
"MIN_X_FACE"The minimum x-axis face.
"MAX_X_FACE"The maximum x-axis face.
"MIN_Y_FACE"The minimum y-axis face.
"MAX_Y_FACE"The maximum y-axis face.
"MIN_Z_FACE"The minimum z-axis face.
"MAX_Z_FACE"The maximum z-axis face.
"UNKNOWN_FACE"Unknown value.
+* @typedef {string} BoxFace +*/ enum BoxFace { MIN_X_FACE, MAX_X_FACE, @@ -27,6 +46,9 @@ enum BoxFace { UNKNOWN_FACE }; +QString boxFaceToString(BoxFace face); +BoxFace boxFaceFromString(const QString& face); + enum BoxVertex { BOTTOM_LEFT_NEAR = 0, BOTTOM_RIGHT_NEAR = 1, diff --git a/libraries/shared/src/FaceshiftConstants.cpp b/libraries/shared/src/FaceshiftConstants.cpp index e6c929409a..0d6f718e49 100644 --- a/libraries/shared/src/FaceshiftConstants.cpp +++ b/libraries/shared/src/FaceshiftConstants.cpp @@ -64,3 +64,18 @@ const char* FACESHIFT_BLENDSHAPES[] = { }; const int NUM_FACESHIFT_BLENDSHAPES = sizeof(FACESHIFT_BLENDSHAPES) / sizeof(char*); + +const int EYE_BLINK_L_INDEX = 0; +const int EYE_BLINK_R_INDEX = 1; +const int EYE_SQUINT_L_INDEX = 2; +const int EYE_SQUINT_R_INDEX = 3; +const int EYE_OPEN_L_INDEX = 8; +const int EYE_OPEN_R_INDEX = 9; +const int BROWS_U_L_INDEX = 17; +const int BROWS_U_R_INDEX = 18; + + +const int EYE_BLINK_INDICES[] = { EYE_BLINK_L_INDEX, EYE_BLINK_R_INDEX }; +const int EYE_SQUINT_INDICES[] = { EYE_SQUINT_L_INDEX, EYE_SQUINT_R_INDEX }; +const int EYE_OPEN_INDICES[] = { EYE_OPEN_L_INDEX, EYE_OPEN_R_INDEX }; +const int BROWS_U_INDICES[] = { BROWS_U_L_INDEX, BROWS_U_R_INDEX }; diff --git a/libraries/shared/src/FaceshiftConstants.h b/libraries/shared/src/FaceshiftConstants.h index ee6e43fdbc..4349a3a21e 100644 --- a/libraries/shared/src/FaceshiftConstants.h +++ b/libraries/shared/src/FaceshiftConstants.h @@ -16,5 +16,10 @@ extern const char* FACESHIFT_BLENDSHAPES[]; /// The size of FACESHIFT_BLENDSHAPES extern const int NUM_FACESHIFT_BLENDSHAPES; +// Eyes and Brows indices +extern const int EYE_BLINK_INDICES[]; +extern const int EYE_OPEN_INDICES[]; +extern const int BROWS_U_INDICES[]; +extern const int EYE_SQUINT_INDICES[]; -#endif // hifi_FaceshiftConstants_h \ No newline at end of file +#endif // hifi_FaceshiftConstants_h diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index 0742a5625b..6fb06eb624 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -1,4 +1,4 @@ -// +// // GeometryUtil.cpp // libraries/shared/src // @@ -15,7 +15,10 @@ #include #include #include +#include +#include #include +#include "glm/gtc/matrix_transform.hpp" #include "NumericalConstants.h" #include "GLMHelpers.h" @@ -187,6 +190,94 @@ glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3& newPenetration - (currentDirection * directionalComponent); } +// finds the intersection between a ray and the facing plane on one axis +bool findIntersection(float origin, float direction, float corner, float size, float& distance) { + if (direction > EPSILON) { + distance = (corner - origin) / direction; + return true; + } else if (direction < -EPSILON) { + distance = (corner + size - origin) / direction; + return true; + } + return false; +} + +// finds the intersection between a ray and the inside facing plane on one axis +bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance) { + if (direction > EPSILON) { + distance = -1.0f * (origin - (corner + size)) / direction; + return true; + } else if (direction < -EPSILON) { + distance = -1.0f * (origin - corner) / direction; + return true; + } + return false; +} + +bool findRayAABoxIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& corner, const glm::vec3& scale, float& distance, + BoxFace& face, glm::vec3& surfaceNormal) { + // handle the trivial case where the box contains the origin + if (aaBoxContains(origin, corner, scale)) { + // We still want to calculate the distance from the origin to the inside out plane + float axisDistance; + if ((findInsideOutIntersection(origin.x, direction.x, corner.x, scale.x, axisDistance) && axisDistance >= 0 && + isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) && + isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) { + distance = axisDistance; + face = direction.x > 0 ? MAX_X_FACE : MIN_X_FACE; + surfaceNormal = glm::vec3(direction.x > 0 ? 1.0f : -1.0f, 0.0f, 0.0f); + return true; + } + if ((findInsideOutIntersection(origin.y, direction.y, corner.y, scale.y, axisDistance) && axisDistance >= 0 && + isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x) && + isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) { + distance = axisDistance; + face = direction.y > 0 ? MAX_Y_FACE : MIN_Y_FACE; + surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? 1.0f : -1.0f, 0.0f); + return true; + } + if ((findInsideOutIntersection(origin.z, direction.z, corner.z, scale.z, axisDistance) && axisDistance >= 0 && + isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) && + isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x))) { + distance = axisDistance; + face = direction.z > 0 ? MAX_Z_FACE : MIN_Z_FACE; + surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? 1.0f : -1.0f); + return true; + } + // This case is unexpected, but mimics the previous behavior for inside out intersections + distance = 0; + return true; + } + + // check each axis + float axisDistance; + if ((findIntersection(origin.x, direction.x, corner.x, scale.x, axisDistance) && axisDistance >= 0 && + isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) && + isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) { + distance = axisDistance; + face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE; + surfaceNormal = glm::vec3(direction.x > 0 ? -1.0f : 1.0f, 0.0f, 0.0f); + return true; + } + if ((findIntersection(origin.y, direction.y, corner.y, scale.y, axisDistance) && axisDistance >= 0 && + isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x) && + isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) { + distance = axisDistance; + face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE; + surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? -1.0f : 1.0f, 0.0f); + return true; + } + if ((findIntersection(origin.z, direction.z, corner.z, scale.z, axisDistance) && axisDistance >= 0 && + isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) && + isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x))) { + distance = axisDistance; + face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE; + surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? -1.0f : 1.0f); + return true; + } + return false; +} + bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& center, float radius, float& distance) { glm::vec3 relativeOrigin = origin - center; @@ -711,6 +802,658 @@ bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& dire return false; } +// determines whether a value is within the extents +bool isWithin(float value, float corner, float size) { + return value >= corner && value <= corner + size; +} + +bool aaBoxContains(const glm::vec3& point, const glm::vec3& corner, const glm::vec3& scale) { + return isWithin(point.x, corner.x, scale.x) && + isWithin(point.y, corner.y, scale.y) && + isWithin(point.z, corner.z, scale.z); +} + +void checkPossibleParabolicIntersectionWithZPlane(float t, float& minDistance, + const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec2& corner, const glm::vec2& scale) { + if (t < minDistance && t > 0.0f && + isWithin(origin.x + velocity.x * t + 0.5f * acceleration.x * t * t, corner.x, scale.x) && + isWithin(origin.y + velocity.y * t + 0.5f * acceleration.y * t * t, corner.y, scale.y)) { + minDistance = t; + } +} + +// Intersect with the plane z = 0 and make sure the intersection is within dimensions +bool findParabolaRectangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec2& dimensions, float& parabolicDistance) { + glm::vec2 localCorner = -0.5f * dimensions; + + float minDistance = FLT_MAX; + if (fabsf(acceleration.z) < EPSILON) { + if (fabsf(velocity.z) > EPSILON) { + // Handle the degenerate case where we only have a line in the z-axis + float possibleDistance = -origin.z / velocity.z; + checkPossibleParabolicIntersectionWithZPlane(possibleDistance, minDistance, origin, velocity, acceleration, localCorner, dimensions); + } + } else { + float a = 0.5f * acceleration.z; + float b = velocity.z; + float c = origin.z; + glm::vec2 possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int i = 0; i < 2; i++) { + checkPossibleParabolicIntersectionWithZPlane(possibleDistances[i], minDistance, origin, velocity, acceleration, localCorner, dimensions); + } + } + } + if (minDistance < FLT_MAX) { + parabolicDistance = minDistance; + return true; + } + return false; +} + +bool findParabolaSphereIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& center, float radius, float& parabolicDistance) { + glm::vec3 localCenter = center - origin; + float radiusSquared = radius * radius; + + float accelerationLength = glm::length(acceleration); + float minDistance = FLT_MAX; + + if (accelerationLength < EPSILON) { + // Handle the degenerate case where acceleration == (0, 0, 0) + glm::vec3 offset = origin - center; + float a = glm::dot(velocity, velocity); + float b = 2.0f * glm::dot(velocity, offset); + float c = glm::dot(offset, offset) - radius * radius; + glm::vec2 possibleDistances(FLT_MAX); + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int i = 0; i < 2; i++) { + if (possibleDistances[i] < minDistance && possibleDistances[i] > 0.0f) { + minDistance = possibleDistances[i]; + } + } + } + } else { + glm::vec3 vectorOnPlane = velocity; + if (fabsf(glm::dot(glm::normalize(velocity), glm::normalize(acceleration))) > 1.0f - EPSILON) { + // Handle the degenerate case where velocity is parallel to acceleration + // We pick t = 1 and calculate a second point on the plane + vectorOnPlane = velocity + 0.5f * acceleration; + } + // Get the normal of the plane, the cross product of two vectors on the plane + glm::vec3 normal = glm::normalize(glm::cross(vectorOnPlane, acceleration)); + + // Project vector from plane to sphere center onto the normal + float distance = glm::dot(localCenter, normal); + // Exit early if the sphere doesn't intersect the plane defined by the parabola + if (fabsf(distance) > radius) { + return false; + } + + glm::vec3 circleCenter = center - distance * normal; + float circleRadius = sqrtf(radiusSquared - distance * distance); + glm::vec3 q = glm::normalize(acceleration); + glm::vec3 p = glm::cross(normal, q); + + float a1 = accelerationLength * 0.5f; + float b1 = glm::dot(velocity, q); + float c1 = glm::dot(origin - circleCenter, q); + float a2 = glm::dot(velocity, p); + float b2 = glm::dot(origin - circleCenter, p); + + float a = a1 * a1; + float b = 2.0f * a1 * b1; + float c = 2.0f * a1 * c1 + b1 * b1 + a2 * a2; + float d = 2.0f * b1 * c1 + 2.0f * a2 * b2; + float e = c1 * c1 + b2 * b2 - circleRadius * circleRadius; + + glm::vec4 possibleDistances(FLT_MAX); + if (computeRealQuarticRoots(a, b, c, d, e, possibleDistances)) { + for (int i = 0; i < 4; i++) { + if (possibleDistances[i] < minDistance && possibleDistances[i] > 0.0f) { + minDistance = possibleDistances[i]; + } + } + } + } + + if (minDistance < FLT_MAX) { + parabolicDistance = minDistance; + return true; + } + return false; +} + +void checkPossibleParabolicIntersectionWithTriangle(float t, float& minDistance, + const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& localVelocity, const glm::vec3& localAcceleration, const glm::vec3& normal, + const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, bool allowBackface) { + // Check if we're hitting the backface in the rotated coordinate space + float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * t; + if (!allowBackface && localIntersectionVelocityZ < 0.0f) { + return; + } + + // Check that the point is within all three sides + glm::vec3 point = origin + velocity * t + 0.5f * acceleration * t * t; + if (t < minDistance && t > 0.0f && + glm::dot(normal, glm::cross(point - v1, v0 - v1)) > 0.0f && + glm::dot(normal, glm::cross(v2 - v1, point - v1)) > 0.0f && + glm::dot(normal, glm::cross(point - v0, v2 - v0)) > 0.0f) { + minDistance = t; + } +} + +bool findParabolaTriangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& parabolicDistance, bool allowBackface) { + glm::vec3 normal = glm::normalize(glm::cross(v2 - v1, v0 - v1)); + + // We transform the parabola and triangle so that the triangle is in the plane z = 0, with v0 at the origin + glm::quat inverseRot; + // Note: OpenGL view matrix is already the inverse of our camera matrix + // if the direction is nearly aligned with the Y axis, then use the X axis for 'up' + const float MAX_ABS_Y_COMPONENT = 0.9999991f; + if (fabsf(normal.y) > MAX_ABS_Y_COMPONENT) { + inverseRot = glm::quat_cast(glm::lookAt(glm::vec3(0.0f), normal, Vectors::UNIT_X)); + } else { + inverseRot = glm::quat_cast(glm::lookAt(glm::vec3(0.0f), normal, Vectors::UNIT_Y)); + } + + glm::vec3 localOrigin = inverseRot * (origin - v0); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + + float minDistance = FLT_MAX; + if (fabsf(localAcceleration.z) < EPSILON) { + if (fabsf(localVelocity.z) > EPSILON) { + float possibleDistance = -localOrigin.z / localVelocity.z; + checkPossibleParabolicIntersectionWithTriangle(possibleDistance, minDistance, origin, velocity, acceleration, + localVelocity, localAcceleration, normal, v0, v1, v2, allowBackface); + } + } else { + float a = 0.5f * localAcceleration.z; + float b = localVelocity.z; + float c = localOrigin.z; + glm::vec2 possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int i = 0; i < 2; i++) { + checkPossibleParabolicIntersectionWithTriangle(possibleDistances[i], minDistance, origin, velocity, acceleration, + localVelocity, localAcceleration, normal, v0, v1, v2, allowBackface); + } + } + } + if (minDistance < FLT_MAX) { + parabolicDistance = minDistance; + return true; + } + return false; +} + +bool findParabolaCapsuleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& start, const glm::vec3& end, float radius, const glm::quat& rotation, float& parabolicDistance) { + if (start == end) { + return findParabolaSphereIntersection(origin, velocity, acceleration, start, radius, parabolicDistance); // handle degenerate case + } + if (glm::distance2(origin, start) < radius * radius) { // inside start sphere + float startDistance; + bool intersectsStart = findParabolaSphereIntersection(origin, velocity, acceleration, start, radius, startDistance); + if (glm::distance2(origin, end) < radius * radius) { // also inside end sphere + float endDistance; + bool intersectsEnd = findParabolaSphereIntersection(origin, velocity, acceleration, end, radius, endDistance); + if (endDistance < startDistance) { + parabolicDistance = endDistance; + return intersectsEnd; + } + } + parabolicDistance = startDistance; + return intersectsStart; + } else if (glm::distance2(origin, end) < radius * radius) { // inside end sphere (and not start sphere) + return findParabolaSphereIntersection(origin, velocity, acceleration, end, radius, parabolicDistance); + } + + // We are either inside the middle of the capsule or outside it completely + // Either way, we need to check all three parts of the capsule and find the closest intersection + glm::vec3 results(FLT_MAX); + findParabolaSphereIntersection(origin, velocity, acceleration, start, radius, results[0]); + findParabolaSphereIntersection(origin, velocity, acceleration, end, radius, results[1]); + + // We rotate the infinite cylinder to be aligned with the y-axis and then cap the values at the end + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - start); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + float capsuleLength = glm::length(end - start); + + const float MIN_ACCELERATION_PRODUCT = 0.00001f; + if (fabsf(localAcceleration.x * localAcceleration.z) < MIN_ACCELERATION_PRODUCT) { + // Handle the degenerate case where we only have a line in the XZ plane + float a = localVelocity.x * localVelocity.x + localVelocity.z * localVelocity.z; + float b = 2.0f * (localVelocity.x * localOrigin.x + localVelocity.z * localOrigin.z); + float c = localOrigin.x * localOrigin.x + localOrigin.z * localOrigin.z - radius * radius; + glm::vec2 possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int i = 0; i < 2; i++) { + if (possibleDistances[i] < results[2] && possibleDistances[i] > 0.0f) { + float y = localOrigin.y + localVelocity.y * possibleDistances[i] + 0.5f * localAcceleration.y * possibleDistances[i] * possibleDistances[i]; + if (y > 0.0f && y < capsuleLength) { + results[2] = possibleDistances[i]; + } + } + } + } + } else { + float a = 0.25f * (localAcceleration.x * localAcceleration.x + localAcceleration.z * localAcceleration.z); + float b = localVelocity.x * localAcceleration.x + localVelocity.z * localAcceleration.z; + float c = localOrigin.x * localAcceleration.x + localOrigin.z * localAcceleration.z + localVelocity.x * localVelocity.x + localVelocity.z * localVelocity.z; + float d = 2.0f * (localOrigin.x * localVelocity.x + localOrigin.z * localVelocity.z); + float e = localOrigin.x * localOrigin.x + localOrigin.z * localOrigin.z - radius * radius; + glm::vec4 possibleDistances(FLT_MAX); + if (computeRealQuarticRoots(a, b, c, d, e, possibleDistances)) { + for (int i = 0; i < 4; i++) { + if (possibleDistances[i] < results[2] && possibleDistances[i] > 0.0f) { + float y = localOrigin.y + localVelocity.y * possibleDistances[i] + 0.5f * localAcceleration.y * possibleDistances[i] * possibleDistances[i]; + if (y > 0.0f && y < capsuleLength) { + results[2] = possibleDistances[i]; + } + } + } + } + } + + float minDistance = FLT_MAX; + for (int i = 0; i < 3; i++) { + minDistance = glm::min(minDistance, results[i]); + } + parabolicDistance = minDistance; + return minDistance != FLT_MAX; +} + +void checkPossibleParabolicIntersection(float t, int i, float& minDistance, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& corner, const glm::vec3& scale, bool& hit) { + if (t < minDistance && t > 0.0f && + isWithin(origin[(i + 1) % 3] + velocity[(i + 1) % 3] * t + 0.5f * acceleration[(i + 1) % 3] * t * t, corner[(i + 1) % 3], scale[(i + 1) % 3]) && + isWithin(origin[(i + 2) % 3] + velocity[(i + 2) % 3] * t + 0.5f * acceleration[(i + 2) % 3] * t * t, corner[(i + 2) % 3], scale[(i + 2) % 3])) { + minDistance = t; + hit = true; + } +} + +inline float parabolaVelocityAtT(float velocity, float acceleration, float t) { + return velocity + acceleration * t; +} + +bool findParabolaAABoxIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& corner, const glm::vec3& scale, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) { + float minDistance = FLT_MAX; + BoxFace minFace = UNKNOWN_FACE; + glm::vec3 minNormal; + glm::vec2 possibleDistances; + float a, b, c; + + // Solve the intersection for each face of the cube. As we go, keep track of the smallest, positive, real distance + // that is within the bounds of the other two dimensions + for (int i = 0; i < 3; i++) { + if (fabsf(acceleration[i]) < EPSILON) { + // Handle the degenerate case where we only have a line in this axis + if (origin[i] < corner[i]) { + { // min + if (velocity[i] > 0.0f) { + float possibleDistance = (corner[i] - origin[i]) / velocity[i]; + bool hit = false; + checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, corner, scale, hit); + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } else if (origin[i] > corner[i] + scale[i]) { + { // max + if (velocity[i] < 0.0f) { + float possibleDistance = (corner[i] + scale[i] - origin[i]) / velocity[i]; + bool hit = false; + checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, corner, scale, hit); + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } else { + { // min + if (velocity[i] < 0.0f) { + float possibleDistance = (corner[i] - origin[i]) / velocity[i]; + bool hit = false; + checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, corner, scale, hit); + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + { // max + if (velocity[i] > 0.0f) { + float possibleDistance = (corner[i] + scale[i] - origin[i]) / velocity[i]; + bool hit = false; + checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, corner, scale, hit); + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } + } else { + a = 0.5f * acceleration[i]; + b = velocity[i]; + if (origin[i] < corner[i]) { + // If we're below corner, we have the following cases: + // - within bounds on other axes + // - if +velocity or +acceleration + // - can only hit MIN_FACE with -normal + // - else + // - if +acceleration + // - can only hit MIN_FACE with -normal + // - else if +velocity + // - can hit MIN_FACE with -normal iff velocity at intersection is + + // - else can hit MAX_FACE with +normal iff velocity at intersection is - + if (origin[(i + 1) % 3] > corner[(i + 1) % 3] && origin[(i + 1) % 3] < corner[(i + 1) % 3] + scale[(i + 1) % 3] && + origin[(i + 2) % 3] > corner[(i + 2) % 3] && origin[(i + 2) % 3] < corner[(i + 2) % 3] + scale[(i + 2) % 3]) { + if (velocity[i] > 0.0f || acceleration[i] > 0.0f) { + { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } + } else { + if (acceleration[i] > 0.0f) { + { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } else if (velocity[i] > 0.0f) { + bool hit = false; + { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + if (!hit) { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } + } + } else if (origin[i] > corner[i] + scale[i]) { + // If we're above corner + scale, we have the following cases: + // - within bounds on other axes + // - if -velocity or -acceleration + // - can only hit MAX_FACE with +normal + // - else + // - if -acceleration + // - can only hit MAX_FACE with +normal + // - else if -velocity + // - can hit MAX_FACE with +normal iff velocity at intersection is - + // - else can hit MIN_FACE with -normal iff velocity at intersection is + + if (origin[(i + 1) % 3] > corner[(i + 1) % 3] && origin[(i + 1) % 3] < corner[(i + 1) % 3] + scale[(i + 1) % 3] && + origin[(i + 2) % 3] > corner[(i + 2) % 3] && origin[(i + 2) % 3] < corner[(i + 2) % 3] + scale[(i + 2) % 3]) { + if (velocity[i] < 0.0f || acceleration[i] < 0.0f) { + { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } + } else { + if (acceleration[i] < 0.0f) { + { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } else if (velocity[i] < 0.0f) { + bool hit = false; + { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + if (!hit) { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } + } + } else { + // If we're between corner and corner + scale, we have the following cases: + // - within bounds on other axes + // - if -velocity and -acceleration + // - can only hit MIN_FACE with +normal + // - else if +velocity and +acceleration + // - can only hit MAX_FACE with -normal + // - else + // - can hit MIN_FACE with +normal iff velocity at intersection is - + // - can hit MAX_FACE with -normal iff velocity at intersection is + + // - else + // - if -velocity and +acceleration + // - can hit MIN_FACE with -normal iff velocity at intersection is + + // - else if +velocity and -acceleration + // - can hit MAX_FACE with +normal iff velocity at intersection is - + if (origin[(i + 1) % 3] > corner[(i + 1) % 3] && origin[(i + 1) % 3] < corner[(i + 1) % 3] + scale[(i + 1) % 3] && + origin[(i + 2) % 3] > corner[(i + 2) % 3] && origin[(i + 2) % 3] < corner[(i + 2) % 3] + scale[(i + 2) % 3]) { + if (velocity[i] < 0.0f && acceleration[i] < 0.0f) { + { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } else if (velocity[i] > 0.0f && acceleration[i] > 0.0f) { + { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } else { + { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } + } else { + if (velocity[i] < 0.0f && acceleration[i] > 0.0f) { + { // min + c = origin[i] - corner[i]; + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i); + minNormal = glm::vec3(0.0f); + minNormal[i] = -1.0f; + } + } + } + } else if (velocity[i] > 0.0f && acceleration[i] < 0.0f) { + { // max + c = origin[i] - (corner[i] + scale[i]); + possibleDistances = { FLT_MAX, FLT_MAX }; + if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { + bool hit = false; + for (int j = 0; j < 2; j++) { + if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) { + checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); + } + } + if (hit) { + minFace = BoxFace(2 * i + 1); + minNormal = glm::vec3(0.0f); + minNormal[i] = 1.0f; + } + } + } + } + } + } + } + } + + if (minDistance < FLT_MAX) { + parabolicDistance = minDistance; + face = minFace; + surfaceNormal = minNormal; + return true; + } + return false; +} + void swingTwistDecomposition(const glm::quat& rotation, const glm::vec3& direction, glm::quat& swing, @@ -941,3 +1684,142 @@ void generateBoundryLinesForDop14(const std::vector& dots, const glm::vec } } } + +bool computeRealQuadraticRoots(float a, float b, float c, glm::vec2& roots) { + float discriminant = b * b - 4.0f * a * c; + if (discriminant < 0.0f) { + return false; + } else if (discriminant == 0.0f) { + roots.x = (-b + sqrtf(discriminant)) / (2.0f * a); + } else { + float discriminantRoot = sqrtf(discriminant); + roots.x = (-b + discriminantRoot) / (2.0f * a); + roots.y = (-b - discriminantRoot) / (2.0f * a); + } + return true; +} + +// The following functions provide an analytical solution to a quartic equation, adapted from the solution here: https://github.com/sasamil/Quartic +unsigned int solveP3(float* x, float a, float b, float c) { + float a2 = a * a; + float q = (a2 - 3.0f * b) / 9.0f; + float r = (a * (2.0f * a2 - 9.0f * b) + 27.0f * c) / 54.0f; + float r2 = r * r; + float q3 = q * q * q; + float A, B; + if (r2 < q3) { + float t = r / sqrtf(q3); + t = glm::clamp(t, -1.0f, 1.0f); + t = acosf(t); + a /= 3.0f; + q = -2.0f * sqrtf(q); + x[0] = q * cosf(t / 3.0f) - a; + x[1] = q * cosf((t + 2.0f * (float)M_PI) / 3.0f) - a; + x[2] = q * cosf((t - 2.0f * (float)M_PI) / 3.0f) - a; + return 3; + } else { + A = -powf(fabsf(r) + sqrtf(r2 - q3), 1.0f / 3.0f); + if (r < 0) { + A = -A; + } + B = (A == 0.0f ? 0.0f : q / A); + + a /= 3.0f; + x[0] = (A + B) - a; + x[1] = -0.5f * (A + B) - a; + x[2] = 0.5f * sqrtf(3.0f) * (A - B); + if (fabsf(x[2]) < EPSILON) { + x[2] = x[1]; + return 2; + } + + return 1; + } +} + +bool solve_quartic(float a, float b, float c, float d, glm::vec4& roots) { + float a3 = -b; + float b3 = a * c - 4.0f *d; + float c3 = -a * a * d - c * c + 4.0f * b * d; + + float px3[3]; + unsigned int iZeroes = solveP3(px3, a3, b3, c3); + + float q1, q2, p1, p2, D, sqD, y; + + y = px3[0]; + if (iZeroes != 1) { + if (fabsf(px3[1]) > fabsf(y)) { + y = px3[1]; + } + if (fabsf(px3[2]) > fabsf(y)) { + y = px3[2]; + } + } + + D = y * y - 4.0f * d; + if (fabsf(D) < EPSILON) { + q1 = q2 = 0.5f * y; + D = a * a - 4.0f * (b - y); + if (fabsf(D) < EPSILON) { + p1 = p2 = 0.5f * a; + } else { + sqD = sqrtf(D); + p1 = 0.5f * (a + sqD); + p2 = 0.5f * (a - sqD); + } + } else { + sqD = sqrtf(D); + q1 = 0.5f * (y + sqD); + q2 = 0.5f * (y - sqD); + p1 = (a * q1 - c) / (q1 - q2); + p2 = (c - a * q2) / (q1 - q2); + } + + std::complex x1, x2, x3, x4; + D = p1 * p1 - 4.0f * q1; + if (D < 0.0f) { + x1.real(-0.5f * p1); + x1.imag(0.5f * sqrtf(-D)); + x2 = std::conj(x1); + } else { + sqD = sqrtf(D); + x1.real(0.5f * (-p1 + sqD)); + x2.real(0.5f * (-p1 - sqD)); + } + + D = p2 * p2 - 4.0f * q2; + if (D < 0.0f) { + x3.real(-0.5f * p2); + x3.imag(0.5f * sqrtf(-D)); + x4 = std::conj(x3); + } else { + sqD = sqrtf(D); + x3.real(0.5f * (-p2 + sqD)); + x4.real(0.5f * (-p2 - sqD)); + } + + bool hasRealRoot = false; + if (fabsf(x1.imag()) < EPSILON) { + roots.x = x1.real(); + hasRealRoot = true; + } + if (fabsf(x2.imag()) < EPSILON) { + roots.y = x2.real(); + hasRealRoot = true; + } + if (fabsf(x3.imag()) < EPSILON) { + roots.z = x3.real(); + hasRealRoot = true; + } + if (fabsf(x4.imag()) < EPSILON) { + roots.w = x4.real(); + hasRealRoot = true; + } + + return hasRealRoot; +} + +bool computeRealQuarticRoots(float a, float b, float c, float d, float e, glm::vec4& roots) { + return solve_quartic(b / a, c / a, d / a, e / a, roots); +} \ No newline at end of file diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index 4832616fbd..54f9062469 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -14,6 +14,7 @@ #include #include +#include "BoxBase.h" class Plane; @@ -73,6 +74,11 @@ bool findCapsulePlanePenetration(const glm::vec3& capsuleStart, const glm::vec3& glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3& newPenetration); +bool findIntersection(float origin, float direction, float corner, float size, float& distance); +bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance); +bool findRayAABoxIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& corner, const glm::vec3& scale, float& distance, + BoxFace& face, glm::vec3& surfaceNormal); + bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& center, float radius, float& distance); @@ -88,6 +94,21 @@ bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& dire bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface = false); +bool findParabolaRectangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec2& dimensions, float& parabolicDistance); + +bool findParabolaSphereIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& center, float radius, float& distance); + +bool findParabolaTriangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& parabolicDistance, bool allowBackface = false); + +bool findParabolaCapsuleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& start, const glm::vec3& end, float radius, const glm::quat& rotation, float& parabolicDistance); + +bool findParabolaAABoxIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& corner, const glm::vec3& scale, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal); + /// \brief decomposes rotation into its components such that: rotation = swing * twist /// \param rotation[in] rotation to decompose /// \param direction[in] normalized axis about which the twist happens (typically original direction before rotation applied) @@ -112,6 +133,11 @@ inline bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3 return findRayTriangleIntersection(origin, direction, triangle.v0, triangle.v1, triangle.v2, distance, allowBackface); } +inline bool findParabolaTriangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, const Triangle& triangle, float& parabolicDistance, bool allowBackface = false) { + return findParabolaTriangleIntersection(origin, velocity, acceleration, triangle.v0, triangle.v1, triangle.v2, parabolicDistance, allowBackface); +} + int clipTriangleWithPlane(const Triangle& triangle, const Plane& plane, Triangle* clippedTriangles, int maxClippedTriangleCount); int clipTriangleWithPlanes(const Triangle& triangle, const Plane* planes, int planeCount, Triangle* clippedTriangles, int maxClippedTriangleCount); @@ -178,4 +204,20 @@ bool findIntersectionOfThreePlanes(const glm::vec4& planeA, const glm::vec4& pla void generateBoundryLinesForDop14(const std::vector& dots, const glm::vec3& center, std::vector& linesOut); +bool computeRealQuadraticRoots(float a, float b, float c, glm::vec2& roots); + +unsigned int solveP3(float *x, float a, float b, float c); +bool solve_quartic(float a, float b, float c, float d, glm::vec4& roots); +bool computeRealQuarticRoots(float a, float b, float c, float d, float e, glm::vec4& roots); + +bool isWithin(float value, float corner, float size); +bool aaBoxContains(const glm::vec3& point, const glm::vec3& corner, const glm::vec3& scale); + +void checkPossibleParabolicIntersectionWithZPlane(float t, float& minDistance, + const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec2& corner, const glm::vec2& scale); +void checkPossibleParabolicIntersectionWithTriangle(float t, float& minDistance, + const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + const glm::vec3& localVelocity, const glm::vec3& localAcceleration, const glm::vec3& normal, + const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, bool allowBackface); + #endif // hifi_GeometryUtil_h diff --git a/libraries/shared/src/OwningBuffer.h b/libraries/shared/src/OwningBuffer.h new file mode 100644 index 0000000000..80184286bc --- /dev/null +++ b/libraries/shared/src/OwningBuffer.h @@ -0,0 +1,29 @@ +// +// OwningBuffer.h +// shared/src +// +// Created by Ryan Huffman on 5/31/2018. +// Copyright 2018 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_OwningBuffer_h +#define hifi_OwningBuffer_h + +#include +class OwningBuffer : public QBuffer { +public: + OwningBuffer(const QByteArray& content) : _content(content) { + setData(_content); + } + OwningBuffer(QByteArray&& content) : _content(std::move(content)) { + setData(_content); + } + +private: + QByteArray _content; +}; + +#endif // hifi_OwningBuffer_h diff --git a/libraries/shared/src/PhysicsHelpers.cpp b/libraries/shared/src/PhysicsHelpers.cpp index b43d55020e..988af98c46 100644 --- a/libraries/shared/src/PhysicsHelpers.cpp +++ b/libraries/shared/src/PhysicsHelpers.cpp @@ -42,22 +42,27 @@ glm::quat computeBulletRotationStep(const glm::vec3& angularVelocity, float time // Exponential map // google for "Practical Parameterization of Rotations Using the Exponential Map", F. Sebastian Grassia - float speed = glm::length(angularVelocity); + glm::vec3 axis = angularVelocity; + float angle = glm::length(axis) * timeStep; // limit the angular motion because the exponential approximation fails for large steps const float ANGULAR_MOTION_THRESHOLD = 0.5f * PI_OVER_TWO; - if (speed * timeStep > ANGULAR_MOTION_THRESHOLD) { - speed = ANGULAR_MOTION_THRESHOLD / timeStep; + if (angle > ANGULAR_MOTION_THRESHOLD) { + angle = ANGULAR_MOTION_THRESHOLD; } - glm::vec3 axis = angularVelocity; - if (speed < 0.001f) { - // use Taylor's expansions of sync function - axis *= (0.5f * timeStep - (timeStep * timeStep * timeStep) * (0.020833333333f * speed * speed)); + const float MIN_ANGLE = 0.001f; + if (angle < MIN_ANGLE) { + // for small angles use Taylor's expansion of sin(x): + // sin(x) = x - (x^3)/(3!) + ... + // where: x = angle/2 + // sin(angle/2) = angle/2 - (angle*angle*angle)/48 + // but (angle = speed * timeStep) and we want to normalize the axis by dividing by speed + // which gives us: + axis *= timeStep * (0.5f - 0.020833333333f * angle * angle); } else { - // sync(speed) = sin(c * speed)/t - axis *= (sinf(0.5f * speed * timeStep) / speed ); + axis *= (sinf(0.5f * angle) * timeStep / angle); } - return glm::quat(cosf(0.5f * speed * timeStep), axis.x, axis.y, axis.z); + return glm::quat(cosf(0.5f * angle), axis.x, axis.y, axis.z); } /* end Bullet code derivation*/ diff --git a/libraries/shared/src/Preferences.h b/libraries/shared/src/Preferences.h index 931508e825..27bcf7a71b 100644 --- a/libraries/shared/src/Preferences.h +++ b/libraries/shared/src/Preferences.h @@ -15,6 +15,7 @@ #include #include #include +#include #include "DependencyManager.h" @@ -80,7 +81,6 @@ public: } void setEnabler(BoolPreference* enabler, bool inverse = false); - virtual Type getType() { return Invalid; }; Q_INVOKABLE virtual void load() {}; diff --git a/libraries/shared/src/QVariantGLM.cpp b/libraries/shared/src/QVariantGLM.cpp index 1c73315d94..f7fd7fbc8a 100644 --- a/libraries/shared/src/QVariantGLM.cpp +++ b/libraries/shared/src/QVariantGLM.cpp @@ -12,11 +12,11 @@ #include "QVariantGLM.h" #include "OctalCode.h" -QVariantList glmToQList(const glm::vec3& g) { +QVariantList vec3ToQList(const glm::vec3& g) { return QVariantList() << g[0] << g[1] << g[2]; } -QVariantList glmToQList(const glm::quat& g) { +QVariantList quatToQList(const glm::quat& g) { return QVariantList() << g.x << g.y << g.z << g.w; } @@ -24,7 +24,7 @@ QVariantList rgbColorToQList(const rgbColor& v) { return QVariantList() << (int)(v[0]) << (int)(v[1]) << (int)(v[2]); } -QVariantMap glmToQMap(const glm::vec3& glmVector) { +QVariantMap vec3ToQMap(const glm::vec3& glmVector) { QVariantMap vectorAsVariantMap; vectorAsVariantMap["x"] = glmVector.x; vectorAsVariantMap["y"] = glmVector.y; @@ -32,7 +32,7 @@ QVariantMap glmToQMap(const glm::vec3& glmVector) { return vectorAsVariantMap; } -QVariantMap glmToQMap(const glm::quat& glmQuat) { +QVariantMap quatToQMap(const glm::quat& glmQuat) { QVariantMap quatAsVariantMap; quatAsVariantMap["x"] = glmQuat.x; quatAsVariantMap["y"] = glmQuat.y; @@ -42,12 +42,12 @@ QVariantMap glmToQMap(const glm::quat& glmQuat) { } -glm::vec3 qListToGlmVec3(const QVariant& q) { +glm::vec3 qListToVec3(const QVariant& q) { QVariantList qList = q.toList(); return glm::vec3(qList[RED_INDEX].toFloat(), qList[GREEN_INDEX].toFloat(), qList[BLUE_INDEX].toFloat()); } -glm::quat qListToGlmQuat(const QVariant& q) { +glm::quat qListToQuat(const QVariant& q) { QVariantList qList = q.toList(); float x = qList[0].toFloat(); float y = qList[1].toFloat(); @@ -56,7 +56,7 @@ glm::quat qListToGlmQuat(const QVariant& q) { return glm::quat(w, x, y, z); } -void qListtoRgbColor(const QVariant& q, rgbColor& returnValue) { +void qListToRgbColor(const QVariant& q, rgbColor& returnValue) { QVariantList qList = q.toList(); returnValue[RED_INDEX] = qList[RED_INDEX].toInt(); returnValue[GREEN_INDEX] = qList[GREEN_INDEX].toInt(); @@ -64,7 +64,7 @@ void qListtoRgbColor(const QVariant& q, rgbColor& returnValue) { } -glm::vec3 qMapToGlmVec3(const QVariant& q) { +glm::vec3 qMapToVec3(const QVariant& q) { QVariantMap qMap = q.toMap(); if (qMap.contains("x") && qMap.contains("y") && qMap.contains("z")) { return glm::vec3( @@ -77,7 +77,7 @@ glm::vec3 qMapToGlmVec3(const QVariant& q) { } } -glm::quat qMapToGlmQuat(const QVariant& q) { +glm::quat qMapToQuat(const QVariant& q) { QVariantMap qMap = q.toMap(); if (qMap.contains("w") && qMap.contains("x") && qMap.contains("y") && qMap.contains("z")) { return glm::quat( @@ -91,7 +91,7 @@ glm::quat qMapToGlmQuat(const QVariant& q) { } } -glm::mat4 qMapToGlmMat4(const QVariant& q) { +glm::mat4 qMapToMat4(const QVariant& q) { QVariantMap qMap = q.toMap(); if (qMap.contains("r0c0") && qMap.contains("r1c0") && qMap.contains("r2c0") && qMap.contains("r3c0") && qMap.contains("r0c1") && qMap.contains("r1c1") && qMap.contains("r2c1") && qMap.contains("r3c1") diff --git a/libraries/shared/src/QVariantGLM.h b/libraries/shared/src/QVariantGLM.h index 314889d5dd..a8f8b531c0 100644 --- a/libraries/shared/src/QVariantGLM.h +++ b/libraries/shared/src/QVariantGLM.h @@ -17,17 +17,17 @@ #include "SharedUtil.h" -QVariantList glmToQList(const glm::vec3& g); -QVariantList glmToQList(const glm::quat& g); +QVariantList vec3ToQList(const glm::vec3& g); +QVariantList quatToQList(const glm::quat& g); QVariantList rgbColorToQList(const rgbColor& v); -QVariantMap glmToQMap(const glm::vec3& glmVector); -QVariantMap glmToQMap(const glm::quat& glmQuat); +QVariantMap vec3ToQMap(const glm::vec3& glmVector); +QVariantMap quatToQMap(const glm::quat& glmQuat); -glm::vec3 qListToGlmVec3(const QVariant& q); -glm::quat qListToGlmQuat(const QVariant& q); -void qListtoRgbColor(const QVariant& q, rgbColor& returnValue); +glm::vec3 qListToVec3(const QVariant& q); +glm::quat qListToQuat(const QVariant& q); +void qListToRgbColor(const QVariant& q, rgbColor& returnValue); -glm::vec3 qMapToGlmVec3(const QVariant& q); -glm::quat qMapToGlmQuat(const QVariant& q); -glm::mat4 qMapToGlmMat4(const QVariant& q); +glm::vec3 qMapToVec3(const QVariant& q); +glm::quat qMapToQuat(const QVariant& q); +glm::mat4 qMapToMat4(const QVariant& q); diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index ed2a1d67eb..a9dbe83b06 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -127,13 +127,28 @@ QScriptValue vec3toScriptValue(QScriptEngine* engine, const glm::vec3 &vec3) { obj.setProperty("x", vec3.x); obj.setProperty("y", vec3.y); obj.setProperty("z", vec3.z); + obj.setProperty("red", vec3.x); + obj.setProperty("green", vec3.y); + obj.setProperty("blue", vec3.z); return obj; } void vec3FromScriptValue(const QScriptValue &object, glm::vec3 &vec3) { - vec3.x = object.property("x").toVariant().toFloat(); - vec3.y = object.property("y").toVariant().toFloat(); - vec3.z = object.property("z").toVariant().toFloat(); + auto x = object.property("x").toVariant(); + if (!x.isValid()) { + x = object.property("red").toVariant(); + } + auto y = object.property("y").toVariant(); + if (!y.isValid()) { + y = object.property("green").toVariant(); + } + auto z = object.property("z").toVariant(); + if (!z.isValid()) { + z = object.property("blue").toVariant(); + } + vec3.x = x.toFloat(); + vec3.y = y.toFloat(); + vec3.z = z.toFloat(); } QVariant vec3toVariant(const glm::vec3& vec3) { diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 467d6374a5..db63237c73 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -219,6 +219,37 @@ public: } }; +/**jsdoc +* A PickParabola defines a parabola with a starting point, intitial velocity, and acceleration. +* +* @typedef {object} PickParabola +* @property {Vec3} origin - The starting position of the PickParabola. +* @property {Vec3} velocity - The starting velocity of the parabola. +* @property {Vec3} acceleration - The acceleration that the parabola experiences. +*/ +class PickParabola : public MathPick { +public: + PickParabola() : origin(NAN), velocity(NAN), acceleration(NAN) { } + PickParabola(const QVariantMap& pickVariant) : origin(vec3FromVariant(pickVariant["origin"])), velocity(vec3FromVariant(pickVariant["velocity"])), acceleration(vec3FromVariant(pickVariant["acceleration"])) {} + PickParabola(const glm::vec3& origin, const glm::vec3 velocity, const glm::vec3 acceleration) : origin(origin), velocity(velocity), acceleration(acceleration) {} + glm::vec3 origin; + glm::vec3 velocity; + glm::vec3 acceleration; + + operator bool() const override { + return !(glm::any(glm::isnan(origin)) || glm::any(glm::isnan(velocity)) || glm::any(glm::isnan(acceleration))); + } + bool operator==(const PickParabola& other) const { + return (origin == other.origin && velocity == other.velocity && acceleration == other.acceleration); + } + QVariantMap toVariantMap() const override { + QVariantMap pickParabola; + pickParabola["origin"] = vec3toVariant(origin); + pickParabola["velocity"] = vec3toVariant(velocity); + pickParabola["acceleration"] = vec3toVariant(acceleration); + return pickParabola; + } +}; namespace std { inline void hash_combine(std::size_t& seed) { } @@ -273,6 +304,15 @@ namespace std { } }; + template <> + struct hash { + size_t operator()(const PickParabola& a) const { + size_t result = 0; + hash_combine(result, a.origin, a.velocity, a.acceleration); + return result; + } + }; + template <> struct hash { size_t operator()(const QString& a) const { diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 7c4a9b6d6c..bb22a1e753 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -297,14 +297,23 @@ void setAtBit(unsigned char& byte, int bitIndex) { byte |= (1 << (7 - bitIndex)); } +bool oneAtBit16(unsigned short word, int bitIndex) { + return (word >> (15 - bitIndex) & 1); +} + +void setAtBit16(unsigned short& word, int bitIndex) { + word |= (1 << (15 - bitIndex)); +} + + void clearAtBit(unsigned char& byte, int bitIndex) { if (oneAtBit(byte, bitIndex)) { byte -= (1 << (7 - bitIndex)); } } -int getSemiNibbleAt(unsigned char byte, int bitIndex) { - return (byte >> (6 - bitIndex) & 3); // semi-nibbles store 00, 01, 10, or 11 +int getSemiNibbleAt(unsigned short word, int bitIndex) { + return (word >> (14 - bitIndex) & 3); // semi-nibbles store 00, 01, 10, or 11 } int getNthBit(unsigned char byte, int ordinal) { @@ -326,9 +335,9 @@ int getNthBit(unsigned char byte, int ordinal) { return ERROR_RESULT; } -void setSemiNibbleAt(unsigned char& byte, int bitIndex, int value) { +void setSemiNibbleAt(unsigned short& word, int bitIndex, int value) { //assert(value <= 3 && value >= 0); - byte |= ((value & 3) << (6 - bitIndex)); // semi-nibbles store 00, 01, 10, or 11 + word |= ((value & 3) << (14 - bitIndex)); // semi-nibbles store 00, 01, 10, or 11 } bool isInEnvironment(const char* environment) { diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 9875314aa4..3b24110f18 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -92,6 +92,8 @@ inline QDebug& operator<<(QDebug& dbg, const rgbColor& c) { } struct xColor { + xColor() {} + xColor(unsigned char r, unsigned char g, unsigned char b) : red(r), green(g), blue(b) {} unsigned char red; unsigned char green; unsigned char blue; @@ -163,9 +165,11 @@ void printVoxelCode(unsigned char* voxelCode); int numberOfOnes(unsigned char byte); bool oneAtBit(unsigned char byte, int bitIndex); void setAtBit(unsigned char& byte, int bitIndex); +bool oneAtBit16(unsigned short word, int bitIndex); +void setAtBit16(unsigned short& word, int bitIndex); void clearAtBit(unsigned char& byte, int bitIndex); -int getSemiNibbleAt(unsigned char byte, int bitIndex); -void setSemiNibbleAt(unsigned char& byte, int bitIndex, int value); +int getSemiNibbleAt(unsigned short word, int bitIndex); +void setSemiNibbleAt(unsigned short& word, int bitIndex, int value); int getNthBit(unsigned char byte, int ordinal); /// determines the bit placement 0-7 of the ordinal set bit diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 324cee3417..ccb275ffc9 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -165,6 +165,10 @@ void SpatiallyNestable::forgetChild(SpatiallyNestablePointer newChild) const { void SpatiallyNestable::setParentJointIndex(quint16 parentJointIndex) { _parentJointIndex = parentJointIndex; + auto parent = _parent.lock(); + if (parent) { + parent->recalculateChildCauterization(); + } } glm::vec3 SpatiallyNestable::worldToLocal(const glm::vec3& position, diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 5d4793ba4e..361f0aaf17 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -218,6 +218,7 @@ protected: virtual void beParentOfChild(SpatiallyNestablePointer newChild) const; virtual void forgetChild(SpatiallyNestablePointer newChild) const; + virtual void recalculateChildCauterization() const { } mutable ReadWriteLockable _childrenLock; mutable QHash _children; diff --git a/libraries/shared/src/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index d7f685f8d3..cde9c20cab 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -51,6 +51,26 @@ bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& return result; } +bool TriangleSet::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface) { + // reset our distance to be the max possible, lower level tests will store best distance here + parabolicDistance = FLT_MAX; + + if (!_isBalanced) { + balanceOctree(); + } + + int trianglesTouched = 0; + auto result = _triangleOctree.findParabolaIntersection(origin, velocity, acceleration, parabolicDistance, face, triangle, precision, trianglesTouched, allowBackface); + +#if WANT_DEBUGGING + if (precision) { + qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population << "_triangles.size:" << _triangles.size(); + } +#endif + return result; +} + bool TriangleSet::convexHullContains(const glm::vec3& point) const { if (!_bounds.contains(point)) { return false; @@ -95,40 +115,63 @@ void TriangleSet::balanceOctree() { // Determine of the given ray (origin/direction) in model space intersects with any triangles // in the set. If an intersection occurs, the distance and surface normal will be provided. bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, bool allowBackface) { - + float& distance, BoxFace& face, Triangle& triangle, bool precision, + int& trianglesTouched, bool allowBackface) { bool intersectedSomething = false; - float boxDistance = distance; - float bestDistance = distance; - glm::vec3 surfaceNormal; + float bestDistance = FLT_MAX; - if (_bounds.findRayIntersection(origin, direction, boxDistance, face, surfaceNormal)) { - - // if our bounding box intersects at a distance greater than the current known - // best distance, and our origin isn't inside the boounds, then we can safely - // not check any of our triangles - if (boxDistance > bestDistance && !_bounds.contains(origin)) { - return false; - } - - if (precision) { - for (const auto& triangleIndex : _triangleIndices) { - const auto& thisTriangle = _allTriangles[triangleIndex]; - float thisTriangleDistance; - trianglesTouched++; - if (findRayTriangleIntersection(origin, direction, thisTriangle, thisTriangleDistance, allowBackface)) { - if (thisTriangleDistance < bestDistance) { - bestDistance = thisTriangleDistance; - intersectedSomething = true; - triangle = thisTriangle; - distance = bestDistance; - } + if (precision) { + for (const auto& triangleIndex : _triangleIndices) { + const auto& thisTriangle = _allTriangles[triangleIndex]; + float thisTriangleDistance; + trianglesTouched++; + if (findRayTriangleIntersection(origin, direction, thisTriangle, thisTriangleDistance, allowBackface)) { + if (thisTriangleDistance < bestDistance) { + bestDistance = thisTriangleDistance; + intersectedSomething = true; + triangle = thisTriangle; } } - } else { - intersectedSomething = true; - distance = boxDistance; } + } else { + intersectedSomething = true; + bestDistance = distance; + } + + if (intersectedSomething) { + distance = bestDistance; + } + + return intersectedSomething; +} + +bool TriangleSet::TriangleOctreeCell::findParabolaIntersectionInternal(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, Triangle& triangle, bool precision, + int& trianglesTouched, bool allowBackface) { + bool intersectedSomething = false; + float bestDistance = FLT_MAX; + + if (precision) { + for (const auto& triangleIndex : _triangleIndices) { + const auto& thisTriangle = _allTriangles[triangleIndex]; + float thisTriangleDistance; + trianglesTouched++; + if (findParabolaTriangleIntersection(origin, velocity, acceleration, thisTriangle, thisTriangleDistance, allowBackface)) { + if (thisTriangleDistance < bestDistance) { + bestDistance = thisTriangleDistance; + intersectedSomething = true; + triangle = thisTriangle; + } + } + } + } else { + intersectedSomething = true; + bestDistance = parabolicDistance; + } + + if (intersectedSomething) { + parabolicDistance = bestDistance; } return intersectedSomething; @@ -204,45 +247,42 @@ void TriangleSet::TriangleOctreeCell::insert(size_t triangleIndex) { _triangleIndices.push_back(triangleIndex); } -bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, - bool allowBackface) { - +bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, + BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, + bool allowBackface) { if (_population < 1) { return false; // no triangles below here, so we can't intersect } - float bestLocalDistance = distance; + float bestLocalDistance = FLT_MAX; BoxFace bestLocalFace; Triangle bestLocalTriangle; glm::vec3 bestLocalNormal; bool intersects = false; - // if the ray intersects our bounding box, then continue - if (getBounds().findRayIntersection(origin, direction, bestLocalDistance, bestLocalFace, bestLocalNormal)) { - + float boxDistance = FLT_MAX; + // if the pick intersects our bounding box, then continue + if (getBounds().findRayIntersection(origin, direction, boxDistance, bestLocalFace, bestLocalNormal)) { // if the intersection with our bounding box, is greater than the current best distance (the distance passed in) // then we know that none of our triangles can represent a better intersection and we can return - - if (bestLocalDistance > distance) { + if (boxDistance > distance) { return false; } - bestLocalDistance = distance; - - float childDistance = distance; - BoxFace childFace; - Triangle childTriangle; - // if we're not yet at the max depth, then check which child the triangle fits in if (_depth < MAX_DEPTH) { + float bestChildDistance = FLT_MAX; for (auto& child : _children) { // check each child, if there's an intersection, it will return some distance that we need // to compare against the other results, because there might be multiple intersections and // we will always choose the best (shortest) intersection + float childDistance = bestChildDistance; + BoxFace childFace; + Triangle childTriangle; if (child.second.findRayIntersection(origin, direction, childDistance, childFace, childTriangle, precision, trianglesTouched)) { if (childDistance < bestLocalDistance) { bestLocalDistance = childDistance; + bestChildDistance = childDistance; bestLocalFace = childFace; bestLocalTriangle = childTriangle; intersects = true; @@ -251,11 +291,14 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi } } // also check our local triangle set - if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childTriangle, precision, trianglesTouched, allowBackface)) { - if (childDistance < bestLocalDistance) { - bestLocalDistance = childDistance; - bestLocalFace = childFace; - bestLocalTriangle = childTriangle; + float internalDistance = boxDistance; + BoxFace internalFace; + Triangle internalTriangle; + if (findRayIntersectionInternal(origin, direction, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) { + if (internalDistance < bestLocalDistance) { + bestLocalDistance = internalDistance; + bestLocalFace = internalFace; + bestLocalTriangle = internalTriangle; intersects = true; } } @@ -267,3 +310,68 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi } return intersects; } + +bool TriangleSet::TriangleOctreeCell::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, float& parabolicDistance, + BoxFace& face, Triangle& triangle, bool precision, + int& trianglesTouched, bool allowBackface) { + if (_population < 1) { + return false; // no triangles below here, so we can't intersect + } + + float bestLocalDistance = FLT_MAX; + BoxFace bestLocalFace; + Triangle bestLocalTriangle; + glm::vec3 bestLocalNormal; + bool intersects = false; + + float boxDistance = FLT_MAX; + // if the pick intersects our bounding box, then continue + if (getBounds().findParabolaIntersection(origin, velocity, acceleration, boxDistance, bestLocalFace, bestLocalNormal)) { + // if the intersection with our bounding box, is greater than the current best distance (the distance passed in) + // then we know that none of our triangles can represent a better intersection and we can return + if (boxDistance > parabolicDistance) { + return false; + } + + // if we're not yet at the max depth, then check which child the triangle fits in + if (_depth < MAX_DEPTH) { + float bestChildDistance = FLT_MAX; + for (auto& child : _children) { + // check each child, if there's an intersection, it will return some distance that we need + // to compare against the other results, because there might be multiple intersections and + // we will always choose the best (shortest) intersection + float childDistance = bestChildDistance; + BoxFace childFace; + Triangle childTriangle; + if (child.second.findParabolaIntersection(origin, velocity, acceleration, childDistance, childFace, childTriangle, precision, trianglesTouched)) { + if (childDistance < bestLocalDistance) { + bestLocalDistance = childDistance; + bestChildDistance = childDistance; + bestLocalFace = childFace; + bestLocalTriangle = childTriangle; + intersects = true; + } + } + } + } + // also check our local triangle set + float internalDistance = boxDistance; + BoxFace internalFace; + Triangle internalTriangle; + if (findParabolaIntersectionInternal(origin, velocity, acceleration, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) { + if (internalDistance < bestLocalDistance) { + bestLocalDistance = internalDistance; + bestLocalFace = internalFace; + bestLocalTriangle = internalTriangle; + intersects = true; + } + } + } + if (intersects) { + parabolicDistance = bestLocalDistance; + face = bestLocalFace; + triangle = bestLocalTriangle; + } + return intersects; +} \ No newline at end of file diff --git a/libraries/shared/src/TriangleSet.h b/libraries/shared/src/TriangleSet.h index 9f688f9def..0b0d0a9ac5 100644 --- a/libraries/shared/src/TriangleSet.h +++ b/libraries/shared/src/TriangleSet.h @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#pragma once + #include #include "AABox.h" @@ -29,6 +31,9 @@ class TriangleSet { bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, bool allowBackface = false); + bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, + bool allowBackface = false); const AABox& getBounds() const { return _bounds; } @@ -41,6 +46,9 @@ class TriangleSet { bool findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, bool allowBackface = false); + bool findParabolaIntersectionInternal(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, + bool allowBackface = false); std::vector& _allTriangles; std::map _children; @@ -63,6 +71,8 @@ public: bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface = false); + bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface = false); void balanceOctree(); @@ -70,12 +80,6 @@ public: size_t size() const { return _triangles.size(); } void clear(); - // Determine if the given ray (origin/direction) in model space intersects with any triangles in the set. If an - // intersection occurs, the distance and surface normal will be provided. - // note: this might side-effect internal structures - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched); - // Determine if a point is "inside" all the triangles of a convex hull. It is the responsibility of the caller to // determine that the triangle set is indeed a convex hull. If the triangles added to this set are not in fact a // convex hull, the result of this method is meaningless and undetermined. diff --git a/libraries/shared/src/shared/FileLogger.cpp b/libraries/shared/src/shared/FileLogger.cpp index 6a10629ee5..b64d08c0d8 100644 --- a/libraries/shared/src/shared/FileLogger.cpp +++ b/libraries/shared/src/shared/FileLogger.cpp @@ -30,7 +30,7 @@ signals: void rollingLogFile(QString newFilename); protected: - void rollFileIfNecessary(QFile& file, bool notifyListenersIfRolled = true); + void rollFileIfNecessary(QFile& file, bool force = false, bool notifyListenersIfRolled = true); virtual bool processQueueItems(const Queue& messages) override; private: @@ -79,12 +79,12 @@ FilePersistThread::FilePersistThread(const FileLogger& logger) : _logger(logger) // A file may exist from a previous run - if it does, roll the file and suppress notifying listeners. QFile file(_logger._fileName); if (file.exists()) { - rollFileIfNecessary(file, false); + rollFileIfNecessary(file, true, false); } } -void FilePersistThread::rollFileIfNecessary(QFile& file, bool notifyListenersIfRolled) { - if (file.size() > MAX_LOG_SIZE) { +void FilePersistThread::rollFileIfNecessary(QFile& file, bool force, bool notifyListenersIfRolled) { + if (force || (file.size() > MAX_LOG_SIZE)) { QString newFileName = getLogRollerFilename(); if (file.copy(newFileName)) { file.open(QIODevice::WriteOnly | QIODevice::Truncate); diff --git a/libraries/task/src/task/Config.h b/libraries/task/src/task/Config.h index 00fa01808b..4379dbbaa6 100644 --- a/libraries/task/src/task/Config.h +++ b/libraries/task/src/task/Config.h @@ -201,8 +201,6 @@ public: class TaskConfig : public JobConfig { Q_OBJECT public: - using QConfigPointer = std::shared_ptr; - using Persistent = PersistentConfig; TaskConfig() = default; @@ -231,7 +229,8 @@ public: if (tokens.empty()) { tokens.push_back(QString()); - } else { + } + else { while (tokens.size() > 1) { auto name = tokens.front(); tokens.pop_front(); diff --git a/libraries/task/src/task/Task.h b/libraries/task/src/task/Task.h index c06ec05121..60ccd26b70 100644 --- a/libraries/task/src/task/Task.h +++ b/libraries/task/src/task/Task.h @@ -179,6 +179,7 @@ public: const std::string& getName() const { return _concept->getName(); } const Varying getInput() const { return _concept->getInput(); } const Varying getOutput() const { return _concept->getOutput(); } + QConfigPointer& getConfiguration() const { return _concept->getConfiguration(); } void applyConfiguration() { return _concept->applyConfiguration(); } @@ -376,6 +377,7 @@ public: using Context = JC; using ContextPointer = std::shared_ptr; using Config = TaskConfig; + using TaskType = Task; using ConceptPointer = typename TaskType::ConceptPointer; diff --git a/libraries/ui/src/InteractiveWindow.cpp b/libraries/ui/src/InteractiveWindow.cpp new file mode 100644 index 0000000000..5078fcb602 --- /dev/null +++ b/libraries/ui/src/InteractiveWindow.cpp @@ -0,0 +1,291 @@ +// +// InteractiveWindow.cpp +// libraries/ui/src +// +// Created by Thijs Wenker on 2018-06-25 +// Copyright 2018 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 "InteractiveWindow.h" + +#include +#include + +#include +#include + +#include "OffscreenUi.h" +#include "shared/QtHelpers.h" + +static auto CONTENT_WINDOW_QML = QUrl("InteractiveWindow.qml"); + +static const char* const FLAGS_PROPERTY = "flags"; +static const char* const SOURCE_PROPERTY = "source"; +static const char* const TITLE_PROPERTY = "title"; +static const char* const POSITION_PROPERTY = "position"; +static const char* const INTERACTIVE_WINDOW_POSITION_PROPERTY = "interactiveWindowPosition"; +static const char* const SIZE_PROPERTY = "size"; +static const char* const INTERACTIVE_WINDOW_SIZE_PROPERTY = "interactiveWindowSize"; +static const char* const VISIBLE_PROPERTY = "visible"; +static const char* const INTERACTIVE_WINDOW_VISIBLE_PROPERTY = "interactiveWindowVisible"; +static const char* const EVENT_BRIDGE_PROPERTY = "eventBridge"; +static const char* const PRESENTATION_MODE_PROPERTY = "presentationMode"; + +static const QStringList KNOWN_SCHEMES = QStringList() << "http" << "https" << "file" << "about" << "atp" << "qrc"; + +void registerInteractiveWindowMetaType(QScriptEngine* engine) { + qScriptRegisterMetaType(engine, interactiveWindowPointerToScriptValue, interactiveWindowPointerFromScriptValue); +} + +QScriptValue interactiveWindowPointerToScriptValue(QScriptEngine* engine, const InteractiveWindowPointer& in) { + return engine->newQObject(in, QScriptEngine::ScriptOwnership); +} + +void interactiveWindowPointerFromScriptValue(const QScriptValue& object, InteractiveWindowPointer& out) { + if (const auto interactiveWindow = qobject_cast(object.toQObject())) { + out = interactiveWindow; + } +} + +InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties) { + auto offscreenUi = DependencyManager::get(); + + // Build the event bridge and wrapper on the main thread + offscreenUi->loadInNewContext(CONTENT_WINDOW_QML, [&](QQmlContext* context, QObject* object) { + _qmlWindow = object; + context->setContextProperty(EVENT_BRIDGE_PROPERTY, this); + if (properties.contains(FLAGS_PROPERTY)) { + object->setProperty(FLAGS_PROPERTY, properties[FLAGS_PROPERTY].toUInt()); + } + if (properties.contains(PRESENTATION_MODE_PROPERTY)) { + object->setProperty(PRESENTATION_MODE_PROPERTY, properties[PRESENTATION_MODE_PROPERTY].toInt()); + } + if (properties.contains(TITLE_PROPERTY)) { + object->setProperty(TITLE_PROPERTY, properties[TITLE_PROPERTY].toString()); + } + if (properties.contains(SIZE_PROPERTY)) { + const auto size = vec2FromVariant(properties[SIZE_PROPERTY]); + object->setProperty(INTERACTIVE_WINDOW_SIZE_PROPERTY, QSize(size.x, size.y)); + } + if (properties.contains(POSITION_PROPERTY)) { + const auto position = vec2FromVariant(properties[POSITION_PROPERTY]); + object->setProperty(INTERACTIVE_WINDOW_POSITION_PROPERTY, QPointF(position.x, position.y)); + } + if (properties.contains(VISIBLE_PROPERTY)) { + object->setProperty(VISIBLE_PROPERTY, properties[INTERACTIVE_WINDOW_VISIBLE_PROPERTY].toBool()); + } + + connect(object, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection); + connect(object, SIGNAL(interactiveWindowPositionChanged()), this, SIGNAL(positionChanged()), Qt::QueuedConnection); + connect(object, SIGNAL(interactiveWindowSizeChanged()), this, SIGNAL(sizeChanged()), Qt::QueuedConnection); + connect(object, SIGNAL(interactiveWindowVisibleChanged()), this, SIGNAL(visibleChanged()), Qt::QueuedConnection); + connect(object, SIGNAL(presentationModeChanged()), this, SIGNAL(presentationModeChanged()), Qt::QueuedConnection); + connect(object, SIGNAL(titleChanged()), this, SIGNAL(titleChanged()), Qt::QueuedConnection); + connect(object, SIGNAL(windowClosed()), this, SIGNAL(closed()), Qt::QueuedConnection); + connect(object, SIGNAL(selfDestruct()), this, SLOT(close()), Qt::QueuedConnection); + + QUrl sourceURL{ sourceUrl }; + // If the passed URL doesn't correspond to a known scheme, assume it's a local file path + if (!KNOWN_SCHEMES.contains(sourceURL.scheme(), Qt::CaseInsensitive)) { + sourceURL = QUrl::fromLocalFile(sourceURL.toString()).toString(); + } + object->setProperty(SOURCE_PROPERTY, sourceURL); + }); +} + +InteractiveWindow::~InteractiveWindow() { + close(); +} + +void InteractiveWindow::sendToQml(const QVariant& message) { + // Forward messages received from the script on to QML + QMetaObject::invokeMethod(_qmlWindow, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message)); +} + +void InteractiveWindow::emitScriptEvent(const QVariant& scriptMessage) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "emitScriptEvent", Qt::QueuedConnection, Q_ARG(QVariant, scriptMessage)); + } else { + emit scriptEventReceived(scriptMessage); + } +} + +void InteractiveWindow::emitWebEvent(const QVariant& webMessage) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "emitWebEvent", Qt::QueuedConnection, Q_ARG(QVariant, webMessage)); + } else { + emit webEventReceived(webMessage); + } +} + +void InteractiveWindow::close() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "close"); + return; + } + + if (_qmlWindow) { + _qmlWindow->deleteLater(); + } + _qmlWindow = nullptr; +} + +void InteractiveWindow::show() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "show"); + return; + } + + if (_qmlWindow) { + QMetaObject::invokeMethod(_qmlWindow, "show", Qt::DirectConnection); + } +} + +void InteractiveWindow::raise() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "raise"); + return; + } + + if (_qmlWindow) { + QMetaObject::invokeMethod(_qmlWindow, "raiseWindow", Qt::DirectConnection); + } +} + +void InteractiveWindow::qmlToScript(const QVariant& message) { + if (message.canConvert()) { + emit fromQml(qvariant_cast(message).toVariant()); + } else if (message.canConvert()) { + emit fromQml(message.toString()); + } else { + qWarning() << "Unsupported message type " << message; + } +} + +void InteractiveWindow::setVisible(bool visible) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setVisible", Q_ARG(bool, visible)); + return; + } + + if (!_qmlWindow.isNull()) { + _qmlWindow->setProperty(INTERACTIVE_WINDOW_VISIBLE_PROPERTY, visible); + } +} + +bool InteractiveWindow::isVisible() const { + if (QThread::currentThread() != thread()) { + bool result = false; + BLOCKING_INVOKE_METHOD(const_cast(this), "isVisible", Q_RETURN_ARG(bool, result)); + return result; + } + + if (_qmlWindow.isNull()) { + return false; + } + + return _qmlWindow->property(INTERACTIVE_WINDOW_VISIBLE_PROPERTY).toBool(); +} + +glm::vec2 InteractiveWindow::getPosition() const { + if (QThread::currentThread() != thread()) { + glm::vec2 result; + BLOCKING_INVOKE_METHOD(const_cast(this), "getPosition", Q_RETURN_ARG(glm::vec2, result)); + return result; + } + + if (_qmlWindow.isNull()) { + return {}; + } + + return toGlm(_qmlWindow->property(INTERACTIVE_WINDOW_POSITION_PROPERTY).toPointF()); +} + +void InteractiveWindow::setPosition(const glm::vec2& position) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setPosition", Q_ARG(const glm::vec2&, position)); + return; + } + + if (!_qmlWindow.isNull()) { + _qmlWindow->setProperty(INTERACTIVE_WINDOW_POSITION_PROPERTY, QPointF(position.x, position.y)); + QMetaObject::invokeMethod(_qmlWindow, "updateInteractiveWindowPositionForMode", Qt::DirectConnection); + } +} + +glm::vec2 InteractiveWindow::getSize() const { + if (QThread::currentThread() != thread()) { + glm::vec2 result; + BLOCKING_INVOKE_METHOD(const_cast(this), "getSize", Q_RETURN_ARG(glm::vec2, result)); + return result; + } + + if (_qmlWindow.isNull()) { + return {}; + } + return toGlm(_qmlWindow->property(INTERACTIVE_WINDOW_SIZE_PROPERTY).toSize()); +} + +void InteractiveWindow::setSize(const glm::vec2& size) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setSize", Q_ARG(const glm::vec2&, size)); + return; + } + + if (!_qmlWindow.isNull()) { + _qmlWindow->setProperty(INTERACTIVE_WINDOW_SIZE_PROPERTY, QSize(size.x, size.y)); + QMetaObject::invokeMethod(_qmlWindow, "updateInteractiveWindowSizeForMode", Qt::DirectConnection); + } +} + +QString InteractiveWindow::getTitle() const { + if (QThread::currentThread() != thread()) { + QString result; + BLOCKING_INVOKE_METHOD(const_cast(this), "getTitle", Q_RETURN_ARG(QString, result)); + return result; + } + + if (_qmlWindow.isNull()) { + return QString(); + } + return _qmlWindow->property(TITLE_PROPERTY).toString(); +} + +void InteractiveWindow::setTitle(const QString& title) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setTitle", Q_ARG(const QString&, title)); + return; + } + + if (!_qmlWindow.isNull()) { + _qmlWindow->setProperty(TITLE_PROPERTY, title); + } +} + +int InteractiveWindow::getPresentationMode() const { + if (QThread::currentThread() != thread()) { + int result; + BLOCKING_INVOKE_METHOD(const_cast(this), "getPresentationMode", + Q_RETURN_ARG(int, result)); + return result; + } + + if (_qmlWindow.isNull()) { + return Virtual; + } + return _qmlWindow->property(PRESENTATION_MODE_PROPERTY).toInt(); +} + +void InteractiveWindow::setPresentationMode(int presentationMode) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setPresentationMode", Q_ARG(int, presentationMode)); + return; + } + + if (!_qmlWindow.isNull()) { + _qmlWindow->setProperty(PRESENTATION_MODE_PROPERTY, presentationMode); + } +} diff --git a/libraries/ui/src/InteractiveWindow.h b/libraries/ui/src/InteractiveWindow.h new file mode 100644 index 0000000000..bf832550b5 --- /dev/null +++ b/libraries/ui/src/InteractiveWindow.h @@ -0,0 +1,206 @@ +// +// InteractiveWindow.h +// libraries/ui/src +// +// Created by Thijs Wenker on 2018-06-25 +// Copyright 2018 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 +// + +#pragma once + +#ifndef hifi_InteractiveWindow_h +#define hifi_InteractiveWindow_h + +#include +#include +#include +#include + +#include +#include + +namespace InteractiveWindowEnums { + Q_NAMESPACE + + enum InteractiveWindowFlags : uint8_t { + AlwaysOnTop = 1 << 0, + CloseButtonHides = 1 << 1 + }; + Q_ENUM_NS(InteractiveWindowFlags); + + enum InteractiveWindowPresentationMode { + Virtual, + Native + }; + Q_ENUM_NS(InteractiveWindowPresentationMode); +} + +using namespace InteractiveWindowEnums; + +/**jsdoc + * @class InteractiveWindow + * + * @hifi-interface + * @hifi-client-en + * + * @property {string} title + * @property {Vec2} position + * @property {Vec2} size + * @property {boolean} visible + * @property {Desktop.PresentationMode} presentationMode + * + */ +class InteractiveWindow : public QObject { + Q_OBJECT + + Q_PROPERTY(QString title READ getTitle WRITE setTitle) + Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition) + Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize) + Q_PROPERTY(bool visible READ isVisible WRITE setVisible) + Q_PROPERTY(int presentationMode READ getPresentationMode WRITE setPresentationMode) + +public: + InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties); + + ~InteractiveWindow(); + +private: + // define property getters and setters as private to not expose them to the JS API + Q_INVOKABLE QString getTitle() const; + Q_INVOKABLE void setTitle(const QString& title); + + Q_INVOKABLE glm::vec2 getPosition() const; + Q_INVOKABLE void setPosition(const glm::vec2& position); + + Q_INVOKABLE glm::vec2 getSize() const; + Q_INVOKABLE void setSize(const glm::vec2& size); + + Q_INVOKABLE void setVisible(bool visible); + Q_INVOKABLE bool isVisible() const; + + Q_INVOKABLE void setPresentationMode(int presentationMode); + Q_INVOKABLE int getPresentationMode() const; + +public slots: + + /**jsdoc + * @function InteractiveWindow.sendToQml + * @param {object} message + */ + // Scripts can use this to send a message to the QML object + void sendToQml(const QVariant& message); + + /**jsdoc + * @function InteractiveWindow.emitScriptEvent + * @param {object} message + */ + // QmlWindow content may include WebView requiring EventBridge. + void emitScriptEvent(const QVariant& scriptMessage); + + /**jsdoc + * @function InteractiveWindow.emitWebEvent + * @param {object} message + */ + void emitWebEvent(const QVariant& webMessage); + + /**jsdoc + * @function InteractiveWindow.close + */ + Q_INVOKABLE void close(); + + /**jsdoc + * @function InteractiveWindow.show + */ + Q_INVOKABLE void show(); + + /**jsdoc + * @function InteractiveWindow.raise + */ + Q_INVOKABLE void raise(); + +signals: + + /**jsdoc + * @function InteractiveWindow.visibleChanged + * @returns {Signal} + */ + void visibleChanged(); + + /**jsdoc + * @function InteractiveWindow.positionChanged + * @returns {Signal} + */ + void positionChanged(); + + /**jsdoc + * @function InteractiveWindow.sizeChanged + * @returns {Signal} + */ + void sizeChanged(); + + /**jsdoc + * @function InteractiveWindow.presentationModeChanged + * @returns {Signal} + */ + void presentationModeChanged(); + + /**jsdoc + * @function InteractiveWindow.titleChanged + * @returns {Signal} + */ + void titleChanged(); + + /**jsdoc + * @function InteractiveWindow.closed + * @returns {Signal} + */ + void closed(); + + /**jsdoc + * @function InteractiveWindow.fromQml + * @param {object} message + * @returns {Signal} + */ + // Scripts can connect to this signal to receive messages from the QML object + void fromQml(const QVariant& message); + + /**jsdoc + * @function InteractiveWindow.scriptEventReceived + * @param {object} message + * @returns {Signal} + */ + // InteractiveWindow content may include WebView requiring EventBridge. + void scriptEventReceived(const QVariant& message); + + /**jsdoc + * @function InteractiveWindow.webEventReceived + * @param {object} message + * @returns {Signal} + */ + void webEventReceived(const QVariant& message); + +protected slots: + /**jsdoc + * @function InteractiveWindow.qmlToScript + * @param {object} message + * @returns {Signal} + */ + void qmlToScript(const QVariant& message); + +private: + QPointer _qmlWindow; +}; + +typedef InteractiveWindow* InteractiveWindowPointer; + +QScriptValue interactiveWindowPointerToScriptValue(QScriptEngine* engine, const InteractiveWindowPointer& in); +void interactiveWindowPointerFromScriptValue(const QScriptValue& object, InteractiveWindowPointer& out); + +void registerInteractiveWindowMetaType(QScriptEngine* engine); + +Q_DECLARE_METATYPE(InteractiveWindowPointer) + +#endif // hifi_InteractiveWindow_h diff --git a/libraries/ui/src/Tooltip.cpp b/libraries/ui/src/Tooltip.cpp index c0c015e72f..bd2c4e6d8f 100644 --- a/libraries/ui/src/Tooltip.cpp +++ b/libraries/ui/src/Tooltip.cpp @@ -83,7 +83,7 @@ void Tooltip::requestHyperlinkImage() { auto accountManager = DependencyManager::get(); JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; + callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "handleAPIResponse"; accountManager->sendRequest(GET_PLACE.arg(_title), @@ -94,9 +94,9 @@ void Tooltip::requestHyperlinkImage() { } } -void Tooltip::handleAPIResponse(QNetworkReply& requestReply) { +void Tooltip::handleAPIResponse(QNetworkReply* requestReply) { // did a preview image come back? - QJsonObject responseObject = QJsonDocument::fromJson(requestReply.readAll()).object(); + QJsonObject responseObject = QJsonDocument::fromJson(requestReply->readAll()).object(); QJsonObject dataObject = responseObject["data"].toObject(); const QString PLACE_KEY = "place"; diff --git a/libraries/ui/src/Tooltip.h b/libraries/ui/src/Tooltip.h index 5e884a7aea..b1bf7b7f3e 100644 --- a/libraries/ui/src/Tooltip.h +++ b/libraries/ui/src/Tooltip.h @@ -49,7 +49,7 @@ signals: void imageURLChanged(); private slots: - void handleAPIResponse(QNetworkReply& requestReply); + void handleAPIResponse(QNetworkReply* requestReply); private: void requestHyperlinkImage(); diff --git a/libraries/ui/src/ui/Menu.cpp b/libraries/ui/src/ui/Menu.cpp index 16af862324..848b2362ee 100644 --- a/libraries/ui/src/ui/Menu.cpp +++ b/libraries/ui/src/ui/Menu.cpp @@ -402,8 +402,10 @@ MenuWrapper* Menu::addMenu(const QString& menuName, const QString& grouping) { // hook our show/hide for popup menus, so we can keep track of whether or not one // of our submenus is currently showing. - connect(menu->_realMenu, &QMenu::aboutToShow, []() { _isSomeSubmenuShown = true; }); - connect(menu->_realMenu, &QMenu::aboutToHide, []() { _isSomeSubmenuShown = false; }); + if (menu && menu->_realMenu) { + connect(menu->_realMenu, &QMenu::aboutToShow, []() { _isSomeSubmenuShown = true; }); + connect(menu->_realMenu, &QMenu::aboutToHide, []() { _isSomeSubmenuShown = false; }); + } return menu; } diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 2c52e669a0..6f00e046af 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -648,6 +648,26 @@ void TabletProxy::loadQMLSource(const QVariant& path, bool resizable) { } } +void TabletProxy::stopQMLSource() { + // For desktop toolbar mode dialogs. + if (!_toolbarMode || !_desktopWindow) { + qCDebug(uiLogging) << "tablet cannot clear QML because not desktop toolbar mode"; + return; + } + + auto root = _desktopWindow->asQuickItem(); + if (root) { + QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, "")); + if (!_currentPathLoaded.toString().isEmpty()) { + emit screenChanged(QVariant("QML"), ""); + } + _currentPathLoaded = ""; + _state = State::Home; + } else { + qCDebug(uiLogging) << "tablet cannot clear QML because _desktopWindow is null"; + } +} + bool TabletProxy::pushOntoStack(const QVariant& path) { if (QThread::currentThread() != thread()) { bool result = false; @@ -719,6 +739,7 @@ void TabletProxy::loadHomeScreen(bool forceOntoHomeScreen) { // close desktop window if (_desktopWindow->asQuickItem()) { QMetaObject::invokeMethod(_desktopWindow->asQuickItem(), "setShown", Q_ARG(const QVariant&, QVariant(false))); + stopQMLSource(); // Stop the currently loaded QML running. } } _state = State::Home; diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index 1ab29ca3fd..2d37402d01 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -443,6 +443,9 @@ protected: bool _showRunningScripts { false }; TabletButtonListModel _buttons; + +private: + void stopQMLSource(); }; Q_DECLARE_METATYPE(TabletProxy*); diff --git a/libraries/workload/CMakeLists.txt b/libraries/workload/CMakeLists.txt index 3fcf00e0e9..beaed55734 100644 --- a/libraries/workload/CMakeLists.txt +++ b/libraries/workload/CMakeLists.txt @@ -1,5 +1,3 @@ set(TARGET_NAME workload) setup_hifi_library() - -link_hifi_libraries(shared) - +link_hifi_libraries(shared task) diff --git a/libraries/workload/src/workload/Engine.cpp b/libraries/workload/src/workload/Engine.cpp new file mode 100644 index 0000000000..fcce6025df --- /dev/null +++ b/libraries/workload/src/workload/Engine.cpp @@ -0,0 +1,19 @@ +// +// Engine.cpp +// libraries/workload/src/workload +// +// Created by Andrew Meadows 2018.02.08 +// Copyright 2018 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 "Engine.h" + +#include + +namespace workload { + WorkloadContext::WorkloadContext(const SpacePointer& space) : task::JobContext(), _space(space) {} +} // namespace workload + diff --git a/libraries/workload/src/workload/Engine.h b/libraries/workload/src/workload/Engine.h new file mode 100644 index 0000000000..7df8d8dd1a --- /dev/null +++ b/libraries/workload/src/workload/Engine.h @@ -0,0 +1,43 @@ +// +// Engine.h +// libraries/workload/src/workload +// +// Created by Andrew Meadows 2018.02.08 +// Copyright 2018 High Fidelity, Inc. +// +// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards +// Simple plane class. +// +// 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_workload_Engine_h +#define hifi_workload_Engine_h + +#include +#include + +#include + +#include "Space.h" + +namespace workload { + class WorkloadContext : public task::JobContext { + public: + WorkloadContext(const SpacePointer& space); + virtual ~WorkloadContext() {} + + SpacePointer _space; + }; + + using WorkloadContextPointer = std::shared_ptr; + + Task_DeclareCategoryTimeProfilerClass(WorkloadTimeProfiler, trace_workload); + + // Instanciate the specialized types of Job Engine and Task for the Workload context + Task_DeclareTypeAliases(WorkloadContext, WorkloadTimeProfiler) + using EnginePointer = std::shared_ptr; +} // namespace workload + +#endif // hifi_workload_Space_h diff --git a/libraries/workload/src/workload/Proxy.h b/libraries/workload/src/workload/Proxy.h new file mode 100644 index 0000000000..8e0be6f6f7 --- /dev/null +++ b/libraries/workload/src/workload/Proxy.h @@ -0,0 +1,59 @@ +// +// Proxy.h +// libraries/workload/src/workload +// +// Created by Andrew Meadows 2018.01.30 +// Copyright 2018 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_workload_Proxy_h +#define hifi_workload_Proxy_h + +#include "View.h" + +namespace workload { + +class Owner { +public: + Owner() = default; + Owner(const Owner& other) = default; + Owner& operator=(const Owner& other) = default; + template Owner(const T& data) : _concept(std::make_shared>(data)) {} + ~Owner() {} + template const T get() const { return std::static_pointer_cast>(_concept)->_data; } +protected: + class Concept { + public: + virtual ~Concept() = default; + }; + template class Model : public Concept { + public: + using Data = T; + Data _data; + Model(const Data& data) : _data(data) {} + virtual ~Model() = default; + }; +private: + std::shared_ptr _concept; +}; + +class Proxy { +public: + Proxy() : sphere(0.0f) {} + Proxy(const Sphere& s) : sphere(s) {} + + Sphere sphere; + uint8_t region{ Region::INVALID }; + uint8_t prevRegion{ Region::INVALID }; + uint16_t _padding; + uint32_t _paddings[3]; + + using Vector = std::vector; +}; + + +} // namespace workload + +#endif // hifi_workload_Proxy_h diff --git a/libraries/workload/src/workload/Region.h b/libraries/workload/src/workload/Region.h new file mode 100644 index 0000000000..43aed9aef4 --- /dev/null +++ b/libraries/workload/src/workload/Region.h @@ -0,0 +1,74 @@ +// +// Region.h +// libraries/workload/src/workload +// +// Created by Sam Gateau 2018.03.05 +// Copyright 2018 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_workload_Region_h +#define hifi_workload_Region_h + +namespace workload { + +class Region { +public: + using Type = uint8_t; + + enum Name : uint8_t { + R1 = 0, + R2, + R3, + UNKNOWN, + INVALID, + }; + + static const uint8_t NUM_CLASSIFICATIONS = 4; + static const uint8_t NUM_TRANSITIONS = NUM_CLASSIFICATIONS * (NUM_CLASSIFICATIONS - 1); + + static const uint8_t NUM_VIEW_REGIONS = (NUM_CLASSIFICATIONS - 1); + + static uint8_t computeTransitionIndex(uint8_t prevIndex, uint8_t newIndex); + +}; + +inline uint8_t Region::computeTransitionIndex(uint8_t prevIndex, uint8_t newIndex) { + // given prevIndex and newIndex compute an index into the transition list, + // where the lists between unchanged indices don't exist (signaled by index = -1). + // + // Given an NxN array + // let p = i + N * j + // + // then k = -1 when i == j + // = p - (1 + p/(N+1)) when i != j + // + // i 0 1 2 3 + // j +-------+-------+-------+-------+ + // |p = 0 | 1 | 2 | 3 | + // 0 | | | | | + // |k = -1 | 0 | 1 | 2 | + // +-------+-------+-------+-------+ + // | 4 | 5 | 6 | 7 | + // 1 | | | | | + // | 3 | -1 | 4 | 5 | + // +-------+-------+-------+-------+ + // | 8 | 9 | 10 | 11 | + // 2 | | | | | + // | 6 | 7 | -1 | 8 | + // +-------+-------+-------+-------+ + // | 12 | 13 | 14 | 15 | + // 3 | | | | | + // | 9 | 10 | 11 | -1 | + // +-------+-------+-------+-------+ + uint8_t p = prevIndex + Region::NUM_CLASSIFICATIONS * newIndex; + if (0 == (p % (Region::NUM_CLASSIFICATIONS + 1))) { + return -1; + } + return p - (1 + p / (Region::NUM_CLASSIFICATIONS + 1)); +} + +} // namespace workload + +#endif // hifi_workload_Region_h \ No newline at end of file diff --git a/libraries/workload/src/workload/RegionState.cpp b/libraries/workload/src/workload/RegionState.cpp new file mode 100644 index 0000000000..47179ad6f7 --- /dev/null +++ b/libraries/workload/src/workload/RegionState.cpp @@ -0,0 +1,76 @@ +// +// RegionState.cpp +// libraries/workload/src/workload +// +// Created by Andrew Meadows 2018.03.07 +// Copyright 2018 High Fidelity, Inc. +// +// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards +// Simple plane class. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "RegionState.h" + +using namespace workload; + +void RegionState::configure(const Config& config) { +} + + +void RegionState::run(const workload::WorkloadContextPointer& renderContext, const Inputs& inputs) { + // inputs is a vector of vectors of proxyId's: + // + // inputs[0] = vector of ids exiting region 0 + // inputs[1] = vector of ids entering region 0 + // ... + // inputs[2N] = vector of ids exiting region N + // inputs[2N + 1] = vector of ids entering region N + assert(inputs.size() == 2 * Region::UNKNOWN); + + // The id's in each vector are sorted in ascending order + // because the source vectors are scanned in ascending order. + + for (uint32_t i = 0; i < _state.size(); ++i) { + const IndexVector& going = inputs[2 * i]; + const IndexVector& coming = inputs[2 * i + 1]; + if (coming.size() == 0 && going.size() == 0) { + continue; + } + if (_state[i].empty()) { + assert(going.empty()); + _state[i] = coming; + } else { + // NOTE: all vectors are sorted by proxyId! + // which means we can build the new vector by walking three vectors (going, current, coming) in one pass + IndexVector& oldState = _state[i]; + IndexVector newState; + newState.reserve(oldState.size() - going.size() + coming.size()); + uint32_t goingIndex = 0; + uint32_t comingIndex = 0; + for (uint32_t j = 0; j < oldState.size(); ++j) { + int32_t proxyId = oldState[j]; + while (comingIndex < coming.size() && coming[comingIndex] < proxyId) { + newState.push_back(coming[comingIndex]); + ++comingIndex; + } + if (goingIndex < going.size() && going[goingIndex] == proxyId) { + ++goingIndex; + } else { + newState.push_back(proxyId); + } + } + assert(goingIndex == going.size()); + while (comingIndex < coming.size()) { + newState.push_back(coming[comingIndex]); + ++comingIndex; + } + oldState.swap(newState); + } + } + + auto config = std::static_pointer_cast(renderContext->jobConfig); + config->setNum(0, (uint32_t) _state[0].size(), (uint32_t) _state[1].size(), (uint32_t) _state[2].size()); +} diff --git a/libraries/workload/src/workload/RegionState.h b/libraries/workload/src/workload/RegionState.h new file mode 100644 index 0000000000..40db9d4982 --- /dev/null +++ b/libraries/workload/src/workload/RegionState.h @@ -0,0 +1,67 @@ +// +// RegionState.h +// libraries/workload/src/workload +// +// Created by Andrew Meadows 2018.03.07 +// Copyright 2018 High Fidelity, Inc. +// +// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards +// Simple plane class. +// +// 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_workload_RegionState_h +#define hifi_workload_RegionState_h + +#include "Space.h" +#include "Engine.h" + +namespace workload { + class RegionStateConfig : public Job::Config{ + Q_OBJECT + Q_PROPERTY(float numR0 READ getNumR0 NOTIFY dirty) + Q_PROPERTY(float numR1 READ getNumR1 NOTIFY dirty) + Q_PROPERTY(float numR2 READ getNumR2 NOTIFY dirty) + Q_PROPERTY(float numR3 READ getNumR3 NOTIFY dirty) + public: + + uint32_t getNumR0() const { return data.numR0; } + uint32_t getNumR1() const { return data.numR1; } + uint32_t getNumR2() const { return data.numR2; } + uint32_t getNumR3() const { return data.numR3; } + + void setNum(const uint32_t r0, const uint32_t r1, const uint32_t r2, const uint32_t r3) { + data.numR0 = r0; data.numR1 = r1; data.numR2 = r2; data.numR3 = r3; emit dirty(); + } + + struct Data { + uint32_t numR0{ 0 }; + uint32_t numR1{ 0 }; + uint32_t numR2{ 0 }; + uint32_t numR3{ 0 }; + } data; + + signals: + void dirty(); + }; + + class RegionState { + public: + using Config = RegionStateConfig; + using Inputs = IndexVectors; + using JobModel = workload::Job::ModelI; + + RegionState() { + _state.resize(Region::UNKNOWN); + } + + void configure(const Config& config); + void run(const workload::WorkloadContextPointer& renderContext, const Inputs& inputs); + + protected: + IndexVectors _state; + }; +} +#endif // hifi_workload_RegionState_h diff --git a/libraries/workload/src/workload/RegionTracker.cpp b/libraries/workload/src/workload/RegionTracker.cpp new file mode 100644 index 0000000000..381b92c414 --- /dev/null +++ b/libraries/workload/src/workload/RegionTracker.cpp @@ -0,0 +1,50 @@ +// +// RegionTracker.cpp +// libraries/workload/src/workload +// +// Created by Andrew Meadows 2018.02.21 +// Copyright 2018 High Fidelity, Inc. +// +// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards +// Simple plane class. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "RegionTracker.h" + +#include "Region.h" + +using namespace workload; + +void RegionTracker::configure(const Config& config) { +} + +void RegionTracker::run(const WorkloadContextPointer& context, Outputs& outputs) { + auto& outChanges = outputs.edit0(); + auto& outRegionChanges = outputs.edit1(); + + + outChanges.clear(); + outRegionChanges.clear(); + + auto space = context->_space; + if (space) { + //Changes changes; + space->categorizeAndGetChanges(outChanges); + + // use exit/enter lists for each region less than Region::UNKNOWN + outRegionChanges.resize(2 * (workload::Region::NUM_CLASSIFICATIONS - 1)); + for (uint32_t i = 0; i < outChanges.size(); ++i) { + Space::Change& change = outChanges[i]; + if (change.prevRegion < Region::UNKNOWN) { + // EXIT list index = 2 * regionIndex + outRegionChanges[2 * change.prevRegion].push_back(change.proxyId); + } + if (change.region < Region::UNKNOWN) { + // ENTER list index = 2 * regionIndex + 1 + outRegionChanges[2 * change.region + 1].push_back(change.proxyId); + } + } + } +} diff --git a/libraries/workload/src/workload/RegionTracker.h b/libraries/workload/src/workload/RegionTracker.h new file mode 100644 index 0000000000..29a9af1f84 --- /dev/null +++ b/libraries/workload/src/workload/RegionTracker.h @@ -0,0 +1,44 @@ +// +// RegionTracker.h +// libraries/workload/src/workload +// +// Created by Andrew Meadows 2018.02.21 +// Copyright 2018 High Fidelity, Inc. +// +// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards +// Simple plane class. +// +// 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_workload_RegionTracker_h +#define hifi_workload_RegionTracker_h + +#include "Space.h" +#include "Engine.h" + +namespace workload { + + class RegionTrackerConfig : public Job::Config { + Q_OBJECT + public: + RegionTrackerConfig() : Job::Config(true) {} + }; + + class RegionTracker { + public: + using Config = RegionTrackerConfig; + using Outputs = VaryingSet2; + using JobModel = workload::Job::ModelO; + + RegionTracker() {} + + void configure(const Config& config); + void run(const workload::WorkloadContextPointer& renderContext, Outputs& outputs); + + protected: + }; +} // namespace workload + +#endif // hifi_workload_RegionTracker_h diff --git a/libraries/workload/src/workload/Space.cpp b/libraries/workload/src/workload/Space.cpp index 1e2bfb228a..747df5f6c4 100644 --- a/libraries/workload/src/workload/Space.cpp +++ b/libraries/workload/src/workload/Space.cpp @@ -13,72 +13,100 @@ // #include "Space.h" - +#include #include #include using namespace workload; -int32_t Space::createProxy(const Space::Sphere& newSphere) { - if (_freeIndices.empty()) { - _proxies.emplace_back(Space::Proxy(newSphere)); - return (int32_t)_proxies.size() - 1; - } else { - int32_t index = _freeIndices.back(); - _freeIndices.pop_back(); - _proxies[index].sphere = newSphere; - _proxies[index].region = Space::REGION_UNKNOWN; - _proxies[index].prevRegion = Space::REGION_UNKNOWN; - return index; - } +Space::Space() : Collection() { } -void Space::deleteProxy(int32_t proxyId) { - if (proxyId >= (int32_t)_proxies.size() || _proxies.empty()) { - return; +void Space::processTransactionFrame(const Transaction& transaction) { + std::unique_lock lock(_proxiesMutex); + // Here we should be able to check the value of last ProxyID allocated + // and allocate new proxies accordingly + ProxyID maxID = _IDAllocator.getNumAllocatedIndices(); + if (maxID > (Index) _proxies.size()) { + _proxies.resize(maxID + 100); // allocate the maxId and more + _owners.resize(maxID + 100); } - if (proxyId == (int32_t)_proxies.size() - 1) { - // remove proxy on back - _proxies.pop_back(); - if (!_freeIndices.empty()) { - // remove any freeIndices on back - std::sort(_freeIndices.begin(), _freeIndices.end()); - while(!_freeIndices.empty() && _freeIndices.back() == (int32_t)_proxies.size() - 1) { - _freeIndices.pop_back(); - _proxies.pop_back(); - } + // Now we know for sure that we have enough items in the array to + // capture anything coming from the transaction + + processResets(transaction._resetItems); + processUpdates(transaction._updatedItems); + processRemoves(transaction._removedItems); +} + +void Space::processResets(const Transaction::Resets& transactions) { + for (auto& reset : transactions) { + // Access the true item + auto proxyID = std::get<0>(reset); + + // Guard against proxyID being past the end of the list. + if (!_IDAllocator.checkIndex(proxyID)) { + continue; } - } else { - _proxies[proxyId].region = Space::REGION_INVALID; - _freeIndices.push_back(proxyId); + auto& item = _proxies[proxyID]; + + // Reset the item with a new payload + item.sphere = (std::get<1>(reset)); + item.prevRegion = item.region = Region::UNKNOWN; + + _owners[proxyID] = (std::get<2>(reset)); } } -void Space::updateProxy(int32_t proxyId, const Space::Sphere& newSphere) { - if (proxyId >= (int32_t)_proxies.size()) { - return; +void Space::processRemoves(const Transaction::Removes& transactions) { + for (auto removedID : transactions) { + if (!_IDAllocator.checkIndex(removedID)) { + continue; + } + _IDAllocator.freeIndex(removedID); + + // Access the true item + auto& item = _proxies[removedID]; + + // Kill it + item.prevRegion = item.region = Region::INVALID; + _owners[removedID] = Owner(); } - _proxies[proxyId].sphere = newSphere; } -void Space::setViews(const std::vector& views) { - _views = views; +void Space::processUpdates(const Transaction::Updates& transactions) { + for (auto& update : transactions) { + auto updateID = std::get<0>(update); + if (!_IDAllocator.checkIndex(updateID)) { + continue; + } + + // Access the true item + auto& item = _proxies[updateID]; + + // Update the item + item.sphere = (std::get<1>(update)); + } } void Space::categorizeAndGetChanges(std::vector& changes) { + std::unique_lock lock(_proxiesMutex); uint32_t numProxies = (uint32_t)_proxies.size(); uint32_t numViews = (uint32_t)_views.size(); for (uint32_t i = 0; i < numProxies; ++i) { Proxy& proxy = _proxies[i]; - if (proxy.region < Space::REGION_INVALID) { - uint8_t region = Space::REGION_UNKNOWN; + if (proxy.region < Region::INVALID) { + glm::vec3 proxyCenter = glm::vec3(proxy.sphere); + float proxyRadius = proxy.sphere.w; + uint8_t region = Region::UNKNOWN; for (uint32_t j = 0; j < numViews; ++j) { - float distance2 = glm::distance2(_views[j].center, glm::vec3(_proxies[i].sphere)); - for (uint8_t c = 0; c < region; ++c) { - float touchDistance = _views[j].radiuses[c] + _proxies[i].sphere.w; - if (distance2 < touchDistance * touchDistance) { - region = c; + auto& view = _views[j]; + // for each 'view' we need only increment 'k' below the current value of 'region' + for (uint8_t k = 0; k < region; ++k) { + float touchDistance = proxyRadius + view.regions[k].w; + if (distance2(proxyCenter, glm::vec3(view.regions[k])) < touchDistance * touchDistance) { + region = k; break; } } @@ -92,3 +120,42 @@ void Space::categorizeAndGetChanges(std::vector& changes) { } } +uint32_t Space::copyProxyValues(Proxy* proxies, uint32_t numDestProxies) const { + std::unique_lock lock(_proxiesMutex); + auto numCopied = std::min(numDestProxies, (uint32_t)_proxies.size()); + memcpy(proxies, _proxies.data(), numCopied * sizeof(Proxy)); + return numCopied; +} + +const Owner Space::getOwner(int32_t proxyID) const { + std::unique_lock lock(_proxiesMutex); + if (isAllocatedID(proxyID) && (proxyID < (Index)_proxies.size())) { + return _owners[proxyID]; + } + return Owner(); +} + +uint8_t Space::getRegion(int32_t proxyID) const { + std::unique_lock lock(_proxiesMutex); + if (isAllocatedID(proxyID) && (proxyID < (Index)_proxies.size())) { + return _proxies[proxyID].region; + } + return (uint8_t)Region::INVALID; +} + +void Space::clear() { + Collection::clear(); + std::unique_lock lock(_proxiesMutex); + _IDAllocator.clear(); + _proxies.clear(); + _owners.clear(); + _views.clear(); +} + +void Space::setViews(const Views& views) { + _views = views; +} + +void Space::copyViews(std::vector& copy) const { + copy = _views; +} diff --git a/libraries/workload/src/workload/Space.h b/libraries/workload/src/workload/Space.h index be47cddc4e..310955f4c6 100644 --- a/libraries/workload/src/workload/Space.h +++ b/libraries/workload/src/workload/Space.h @@ -15,39 +15,17 @@ #ifndef hifi_workload_Space_h #define hifi_workload_Space_h +#include #include #include +#include "Transaction.h" + namespace workload { -class Space { +class Space : public Collection { public: - static const uint8_t REGION_NEAR = 0; - static const uint8_t REGION_MIDDLE = 1; - static const uint8_t REGION_FAR = 2; - static const uint8_t REGION_UNKNOWN = 3; - static const uint8_t REGION_INVALID = 4; - - using Sphere = glm::vec4; // = center, w = radius - - class Proxy { - public: - Proxy(const Sphere& s) : sphere(s) {} - Sphere sphere; - uint8_t region { REGION_UNKNOWN }; - uint8_t prevRegion { REGION_UNKNOWN }; - }; - - class View { - public: - View(const glm::vec3& pos, float nearRadius, float midRadius, float farRadius) : center(pos) { - radiuses[0] = nearRadius; - radiuses[1] = midRadius; - radiuses[2] = farRadius; - } - glm::vec3 center { 0.0f, 0.0f, 0.0f }; - float radiuses[3] { 0.0f, 0.0f, 0.0f }; - }; + using ProxyUpdate = std::pair; class Change { public: @@ -57,23 +35,43 @@ public: uint8_t prevRegion { 0 }; }; - Space() {} + Space(); - int32_t createProxy(const Sphere& sphere); - void deleteProxy(int32_t proxyId); - void updateProxy(int32_t proxyId, const Sphere& sphere); - void setViews(const std::vector& views); + void setViews(const Views& views); - uint32_t getNumObjects() const { return (uint32_t)(_proxies.size() - _freeIndices.size()); } + uint32_t getNumViews() const { return (uint32_t)(_views.size()); } + void copyViews(std::vector& copy) const; + + uint32_t getNumObjects() const { return _IDAllocator.getNumLiveIndices(); } + uint32_t getNumAllocatedProxies() const { return (uint32_t)(_IDAllocator.getNumAllocatedIndices()); } void categorizeAndGetChanges(std::vector& changes); + uint32_t copyProxyValues(Proxy* proxies, uint32_t numDestProxies) const; + const Owner getOwner(int32_t proxyID) const; + uint8_t getRegion(int32_t proxyID) const; + + void clear() override; private: - std::vector _proxies; - std::vector _views; - std::vector _freeIndices; + + void processTransactionFrame(const Transaction& transaction) override; + void processResets(const Transaction::Resets& transactions); + void processRemoves(const Transaction::Removes& transactions); + void processUpdates(const Transaction::Updates& transactions); + + // The database of proxies is protected for editing by a mutex + mutable std::mutex _proxiesMutex; + Proxy::Vector _proxies; + std::vector _owners; + + Views _views; }; +using SpacePointer = std::shared_ptr; +using Changes = std::vector; +using IndexVectors = std::vector; +using Timings = std::vector; + } // namespace workload #endif // hifi_workload_Space_h diff --git a/libraries/workload/src/workload/SpaceClassifier.cpp b/libraries/workload/src/workload/SpaceClassifier.cpp new file mode 100644 index 0000000000..418a510589 --- /dev/null +++ b/libraries/workload/src/workload/SpaceClassifier.cpp @@ -0,0 +1,34 @@ +// +// SpaceClassifier.cpp +// libraries/workload/src/workload +// +// Created by Andrew Meadows 2018.02.21 +// Copyright 2018 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 "SpaceClassifier.h" + +#include "ViewTask.h" +#include "RegionState.h" + +using namespace workload; + +void PerformSpaceTransaction::configure(const Config& config) { + +} +void PerformSpaceTransaction::run(const WorkloadContextPointer& context) { + context->_space->enqueueFrame(); + context->_space->processTransactionQueue(); +} + +void SpaceClassifierTask::build(JobModel& model, const Varying& in, Varying& out) { + model.addJob("assignSpaceViews", in); + model.addJob("updateSpace"); + const auto regionTrackerOut = model.addJob("regionTracker"); + const auto regionChanges = regionTrackerOut.getN(1); + model.addJob("regionState", regionChanges); + out = regionTrackerOut; +} + diff --git a/libraries/workload/src/workload/SpaceClassifier.h b/libraries/workload/src/workload/SpaceClassifier.h new file mode 100644 index 0000000000..b6231a91ff --- /dev/null +++ b/libraries/workload/src/workload/SpaceClassifier.h @@ -0,0 +1,47 @@ +// +// SpaceClassifier.h +// libraries/workload/src/workload +// +// Created by Andrew Meadows 2018.02.21 +// Copyright 2018 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_workload_SpaceClassifier_h +#define hifi_workload_SpaceClassifier_h + +#include "ViewTask.h" +#include "RegionTracker.h" + +namespace workload { + class SpaceClassifierTask { + public: + using Inputs = Views; + using Outputs = RegionTracker::Outputs; + using JobModel = Task::ModelIO; + void build(JobModel& model, const Varying& in, Varying& out); + }; + + + class PerformSpaceTransactionConfig : public Job::Config { + Q_OBJECT + public: + signals : + void dirty(); + + protected: + }; + + class PerformSpaceTransaction { + public: + using Config = PerformSpaceTransactionConfig; + using JobModel = Job::Model; + + void configure(const Config& config); + void run(const WorkloadContextPointer& context); + protected: + }; +} // namespace workload + +#endif // hifi_workload_SpaceClassifier_h diff --git a/libraries/workload/src/workload/Transaction.cpp b/libraries/workload/src/workload/Transaction.cpp new file mode 100644 index 0000000000..14984e9c77 --- /dev/null +++ b/libraries/workload/src/workload/Transaction.cpp @@ -0,0 +1,172 @@ +// +// Transaction.cpp +// libraries/workload/src/workload +// +// Created by Sam Gateau 2018.03.12 +// Copyright 2018 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 "Transaction.h" + +using namespace workload; + + +void Transaction::reset(ProxyID id, const ProxyPayload& payload, const Owner& owner) { + _resetItems.emplace_back(Reset{ id, payload, owner }); +} + +void Transaction::remove(ProxyID id) { + _removedItems.emplace_back(id); +} + +void Transaction::update(ProxyID id, const ProxyPayload& payload) { + _updatedItems.emplace_back(id, payload); +} + +void Transaction::reserve(const std::vector& transactionContainer) { + size_t resetItemsCount = 0; + size_t removedItemsCount = 0; + size_t updatedItemsCount = 0; + + for (const auto& transaction : transactionContainer) { + resetItemsCount += transaction._resetItems.size(); + removedItemsCount += transaction._removedItems.size(); + updatedItemsCount += transaction._updatedItems.size(); + } + + _resetItems.reserve(resetItemsCount); + _removedItems.reserve(removedItemsCount); + _updatedItems.reserve(updatedItemsCount); +} + +void Transaction::merge(const std::vector& transactionContainer) { + reserve(transactionContainer); + for (const auto& transaction : transactionContainer) { + merge(transaction); + } +} + + +void Transaction::merge(std::vector&& transactionContainer) { + reserve(transactionContainer); + auto begin = std::make_move_iterator(transactionContainer.begin()); + auto end = std::make_move_iterator(transactionContainer.end()); + for (auto itr = begin; itr != end; ++itr) { + merge(*itr); + } + transactionContainer.clear(); +} + + +template +void moveElements(T& target, T& source) { + target.insert(target.end(), std::make_move_iterator(source.begin()), std::make_move_iterator(source.end())); + source.clear(); +} + +template +void copyElements(T& target, const T& source) { + target.insert(target.end(), source.begin(), source.end()); +} + + +void Transaction::reset(const Resets& resets) { + copyElements(_resetItems, resets); +} + +void Transaction::remove(const Removes& removes) { + copyElements(_removedItems, removes); +} + +void Transaction::update(const Updates& updates) { + copyElements(_updatedItems, updates); +} + +void Transaction::merge(Transaction&& transaction) { + moveElements(_resetItems, transaction._resetItems); + moveElements(_removedItems, transaction._removedItems); + moveElements(_updatedItems, transaction._updatedItems); +} + +void Transaction::merge(const Transaction& transaction) { + copyElements(_resetItems, transaction._resetItems); + copyElements(_removedItems, transaction._removedItems); + copyElements(_updatedItems, transaction._updatedItems); +} + +void Transaction::clear() { + _resetItems.clear(); + _removedItems.clear(); + _updatedItems.clear(); +} + + + + +Collection::Collection() { +} + +Collection::~Collection() { +} + +void Collection::clear() { + std::unique_lock lock(_transactionQueueMutex); + _transactionQueue.clear(); + _transactionFrames.clear(); +} + +ProxyID Collection::allocateID() { + // Just increment and return the previous value initialized at 0 + return _IDAllocator.allocateIndex(); +} + +bool Collection::isAllocatedID(const ProxyID& id) const { + return _IDAllocator.checkIndex(id); +} + +/// Enqueue change batch to the Collection +void Collection::enqueueTransaction(const Transaction& transaction) { + std::unique_lock lock(_transactionQueueMutex); + _transactionQueue.emplace_back(transaction); +} + +void Collection::enqueueTransaction(Transaction&& transaction) { + std::unique_lock lock(_transactionQueueMutex); + _transactionQueue.emplace_back(std::move(transaction)); +} + +uint32_t Collection::enqueueFrame() { + TransactionQueue localTransactionQueue; + { + std::unique_lock lock(_transactionQueueMutex); + localTransactionQueue.swap(_transactionQueue); + } + + Transaction consolidatedTransaction; + consolidatedTransaction.merge(std::move(localTransactionQueue)); + { + std::unique_lock lock(_transactionFramesMutex); + _transactionFrames.push_back(consolidatedTransaction); + } + + return ++_transactionFrameNumber; +} + + +void Collection::processTransactionQueue() { + static TransactionFrames queuedFrames; + { + // capture the queued frames and clear the queue + std::unique_lock lock(_transactionFramesMutex); + queuedFrames.swap(_transactionFrames); + } + + // go through the queue of frames and process them + for (auto& frame : queuedFrames) { + processTransactionFrame(frame); + } + + queuedFrames.clear(); +} diff --git a/libraries/workload/src/workload/Transaction.h b/libraries/workload/src/workload/Transaction.h new file mode 100644 index 0000000000..22328cf4b1 --- /dev/null +++ b/libraries/workload/src/workload/Transaction.h @@ -0,0 +1,183 @@ +// +// Transaction.h +// libraries/workload/src/workload +// +// Created by Sam Gateau 2018.03.12 +// Copyright 2018 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_workload_Transaction_h +#define hifi_workload_Transaction_h + +#include +#include +#include +#include +#include + +#include "Proxy.h" + + +namespace workload { + + namespace indexed_container { + + using Index = int32_t; + const Index MAXIMUM_INDEX{ 1 << 30 }; + const Index INVALID_INDEX{ -1 }; + using Indices = std::vector< Index >; + + template + class Allocator { + public: + Allocator() {} + Indices _freeIndices; + Index _nextNewIndex{ 0 }; + + bool checkIndex(Index index) const { return ((index >= 0) && (index < _nextNewIndex)); } + Index getNumLiveIndices() const { return _nextNewIndex - (Index)_freeIndices.size(); } + Index getNumFreeIndices() const { return (Index)_freeIndices.size(); } + Index getNumAllocatedIndices() const { return _nextNewIndex; } + + Index allocateIndex() { + if (_freeIndices.empty()) { + Index index = _nextNewIndex; + if (index >= MaxNumElements) { + // abort! we are trying to go overboard with the total number of allocated elements + assert(false); + // This should never happen because Bricks are allocated along with the cells and there + // is already a cap on the cells allocation + return INVALID_INDEX; + } + _nextNewIndex++; + return index; + } else { + // TODO: sort _freeIndices when neccessary to help keep used allocated indices more tightly packed + Index index = _freeIndices.back(); + _freeIndices.pop_back(); + return index; + } + } + + void freeIndex(Index index) { + if (checkIndex(index)) { + _freeIndices.push_back(index); + } + } + + void clear() { + _freeIndices.clear(); + _nextNewIndex = 0; + } + }; + } + + + using Index = indexed_container::Index; + using IndexVector = indexed_container::Indices; + + using ProxyID = Index; + const ProxyID INVALID_PROXY_ID{ indexed_container ::INVALID_INDEX }; + +// Transaction is the mechanism to make any change to the Space. +// Whenever a new proxy need to be reset, +// or when an proxy changes its position or its size +// or when an proxy is destroyed +// These changes must be expressed through the corresponding command from the Transaction +// The Transaction is then queued on the Space so all the pending transactions can be consolidated and processed at the time +// of updating the space at the Frame boundary. +// +class Transaction { + friend class Space; +public: + using ProxyPayload = Sphere; + + using Reset = std::tuple; + using Remove = ProxyID; + using Update = std::tuple; + + using Resets = std::vector; + using Removes = std::vector; + using Updates = std::vector; + + Transaction() {} + ~Transaction() {} + + // Proxy transactions + void reset(ProxyID id, const ProxyPayload& sphere, const Owner& owner); + void reset(const Resets& resets); + void remove(ProxyID id); + void remove(const Removes& removes); + bool hasRemovals() const { return !_removedItems.empty(); } + + void update(ProxyID id, const ProxyPayload& sphere); + void update(const Updates& updates); + + void reserve(const std::vector& transactionContainer); + void merge(const std::vector& transactionContainer); + void merge(std::vector&& transactionContainer); + void merge(const Transaction& transaction); + void merge(Transaction&& transaction); + void clear(); + +protected: + + + Resets _resetItems; + Removes _removedItems; + Updates _updatedItems; +}; +typedef std::vector TransactionQueue; + +class Collection { +public: + Collection(); + ~Collection(); + + virtual void clear(); + + // This call is thread safe, can be called from anywhere to allocate a new ID + ProxyID allocateID(); + + // Check that the ID is valid and allocated for this collection, this a threadsafe call + bool isAllocatedID(const ProxyID& id) const; + + // THis is the total number of allocated proxies, this a threadsafe call + Index getNumAllocatedProxies() const { return _IDAllocator.getNumAllocatedIndices(); } + + // Enqueue transaction to the collection + void enqueueTransaction(const Transaction& transaction); + + // Enqueue transaction to the collection + void enqueueTransaction(Transaction&& transaction); + + // Enqueue end of frame transactions boundary + uint32_t enqueueFrame(); + + // Process the pending transactions queued + virtual void processTransactionQueue(); + +protected: + + // Thread safe elements that can be accessed from anywhere + indexed_container::Allocator<> _IDAllocator; + + std::mutex _transactionQueueMutex; + TransactionQueue _transactionQueue; + + + std::mutex _transactionFramesMutex; + using TransactionFrames = std::vector; + TransactionFrames _transactionFrames; + uint32_t _transactionFrameNumber{ 0 }; + + // Process one transaction frame + virtual void processTransactionFrame(const Transaction& transaction) = 0; +}; + +} // namespace workload + +#endif // hifi_workload_Transaction_h diff --git a/libraries/workload/src/workload/View.cpp b/libraries/workload/src/workload/View.cpp new file mode 100644 index 0000000000..8cb593d2a7 --- /dev/null +++ b/libraries/workload/src/workload/View.cpp @@ -0,0 +1,69 @@ +// +// View.cpp +// libraries/workload/src/workload +// +// Created by Sam Gateau 2018.03.05 +// Copyright 2018 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 "View.h" +#include + +using namespace workload; + +void View::setFov(float angleRad) { + float halfAngle = angleRad * 0.5f; + + fov_halfAngle_tan_cos_sin.x = halfAngle; + fov_halfAngle_tan_cos_sin.y = tanf(halfAngle); + fov_halfAngle_tan_cos_sin.z = cosf(halfAngle); + fov_halfAngle_tan_cos_sin.w = sinf(halfAngle); +} + +void View::makeHorizontal() { + direction = glm::normalize(glm::vec3(direction.x, 0.0f, direction.z)); +} + +View View::evalFromFrustum(const ViewFrustum& frustum, const glm::vec3& offset) { + View view; + view.origin = frustum.getPosition() + offset; + view.direction = frustum.getDirection(); + view.setFov(frustum.getFieldOfView()); + + return view; +} + +Sphere View::evalRegionSphere(const View& view, float originRadius, float maxDistance) { + float radius = (maxDistance + originRadius) / 2.0f; + float center = radius - originRadius; + return Sphere(view.origin + view.direction * center, radius); +} + +void View::updateRegionsDefault(View& view) { + std::vector config(Region::NUM_VIEW_REGIONS * 2, 0.0f); + + float refFar = 10.0f; + float refClose = 2.0f; + for (int i = 0; i < Region::NUM_VIEW_REGIONS; i++) { + float weight = i + 1.0f; + config[i * 2] = refClose; + config[i * 2 + 1] = refFar * weight; + refFar *= 2.0f; + } + updateRegionsFromBackFrontDistances(view, config.data()); +} + +void View::updateRegionsFromBackFronts(View& view) { + for (int i = 0; i < Region::NUM_VIEW_REGIONS; i++) { + view.regions[i] = evalRegionSphere(view, view.regionBackFronts[i].x, view.regionBackFronts[i].y); + } +} + +void View::updateRegionsFromBackFrontDistances(View& view, const float* configDistances) { + for (int i = 0; i < Region::NUM_VIEW_REGIONS; i++) { + view.regionBackFronts[i] = glm::vec2(configDistances[i * 2], configDistances[i * 2 + 1]); + } + updateRegionsFromBackFronts(view); +} diff --git a/libraries/workload/src/workload/View.h b/libraries/workload/src/workload/View.h new file mode 100644 index 0000000000..972caf5101 --- /dev/null +++ b/libraries/workload/src/workload/View.h @@ -0,0 +1,74 @@ +// +// View.h +// libraries/workload/src/workload +// +// Created by Sam Gateau 2018.03.05 +// Copyright 2018 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_workload_View_h +#define hifi_workload_View_h + +#include +#include +#include +#include + +#include "Region.h" + +class ViewFrustum; + +namespace workload { + +using Sphere = glm::vec4; + +class View { +public: + View() = default; + View(const View& view) = default; + + // View attributes: + + // direction + glm::vec3 direction{ 0.0f, 0.0f, -1.0f }; + + // Max radius + float maxRadius{ FLT_MAX }; + + // Fov stores the half field of view angle, and tan/cos/sin ready to go, default is fov of 90deg + glm::vec4 fov_halfAngle_tan_cos_sin { glm::quarter_pi(), 1.0f, glm::root_two() * 0.5f, glm::root_two() * 0.5f}; + + // Origin position + glm::vec3 origin{ 0.0f }; + + // Origin radius + float originRadius{ 0.5f }; + + // N regions distances + glm::vec2 regionBackFronts[Region::NUM_VIEW_REGIONS + 1]; + + // N regions spheres + Sphere regions[Region::NUM_VIEW_REGIONS]; + + // Set fov properties from angle + void setFov(float angleRad); + + // Helper function to force the direction in the XZ plane + void makeHorizontal(); + + static View evalFromFrustum(const ViewFrustum& frustum, const glm::vec3& offset = glm::vec3()); + static Sphere evalRegionSphere(const View& view, float originRadius, float maxDistance); + + static void updateRegionsDefault(View& view); + static void updateRegionsFromBackFronts(View& view); + static void updateRegionsFromBackFrontDistances(View& view, const float* configDistances); +}; + +using Views = std::vector; + +} // namespace workload + +#endif // hifi_workload_View_h diff --git a/libraries/workload/src/workload/ViewTask.cpp b/libraries/workload/src/workload/ViewTask.cpp new file mode 100644 index 0000000000..4b77733c52 --- /dev/null +++ b/libraries/workload/src/workload/ViewTask.cpp @@ -0,0 +1,160 @@ +// +// ViewTask.cpp +// libraries/workload/src/workload +// +// Created by Sam Gateau 2018.03.05 +// Copyright 2018 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 "ViewTask.h" + +using namespace workload; + + +void AssignSpaceViews::run(const WorkloadContextPointer& renderContext, const Input& inputs) { + // Just do what it says + renderContext->_space->setViews(inputs); +} + +void SetupViews::configure(const Config& config) { + data = config.data; +} + +void SetupViews::run(const WorkloadContextPointer& renderContext, const Input& inputs, Output& outputs) { + // If views are frozen don't use the input + if (!data.freezeViews) { + _views = inputs; + } + + auto& outViews = outputs; + outViews.clear(); + + // Filter the first view centerer on the avatar head if needed + if (_views.size() >= 2) { + if (data.useAvatarView) { + outViews.push_back(_views[0]); + outViews.insert(outViews.end(), _views.begin() + 2, _views.end()); + } else { + outViews.insert(outViews.end(), _views.begin() + 1, _views.end()); + } + } else { + outViews = _views; + } + + // Force frutum orientation horizontal if needed + if (outViews.size() > 0 && data.forceViewHorizontal) { + outViews[0].makeHorizontal(); + } + + // Force frutum orientation horizontal if needed + if (outViews.size() > 0 && data.simulateSecondaryCamera) { + auto view = outViews[0]; + auto secondaryDirectionFlat = glm::normalize(glm::vec3(view.direction.x, 0.0f, view.direction.z)); + auto secondaryDirection = glm::normalize(glm::vec3(secondaryDirectionFlat.z, 0.0f, -secondaryDirectionFlat.x)); + + view.origin += -20.0f * secondaryDirection; + view.direction = -secondaryDirection; + + outViews.insert(outViews.begin() + 1, view); + } + + // Update regions based on the current config + for (auto& v : outViews) { + View::updateRegionsFromBackFrontDistances(v, (float*) &data); + } + + // outViews is ready to be used +} + + +ControlViews::ControlViews() { + for (int32_t i = 0; i < workload::Region::NUM_VIEW_REGIONS; i++) { + regionBackFronts[i] = MIN_VIEW_BACK_FRONTS[i]; + regionRegulators[i] = Regulator(std::chrono::milliseconds(2), MIN_VIEW_BACK_FRONTS[i], MAX_VIEW_BACK_FRONTS[i], glm::vec2(RELATIVE_STEP_DOWN), glm::vec2(RELATIVE_STEP_UP)); + } +} + +void ControlViews::configure(const Config& config) { + _data = config.data; +} + +void ControlViews::run(const workload::WorkloadContextPointer& runContext, const Input& inputs, Output& outputs) { + const auto& inViews = inputs.get0(); + const auto& inTimings = inputs.get1(); + auto& outViews = outputs; + outViews.clear(); + outViews = inViews; + + if (_data.regulateViewRanges && inTimings.size()) { + regulateViews(outViews, inTimings); + } + + // Export the timings for debuging + if (inTimings.size()) { + _dataExport.timings[workload::Region::R1] = std::chrono::duration(inTimings[0]).count(); + _dataExport.timings[workload::Region::R2] = _dataExport.timings[workload::Region::R1]; + _dataExport.timings[workload::Region::R3] = std::chrono::duration(inTimings[1]).count(); + + auto config = std::static_pointer_cast(runContext->jobConfig); + config->dataExport = _dataExport; + config->emitDirty(); + } +} + +glm::vec2 Regulator::run(const Timing_ns& regulationDuration, const Timing_ns& measured, const glm::vec2& current) { + // Regulate next value based on current moving toward the goal budget + float error_ms = std::chrono::duration(_budget - measured).count(); + float coef = glm::clamp(error_ms / std::chrono::duration(regulationDuration).count(), -1.0f, 1.0f); + return current * (1.0f + coef * (error_ms < 0.0f ? _relativeStepDown : _relativeStepUp)); +} + +glm::vec2 Regulator::clamp(const glm::vec2& backFront) const { + // Clamp to min max + return glm::clamp(backFront, _minRange, _maxRange); +} + +void ControlViews::regulateViews(workload::Views& outViews, const workload::Timings& timings) { + + for (auto& outView : outViews) { + for (int32_t r = 0; r < workload::Region::NUM_VIEW_REGIONS; r++) { + outView.regionBackFronts[r] = regionBackFronts[r]; + } + } + + auto loopDuration = std::chrono::nanoseconds{ std::chrono::milliseconds(16) }; + regionBackFronts[workload::Region::R1] = regionRegulators[workload::Region::R1].run(loopDuration, timings[0], regionBackFronts[workload::Region::R1]); + regionBackFronts[workload::Region::R2] = regionRegulators[workload::Region::R2].run(loopDuration, timings[0], regionBackFronts[workload::Region::R2]); + regionBackFronts[workload::Region::R3] = regionRegulators[workload::Region::R3].run(loopDuration, timings[1], regionBackFronts[workload::Region::R3]); + + enforceRegionContainment(); + + _dataExport.ranges[workload::Region::R1] = regionBackFronts[workload::Region::R1]; + _dataExport.ranges[workload::Region::R2] = regionBackFronts[workload::Region::R2]; + _dataExport.ranges[workload::Region::R3] = regionBackFronts[workload::Region::R3]; + + for (auto& outView : outViews) { + outView.regionBackFronts[workload::Region::R1] = regionBackFronts[workload::Region::R1]; + outView.regionBackFronts[workload::Region::R2] = regionBackFronts[workload::Region::R2]; + outView.regionBackFronts[workload::Region::R3] = regionBackFronts[workload::Region::R3]; + + workload::View::updateRegionsFromBackFronts(outView); + } +} + +void ControlViews::enforceRegionContainment() { + // inner regions should never overreach outer + // and each region should never exceed its min/max limits + const glm::vec2 MIN_REGION_GAP = { 1.0f, 2.0f }; + // enforce outside --> in + for (int32_t i = workload::Region::NUM_VIEW_REGIONS - 2; i >= 0; --i) { + int32_t j = i + 1; + regionBackFronts[i] = regionRegulators[i].clamp(glm::min(regionBackFronts[i], regionBackFronts[j] - MIN_REGION_GAP)); + } + // enforce inside --> out + for (int32_t i = 1; i < workload::Region::NUM_VIEW_REGIONS; ++i) { + int32_t j = i - 1; + regionBackFronts[i] = regionRegulators[i].clamp(glm::max(regionBackFronts[i], regionBackFronts[j] + MIN_REGION_GAP)); + } +} diff --git a/libraries/workload/src/workload/ViewTask.h b/libraries/workload/src/workload/ViewTask.h new file mode 100644 index 0000000000..867f22d534 --- /dev/null +++ b/libraries/workload/src/workload/ViewTask.h @@ -0,0 +1,257 @@ +// +// ViewTask.h +// libraries/workload/src/workload +// +// Created by Sam Gateau 2018.03.05 +// Copyright 2018 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_workload_ViewTask_h +#define hifi_workload_ViewTask_h + +#include "Engine.h" + +template +QVariantList toVariantList(const QList &list) +{ + QVariantList newList; + foreach(const T &item, list) + newList << item; + + return newList; +} + +namespace workload { + + const std::vector MIN_VIEW_BACK_FRONTS = { + { 3.0f, 4.0f }, + { 6.0f, 8.0f }, + { 9.0f, 12.0f } + }; + + const std::vector MAX_VIEW_BACK_FRONTS = { + { 100.0f, 200.0f }, + { 150.0f, 300.0f }, + { 250.0f, 500.0f } + }; + + const float RELATIVE_STEP_DOWN = 0.05f; + const float RELATIVE_STEP_UP = 0.04f; + + class SetupViewsConfig : public Job::Config{ + Q_OBJECT + Q_PROPERTY(float r1Front READ getR1Front WRITE setR1Front NOTIFY dirty) + Q_PROPERTY(float r1Back READ getR1Back WRITE setR1Back NOTIFY dirty) + Q_PROPERTY(float r2Front READ getR2Front WRITE setR2Front NOTIFY dirty) + Q_PROPERTY(float r2Back READ getR2Back WRITE setR2Back NOTIFY dirty) + Q_PROPERTY(float r3Front READ getR3Front WRITE setR3Front NOTIFY dirty) + Q_PROPERTY(float r3Back READ getR3Back WRITE setR3Back NOTIFY dirty) + Q_PROPERTY(bool freezeViews READ getFreezeView WRITE setFreezeView NOTIFY dirty) + Q_PROPERTY(bool useAvatarView READ useAvatarView WRITE setUseAvatarView NOTIFY dirty) + Q_PROPERTY(bool forceViewHorizontal READ forceViewHorizontal WRITE setForceViewHorizontal NOTIFY dirty) + + Q_PROPERTY(bool simulateSecondaryCamera READ simulateSecondaryCamera WRITE setSimulateSecondaryCamera NOTIFY dirty) + + public: + + float getR1Front() const { return data.r1Front; } + float getR1Back() const { return data.r1Back; } + float getR2Front() const { return data.r2Front; } + float getR2Back() const { return data.r2Back; } + float getR3Front() const { return data.r3Front; } + float getR3Back() const { return data.r3Back; } + + void setR1Front(float d) { data.r1Front = d; emit dirty(); } + void setR1Back(float d) { data.r1Back = d; emit dirty(); } + void setR2Front(float d) { data.r2Front = d; emit dirty(); } + void setR2Back(float d) { data.r2Back = d; emit dirty(); } + void setR3Front(float d) { data.r3Front = d; emit dirty(); } + void setR3Back(float d) { data.r3Back = d; emit dirty(); } + + bool getFreezeView() const { return data.freezeViews; } + void setFreezeView(bool freeze) { data.freezeViews = freeze; emit dirty(); } + bool useAvatarView() const { return data.useAvatarView; } + void setUseAvatarView(bool use) { data.useAvatarView = use; emit dirty(); } + bool forceViewHorizontal() const { return data.forceViewHorizontal; } + void setForceViewHorizontal(bool use) { data.forceViewHorizontal = use; emit dirty(); } + + bool simulateSecondaryCamera() const { return data.simulateSecondaryCamera; } + void setSimulateSecondaryCamera(bool use) { data.simulateSecondaryCamera = use; emit dirty(); } + + struct Data { + float r1Back { MAX_VIEW_BACK_FRONTS[0].x }; + float r1Front { MAX_VIEW_BACK_FRONTS[0].y }; + + float r2Back{ MAX_VIEW_BACK_FRONTS[1].x }; + float r2Front{ MAX_VIEW_BACK_FRONTS[1].y }; + + float r3Back{ MAX_VIEW_BACK_FRONTS[2].x }; + float r3Front{ MAX_VIEW_BACK_FRONTS[2].y }; + + bool freezeViews{ false }; + bool useAvatarView{ false }; + bool forceViewHorizontal{ false }; + bool simulateSecondaryCamera{ false }; + } data; + + signals: + void dirty(); + }; + + class SetupViews { + public: + using Config = SetupViewsConfig; + using Input = Views; + using Output = Views; + using JobModel = Job::ModelIO; + + void configure(const Config& config); + void run(const workload::WorkloadContextPointer& renderContext, const Input& inputs, Output& outputs); + + protected: + Config::Data data; + Views _views; + }; + + class AssignSpaceViews { + public: + using Input = Views; + using JobModel = Job::ModelI; + + void run(const workload::WorkloadContextPointer& renderContext, const Input& inputs); + }; + + + class ControlViewsConfig : public workload::Job::Config { + Q_OBJECT + Q_PROPERTY(bool regulateViewRanges READ regulateViewRanges WRITE setRegulateViewRanges NOTIFY dirty) + + + Q_PROPERTY(float r1Timing READ r1Timing NOTIFY dirty) + Q_PROPERTY(float r2Timing READ r2Timing NOTIFY dirty) + Q_PROPERTY(float r3Timing READ r3Timing NOTIFY dirty) + + Q_PROPERTY(float r1RangeBack READ r1RangeBack NOTIFY dirty) + Q_PROPERTY(float r2RangeBack READ r2RangeBack NOTIFY dirty) + Q_PROPERTY(float r3RangeBack READ r3RangeBack NOTIFY dirty) + + Q_PROPERTY(float r1RangeFront READ r1RangeFront NOTIFY dirty) + Q_PROPERTY(float r2RangeFront READ r2RangeFront NOTIFY dirty) + Q_PROPERTY(float r3RangeFront READ r3RangeFront NOTIFY dirty) + /* + Q_PROPERTY(float r1MinRangeBack READ r1MinRangeBack WRITE setR1MinRangeBack NOTIFY dirty) + Q_PROPERTY(float r2MinRangeBack READ r2MinRangeBack WRITE setR2MinRangeBack NOTIFY dirty) + Q_PROPERTY(float r3MinRangeBack READ r3MinRangeBack WRITE setR3MinRangeBack NOTIFY dirty) + + Q_PROPERTY(float r1MinRangeFront READ r1MinRangeFront WRITE setR1MinRangeFront NOTIFY dirty) + Q_PROPERTY(float r2MinRangeFront READ r2MinRangeFront WRITE setR2MinRangeFront NOTIFY dirty) + Q_PROPERTY(float r3MinRangeFront READ r3MinRangeFront WRITE setR3MinRangeFront NOTIFY dirty) + + Q_PROPERTY(float r1MaxRangeBack READ r1MaxRangeBack WRITE setR1MaxRangeBack NOTIFY dirty) + Q_PROPERTY(float r2MaxRangeBack READ r2MaxRangeBack WRITE setR2MaxRangeBack NOTIFY dirty) + Q_PROPERTY(float r3MaxRangeBack READ r3MaxRangeBack WRITE setR3MaxRangeBack NOTIFY dirty) + + Q_PROPERTY(float r1MaxRangeFront READ r1MaxRangeFront WRITE setR1MaxRangeFront NOTIFY dirty) + Q_PROPERTY(float r2MaxRangeFront READ r2MaxRangeFront WRITE setR2MaxRangeFront NOTIFY dirty) + Q_PROPERTY(float r3MaxRangeFront READ r3MaxRangeFront WRITE setR3MaxRangeFront NOTIFY dirty) + + Q_PROPERTY(float r1SpeedDownBack READ r1SpeedDownBack WRITE setR1SpeedDownBack NOTIFY dirty) + Q_PROPERTY(float r2SpeedDownBack READ r2SpeedDownBack WRITE setR2SpeedDownBack NOTIFY dirty) + Q_PROPERTY(float r3SpeedDownBack READ r3SpeedDownBack WRITE setR3SpeedDownBack NOTIFY dirty) + + Q_PROPERTY(float r1SpeedDownFront READ r1SpeedDownFront WRITE setR1SpeedDownFront NOTIFY dirty) + Q_PROPERTY(float r2SpeedDownFront READ r2SpeedDownFront WRITE setR2SpeedDownFront NOTIFY dirty) + Q_PROPERTY(float r3SpeedDownFront READ r3SpeedDownFront WRITE setR3SpeedDownFront NOTIFY dirty) + + Q_PROPERTY(float r1SpeedUpBack READ r1SpeedUpBack WRITE setR1SpeedUpBack NOTIFY dirty) + Q_PROPERTY(float r2SpeedUpBack READ r2SpeedUpBack WRITE setR2SpeedUpBack NOTIFY dirty) + Q_PROPERTY(float r3SpeedUpBack READ r3SpeedUpBack WRITE setR3SpeedUpBack NOTIFY dirty) + + Q_PROPERTY(float r1SpeedUpFront READ r1SpeedUpFront WRITE setR1SpeedUpFront NOTIFY dirty) + Q_PROPERTY(float r2SpeedUpFront READ r2SpeedUpFront WRITE setR2SpeedUpFront NOTIFY dirty) + Q_PROPERTY(float r3SpeedUpFront READ r3SpeedUpFront WRITE setR3SpeedUpFront NOTIFY dirty)*/ + + public: + + bool regulateViewRanges() const { return data.regulateViewRanges; } + void setRegulateViewRanges(bool use) { data.regulateViewRanges = use; emit dirty(); } + + float r1Timing() const { return dataExport.timings[workload::Region::R1]; } + float r2Timing() const { return dataExport.timings[workload::Region::R2]; } + float r3Timing() const { return dataExport.timings[workload::Region::R3]; } + + float r1RangeBack() const { return dataExport.ranges[workload::Region::R1].x; } + float r2RangeBack() const { return dataExport.ranges[workload::Region::R2].x; } + float r3RangeBack() const { return dataExport.ranges[workload::Region::R3].x; } + + float r1RangeFront() const { return dataExport.ranges[workload::Region::R1].y; } + float r2RangeFront() const { return dataExport.ranges[workload::Region::R2].y; } + float r3RangeFront() const { return dataExport.ranges[workload::Region::R3].y; } + + + struct Data { + bool regulateViewRanges{ false }; + } data; + + struct DataExport { + static const int SIZE{ workload::Region::NUM_VIEW_REGIONS }; + float timings[SIZE]; + glm::vec2 ranges[SIZE]; + QList _timings { 6, 2.0 }; + + } dataExport; + + void emitDirty() { emit dirty(); } + + public slots: + Q_INVOKABLE QVariantList getTimings() const { return toVariantList(dataExport._timings); } + signals: + void dirty(); + }; + + struct Regulator { + using Timing_ns = std::chrono::nanoseconds; + Timing_ns _budget{ std::chrono::milliseconds(2) }; + glm::vec2 _minRange{ MIN_VIEW_BACK_FRONTS[0] }; + glm::vec2 _maxRange{ MAX_VIEW_BACK_FRONTS[0] }; + + glm::vec2 _relativeStepDown{ RELATIVE_STEP_DOWN }; + glm::vec2 _relativeStepUp{ RELATIVE_STEP_UP }; + + + Regulator() {} + Regulator(const Timing_ns& budget_ns, const glm::vec2& minRange, const glm::vec2& maxRange, const glm::vec2& relativeStepDown, const glm::vec2& relativeStepUp) : + _budget(budget_ns), _minRange(minRange), _maxRange(maxRange), _relativeStepDown(relativeStepDown), _relativeStepUp(relativeStepUp) {} + + glm::vec2 run(const Timing_ns& regulationDuration, const Timing_ns& measured, const glm::vec2& current); + glm::vec2 clamp(const glm::vec2& backFront) const; + }; + + class ControlViews { + public: + using Config = ControlViewsConfig; + using Input = workload::VaryingSet2; + using Output = workload::Views; + using JobModel = workload::Job::ModelIO; + + ControlViews(); + + void configure(const Config& config); + void run(const workload::WorkloadContextPointer& runContext, const Input& inputs, Output& outputs); + + std::array regionBackFronts; + std::array regionRegulators; + + void regulateViews(workload::Views& views, const workload::Timings& timings); + void enforceRegionContainment(); + + protected: + Config::Data _data; + Config::DataExport _dataExport; + }; + +} // namespace workload + +#endif // hifi_workload_ViewTask_h diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index 943af301a2..392d990638 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -51,7 +51,7 @@ void OculusControllerManager::checkForConnectedDevices() { unsigned int controllerConnected = ovr_GetConnectedControllerTypes(session); if (!_remote && (controllerConnected & ovrControllerType_Remote) == ovrControllerType_Remote) { - if (OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Remote, &_inputState))) { + if (OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Remote, &_remoteInputState))) { auto userInputMapper = DependencyManager::get(); _remote = std::make_shared(*this); userInputMapper->registerDevice(_remote); @@ -59,7 +59,7 @@ void OculusControllerManager::checkForConnectedDevices() { } if (!_touch && (controllerConnected & ovrControllerType_Touch) != 0) { - if (OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Touch, &_inputState))) { + if (OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Touch, &_touchInputState))) { auto userInputMapper = DependencyManager::get(); _touch = std::make_shared(*this); userInputMapper->registerDevice(_touch); @@ -90,13 +90,13 @@ void OculusControllerManager::pluginUpdate(float deltaTime, const controller::In ovr::withSession([&](ovrSession session) { if (_touch) { - updateTouch = OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Touch, &_inputState)); + updateTouch = OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Touch, &_touchInputState)); if (!updateTouch) { qCWarning(oculusLog) << "Unable to read Oculus touch input state" << ovr::getError(); } } if (_remote) { - updateRemote = OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Remote, &_inputState)); + updateRemote = OVR_SUCCESS(ovr_GetInputState(session, ovrControllerType_Remote, &_remoteInputState)); if (!updateRemote) { qCWarning(oculusLog) << "Unable to read Oculus remote input state" << ovr::getError(); } @@ -194,7 +194,7 @@ QString OculusControllerManager::RemoteDevice::getDefaultMappingConfig() const { void OculusControllerManager::RemoteDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { _buttonPressedMap.clear(); - const auto& inputState = _parent._inputState; + const auto& inputState = _parent._remoteInputState; for (const auto& pair : BUTTON_MAP) { if (inputState.Buttons & pair.first) { _buttonPressedMap.insert(pair.second); @@ -257,7 +257,7 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, using namespace controller; // Axes - const auto& inputState = _parent._inputState; + const auto& inputState = _parent._touchInputState; _axisStateMap[LX] = inputState.Thumbstick[ovrHand_Left].x; _axisStateMap[LY] = inputState.Thumbstick[ovrHand_Left].y; _axisStateMap[LT] = inputState.IndexTrigger[ovrHand_Left]; diff --git a/plugins/oculus/src/OculusControllerManager.h b/plugins/oculus/src/OculusControllerManager.h index b08d54babe..ee06115b26 100644 --- a/plugins/oculus/src/OculusControllerManager.h +++ b/plugins/oculus/src/OculusControllerManager.h @@ -103,7 +103,8 @@ private: void checkForConnectedDevices(); - ovrInputState _inputState {}; + ovrInputState _remoteInputState {}; + ovrInputState _touchInputState {}; RemoteDevice::Pointer _remote; TouchDevice::Pointer _touch; static const char* NAME; diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 635d9d0529..0deb4c75e7 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -48,7 +48,6 @@ const quint64 CALIBRATION_TIMELAPSE = 1 * USECS_PER_SECOND; static const char* MENU_PARENT = "Avatar"; static const char* MENU_NAME = "Vive Controllers"; static const char* MENU_PATH = "Avatar" ">" "Vive Controllers"; -static const char* RENDER_CONTROLLERS = "Render Hand Controllers"; static const int MIN_HEAD = 1; static const int MIN_PUCK_COUNT = 2; @@ -205,11 +204,6 @@ bool ViveControllerManager::activate() { return false; } - _container->addMenu(MENU_PATH); - _container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, RENDER_CONTROLLERS, - [this](bool clicked) { this->setRenderControllers(clicked); }, - true, true); - enableOpenVrKeyboard(_container); // register with UserInputMapper @@ -224,9 +218,6 @@ void ViveControllerManager::deactivate() { disableOpenVrKeyboard(); - _container->removeMenuItem(MENU_NAME, RENDER_CONTROLLERS); - _container->removeMenu(MENU_PATH); - if (_system) { _container->makeRenderingContextCurrent(); releaseOpenVrSystem(); diff --git a/plugins/openvr/src/ViveControllerManager.h b/plugins/openvr/src/ViveControllerManager.h index f3631ece9d..30f8590062 100644 --- a/plugins/openvr/src/ViveControllerManager.h +++ b/plugins/openvr/src/ViveControllerManager.h @@ -57,8 +57,6 @@ public: void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override; - void setRenderControllers(bool renderControllers) { _renderControllers = renderControllers; } - virtual void saveSettings() const override; virtual void loadSettings() override; @@ -219,7 +217,6 @@ private: int _leftHandRenderID { 0 }; int _rightHandRenderID { 0 }; - bool _renderControllers { false }; vr::IVRSystem* _system { nullptr }; std::shared_ptr _inputDevice { std::make_shared(_system) }; diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index ddbeaaeea9..b275660c0f 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -21,6 +21,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/bubble.js", "system/snapshot.js", "system/pal.js", // "system/mod.js", // older UX, if you prefer + "system/avatarapp.js", "system/makeUserConnection.js", "system/tablet-goto.js", "system/marketplaces/marketplaces.js", diff --git a/scripts/developer/facialExpressions.js b/scripts/developer/facialExpressions.js new file mode 100644 index 0000000000..37a4f4f796 --- /dev/null +++ b/scripts/developer/facialExpressions.js @@ -0,0 +1,374 @@ +// +// facialExpressions.js +// A script to set different emotions using blend shapes +// +// Author: Elisa Lupin-Jimenez +// Copyright High Fidelity 2018 +// +// 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 TABLET_BUTTON_NAME = "EMOTIONS"; + // TODO: ADD HTML LANDING PAGE + + var TRANSITION_TIME_SECONDS = 0.25; + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var icon = "https://hifi-content.s3.amazonaws.com/elisalj/emoji_scripts/icons/emoji-i.svg"; + var activeIcon = "https://hifi-content.s3.amazonaws.com/elisalj/emoji_scripts/icons/emoji-a.svg"; + var isActive = true; + + var controllerMappingName; + var controllerMapping; + + var tabletButton = tablet.addButton({ + icon: icon, + activeIcon: activeIcon, + text: TABLET_BUTTON_NAME, + isActive: true + }); + + var toggle = function() { + isActive = !isActive; + tabletButton.editProperties({isActive: isActive}); + if (isActive) { + Controller.enableMapping(controllerMappingName); + } else { + setEmotion(DEFAULT); + Controller.disableMapping(controllerMappingName); + } + }; + + tabletButton.clicked.connect(toggle); + + var DEFAULT = { + "EyeOpen_L": 0.00, + "EyeOpen_R": 0.00, + "EyeBlink_L": 0.00, + "EyeBlink_R": 0.00, + "EyeSquint_L": 0.00, + "EyeSquint_R": 0.00, + "BrowsD_L": 0.00, + "BrowsD_R": 0.00, + "BrowsU_L": 0.00, + "BrowsU_C": 0.00, + "JawOpen": 0.00, + "JawFwd": 0.00, + "MouthFrown_L": 0.00, + "MouthFrown_R": 0.00, + "MouthSmile_L": 0.00, + "MouthSmile_R": 0.00, + "MouthDimple_L": 0.00, + "MouthDimple_R": 0.00, + "LipsUpperClose": 0.00, + "LipsLowerClose": 0.00, + "LipsLowerOpen": 0.00, + "ChinUpperRaise": 0.00, + "Sneer": 0.00, + "Puff": 0.00 + }; + + var SMILE = { + "EyeOpen_L": 0.00, + "EyeOpen_R": 0.00, + "EyeBlink_L": 0.30, + "EyeBlink_R": 0.30, + "EyeSquint_L": 0.90, + "EyeSquint_R": 0.90, + "BrowsD_L": 1.00, + "BrowsD_R": 1.00, + "BrowsU_L": 0.00, + "BrowsU_C": 0.00, + "JawOpen": 0.00, + "JawFwd": 0.00, + "MouthFrown_L": 0.00, + "MouthFrown_R": 0.00, + "MouthSmile_L": 1.00, + "MouthSmile_R": 1.00, + "MouthDimple_L": 1.00, + "MouthDimple_R": 1.00, + "LipsUpperClose": 0.40, + "LipsLowerClose": 0.30, + "LipsLowerOpen": 0.25, + "ChinUpperRaise": 0.35, + "Sneer": 0.00, + "Puff": 0.00 + }; + + var LAUGH = { + "EyeOpen_L": 0.00, + "EyeOpen_R": 0.00, + "EyeBlink_L": 0.45, + "EyeBlink_R": 0.45, + "EyeSquint_L": 0.75, + "EyeSquint_R": 0.75, + "BrowsD_L": 0.00, + "BrowsD_R": 0.00, + "BrowsU_L": 0.00, + "BrowsU_C": 0.50, + "JawOpen": 0.50, + "JawFwd": 0.00, + "MouthFrown_L": 0.00, + "MouthFrown_R": 0.00, + "MouthSmile_L": 1.00, + "MouthSmile_R": 1.00, + "MouthDimple_L": 1.00, + "MouthDimple_R": 1.00, + "LipsUpperClose": 0.00, + "LipsLowerClose": 0.00, + "LipsLowerOpen": 0.00, + "ChinUpperRaise": 0.30, + "Sneer": 1.00, + "Puff": 0.30 + }; + + var FLIRT = { + "EyeOpen_L": 0.00, + "EyeOpen_R": 0.00, + "EyeBlink_L": 0.50, + "EyeBlink_R": 0.50, + "EyeSquint_L": 0.25, + "EyeSquint_R": 0.25, + "BrowsD_L": 0.00, + "BrowsD_R": 1.00, + "BrowsU_L": 0.55, + "BrowsU_C": 0.00, + "JawOpen": 0.00, + "JawFwd": 0.00, + "MouthFrown_L": 0.00, + "MouthFrown_R": 0.00, + "MouthSmile_L": 0.50, + "MouthSmile_R": 0.00, + "MouthDimple_L": 1.00, + "MouthDimple_R": 1.00, + "LipsUpperClose": 0.00, + "LipsLowerClose": 0.00, + "LipsLowerOpen": 0.00, + "ChinUpperRaise": 0.00, + "Sneer": 0.00, + "Puff": 0.00 + }; + + var SAD = { + "EyeOpen_L": 0.00, + "EyeOpen_R": 0.00, + "EyeBlink_L": 0.30, + "EyeBlink_R": 0.30, + "EyeSquint_L": 0.30, + "EyeSquint_R": 0.30, + "BrowsD_L": 0.00, + "BrowsD_R": 0.00, + "BrowsU_L": 0.00, + "BrowsU_C": 0.50, + "JawOpen": 0.00, + "JawFwd": 0.80, + "MouthFrown_L": 0.80, + "MouthFrown_R": 0.80, + "MouthSmile_L": 0.00, + "MouthSmile_R": 0.00, + "MouthDimple_L": 0.00, + "MouthDimple_R": 0.00, + "LipsUpperClose": 0.00, + "LipsLowerClose": 0.50, + "LipsLowerOpen": 0.00, + "ChinUpperRaise": 0.00, + "Sneer": 0.00, + "Puff": 0.00 + }; + + var ANGRY = { + "EyeOpen_L": 1.00, + "EyeOpen_R": 1.00, + "EyeBlink_L": 0.00, + "EyeBlink_R": 0.00, + "EyeSquint_L": 1.00, + "EyeSquint_R": 1.00, + "BrowsD_L": 1.00, + "BrowsD_R": 1.00, + "BrowsU_L": 0.00, + "BrowsU_C": 0.00, + "JawOpen": 0.00, + "JawFwd": 0.00, + "MouthFrown_L": 0.50, + "MouthFrown_R": 0.50, + "MouthSmile_L": 0.00, + "MouthSmile_R": 0.00, + "MouthDimple_L": 0.00, + "MouthDimple_R": 0.00, + "LipsUpperClose": 0.50, + "LipsLowerClose": 0.50, + "LipsLowerOpen": 0.00, + "ChinUpperRaise": 0.00, + "Sneer": 0.50, + "Puff": 0.00 + }; + + var FEAR = { + "EyeOpen_L": 1.00, + "EyeOpen_R": 1.00, + "EyeBlink_L": 0.00, + "EyeBlink_R": 0.00, + "EyeSquint_L": 0.00, + "EyeSquint_R": 0.00, + "BrowsD_L": 0.00, + "BrowsD_R": 0.00, + "BrowsU_L": 0.00, + "BrowsU_C": 1.00, + "JawOpen": 0.15, + "JawFwd": 0.00, + "MouthFrown_L": 0.30, + "MouthFrown_R": 0.30, + "MouthSmile_L": 0.00, + "MouthSmile_R": 0.00, + "MouthDimple_L": 0.00, + "MouthDimple_R": 0.00, + "LipsUpperClose": 0.00, + "LipsLowerClose": 0.00, + "LipsLowerOpen": 0.00, + "ChinUpperRaise": 0.00, + "Sneer": 0.00, + "Puff": 0.00 + }; + + var DISGUST = { + "EyeOpen_L": 0.00, + "EyeOpen_R": 0.00, + "EyeBlink_L": 0.25, + "EyeBlink_R": 0.25, + "EyeSquint_L": 1.00, + "EyeSquint_R": 1.00, + "BrowsD_L": 1.00, + "BrowsD_R": 1.00, + "BrowsU_L": 0.00, + "BrowsU_C": 0.00, + "JawOpen": 0.00, + "JawFwd": 0.00, + "MouthFrown_L": 1.00, + "MouthFrown_R": 1.00, + "MouthSmile_L": 0.00, + "MouthSmile_R": 0.00, + "MouthDimple_L": 0.00, + "MouthDimple_R": 0.00, + "LipsUpperClose": 0.00, + "LipsLowerClose": 0.75, + "LipsLowerOpen": 0.00, + "ChinUpperRaise": 0.75, + "Sneer": 1.00, + "Puff": 0.00 + }; + + + function mixValue(valueA, valueB, percentage) { + return valueA + ((valueB - valueA) * percentage); + } + + var lastEmotionUsed = DEFAULT; + var emotion = DEFAULT; + var isChangingEmotion = false; + var changingEmotionPercentage = 0.0; + + Script.update.connect(function(deltaTime) { + if (!isChangingEmotion) { + return; + } + changingEmotionPercentage += deltaTime / TRANSITION_TIME_SECONDS; + if (changingEmotionPercentage >= 1.0) { + changingEmotionPercentage = 1.0; + isChangingEmotion = false; + if (emotion === DEFAULT) { + MyAvatar.hasScriptedBlendshapes = false; + } + } + for (var blendshape in emotion) { + MyAvatar.setBlendshape(blendshape, + mixValue(lastEmotionUsed[blendshape], emotion[blendshape], changingEmotionPercentage)); + } + }); + + function setEmotion(currentEmotion) { + if (emotion !== lastEmotionUsed) { + lastEmotionUsed = emotion; + } + if (currentEmotion !== lastEmotionUsed) { + changingEmotionPercentage = 0.0; + emotion = currentEmotion; + isChangingEmotion = true; + MyAvatar.hasScriptedBlendshapes = true; + } + } + + + controllerMappingName = 'Hifi-FacialExpressions-Mapping'; + controllerMapping = Controller.newMapping(controllerMappingName); + + controllerMapping.from(Controller.Hardware.Keyboard.H).to(function(value) { + if (value !== 0) { + setEmotion(SMILE); + } + }); + + controllerMapping.from(Controller.Hardware.Keyboard.J).to(function(value) { + if (value !== 0) { + setEmotion(LAUGH); + } + }); + + controllerMapping.from(Controller.Hardware.Keyboard.K).to(function(value) { + if (value !== 0) { + setEmotion(FLIRT); + } + }); + + controllerMapping.from(Controller.Hardware.Keyboard.L).to(function(value) { + if (value !== 0) { + setEmotion(SAD); + } + }); + + controllerMapping.from(Controller.Hardware.Keyboard.V).to(function(value) { + if (value !== 0) { + setEmotion(ANGRY); + } + }); + + controllerMapping.from(Controller.Hardware.Keyboard.B).to(function(value) { + if (value !== 0) { + setEmotion(FEAR); + } + }); + + controllerMapping.from(Controller.Hardware.Keyboard.M).to(function(value) { + if (value !== 0) { + setEmotion(DISGUST); + } + }); + + controllerMapping.from(Controller.Hardware.Keyboard.N).to(function(value) { + if (value !== 0) { + setEmotion(DEFAULT); + } + }); + + Controller.enableMapping(controllerMappingName); + + Script.scriptEnding.connect(function() { + tabletButton.clicked.disconnect(toggle); + tablet.removeButton(tabletButton); + Controller.disableMapping(controllerMappingName); + + if (emotion !== DEFAULT || isChangingEmotion) { + isChangingEmotion = false; + for (var blendshape in DEFAULT) { + MyAvatar.setBlendshape(blendshape, DEFAULT[blendshape]); + } + MyAvatar.hasScriptedBlendshapes = false; + } + }); + +}()); \ No newline at end of file diff --git a/scripts/developer/tests/ControlsGallery.qml b/scripts/developer/tests/ControlsGallery.qml new file mode 100644 index 0000000000..ceb8a26dc9 --- /dev/null +++ b/scripts/developer/tests/ControlsGallery.qml @@ -0,0 +1,103 @@ +import QtQuick 2.10 +import QtQuick.Window 2.10 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import "qrc:////qml//styles-uit" as HifiStylesUit +import "qrc:////qml//controls-uit" as HifiControlsUit + +//uncomment to use from qmlscratch tool +//import '../../../interface/resources/qml/controls-uit' as HifiControlsUit +//import '../../../interface/resources/qml/styles-uit' + +//uncomment to use with HIFI_USE_SOURCE_TREE_RESOURCES=1 +//import '../../../resources/qml/controls-uit' as HifiControlsUit +//import '../../../resources/qml/styles-uit' + +Item { + visible: true + width: 640 + height: 480 + + Introspector { + id: introspector + properties: ['realFrom', 'realTo', 'realValue', 'realStepSize', 'decimals'] + visible: true + y: 50 + x: 130 + } + + HifiStylesUit.HifiConstants { + id: hifi + } + + TabBar { + id: bar + width: parent.width + TabButton { + text: "Spinbox" + } + TabButton { + text: "... Other Controls" + } + } + + StackLayout { + id: controlsLayout + currentIndex: bar.currentIndex + anchors.top: bar.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 20 + + Item { + id: spinboxTab + anchors.fill: parent + + Column { + spacing: 20 + + HifiControlsUit.SpinBox { + realValue: 5.0 + realFrom: 16.0 + realTo: 20.0 + decimals: 2 + realStepSize: 0.01 + + width: 100 + height: 30 + + colorScheme: hifi.colorSchemes.dark + + onFocusChanged: { + if(focus) { + introspector.object = this + } + } + } + + HifiControlsUit.SpinBox { + realValue: 5.0 + realFrom: 1.0 + realTo: 20.0 + decimals: 2 + realStepSize: 0.01 + + width: 100 + height: 30 + + colorScheme: hifi.colorSchemes.light + + onFocusChanged: { + if(focus) { + introspector.object = this + } + } + } + } + } + Item { + id: otherTab + } + } +} diff --git a/scripts/developer/tests/Introspector.qml b/scripts/developer/tests/Introspector.qml new file mode 100644 index 0000000000..d21f5da976 --- /dev/null +++ b/scripts/developer/tests/Introspector.qml @@ -0,0 +1,166 @@ +import QtQuick 2.1; +import QtQuick.Window 2.1; + +MouseArea { + id: base; + opacity: 0.65; + // anchors.fill: parent; + width: 400; + height: 300; + + drag.target: list; + onWheel: { } + + onClicked: { object = null } + property var object: null + onObjectChanged: { + visible = (object != null) + } + + property var properties: [] + onPropertiesChanged: { + console.debug('properties: ', JSON.stringify(properties, 4, 0)) + } + + function getPropertiesList(obj) { + var props = []; + var propertiesObject = obj; + if(properties.length !== 0) { + propertiesObject = {}; + for(var i = 0; i < properties.length; ++i) { + propertiesObject[properties[i]] = properties[i]; + } + } + + for(var prop in propertiesObject) { + + var info = {'name' : prop}; + var value = obj[prop]; + var typeOfValue = typeof(value); + + if(typeof(value) === 'string') { + info['type'] = 'string' + } else if(typeof(value) === 'number') { + if(Number.isInteger(value)) + info['type'] = 'int' + else + info['type'] = 'float' + } else if(typeof(value) === 'boolean') { + info['type'] = 'boolean' + } else if(typeof(value) === 'function') { + continue; + } + + /* + if(prop !== 'parent' && prop !== 'data' && prop !== 'children') + console.debug('typeof(value): ', typeof(value), JSON.stringify(value, null, 4)); + */ + + info['subName'] = '' + props.push(info); + } + + return props; + } + + Rectangle { + color: "lightgray"; + anchors.fill: list; + anchors.margins: -50; + } + ListView { + id: list; + x: 50; + y: 50; + width: 400; + height: 300; + spacing: 5; + model: object !== null ? getPropertiesList(object) : []; + header: Text { + text: object !== null ? object.toString () : ''; + font.bold: true; + font.pixelSize: 20; + } + delegate: Row { + spacing: 20; + + Column { + width: 180; + + Text { + text: (modelData ["subName"] !== "" ? (modelData ["name"] + "." + modelData ["subName"]) : modelData ["name"]); + font.pixelSize: 16; + } + } + Column { + width: 200; + + Text { + text: { + return modelData ["type"] + } + font.pixelSize: 10; + } + TextInput { + id: input; + text: display; + width: parent.width; + font.pixelSize: 16; + font.underline: (text !== display); + Keys.onReturnPressed: { save (); } + Keys.onEnterPressed: { save (); } + Keys.onEscapePressed: { cancel (); } + + property string display : ""; + + function save () { + var tmp; + switch (modelData ["type"]) { + case 'boolean': + tmp = (text === "true" || text === "1"); + break; + case 'float': + tmp = parseFloat (text); + break; + case 'int': + tmp = parseInt (text); + break; + case 'string': + tmp = text; + break; + + default: + break; + } + if (modelData ["subName"] !== "") { + object [modelData ["name"]][modelData ["subName"]] = tmp; + } + else { + object [modelData ["name"]] = tmp; + } + text = display; + } + + function cancel () { + text = display; + } + + Binding on text { value: input.display; } + Binding on display { + value: { + var ret = (modelData ["subName"] !== "" + ? object [modelData ["name"]][modelData ["subName"]] + : object [modelData ["name"]]); + return ret.toString (); + } + } + Rectangle { + z: -1; + color: "white"; + anchors.fill: parent; + } + } + } + } + } +} diff --git a/scripts/developer/tests/agentAPITest.js b/scripts/developer/tests/agentAPITest.js new file mode 100644 index 0000000000..b7d21efbdf --- /dev/null +++ b/scripts/developer/tests/agentAPITest.js @@ -0,0 +1,55 @@ +// agentAPITest.js +// scripts/developer/tests +// +// Created by Thijs Wenker on 7/23/18. +// Copyright 2018 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 + +var SOUND_DATA = { url: "http://hifi-content.s3.amazonaws.com/howard/sounds/piano1.wav" }; + +// getSound function from crowd-agent.js +function getSound(data, callback) { // callback(sound) when downloaded (which may be immediate). + var sound = SoundCache.getSound(data.url); + if (sound.downloaded) { + return callback(sound); + } + function onDownloaded() { + sound.ready.disconnect(onDownloaded); + callback(sound); + } + sound.ready.connect(onDownloaded); +} + + +function agentAPITest() { + console.warn('Agent.isAvatar =', Agent.isAvatar); + + Agent.isAvatar = true; + console.warn('Agent.isAvatar =', Agent.isAvatar); + + console.warn('Agent.isListeningToAudioStream =', Agent.isListeningToAudioStream); + + Agent.isListeningToAudioStream = true; + console.warn('Agent.isListeningToAudioStream =', Agent.isListeningToAudioStream); + + console.warn('Agent.isNoiseGateEnabled =', Agent.isNoiseGateEnabled); + + Agent.isNoiseGateEnabled = true; + console.warn('Agent.isNoiseGateEnabled =', Agent.isNoiseGateEnabled); + console.warn('Agent.lastReceivedAudioLoudness =', Agent.lastReceivedAudioLoudness); + console.warn('Agent.sessionUUID =', Agent.sessionUUID); + + getSound(SOUND_DATA, function (sound) { + console.warn('Agent.isPlayingAvatarSound =', Agent.isPlayingAvatarSound); + Agent.playAvatarSound(sound); + console.warn('Agent.isPlayingAvatarSound =', Agent.isPlayingAvatarSound); + }); +} + +if (Script.context === "agent") { + agentAPITest(); +} else { + console.error('This script should be run as agent script. EXITING.'); +} diff --git a/scripts/developer/tests/controlsGallery.js b/scripts/developer/tests/controlsGallery.js new file mode 100644 index 0000000000..dc3fa7ba3c --- /dev/null +++ b/scripts/developer/tests/controlsGallery.js @@ -0,0 +1,23 @@ +(function() { // BEGIN LOCAL_SCOPE + + console.debug('controlsGallery: creating window') + + var qml = Script.resolvePath('ControlsGallery.qml'); + var qmlWindow = new OverlayWindow({ + title: 'Hifi Controls Gallery', + source: qml, + height: 480, + width: 640, + visible: true + }); + + console.debug('controlsGallery: creating window... done') + + qmlWindow.closed.connect(function() { Script.stop(); }); + + Script.scriptEnding.connect(function() { + console.debug('controlsGallery: end of scripting') + delete qmlWindow; + }); + +}()); // END LOCAL_SCOPE diff --git a/scripts/developer/tests/interactiveWindowTest.js b/scripts/developer/tests/interactiveWindowTest.js new file mode 100644 index 0000000000..c17deba617 --- /dev/null +++ b/scripts/developer/tests/interactiveWindowTest.js @@ -0,0 +1,34 @@ +// +// interactiveWindowTest.js +// +// Created by Thijs Wenker on 2018-07-03 +// Copyright 2018 High Fidelity, Inc. +// +// An example of an interactive window that toggles presentation mode when toggling HMD on/off +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +function getPreferredPresentationMode() { + return HMD.active ? Desktop.PresentationMode.VIRTUAL : Desktop.PresentationMode.NATIVE; +} + +function getPreferredTitle() { + return HMD.active ? 'Virtual Desktop Window' : 'Native Desktop Window'; +} + +var virtualWindow = Desktop.createWindow(Script.resourcesPath() + 'qml/OverlayWindowTest.qml', { + title: getPreferredTitle(), + flags: Desktop.ALWAYS_ON_TOP, + presentationMode: getPreferredPresentationMode(), + size: {x: 500, y: 400} +}); + +HMD.displayModeChanged.connect(function() { + virtualWindow.presentationMode = getPreferredPresentationMode(); + virtualWindow.title = getPreferredTitle(); +}); + +Script.scriptEnding.connect(function() { + virtualWindow.close(); +}); diff --git a/scripts/developer/tests/raypickTester.js b/scripts/developer/tests/raypickTester.js new file mode 100644 index 0000000000..cebee4f29a --- /dev/null +++ b/scripts/developer/tests/raypickTester.js @@ -0,0 +1,73 @@ +// raypickTester.js +// +// display intersection details (including material) when hovering over entities/avatars/overlays +// + +/* eslint-disable comma-dangle, no-empty, no-magic-numbers */ + +var PICK_FILTERS = Picks.PICK_ENTITIES | Picks.PICK_OVERLAYS | Picks.PICK_AVATARS | Picks.PICK_INCLUDE_NONCOLLIDABLE; +var HAND_JOINT = '_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND'.replace('RIGHT', MyAvatar.getDominantHand().toUpperCase()); +var JOINT_NAME = HMD.active ? HAND_JOINT : 'Mouse'; +var UPDATE_MS = 1000/30; + +// create tect3d overlay to display hover results +var overlayID = Overlays.addOverlay('text3d', { + text: 'hover', + visible: false, + backgroundAlpha: 0, + isFacingAvatar: true, + lineHeight: 0.05, + dimensions: Vec3.HALF, +}); +Script.scriptEnding.connect(function() { + Overlays.deleteOverlay(overlayID); +}); + +// create raycast picker +var pickID = Picks.createPick(PickType.Ray, { + joint: JOINT_NAME, + filter: PICK_FILTERS, + enabled: true, +}); +var blacklist = [ overlayID ]; // exclude hover text from ray pick results +Picks.setIgnoreItems(pickID, blacklist); +Script.scriptEnding.connect(function() { + Picks.removePick(pickID); +}); + +// query object materials (using the Graphics.* API) +function getSubmeshMaterial(objectID, shapeID) { + try { + var materialLayers = Graphics.getModel(objectID).materialLayers; + var shapeMaterialLayers = materialLayers[shapeID]; + return shapeMaterialLayers[0].material; + } catch (e) { + return { name: '' }; + } +} + +// refresh hover overlay text based on intersection results +function updateOverlay(overlayID, result) { + var material = this.getSubmeshMaterial(result.objectID, result.extraInfo.shapeID); + var position = Vec3.mix(result.searchRay.origin, result.intersection, 0.5); + var extraInfo = result.extraInfo; + var text = [ + 'mesh: ' + extraInfo.subMeshName, + 'materialName: ' + material.name, + 'type: ' + Entities.getNestableType(result.objectID), + 'distance: ' + result.distance.toFixed(2)+'m', + ['submesh: ' + extraInfo.subMeshIndex, 'part: '+extraInfo.partIndex, 'shape: '+extraInfo.shapeID].join(' | '), + ].filter(Boolean).join('\n'); + + Overlays.editOverlay(overlayID, { + text: text, + position: position, + visible: result.intersects, + }); +} + +// monitor for enw results at 30fps +Script.setInterval(function() { + var result = Picks.getPrevPickResult(pickID); + updateOverlay(overlayID, result); +}, UPDATE_MS); diff --git a/scripts/developer/utilities/lib/jet/jet.js b/scripts/developer/utilities/lib/jet/jet.js index 16840e59bd..85842b8861 100644 --- a/scripts/developer/utilities/lib/jet/jet.js +++ b/scripts/developer/utilities/lib/jet/jet.js @@ -13,7 +13,7 @@ // traverse task tree function task_traverse(root, functor, depth) { if (root.isTask()) { - depth++; + depth++; for (var i = 0; i -GRID_HALFSIZE; i--) { + for (j = -GRID_HALFSIZE; j < GRID_HALFSIZE; j++) { + backdrop.push(addObject(i, j, GRID_DROP_C, lifetime)); + } + } + +} + +function addFloor(lifetime) { + var floorDim = { x:GRID_WORLD_SIZE + 2 * GRID_WORLD_MARGIN, y: TILE_DIM.y, z:GRID_WORLD_SIZE + 2 * GRID_WORLD_MARGIN}; + var center = getStagePosOriAt(0, 0, -0.5).pos; + + return (Entities.addEntity({ + type: "Shape", + shape: "Cube", + name: "Floor", + color: { red: 20, green: 20, blue: 40 }, + position: center, + rotation: stageOrientation, + dimensions: floorDim, + lifetime: (lifetime === undefined) ? DEFAULT_LIFETIME : lifetime, + + shapeType:shapeTypes[1], + // dynamic: true, + // gravity:{"x":0,"y":-9.8,"z":0}, + // velocity:{"x":0,"y":0.01,"z":0}, + restitution:0.999, + friction:0.001, + damping:0.3, + + })); +} + +function addZone(hasKeyLight, hasAmbient, lifetime) { + var zoneDim = { x:GRID_WORLD_SIZE + 2 * GRID_WORLD_MARGIN, y:GRID_WORLD_SIZE, z:GRID_WORLD_SIZE + 2 * GRID_WORLD_MARGIN}; + var center = getStagePosOriAt(0, 0, -1).pos; + + var lightDir = Vec3.normalize(Vec3.sum(Vec3.multiply(-1, Quat.getUp(stageOrientation)), Vec3.multiply(-1, Quat.getRight(stageOrientation)))) + + return (Entities.addEntity({ + type: "Zone", + name: "Backdrop zone", + + position: center, + rotation: stageOrientation, + dimensions: zoneDim, + lifetime: (lifetime === undefined) ? DEFAULT_LIFETIME : lifetime, + + keyLightMode: "enabled", + skyboxMode: "enabled", + ambientLightMode: "enabled", + + keyLight:{ + intensity: 0.8 * hasKeyLight, + direction: { + "x": 0.037007175385951996, + "y": -0.7071067690849304, + "z": -0.7061376571655273 + }, + castShadows: true, + }, + ambientLight: { + ambientIntensity: 1.0 * hasAmbient, + ambientURL: "https://github.com/highfidelity/hifi_tests/blob/master/assets/skymaps/Sky_Day-Sun-Mid-photo.ktx?raw=true", + }, + + hazeMode:"disabled", + backgroundMode:"skybox", + skybox:{ + color: {"red":2,"green":2,"blue":2}, // Dark grey background + skyboxURL: "https://github.com/highfidelity/hifi_tests/blob/master/assets/skymaps/Sky_Day-Sun-Mid-photo.ktx?raw=true", + } + })); +} + +function addTestScene(name, lifetime) { + var scene = []; + // scene.push(addFloor(lifetime)); + // scene.push(addZone(true, true, lifetime)); + + addObjectGrid(scene, lifetime); + + return scene; +} + + + +// Stage position and orientation initialised at setup +stageOrientation = Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0); +stageRoot = {"x":0.0,"y":0.0,"z":0.0}; +stageAxisA = Vec3.multiply(TILE_UNIT, Quat.getForward(stageOrientation)); +stageAxisB = Vec3.multiply(TILE_UNIT, Quat.getRight(stageOrientation)); +stageAxisC = Vec3.multiply(TILE_UNIT, Quat.getUp(stageOrientation)); + +setupScene = function (lifetime) { + MyAvatar.orientation = Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0); + var orientation = MyAvatar.orientation; + orientation = Quat.safeEulerAngles(orientation); + orientation.x = 0; + orientation = Quat.fromVec3Degrees(orientation); + + stageOrientation = orientation; + stageAxisA = Vec3.multiply(TILE_UNIT, Quat.getForward(stageOrientation)); + stageAxisB = Vec3.multiply(TILE_UNIT, Quat.getRight(stageOrientation)); + stageAxisC = Vec3.multiply(TILE_UNIT, Quat.getUp(stageOrientation)); + + stageRoot = Vec3.sum(MyAvatar.position, Vec3.multiply(-ROOT_Z_OFFSET, Quat.getForward(orientation))); + stageRoot = Vec3.sum(stageRoot, Vec3.multiply(ROOT_Y_OFFSET, Quat.getUp(orientation))); + + return addTestScene("Physics_stage_backdrop", lifetime); +} + +getStagePosOriAt = function (a, b, c) { + var center = Vec3.sum(stageRoot, Vec3.multiply(a, stageAxisA)); + center = Vec3.sum(center, Vec3.multiply(b, stageAxisB)); + center = Vec3.sum(center, Vec3.multiply(c, stageAxisC)); + + return { "pos": center, "ori": stageOrientation}; +} + + +var scene = [] + +createScene = function() { + clearScene(); + scene = setupScene(); +} + +clearScene = function() { + for (var i = 0; i < scene.length; i++) { + Entities.deleteEntity(scene[i]); + } +} + +changeResolution = function(res) { + updateWorldSizeAndResolution(GRID_WORLD_SIZE, res); +} + +getResolution = function() { + return GRID_WORLD_RESOLUTION; +} + +changeSize = function(size) { + updateWorldSizeAndResolution(size, GRID_WORLD_RESOLUTION); +} + +getSize = function() { + return GRID_WORLD_SIZE; +} + + +getNumObjects = function() { + return GRID_SIZE * GRID_SIZE; +} + +bumpUpFloor = function() { + print("bumpUpFloor") + if (scene.length > 0) { + Entities.editEntity(scene[0],{ velocity: {x: 0, y:-2.0,z: 0}}) + } +} \ No newline at end of file diff --git a/scripts/developer/utilities/workload/workload.js b/scripts/developer/utilities/workload/workload.js new file mode 100644 index 0000000000..d74eb4e6d5 --- /dev/null +++ b/scripts/developer/utilities/workload/workload.js @@ -0,0 +1,120 @@ +"use strict"; + +// +// Workload.js +// tablet-workload-engine app +// +// Copyright 2018 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 +// + +(function() { + var TABLET_BUTTON_NAME = "Workload"; + var QMLAPP_URL = Script.resolvePath("./workloadInspector.qml"); + var ICON_URL = Script.resolvePath("../../../system/assets/images/luci-i.svg"); + var ACTIVE_ICON_URL = Script.resolvePath("../../../system/assets/images/luci-a.svg"); + + + var onAppScreen = false; + + function onClicked() { + if (onAppScreen) { + tablet.gotoHomeScreen(); + } else { + tablet.loadQMLSource(QMLAPP_URL); + } + } + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + text: TABLET_BUTTON_NAME, + icon: ICON_URL, + activeIcon: ACTIVE_ICON_URL + }); + + var hasEventBridge = false; + + function wireEventBridge(on) { + if (!tablet) { + print("Warning in wireEventBridge(): 'tablet' undefined!"); + return; + } + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } + } + + function onScreenChanged(type, url) { + if (url === QMLAPP_URL) { + onAppScreen = true; + } else { + onAppScreen = false; + } + + button.editProperties({isActive: onAppScreen}); + wireEventBridge(onAppScreen); + } + + function fromQml(message) { + } + + button.clicked.connect(onClicked); + tablet.screenChanged.connect(onScreenChanged); + + Script.scriptEnding.connect(function () { + if (onAppScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + tablet.removeButton(button); + }); + + + Script.include("./test_physics_scene.js") + + function fromQml(message) { + switch (message.method) { + case "createScene": + createScene(); + updateGridInQML() + break; + case "clearScene": + clearScene(); + updateGridInQML() + break; + case "changeSize": + changeSize(message.params.count); + updateGridInQML() + break; + case "changeResolution": + changeResolution(message.params.count); + updateGridInQML() + break; + case "bumpUpFloor": + bumpUpFloor(); + break; + } + + } + function updateGridInQML() { + sendToQml({method: "gridSize", params: { v: getSize() }}) + sendToQml({method: "objectCount", params: { v: getNumObjects() }}) + } + + function sendToQml(message) { + tablet.sendToQml(message); + } + + updateGridInQML() +}()); \ No newline at end of file diff --git a/scripts/developer/utilities/workload/workloadInspector.qml b/scripts/developer/utilities/workload/workloadInspector.qml new file mode 100644 index 0000000000..8076f5c1c2 --- /dev/null +++ b/scripts/developer/utilities/workload/workloadInspector.qml @@ -0,0 +1,372 @@ +// +// _workload.qml +// +// Created by Sam Gateau on 3/1/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 + +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls +import "../render/configSlider" +import "../lib/jet/qml" as Jet +import "../lib/plotperf" + + +Rectangle { + HifiConstants { id: hifi;} + id: _workload; + + width: parent ? parent.width : 400 + height: parent ? parent.height : 600 + anchors.margins: hifi.dimensions.contentMargin.x + + color: hifi.colors.baseGray; + + function broadcastCreateScene() { + sendToScript({method: "createScene", params: { count:2 }}); + } + + function broadcastClearScene() { + sendToScript({method: "clearScene", params: { count:2 }}); + } + + function broadcastChangeSize(value) { + sendToScript({method: "changeSize", params: { count:value }}); + } + + function broadcastChangeResolution(value) { + sendToScript({method: "changeResolution", params: { count:value }}); + } + + function broadcastBumpUpFloor(value) { + sendToScript({method: "bumpUpFloor", params: { count:0 }}); + } + + function fromScript(message) { + switch (message.method) { + case "gridSize": + print("assigned value! " + message.params.v) + gridSizeLabel.text = ("Grid size [m] = " + message.params.v) + gridSize.setValue(message.params.v) + break; + case "resolution": + print("assigned value! " + message.params.v) + resolution.setValue(message.params.v) + break; + case "objectCount": + print("assigned objectCount! " + message.params.v) + objectCount.text = ("Num objects = " + message.params.v) + break; + } + } + + Column { + id: stats + spacing: 5 + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: hifi.dimensions.contentMargin.x + //padding: hifi.dimensions.contentMargin.x + + HifiControls.Label { + text: "Workload" + } + + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + HifiControls.CheckBox { + boxSize: 20 + text: "Freeze Views" + checked: Workload.getConfig("setupViews")["freezeViews"] + onCheckedChanged: { Workload.getConfig("SpaceToRender")["freezeViews"] = checked, Workload.getConfig("setupViews")["freezeViews"] = checked; } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Use Avatar View" + checked: Workload.getConfig("setupViews")["useAvatarView"] + onCheckedChanged: { Workload.getConfig("setupViews")["useAvatarView"] = checked; } + } + HifiControls.CheckBox { + boxSize: 20 + text: "force View Horizontal" + checked: Workload.getConfig("setupViews")["forceViewHorizontal"] + onCheckedChanged: { Workload.getConfig("setupViews")["forceViewHorizontal"] = checked; } + } + } + + RowLayout { + anchors.left: parent.left + anchors.right: parent.right + HifiControls.CheckBox { + boxSize: 20 + text: "Simulate Secondary" + checked: Workload.getConfig("setupViews")["simulateSecondaryCamera"] + onCheckedChanged: { Workload.getConfig("setupViews")["simulateSecondaryCamera"] = checked; } + } + } + + Separator {} + HifiControls.CheckBox { + boxSize: 20 + text: "Regulate View Ranges" + checked: Workload.getConfig("controlViews")["regulateViewRanges"] + onCheckedChanged: { Workload.getConfig("controlViews")["regulateViewRanges"] = checked; } + } + + RowLayout { + visible: !Workload.getConfig("controlViews")["regulateViewRanges"] + anchors.left: parent.left + anchors.right: parent.right + Column { + anchors.left: parent.left + anchors.right: parent.horizontalCenter + HifiControls.Label { + text: "Back [m]" + anchors.horizontalCenter: parent.horizontalCenter + } + Repeater { + model: [ + "R1:r1Back:250.0:0.0", + "R2:r2Back:250.0:0.0", + "R3:r3Back:250.0:0.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + config: Workload.getConfig("setupViews") + property: modelData.split(":")[1] + max: modelData.split(":")[2] + min: modelData.split(":")[3] + integral: true + + labelAreaWidthScale: 0.4 + anchors.left: parent.left + anchors.right: parent.right + } + } + } + Column { + anchors.left: parent.horizontalCenter + anchors.right: parent.right + HifiControls.Label { + text: "Front [m]" + anchors.horizontalCenter: parent.horizontalCenter + } + Repeater { + model: [ + "r1Front:500:1.0", + "r2Front:500:1.0", + "r3Front:500:1.0" + ] + ConfigSlider { + showLabel: false + config: Workload.getConfig("setupViews") + property: modelData.split(":")[0] + max: modelData.split(":")[1] + min: modelData.split(":")[2] + integral: true + + labelAreaWidthScale: 0.3 + anchors.left: parent.left + anchors.right: parent.right + } + } + } + } + /*RowLayout { + visible: Workload.getConfig("controlViews")["regulateViewRanges"] + anchors.left: parent.left + anchors.right: parent.right + Column { + anchors.left: parent.left + anchors.right: parent.horizontalCenter + HifiControls.Label { + text: "Back [m]" + anchors.horizontalCenter: parent.horizontalCenter + } + Repeater { + model: [ + "R1:r1RangeBack:50.0:0.0", + "R2:r2RangeBack:50.0:0.0", + "R3:r3RangeBack:50.0:0.0" + ] + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + config: Workload.getConfig("controlViews") + property: modelData.split(":")[1] + max: modelData.split(":")[2] + min: modelData.split(":")[3] + integral: true + + labelAreaWidthScale: 0.4 + anchors.left: parent.left + anchors.right: parent.right + } + } + } + Column { + anchors.left: parent.horizontalCenter + anchors.right: parent.right + HifiControls.Label { + text: "Front [m]" + anchors.horizontalCenter: parent.horizontalCenter + } + Repeater { + model: [ + "r1RangeFront:300:1.0", + "r2RangeFront:300:1.0", + "r3RangeFront:300:1.0" + ] + ConfigSlider { + showLabel: false + config: Workload.getConfig("controlViews") + property: modelData.split(":")[0] + max: modelData.split(":")[1] + min: modelData.split(":")[2] + integral: true + + labelAreaWidthScale: 0.3 + anchors.left: parent.left + anchors.right: parent.right + } + } + } + }*/ + property var controlViews: Workload.getConfig("controlViews") + + PlotPerf { + title: "Timings" + height: 100 + object: stats.controlViews + valueScale: 1.0 + valueUnit: "ms" + plots: [ + { + prop: "r2Timing", + label: "Physics + Collisions", + color: "#1AC567" + }, + { + prop: "r3Timing", + label: "Kinematic + Update", + color: "#1A67C5" + } + ] + } + Separator {} + HifiControls.Label { + text: "Numbers:"; + } + HifiControls.Label { + text: "R1= " + Workload.getConfig("regionState")["numR1"]; + } + HifiControls.Label { + text: "R2= " + Workload.getConfig("regionState")["numR2"]; + } + HifiControls.Label { + text: "R3= " + Workload.getConfig("regionState")["numR3"]; + } + + Separator {} + HifiControls.Label { + text: "Display" + } + HifiControls.CheckBox { + boxSize: 20 + text: "Show Proxies" + checked: Workload.getConfig("SpaceToRender")["showProxies"] + onCheckedChanged: { Workload.getConfig("SpaceToRender")["showProxies"] = checked } + } + HifiControls.CheckBox { + boxSize: 20 + text: "Show Views" + checked: Workload.getConfig("SpaceToRender")["showViews"] + onCheckedChanged: { Workload.getConfig("SpaceToRender")["showViews"] = checked } + } + Separator {} + HifiControls.Label { + text: "Test" + } + Row { + spacing: 5 + anchors.left: parent.left + anchors.right: parent.right + HifiControls.Button { + text: "create scene" + onClicked: { + print("pressed") + _workload.broadcastCreateScene() + } + } + HifiControls.Button { + text: "clear scene" + onClicked: { + print("pressed") + _workload.broadcastClearScene() + } + } + /*HifiControls.Button { + text: "bump floor" + onClicked: { + print("pressed") + _workload.broadcastBumpUpFloor() + } + }*/ + } + HifiControls.Label { + id: gridSizeLabel + anchors.left: parent.left + anchors.right: parent.right + text: "Grid side size [m]" + } + HifiControls.Slider { + id: gridSize + stepSize: 1.0 + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.topMargin: 0 + minimumValue: 1 + maximumValue: 200 + value: 100 + + onValueChanged: { _workload.broadcastChangeSize(value) } + } + + HifiControls.Label { + id: objectCount + anchors.left: parent.left + anchors.right: parent.right + text: "Num objects" + } + HifiControls.Slider { + id: resolution + stepSize: 1.0 + anchors.left: parent.left + anchors.right: parent.right + anchors.rightMargin: 0 + anchors.topMargin: 0 + minimumValue: 5 + maximumValue: 75 + value: 5 + + onValueChanged: { _workload.broadcastChangeResolution(value) } + } + + Separator {} + + /*Jet.TaskList { + rootConfig: Workload + anchors.left: parent.left + anchors.right: parent.right + + height: 300 + }*/ + } +} diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js new file mode 100644 index 0000000000..db81af3755 --- /dev/null +++ b/scripts/modules/appUi.js @@ -0,0 +1,187 @@ +"use strict"; +/*global Tablet, Script*/ +// +// libraries/appUi.js +// +// Created by Howard Stearns on 3/20/18. +// Copyright 2018 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 +// + +function AppUi(properties) { + /* Example development order: + 1. var AppUi = Script.require('appUi'); + 2. Put appname-i.svg, appname-a.svg in graphicsDirectory (where non-default graphicsDirectory can be added in #3). + 3. ui = new AppUi({buttonName: "APPNAME", home: "qml-or-html-path"}); + (And if converting an existing app, + define var tablet = ui.tablet, button = ui.button; as needed. + remove button.clicked.[dis]connect and tablet.remove(button).) + 4. Define onOpened and onClosed behavior in #3, if any. + (And if converting an existing app, remove screenChanged.[dis]connect.) + 5. Define onMessage and sendMessage in #3, if any. onMessage is wired/unwired on open/close. If you + want a handler to be "always on", connect it yourself at script startup. + (And if converting an existing app, remove code that [un]wires that message handling such as + fromQml/sendToQml or webEventReceived/emitScriptEvent.) + 6. (If converting an existing app, cleanup stuff that is no longer necessary, like references to button, tablet, + and use isOpen, open(), and close() as needed.) + 7. lint! + */ + var that = this; + function defaultButton(name, suffix) { + var base = that[name] || (that.buttonPrefix + suffix); + that[name] = (base.indexOf('/') >= 0) ? base : (that.graphicsDirectory + base); //poor man's merge + } + + // Defaults: + that.tabletName = "com.highfidelity.interface.tablet.system"; + that.inject = ""; + that.graphicsDirectory = "icons/tablet-icons/"; // Where to look for button svgs. See below. + that.checkIsOpen = function checkIsOpen(type, tabletUrl) { // Are we active? Value used to set isOpen. + return (type === that.type) && that.currentUrl && (tabletUrl.indexOf(that.currentUrl) >= 0); // Actual url may have prefix or suffix. + }; + that.setCurrentData = function setCurrentData(url) { + that.currentUrl = url; + that.type = /.qml$/.test(url) ? 'QML' : 'Web'; + } + that.open = function open(optionalUrl) { // How to open the app. + var url = optionalUrl || that.home; + that.setCurrentData(url); + if (that.isQML()) { + that.tablet.loadQMLSource(url); + } else { + that.tablet.gotoWebScreen(url, that.inject); + } + }; + that.close = function close() { // How to close the app. + that.currentUrl = ""; + // for toolbar-mode: go back to home screen, this will close the window. + that.tablet.gotoHomeScreen(); + }; + that.buttonActive = function buttonActive(isActive) { // How to make the button active (white). + that.button.editProperties({isActive: isActive}); + }; + that.messagesWaiting = function messagesWaiting(isWaiting) { // How to indicate a message light on button. + // Note that waitingButton doesn't have to exist unless someone explicitly calls this with isWaiting true. + that.button.editProperties({ + icon: isWaiting ? that.normalMessagesButton : that.normalButton, + activeIcon: isWaiting ? that.activeMessagesButton : that.activeButton + }); + }; + that.isQML = function isQML() { // We set type property in onClick. + return that.type === 'QML'; + }; + that.eventSignal = function eventSignal() { // What signal to hook onMessage to. + return that.isQML() ? that.tablet.fromQml : that.tablet.webEventReceived; + }; + + // Overwrite with the given properties: + Object.keys(properties).forEach(function (key) { that[key] = properties[key]; }); + + // Properties: + that.tablet = Tablet.getTablet(that.tabletName); + // Must be after we gather properties. + that.buttonPrefix = that.buttonPrefix || that.buttonName.toLowerCase() + "-"; + defaultButton('normalButton', 'i.svg'); + defaultButton('activeButton', 'a.svg'); + defaultButton('normalMessagesButton', 'i-msg.svg'); + defaultButton('activeMessagesButton', 'a-msg.svg'); + that.button = that.tablet.addButton({ + icon: that.normalButton, + activeIcon: that.activeButton, + text: that.buttonName, + sortOrder: that.sortOrder + }); + that.ignore = function ignore() { }; + + // Handlers + that.onScreenChanged = function onScreenChanged(type, url) { + // Set isOpen, wireEventBridge, set buttonActive as appropriate, + // and finally call onOpened() or onClosed() IFF defined. + console.debug(that.buttonName, 'onScreenChanged', type, url, that.isOpen); + if (that.checkIsOpen(type, url)) { + if (!that.isOpen) { + that.wireEventBridge(true); + that.buttonActive(true); + if (that.onOpened) { + that.onOpened(); + } + that.isOpen = true; + } + + } else { // Not us. Should we do something for type Home, Menu, and particularly Closed (meaning tablet hidden? + if (that.isOpen) { + that.wireEventBridge(false); + that.buttonActive(false); + if (that.onClosed) { + that.onClosed(); + } + that.isOpen = false; + } + } + }; + that.hasEventBridge = false; + // HTML event bridge uses strings, not objects. Here we abstract over that. + // (Although injected javascript still has to use JSON.stringify/JSON.parse.) + that.sendToHtml = function (messageObject) { that.tablet.emitScriptEvent(JSON.stringify(messageObject)); }; + that.fromHtml = function (messageString) { that.onMessage(JSON.parse(messageString)); }; + that.wireEventBridge = function wireEventBridge(on) { + // Uniquivocally sets that.sendMessage(messageObject) to do the right thing. + // Sets hasEventBridge and wires onMessage to eventSignal as appropriate, IFF onMessage defined. + var handler, isQml = that.isQML(); + // Outbound (always, regardless of whether there is an inbound handler). + if (on) { + that.sendMessage = isQml ? that.tablet.sendToQml : that.sendToHtml; + } else { + that.sendMessage = that.ignore; + } + + if (!that.onMessage) { return; } + + // Inbound + handler = isQml ? that.onMessage : that.fromHtml; + if (on) { + if (!that.hasEventBridge) { + console.debug(that.buttonName, 'connecting', that.eventSignal()); + that.eventSignal().connect(handler); + that.hasEventBridge = true; + } + } else { + if (that.hasEventBridge) { + console.debug(that.buttonName, 'disconnecting', that.eventSignal()); + that.eventSignal().disconnect(handler); + that.hasEventBridge = false; + } + } + }; + that.isOpen = false; + // To facilitate incremental development, only wire onClicked to do something when "home" is defined in properties. + that.onClicked = that.home + ? function onClicked() { + // Call open() or close(), and reset type based on current home property. + if (that.isOpen) { + that.close(); + } else { + that.open(); + } + } : that.ignore; + that.onScriptEnding = function onScriptEnding() { + // Close if necessary, clean up any remaining handlers, and remove the button. + if (that.isOpen) { + that.close(); + } + that.tablet.screenChanged.disconnect(that.onScreenChanged); + if (that.button) { + if (that.onClicked) { + that.button.clicked.disconnect(that.onClicked); + } + that.tablet.removeButton(that.button); + } + }; + // Set up the handlers. + that.tablet.screenChanged.connect(that.onScreenChanged); + that.button.clicked.connect(that.onClicked); + Script.scriptEnding.connect(that.onScriptEnding); +} +module.exports = AppUi; diff --git a/scripts/system/+android/clickWeb.js b/scripts/system/+android/clickWeb.js new file mode 100644 index 0000000000..dc75a58327 --- /dev/null +++ b/scripts/system/+android/clickWeb.js @@ -0,0 +1,106 @@ +"use strict"; +// +// clickWeb.js +// scripts/system/+android +// +// Created by Gabriel Calero & Cristian Duarte on Jun 22, 2018 +// Copyright 2018 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 +// + +(function() { // BEGIN LOCAL_SCOPE + +var logEnabled = false; +var touchOverlayID; +var touchEntityID; + +function printd(str) { + if (logEnabled) + print("[clickWeb.js] " + str); +} + +function intersectsWebOverlay(intersection) { + return intersection && intersection.intersects && intersection.overlayID && + Overlays.getOverlayType(intersection.overlayID) == "web3d"; +} + +function intersectsWebEntity(intersection) { + if (intersection && intersection.intersects && intersection.entityID) { + var properties = Entities.getEntityProperties(intersection.entityID, ["type", "sourceUrl"]); + return properties.type && properties.type == "Web" && properties.sourceUrl; + } + return false; +} + +function findRayIntersection(pickRay) { + // Check 3D overlays and entities. Argument is an object with origin and direction. + var overlayRayIntersection = Overlays.findRayIntersection(pickRay); + var entityRayIntersection = Entities.findRayIntersection(pickRay, true); + var isOverlayInters = intersectsWebOverlay(overlayRayIntersection); + var isEntityInters = intersectsWebEntity(entityRayIntersection); + if (isOverlayInters && + (!isEntityInters || + overlayRayIntersection.distance < entityRayIntersection.distance)) { + return { type: 'overlay', obj: overlayRayIntersection }; + } else if (isEntityInters && + (!isOverlayInters || + entityRayIntersection.distance < overlayRayIntersection.distance)) { + return { type: 'entity', obj: entityRayIntersection }; + } + return false; +} + +function touchBegin(event) { + var intersection = findRayIntersection(Camera.computePickRay(event.x, event.y)); + if (intersection && intersection.type == 'overlay') { + touchOverlayID = intersection.obj.overlayID; + touchEntityID = null; + } else if (intersection && intersection.type == 'entity') { + touchEntityID = intersection.obj.entityID; + touchOverlayID = null; + } +} + +function touchEnd(event) { + var intersection = findRayIntersection(Camera.computePickRay(event.x, event.y)); + if (intersection && intersection.type == 'overlay' && touchOverlayID == intersection.obj.overlayID) { + var propertiesToGet = {}; + propertiesToGet[overlayID] = ['url']; + var properties = Overlays.getOverlaysProperties(propertiesToGet); + if (properties[overlayID].url) { + Window.openUrl(properties[overlayID].url); + } + } else if (intersection && intersection.type == 'entity' && touchEntityID == intersection.obj.entityID) { + var properties = Entities.getEntityProperties(touchEntityID, ["sourceUrl"]); + if (properties.sourceUrl) { + Window.openUrl(properties.sourceUrl); + } + } + + touchOverlayID = null; + touchEntityID = null; +} + +function ending() { + Controller.touchBeginEvent.disconnect(touchBegin); + Controller.touchEndEvent.disconnect(touchEnd); +} + +function init() { + Controller.touchBeginEvent.connect(touchBegin); + Controller.touchEndEvent.connect(touchEnd); + + Script.scriptEnding.connect(function () { + ending(); + }); + +} + +module.exports = { + init: init, + ending: ending +} + +}()); // END LOCAL_SCOPE diff --git a/scripts/system/+android/modes.js b/scripts/system/+android/modes.js index f0dfb64677..f495af3bba 100644 --- a/scripts/system/+android/modes.js +++ b/scripts/system/+android/modes.js @@ -29,6 +29,7 @@ var logEnabled = false; var radar = Script.require('./radar.js'); var uniqueColor = Script.require('./uniqueColor.js'); var displayNames = Script.require('./displayNames.js'); +var clickWeb = Script.require('./clickWeb.js'); function printd(str) { if (logEnabled) { @@ -97,9 +98,11 @@ function switchToMode(newMode) { if (currentMode == MODE_RADAR) { radar.startRadarMode(); displayNames.ending(); + clickWeb.ending(); } else if (currentMode == MODE_MY_VIEW) { // nothing to do yet displayNames.init(); + clickWeb.init(); } else { printd("Unknown view mode " + currentMode); } diff --git a/scripts/system/assets/animations/Love.fbx b/scripts/system/assets/animations/Love.fbx new file mode 100644 index 0000000000..159ccafd04 Binary files /dev/null and b/scripts/system/assets/animations/Love.fbx differ diff --git a/scripts/system/assets/animations/Sit1.fbx b/scripts/system/assets/animations/Sit1.fbx new file mode 100644 index 0000000000..db75219980 Binary files /dev/null and b/scripts/system/assets/animations/Sit1.fbx differ diff --git a/scripts/system/assets/animations/Sit2.fbx b/scripts/system/assets/animations/Sit2.fbx new file mode 100644 index 0000000000..400b599794 Binary files /dev/null and b/scripts/system/assets/animations/Sit2.fbx differ diff --git a/scripts/system/assets/animations/Sit3.fbx b/scripts/system/assets/animations/Sit3.fbx new file mode 100644 index 0000000000..174fd75c4e Binary files /dev/null and b/scripts/system/assets/animations/Sit3.fbx differ diff --git a/scripts/system/assets/models/teleport-destination.fbm/Teleportation-Destination-Texture2.png b/scripts/system/assets/models/teleport-destination.fbm/Teleportation-Destination-Texture2.png new file mode 100644 index 0000000000..eb9addcfca Binary files /dev/null and b/scripts/system/assets/models/teleport-destination.fbm/Teleportation-Destination-Texture2.png differ diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js new file mode 100644 index 0000000000..03b7b3969d --- /dev/null +++ b/scripts/system/avatarapp.js @@ -0,0 +1,570 @@ +"use strict"; +/*jslint vars:true, plusplus:true, forin:true*/ +/*global Tablet, Settings, Script, AvatarList, Users, Entities, MyAvatar, Camera, Overlays, Vec3, Quat, HMD, Controller, Account, UserActivityLogger, Messages, Window, XMLHttpRequest, print, location, getControllerWorldLocation*/ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +// +// avatarapp.js +// +// Created by Alexander Ivash on April 30, 2018 +// Copyright 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 +// + +(function() { // BEGIN LOCAL_SCOPE + +var request = Script.require('request').request; +var AVATARAPP_QML_SOURCE = "hifi/AvatarApp.qml"; +Script.include("/~/system/libraries/controllers.js"); + +// constants from AvatarBookmarks.h +var ENTRY_AVATAR_URL = "avatarUrl"; +var ENTRY_AVATAR_ATTACHMENTS = "attachments"; +var ENTRY_AVATAR_ENTITIES = "avatarEntites"; +var ENTRY_AVATAR_SCALE = "avatarScale"; +var ENTRY_VERSION = "version"; + +function executeLater(callback) { + Script.setTimeout(callback, 300); +} + +var INVALID_JOINT_INDEX = -1 +function isWearable(avatarEntity) { + return avatarEntity.properties.visible === true && avatarEntity.properties.parentJointIndex !== INVALID_JOINT_INDEX && + (avatarEntity.properties.parentID === MyAvatar.sessionUUID || avatarEntity.properties.parentID === MyAvatar.SELF_ID); +} + +function getMyAvatarWearables() { + var entitiesArray = MyAvatar.getAvatarEntitiesVariant(); + var wearablesArray = []; + + for (var i = 0; i < entitiesArray.length; ++i) { + var entity = entitiesArray[i]; + if (!isWearable(entity)) { + continue; + } + + var localRotation = entity.properties.localRotation; + entity.properties.localRotationAngles = Quat.safeEulerAngles(localRotation) + wearablesArray.push(entity); + } + + return wearablesArray; +} + +function getMyAvatar() { + var avatar = {} + avatar[ENTRY_AVATAR_URL] = MyAvatar.skeletonModelURL; + avatar[ENTRY_AVATAR_SCALE] = MyAvatar.getAvatarScale(); + avatar[ENTRY_AVATAR_ATTACHMENTS] = MyAvatar.getAttachmentsVariant(); + avatar[ENTRY_AVATAR_ENTITIES] = getMyAvatarWearables(); + return avatar; +} + +function getMyAvatarSettings() { + return { + dominantHand: MyAvatar.getDominantHand(), + collisionsEnabled : MyAvatar.getCollisionsEnabled(), + collisionSoundUrl : MyAvatar.collisionSoundURL, + animGraphUrl: MyAvatar.getAnimGraphUrl(), + animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(), + } +} + +function updateAvatarWearables(avatar, bookmarkAvatarName) { + executeLater(function() { + var wearables = getMyAvatarWearables(); + avatar[ENTRY_AVATAR_ENTITIES] = wearables; + + sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables, 'avatarName' : bookmarkAvatarName}) + }); +} + +var adjustWearables = { + opened : false, + cameraMode : '', + setOpened : function(value) { + if(this.opened !== value) { + if(value) { + this.cameraMode = Camera.mode; + + if(!HMD.active) { + Camera.mode = 'mirror'; + } + } else { + Camera.mode = this.cameraMode; + } + + this.opened = value; + } + } +} + +var currentAvatarWearablesBackup = null; +var currentAvatar = null; +var currentAvatarSettings = getMyAvatarSettings(); + +var notifyScaleChanged = true; +function onTargetScaleChanged() { + if(currentAvatar.scale !== MyAvatar.getAvatarScale()) { + currentAvatar.scale = MyAvatar.getAvatarScale(); + if(notifyScaleChanged) { + sendToQml({'method' : 'scaleChanged', 'value' : currentAvatar.scale}) + } + } +} + +function onSkeletonModelURLChanged() { + if(currentAvatar || (currentAvatar.skeletonModelURL !== MyAvatar.skeletonModelURL)) { + fromQml({'method' : 'getAvatars'}); + } +} + +function onDominantHandChanged(dominantHand) { + if(currentAvatarSettings.dominantHand !== dominantHand) { + currentAvatarSettings.dominantHand = dominantHand; + sendToQml({'method' : 'settingChanged', 'name' : 'dominantHand', 'value' : dominantHand}) + } +} + +function onCollisionsEnabledChanged(enabled) { + if(currentAvatarSettings.collisionsEnabled !== enabled) { + currentAvatarSettings.collisionsEnabled = enabled; + sendToQml({'method' : 'settingChanged', 'name' : 'collisionsEnabled', 'value' : enabled}) + } +} + +function onNewCollisionSoundUrl(url) { + if(currentAvatarSettings.collisionSoundUrl !== url) { + currentAvatarSettings.collisionSoundUrl = url; + sendToQml({'method' : 'settingChanged', 'name' : 'collisionSoundUrl', 'value' : url}) + } +} + +function onAnimGraphUrlChanged(url) { + if (currentAvatarSettings.animGraphUrl !== url) { + currentAvatarSettings.animGraphUrl = url; + sendToQml({ 'method': 'settingChanged', 'name': 'animGraphUrl', 'value': currentAvatarSettings.animGraphUrl }) + + if (currentAvatarSettings.animGraphOverrideUrl !== MyAvatar.getAnimGraphOverrideUrl()) { + currentAvatarSettings.animGraphOverrideUrl = MyAvatar.getAnimGraphOverrideUrl(); + sendToQml({ 'method': 'settingChanged', 'name': 'animGraphOverrideUrl', 'value': currentAvatarSettings.animGraphOverrideUrl }) + } + } +} + +var selectedAvatarEntityGrabbable = false; +var selectedAvatarEntityID = null; +var grabbedAvatarEntityChangeNotifier = null; + +var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; +var MARKETPLACE_URL = Account.metaverseServerURL + "/marketplace"; +var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("html/js/marketplacesInject.js"); + +function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. + switch (message.method) { + case 'getAvatars': + currentAvatar = getMyAvatar(); + currentAvatarSettings = getMyAvatarSettings(); + + message.data = { + 'bookmarks' : AvatarBookmarks.getBookmarks(), + 'displayName' : MyAvatar.displayName, + 'currentAvatar' : currentAvatar, + 'currentAvatarSettings' : currentAvatarSettings + }; + + for(var bookmarkName in message.data.bookmarks) { + var bookmark = message.data.bookmarks[bookmarkName]; + + if (bookmark.avatarEntites) { + bookmark.avatarEntites.forEach(function(avatarEntity) { + avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation); + }); + } + } + + sendToQml(message) + break; + case 'selectAvatar': + AvatarBookmarks.loadBookmark(message.name); + break; + case 'deleteAvatar': + AvatarBookmarks.removeBookmark(message.name); + break; + case 'addAvatar': + AvatarBookmarks.addBookmark(message.name); + break; + case 'adjustWearable': + if(message.properties.localRotationAngles) { + message.properties.localRotation = Quat.fromVec3Degrees(message.properties.localRotationAngles) + } + + Entities.editEntity(message.entityID, message.properties); + message.properties = Entities.getEntityProperties(message.entityID, Object.keys(message.properties)); + + if(message.properties.localRotation) { + message.properties.localRotationAngles = Quat.safeEulerAngles(message.properties.localRotation); + } + + sendToQml({'method' : 'wearableUpdated', 'entityID' : message.entityID, wearableIndex : message.wearableIndex, properties : message.properties, updateUI : false}) + break; + case 'adjustWearablesOpened': + currentAvatarWearablesBackup = getMyAvatarWearables(); + adjustWearables.setOpened(true); + + Entities.mousePressOnEntity.connect(onSelectedEntity); + Messages.subscribe('Hifi-Object-Manipulation'); + Messages.messageReceived.connect(handleWearableMessages); + break; + case 'adjustWearablesClosed': + if(!message.save) { + // revert changes using snapshot of wearables + if(currentAvatarWearablesBackup !== null) { + AvatarBookmarks.updateAvatarEntities(currentAvatarWearablesBackup); + updateAvatarWearables(currentAvatar, message.avatarName); + } + } else { + sendToQml({'method' : 'updateAvatarInBookmarks'}); + } + + adjustWearables.setOpened(false); + ensureWearableSelected(null); + Entities.mousePressOnEntity.disconnect(onSelectedEntity); + Messages.messageReceived.disconnect(handleWearableMessages); + Messages.unsubscribe('Hifi-Object-Manipulation'); + break; + case 'selectWearable': + ensureWearableSelected(message.entityID); + break; + case 'deleteWearable': + Entities.deleteEntity(message.entityID); + updateAvatarWearables(currentAvatar, message.avatarName); + break; + case 'changeDisplayName': + if (MyAvatar.displayName !== message.displayName) { + MyAvatar.displayName = message.displayName; + UserActivityLogger.palAction("display_name_change", message.displayName); + } + break; + case 'applyExternalAvatar': + var currentAvatarURL = MyAvatar.getFullAvatarURLFromPreferences(); + if(currentAvatarURL !== message.avatarURL) { + MyAvatar.useFullAvatarURL(message.avatarURL); + sendToQml({'method' : 'externalAvatarApplied', 'avatarURL' : message.avatarURL}) + } + break; + case 'navigate': + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system") + if(message.url.indexOf('app://') === 0) { + if(message.url === 'app://marketplace') { + tablet.gotoWebScreen(MARKETPLACE_URL, MARKETPLACES_INJECT_SCRIPT_URL); + } else if(message.url === 'app://purchases') { + tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); + } + + } else if(message.url.indexOf('hifi://') === 0) { + AddressManager.handleLookupString(message.url, false); + } else if(message.url.indexOf('https://') === 0 || message.url.indexOf('http://') === 0) { + tablet.gotoWebScreen(message.url, MARKETPLACES_INJECT_SCRIPT_URL); + } + + break; + case 'setScale': + notifyScaleChanged = false; + MyAvatar.setAvatarScale(message.avatarScale); + currentAvatar.avatarScale = message.avatarScale; + notifyScaleChanged = true; + break; + case 'revertScale': + MyAvatar.setAvatarScale(message.avatarScale); + currentAvatar.avatarScale = message.avatarScale; + break; + case 'saveSettings': + MyAvatar.setAvatarScale(message.avatarScale); + currentAvatar.avatarScale = message.avatarScale; + + MyAvatar.setDominantHand(message.settings.dominantHand); + MyAvatar.setCollisionsEnabled(message.settings.collisionsEnabled); + MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl; + MyAvatar.setAnimGraphOverrideUrl(message.settings.animGraphOverrideUrl); + + settings = getMyAvatarSettings(); + break; + default: + print('Unrecognized message from AvatarApp.qml:', JSON.stringify(message)); + } +} + +function isGrabbable(entityID) { + if(entityID === null) { + return false; + } + + var properties = Entities.getEntityProperties(entityID, ['clientOnly', 'userData']); + if (properties.clientOnly) { + var userData; + try { + userData = JSON.parse(properties.userData); + } catch (e) { + userData = {}; + } + + return userData.grabbableKey && userData.grabbableKey.grabbable; + } + + return false; +} + +function setGrabbable(entityID, grabbable) { + var properties = Entities.getEntityProperties(entityID, ['clientOnly', 'userData']); + if (properties.clientOnly) { + var userData; + try { + userData = JSON.parse(properties.userData); + } catch (e) { + userData = {}; + } + + if (userData.grabbableKey === undefined) { + userData.grabbableKey = {}; + } + userData.grabbableKey.grabbable = grabbable; + Entities.editEntity(entityID, {userData: JSON.stringify(userData)}); + } +} + +function ensureWearableSelected(entityID) { + if(selectedAvatarEntityID !== entityID) { + if(grabbedAvatarEntityChangeNotifier !== null) { + Script.clearInterval(grabbedAvatarEntityChangeNotifier); + grabbedAvatarEntityChangeNotifier = null; + } + + if(selectedAvatarEntityID !== null) { + setGrabbable(selectedAvatarEntityID, selectedAvatarEntityGrabbable); + } + + selectedAvatarEntityID = entityID; + selectedAvatarEntityGrabbable = isGrabbable(entityID); + + if(selectedAvatarEntityID !== null) { + setGrabbable(selectedAvatarEntityID, true); + } + + return true; + } + + return false; +} + +function isEntityBeingWorn(entityID) { + return Entities.getEntityProperties(entityID, 'parentID').parentID === MyAvatar.sessionUUID; +}; + +function onSelectedEntity(entityID, pointerEvent) { + if(selectedAvatarEntityID !== entityID && isEntityBeingWorn(entityID)) + { + if(ensureWearableSelected(entityID)) { + sendToQml({'method' : 'selectAvatarEntity', 'entityID' : selectedAvatarEntityID}); + } + } +} + +function handleWearableMessages(channel, message, sender) { + if (channel !== 'Hifi-Object-Manipulation') { + return; + } + + var parsedMessage = null; + + try { + parsedMessage = JSON.parse(message); + } catch (e) { + return; + } + + var entityID = parsedMessage.grabbedEntity; + if(parsedMessage.action === 'grab') { + if(selectedAvatarEntityID !== entityID) { + ensureWearableSelected(entityID); + sendToQml({'method' : 'selectAvatarEntity', 'entityID' : selectedAvatarEntityID}); + } + + grabbedAvatarEntityChangeNotifier = Script.setInterval(function() { + // for some reasons Entities.getEntityProperties returns more than was asked.. + var propertyNames = ['localPosition', 'localRotation', 'dimensions', 'naturalDimensions']; + var entityProperties = Entities.getEntityProperties(selectedAvatarEntityID, propertyNames); + var properties = {} + + propertyNames.forEach(function(propertyName) { + properties[propertyName] = entityProperties[propertyName]; + }) + + properties.localRotationAngles = Quat.safeEulerAngles(properties.localRotation); + sendToQml({'method' : 'wearableUpdated', 'entityID' : selectedAvatarEntityID, 'wearableIndex' : -1, 'properties' : properties, updateUI : true}) + + }, 1000); + } else if(parsedMessage.action === 'release') { + if(grabbedAvatarEntityChangeNotifier !== null) { + Script.clearInterval(grabbedAvatarEntityChangeNotifier); + grabbedAvatarEntityChangeNotifier = null; + } + } +} + +function sendToQml(message) { + tablet.sendToQml(message); +} + +function onBookmarkLoaded(bookmarkName) { + executeLater(function() { + currentAvatar = getMyAvatar(); + sendToQml({'method' : 'bookmarkLoaded', 'data' : {'name' : bookmarkName, 'currentAvatar' : currentAvatar} }); + }); +} + +function onBookmarkDeleted(bookmarkName) { + sendToQml({'method' : 'bookmarkDeleted', 'name' : bookmarkName}); +} + +function onBookmarkAdded(bookmarkName) { + var bookmark = AvatarBookmarks.getBookmark(bookmarkName); + bookmark.avatarEntites.forEach(function(avatarEntity) { + avatarEntity.properties.localRotationAngles = Quat.safeEulerAngles(avatarEntity.properties.localRotation) + }) + + sendToQml({ 'method': 'bookmarkAdded', 'bookmarkName': bookmarkName, 'bookmark': bookmark }); +} + +// +// Manage the connection between the button and the window. +// +var button; +var buttonName = "AVATAR"; +var tablet = null; + +function startup() { + tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + button = tablet.addButton({ + text: buttonName, + icon: "icons/tablet-icons/avatar-i.svg", + activeIcon: "icons/tablet-icons/avatar-a.svg", + sortOrder: 7 + }); + button.clicked.connect(onTabletButtonClicked); + tablet.screenChanged.connect(onTabletScreenChanged); +} + +startup(); + +var isWired = false; +function off() { + if(adjustWearables.opened) { + adjustWearables.setOpened(false); + ensureWearableSelected(null); + Entities.mousePressOnEntity.disconnect(onSelectedEntity); + + Messages.messageReceived.disconnect(handleWearableMessages); + Messages.unsubscribe('Hifi-Object-Manipulation'); + } + + if (isWired) { // It is not ok to disconnect these twice, hence guard. + isWired = false; + + AvatarBookmarks.bookmarkLoaded.disconnect(onBookmarkLoaded); + AvatarBookmarks.bookmarkDeleted.disconnect(onBookmarkDeleted); + AvatarBookmarks.bookmarkAdded.disconnect(onBookmarkAdded); + + MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged); + MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); + MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged); + MyAvatar.newCollisionSoundURL.disconnect(onNewCollisionSoundUrl); + MyAvatar.animGraphUrlChanged.disconnect(onAnimGraphUrlChanged); + MyAvatar.targetScaleChanged.disconnect(onTargetScaleChanged); + } +} + +function on() { + AvatarBookmarks.bookmarkLoaded.connect(onBookmarkLoaded); + AvatarBookmarks.bookmarkDeleted.connect(onBookmarkDeleted); + AvatarBookmarks.bookmarkAdded.connect(onBookmarkAdded); + + MyAvatar.skeletonModelURLChanged.connect(onSkeletonModelURLChanged); + MyAvatar.dominantHandChanged.connect(onDominantHandChanged); + MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged); + MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl); + MyAvatar.animGraphUrlChanged.connect(onAnimGraphUrlChanged); + MyAvatar.targetScaleChanged.connect(onTargetScaleChanged); +} + +function onTabletButtonClicked() { + if (onAvatarAppScreen) { + // for toolbar-mode: go back to home screen, this will close the window. + tablet.gotoHomeScreen(); + } else { + ContextOverlay.enabled = false; + tablet.loadQMLSource(AVATARAPP_QML_SOURCE); + isWired = true; + } +} +var hasEventBridge = false; +function wireEventBridge(on) { + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } +} + +var onAvatarAppScreen = false; +function onTabletScreenChanged(type, url) { + var onAvatarAppScreenNow = (type === "QML" && url === AVATARAPP_QML_SOURCE); + wireEventBridge(onAvatarAppScreenNow); + // for toolbar mode: change button to active when window is first openend, false otherwise. + button.editProperties({isActive: onAvatarAppScreenNow}); + + if (!onAvatarAppScreen && onAvatarAppScreenNow) { + on(); + } else if(onAvatarAppScreen && !onAvatarAppScreenNow) { + off(); + } + + onAvatarAppScreen = onAvatarAppScreenNow; + + if(onAvatarAppScreenNow) { + var message = { + 'method' : 'initialize', + 'data' : { + 'jointNames' : MyAvatar.getJointNames() + } + }; + + sendToQml(message) + } +} + +function shutdown() { + if (onAvatarAppScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onTabletButtonClicked); + tablet.removeButton(button); + tablet.screenChanged.disconnect(onTabletScreenChanged); + + off(); +} + +// +// Cleanup. +// +Script.scriptEnding.connect(shutdown); + +}()); // END LOCAL_SCOPE diff --git a/scripts/system/away.js b/scripts/system/away.js index dc9b33e952..a2e73ae63c 100644 --- a/scripts/system/away.js +++ b/scripts/system/away.js @@ -177,6 +177,10 @@ function goActive() { UserActivityLogger.toggledAway(false); MyAvatar.isAway = false; + + if (!Window.hasFocus()) { + Window.setFocus(); + } } MyAvatar.wentAway.connect(setAwayProperties); diff --git a/scripts/system/controllers/controllerDisplay.js b/scripts/system/controllers/controllerDisplay.js index dc7c9b37bd..8aa0393357 100644 --- a/scripts/system/controllers/controllerDisplay.js +++ b/scripts/system/controllers/controllerDisplay.js @@ -108,14 +108,13 @@ createControllerDisplay = function(config) { for (var partName in controller.parts) { overlayID = this.overlays[i++]; var part = controller.parts[partName]; - localPosition = Vec3.sum(controller.position, Vec3.multiplyQbyV(controller.rotation, part.naturalPosition)); + localPosition = Vec3.subtract(part.naturalPosition, controller.naturalPosition); var localRotation; var value = this.partValues[partName]; var offset, rotation; if (value !== undefined) { if (part.type === "linear") { - var axis = Vec3.multiplyQbyV(controller.rotation, part.axis); - offset = Vec3.multiply(part.maxTranslation * value, axis); + offset = Vec3.multiply(part.maxTranslation * value, part.axis); localPosition = Vec3.sum(localPosition, offset); localRotation = undefined; } else if (part.type === "joystick") { @@ -126,8 +125,8 @@ createControllerDisplay = function(config) { } else { offset = { x: 0, y: 0, z: 0 }; } - localPosition = Vec3.sum(controller.position, Vec3.multiplyQbyV(controller.rotation, Vec3.sum(offset, part.naturalPosition))); - localRotation = Quat.multiply(controller.rotation, rotation); + localPosition = Vec3.sum(offset, localPosition); + localRotation = rotation; } else if (part.type === "rotational") { value = clamp(value, part.minValue, part.maxValue); var pct = (value - part.minValue) / part.maxValue; @@ -139,8 +138,8 @@ createControllerDisplay = function(config) { } else { offset = { x: 0, y: 0, z: 0 }; } - localPosition = Vec3.sum(controller.position, Vec3.multiplyQbyV(controller.rotation, Vec3.sum(offset, part.naturalPosition))); - localRotation = Quat.multiply(controller.rotation, rotation); + localPosition = Vec3.sum(offset, localPosition); + localRotation = rotation; } } if (localRotation !== undefined) { @@ -169,9 +168,11 @@ createControllerDisplay = function(config) { if (controller.naturalPosition) { position = Vec3.sum(Vec3.multiplyQbyV(controller.rotation, controller.naturalPosition), position); + } else { + controller.naturalPosition = { x: 0, y: 0, z: 0 }; } - var overlayID = Overlays.addOverlay("model", { + var baseOverlayID = Overlays.addOverlay("model", { url: controller.modelURL, dimensions: Vec3.multiply(sensorScaleFactor, controller.dimensions), localRotation: controller.rotation, @@ -181,23 +182,21 @@ createControllerDisplay = function(config) { ignoreRayIntersection: true }); - controllerDisplay.overlays.push(overlayID); - overlayID = null; + controllerDisplay.overlays.push(baseOverlayID); if (controller.parts) { for (var partName in controller.parts) { var part = controller.parts[partName]; - var partPosition = Vec3.sum(controller.position, Vec3.multiplyQbyV(controller.rotation, part.naturalPosition)); - var innerRotation = controller.rotation; + var localPosition = Vec3.subtract(part.naturalPosition, controller.naturalPosition); + var localRotation = { x: 0, y: 0, z: 0, w: 1 } controllerDisplay.parts[partName] = controller.parts[partName]; var properties = { url: part.modelURL, - localPosition: partPosition, - localRotation: innerRotation, - parentID: MyAvatar.SELF_ID, - parentJointIndex: controller.jointIndex, + localPosition: localPosition, + localRotation: localRotation, + parentID: baseOverlayID, ignoreRayIntersection: true }; @@ -207,11 +206,10 @@ createControllerDisplay = function(config) { properties['textures'] = textures; } - overlayID = Overlays.addOverlay("model", properties); + var overlayID = Overlays.addOverlay("model", properties); if (part.type === "rotational") { var input = resolveHardware(part.input); - print("Mapping to: ", part.input, input); mapping.from([input]).peek().to(function(partName) { return function(value) { // insert the most recent controller value into controllerDisplay.partValues. diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index 5d90898b82..377167d7bf 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -10,7 +10,7 @@ /* global Script, Controller, RIGHT_HAND, LEFT_HAND, enableDispatcherModule, disableDispatcherModule, makeRunningValues, Messages, makeDispatcherModuleParameters, HMD, getGrabPointSphereOffset, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE, COLORS_GRAB_DISTANCE_HOLD, DEFAULT_SEARCH_SPHERE_DISTANCE, TRIGGER_ON_VALUE, - getEnabledModuleByName, PICK_MAX_DISTANCE, isInEditMode, Picks, makeLaserParams + getEnabledModuleByName, PICK_MAX_DISTANCE, isInEditMode, Picks, makeLaserParams, Entities */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -18,10 +18,16 @@ Script.include("/~/system/libraries/controllers.js"); Script.include("/~/system/libraries/utils.js"); (function () { + var MARGIN = 25; + var TABLET_MATERIAL_ENTITY_NAME = 'Tablet-Material-Entity'; function InEditMode(hand) { this.hand = hand; this.triggerClicked = false; this.selectedTarget = null; + this.reticleMinX = MARGIN; + this.reticleMaxX; + this.reticleMinY = MARGIN; + this.reticleMaxY; this.parameters = makeDispatcherModuleParameters( 160, @@ -48,6 +54,16 @@ Script.include("/~/system/libraries/utils.js"); || (HMD.homeButtonID && objectID === HMD.homeButtonID); }; + this.calculateNewReticlePosition = function(intersection) { + var dims = Controller.getViewportDimensions(); + this.reticleMaxX = dims.x - MARGIN; + this.reticleMaxY = dims.y - MARGIN; + var point2d = HMD.overlayFromWorldPoint(intersection); + point2d.x = Math.max(this.reticleMinX, Math.min(point2d.x, this.reticleMaxX)); + point2d.y = Math.max(this.reticleMinY, Math.min(point2d.y, this.reticleMaxY)); + return point2d; + }; + this.sendPickData = function(controllerData) { if (controllerData.triggerClicks[this.hand]) { if (!this.triggerClicked) { @@ -59,10 +75,12 @@ Script.include("/~/system/libraries/utils.js"); } } if (this.selectedTarget.type === Picks.INTERSECTED_ENTITY) { - Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ - method: "selectEntity", - entityID: this.selectedTarget.objectID - })); + if (!this.isTabletMaterialEntity(this.selectedTarget.objectID)) { + Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ + method: "selectEntity", + entityID: this.selectedTarget.objectID + })); + } } else if (this.selectedTarget.type === Picks.INTERSECTED_OVERLAY) { Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ method: "selectOverlay", @@ -72,6 +90,29 @@ Script.include("/~/system/libraries/utils.js"); this.triggerClicked = true; } + + this.sendPointingAtData(controllerData); + }; + + + this.isTabletMaterialEntity = function(entityID) { + return ((entityID === HMD.homeButtonHighlightMaterialID) || + (entityID === HMD.homeButtonUnhighlightMaterialID)); + }; + + this.sendPointingAtData = function(controllerData) { + var rayPick = controllerData.rayPicks[this.hand]; + var hudRayPick = controllerData.hudRayPicks[this.hand]; + var point2d = this.calculateNewReticlePosition(hudRayPick.intersection); + var desktopWindow = Window.isPointOnDesktopWindow(point2d); + var tablet = this.pointingAtTablet(rayPick.objectID); + var rightHand = this.hand === RIGHT_HAND; + Messages.sendLocalMessage("entityToolUpdates", JSON.stringify({ + method: "pointingAt", + desktopWindow: desktopWindow, + tablet: tablet, + rightHand: rightHand + })); }; this.exitModule = function() { @@ -104,6 +145,7 @@ Script.include("/~/system/libraries/utils.js"); if (overlayLaser) { var overlayLaserReady = overlayLaser.isReady(controllerData); var target = controllerData.rayPicks[this.hand].objectID; + this.sendPointingAtData(controllerData); if (overlayLaserReady.active && this.pointingAtTablet(target)) { return this.exitModule(); } diff --git a/scripts/system/controllers/controllerModules/mouseHMD.js b/scripts/system/controllers/controllerModules/mouseHMD.js index 1d8aeee1f9..101a3502e1 100644 --- a/scripts/system/controllers/controllerModules/mouseHMD.js +++ b/scripts/system/controllers/controllerModules/mouseHMD.js @@ -101,12 +101,15 @@ this.isReady = function(controllerData, deltaTime) { var now = Date.now(); this.triggersPressed(controllerData, now); - if ((HMD.active && !this.mouseActivity.expired(now)) && _this.handControllerActivity.expired()) { - Reticle.visible = true; - return ControllerDispatcherUtils.makeRunningValues(true, [], []); - } if (HMD.active) { - Reticle.visible = false; + if (!this.mouseActivity.expired(now) && _this.handControllerActivity.expired()) { + Reticle.visible = true; + return ControllerDispatcherUtils.makeRunningValues(true, [], []); + } else { + Reticle.visible = false; + } + } else if (!Reticle.visible) { + Reticle.visible = true; } return ControllerDispatcherUtils.makeRunningValues(false, [], []); diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index 00d7ad0491..38334f5523 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -11,7 +11,8 @@ TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS, findGroupParent, Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, findHandChildEntities, TEAR_AWAY_DISTANCE, MSECS_PER_SEC, TEAR_AWAY_CHECK_TIME, - TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox, print, Uuid, highlightTargetEntity, unhighlightTargetEntity + TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox, print, Uuid, highlightTargetEntity, unhighlightTargetEntity, + distanceBetweenEntityLocalPositionAndBoundingBox */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -172,12 +173,9 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); if (now - this.lastUnequipCheckTime > MSECS_PER_SEC * TEAR_AWAY_CHECK_TIME) { this.lastUnequipCheckTime = now; if (props.parentID === MyAvatar.SELF_ID) { - var sensorScaleFactor = MyAvatar.sensorToWorldScale; - var handPosition = controllerData.controllerLocations[this.hand].position; - var dist = distanceBetweenPointAndEntityBoundingBox(handPosition, props); - var distance = Vec3.distance(props.position, handPosition); - if ((dist > TEAR_AWAY_DISTANCE) || - (distance > NEAR_GRAB_RADIUS * sensorScaleFactor)) { + var tearAwayDistance = TEAR_AWAY_DISTANCE * MyAvatar.sensorToWorldScale; + var distance = distanceBetweenEntityLocalPositionAndBoundingBox(props); + if (distance > tearAwayDistance) { this.autoUnequipCounter++; } else { this.autoUnequipCounter = 0; diff --git a/scripts/system/controllers/controllerModules/teleport.js b/scripts/system/controllers/controllerModules/teleport.js index 3bf99ca26a..7200872e00 100644 --- a/scripts/system/controllers/controllerModules/teleport.js +++ b/scripts/system/controllers/controllerModules/teleport.js @@ -49,7 +49,6 @@ Script.include("/~/system/libraries/controllers.js"); blue: 73 }; - var TELEPORT_CANCEL_RANGE = 1; var COOL_IN_DURATION = 300; var handInfo = { @@ -64,7 +63,7 @@ Script.include("/~/system/libraries/controllers.js"); var cancelPath = { type: "line3d", color: COLORS_TELEPORT_CANCEL, - ignoreRayIntersection: true, + ignorePickIntersection: true, alpha: 1, solid: true, drawInFront: true, @@ -73,7 +72,7 @@ Script.include("/~/system/libraries/controllers.js"); var teleportPath = { type: "line3d", color: COLORS_TELEPORT_CAN_TELEPORT, - ignoreRayIntersection: true, + ignorePickIntersection: true, alpha: 1, solid: true, drawInFront: true, @@ -82,7 +81,7 @@ Script.include("/~/system/libraries/controllers.js"); var seatPath = { type: "line3d", color: COLORS_TELEPORT_SEAT, - ignoreRayIntersection: true, + ignorePickIntersection: true, alpha: 1, solid: true, drawInFront: true, @@ -92,19 +91,19 @@ Script.include("/~/system/libraries/controllers.js"); type: "model", url: TOO_CLOSE_MODEL_URL, dimensions: TARGET_MODEL_DIMENSIONS, - ignoreRayIntersection: true + ignorePickIntersection: true }; var teleportEnd = { type: "model", url: TARGET_MODEL_URL, dimensions: TARGET_MODEL_DIMENSIONS, - ignoreRayIntersection: true + ignorePickIntersection: true }; var seatEnd = { type: "model", url: SEAT_MODEL_URL, dimensions: TARGET_MODEL_DIMENSIONS, - ignoreRayIntersection: true + ignorePickIntersection: true }; @@ -134,6 +133,9 @@ Script.include("/~/system/libraries/controllers.js"); SEAT: 'seat' // The current target is a seat }; + var speed = 7.0; + var accelerationAxis = {x: 0.0, y: -5.0, z: 0.0}; + function Teleporter(hand) { var _this = this; this.hand = hand; @@ -149,46 +151,58 @@ Script.include("/~/system/libraries/controllers.js"); return otherModule; }; - this.teleportRayHandVisible = Pointers.createPointer(PickType.Ray, { - joint: (_this.hand === RIGHT_HAND) ? "RightHand" : "LeftHand", + this.teleportParabolaHandVisible = Pointers.createPointer(PickType.Ray, { + joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", filter: Picks.PICK_ENTITIES, faceAvatar: true, scaleWithAvatar: true, centerEndY: false, + speed: speed, + accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, renderStates: teleportRenderStates, defaultRenderStates: teleportDefaultRenderStates }); - this.teleportRayHandInvisible = Pointers.createPointer(PickType.Ray, { - joint: (_this.hand === RIGHT_HAND) ? "RightHand" : "LeftHand", + this.teleportParabolaHandInvisible = Pointers.createPointer(PickType.Ray, { + joint: (_this.hand === RIGHT_HAND) ? "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, faceAvatar: true, scaleWithAvatar: true, centerEndY: false, + speed: speed, + accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, renderStates: teleportRenderStates }); - this.teleportRayHeadVisible = Pointers.createPointer(PickType.Ray, { + this.teleportParabolaHeadVisible = Pointers.createPointer(PickType.Ray, { joint: "Avatar", filter: Picks.PICK_ENTITIES, faceAvatar: true, scaleWithAvatar: true, centerEndY: false, + speed: speed, + accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, renderStates: teleportRenderStates, defaultRenderStates: teleportDefaultRenderStates }); - this.teleportRayHeadInvisible = Pointers.createPointer(PickType.Ray, { + this.teleportParabolaHeadInvisible = Pointers.createPointer(PickType.Ray, { joint: "Avatar", filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_INVISIBLE, faceAvatar: true, scaleWithAvatar: true, centerEndY: false, + speed: speed, + accelerationAxis: accelerationAxis, + rotateAccelerationWithAvatar: true, renderStates: teleportRenderStates }); this.cleanup = function() { - Pointers.removePointer(this.teleportRayHandVisible); - Pointers.removePointer(this.teleportRayHandInvisible); - Pointers.removePointer(this.teleportRayHeadVisible); - Pointers.removePointer(this.teleportRayHeadInvisible); + Pointers.removePointer(this.teleportParabolaHandVisible); + Pointers.removePointer(this.teleportParabolaHandInvisible); + Pointers.removePointer(this.teleportParabolaHeadVisible); + Pointers.removePointer(this.teleportParabolaHeadInvisible); }; this.buttonPress = function(value) { @@ -212,34 +226,6 @@ Script.include("/~/system/libraries/controllers.js"); _this.state = TELEPORTER_STATES.TARGETTING; } }, COOL_IN_DURATION); - - // pad scale with avatar size - var AVATAR_PROPORTIONAL_TARGET_MODEL_DIMENSIONS = Vec3.multiply(MyAvatar.sensorToWorldScale, TARGET_MODEL_DIMENSIONS); - - if (!Vec3.equal(AVATAR_PROPORTIONAL_TARGET_MODEL_DIMENSIONS, cancelEnd.dimensions)) { - cancelEnd.dimensions = AVATAR_PROPORTIONAL_TARGET_MODEL_DIMENSIONS; - teleportEnd.dimensions = AVATAR_PROPORTIONAL_TARGET_MODEL_DIMENSIONS; - seatEnd.dimensions = AVATAR_PROPORTIONAL_TARGET_MODEL_DIMENSIONS; - - teleportRenderStates = [{name: "cancel", path: cancelPath, end: cancelEnd}, - {name: "teleport", path: teleportPath, end: teleportEnd}, - {name: "seat", path: seatPath, end: seatEnd}]; - - Pointers.editRenderState(this.teleportRayHandVisible, "cancel", teleportRenderStates[0]); - Pointers.editRenderState(this.teleportRayHandInvisible, "cancel", teleportRenderStates[0]); - Pointers.editRenderState(this.teleportRayHeadVisible, "cancel", teleportRenderStates[0]); - Pointers.editRenderState(this.teleportRayHeadInvisible, "cancel", teleportRenderStates[0]); - - Pointers.editRenderState(this.teleportRayHandVisible, "teleport", teleportRenderStates[1]); - Pointers.editRenderState(this.teleportRayHandInvisible, "teleport", teleportRenderStates[1]); - Pointers.editRenderState(this.teleportRayHeadVisible, "teleport", teleportRenderStates[1]); - Pointers.editRenderState(this.teleportRayHeadInvisible, "teleport", teleportRenderStates[1]); - - Pointers.editRenderState(this.teleportRayHandVisible, "seat", teleportRenderStates[2]); - Pointers.editRenderState(this.teleportRayHandInvisible, "seat", teleportRenderStates[2]); - Pointers.editRenderState(this.teleportRayHeadVisible, "seat", teleportRenderStates[2]); - Pointers.editRenderState(this.teleportRayHeadInvisible, "seat", teleportRenderStates[2]); - } }; this.isReady = function(controllerData, deltaTime) { @@ -258,18 +244,18 @@ Script.include("/~/system/libraries/controllers.js"); var pose = Controller.getPoseValue(handInfo[(_this.hand === RIGHT_HAND) ? 'right' : 'left'].controllerInput); var mode = pose.valid ? _this.hand : 'head'; if (!pose.valid) { - Pointers.disablePointer(_this.teleportRayHandVisible); - Pointers.disablePointer(_this.teleportRayHandInvisible); - Pointers.enablePointer(_this.teleportRayHeadVisible); - Pointers.enablePointer(_this.teleportRayHeadInvisible); + Pointers.disablePointer(_this.teleportParabolaHandVisible); + Pointers.disablePointer(_this.teleportParabolaHandInvisible); + Pointers.enablePointer(_this.teleportParabolaHeadVisible); + Pointers.enablePointer(_this.teleportParabolaHeadInvisible); } else { - Pointers.enablePointer(_this.teleportRayHandVisible); - Pointers.enablePointer(_this.teleportRayHandInvisible); - Pointers.disablePointer(_this.teleportRayHeadVisible); - Pointers.disablePointer(_this.teleportRayHeadInvisible); + Pointers.enablePointer(_this.teleportParabolaHandVisible); + Pointers.enablePointer(_this.teleportParabolaHandInvisible); + Pointers.disablePointer(_this.teleportParabolaHeadVisible); + Pointers.disablePointer(_this.teleportParabolaHeadInvisible); } - // We do up to 2 ray picks to find a teleport location. + // We do up to 2 picks to find a teleport location. // There are 2 types of teleport locations we are interested in: // 1. A visible floor. This can be any entity surface that points within some degree of "up" // 2. A seat. The seat can be visible or invisible. @@ -280,17 +266,17 @@ Script.include("/~/system/libraries/controllers.js"); // var result; if (mode === 'head') { - result = Pointers.getPrevPickResult(_this.teleportRayHeadInvisible); + result = Pointers.getPrevPickResult(_this.teleportParabolaHeadInvisible); } else { - result = Pointers.getPrevPickResult(_this.teleportRayHandInvisible); + result = Pointers.getPrevPickResult(_this.teleportParabolaHandInvisible); } var teleportLocationType = getTeleportTargetType(result); if (teleportLocationType === TARGET.INVISIBLE) { if (mode === 'head') { - result = Pointers.getPrevPickResult(_this.teleportRayHeadVisible); + result = Pointers.getPrevPickResult(_this.teleportParabolaHeadVisible); } else { - result = Pointers.getPrevPickResult(_this.teleportRayHandVisible); + result = Pointers.getPrevPickResult(_this.teleportParabolaHandVisible); } teleportLocationType = getTeleportTargetType(result); } @@ -325,7 +311,7 @@ Script.include("/~/system/libraries/controllers.js"); } else if (target === TARGET.SURFACE) { var offset = getAvatarFootOffset(); result.intersection.y += offset; - MyAvatar.goToLocation(result.intersection, false, {x: 0, y: 0, z: 0, w: 1}, false); + MyAvatar.goToLocation(result.intersection, true, HMD.orientation, false); HMD.centerUI(); MyAvatar.centerBody(); } @@ -336,27 +322,27 @@ Script.include("/~/system/libraries/controllers.js"); }; this.disableLasers = function() { - Pointers.disablePointer(_this.teleportRayHandVisible); - Pointers.disablePointer(_this.teleportRayHandInvisible); - Pointers.disablePointer(_this.teleportRayHeadVisible); - Pointers.disablePointer(_this.teleportRayHeadInvisible); + Pointers.disablePointer(_this.teleportParabolaHandVisible); + Pointers.disablePointer(_this.teleportParabolaHandInvisible); + Pointers.disablePointer(_this.teleportParabolaHeadVisible); + Pointers.disablePointer(_this.teleportParabolaHeadInvisible); }; this.setTeleportState = function(mode, visibleState, invisibleState) { if (mode === 'head') { - Pointers.setRenderState(_this.teleportRayHeadVisible, visibleState); - Pointers.setRenderState(_this.teleportRayHeadInvisible, invisibleState); + Pointers.setRenderState(_this.teleportParabolaHeadVisible, visibleState); + Pointers.setRenderState(_this.teleportParabolaHeadInvisible, invisibleState); } else { - Pointers.setRenderState(_this.teleportRayHandVisible, visibleState); - Pointers.setRenderState(_this.teleportRayHandInvisible, invisibleState); + Pointers.setRenderState(_this.teleportParabolaHandVisible, visibleState); + Pointers.setRenderState(_this.teleportParabolaHandInvisible, invisibleState); } }; this.setIgnoreEntities = function(entitiesToIgnore) { - Pointers.setIgnoreItems(this.teleportRayHandVisible, entitiesToIgnore); - Pointers.setIgnoreItems(this.teleportRayHandInvisible, entitiesToIgnore); - Pointers.setIgnoreItems(this.teleportRayHeadVisible, entitiesToIgnore); - Pointers.setIgnoreItems(this.teleportRayHeadInvisible, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHandVisible, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHandInvisible, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHeadVisible, entitiesToIgnore); + Pointers.setIgnoreItems(this.teleportParabolaHeadInvisible, entitiesToIgnore); }; } @@ -399,7 +385,7 @@ Script.include("/~/system/libraries/controllers.js"); } // When determininig whether you can teleport to a location, the normal of the // point that is being intersected with is looked at. If this normal is more - // than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from <0, 1, 0> (straight up), then + // than MAX_ANGLE_FROM_UP_TO_TELEPORT degrees from your avatar's up, then // you can't teleport there. var MAX_ANGLE_FROM_UP_TO_TELEPORT = 70; function getTeleportTargetType(result) { @@ -423,12 +409,9 @@ Script.include("/~/system/libraries/controllers.js"); } 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); + var angle = Math.acos(Vec3.dot(surfaceNormal, Quat.getUp(MyAvatar.orientation))) * (180.0 / Math.PI); - if (angleUp < (90 - MAX_ANGLE_FROM_UP_TO_TELEPORT) || - angleUp > (90 + MAX_ANGLE_FROM_UP_TO_TELEPORT) || - Vec3.distance(MyAvatar.position, result.intersection) <= TELEPORT_CANCEL_RANGE * MyAvatar.sensorToWorldScale) { + if (angle > MAX_ANGLE_FROM_UP_TO_TELEPORT) { return TARGET.INVALID; } else { return TARGET.SURFACE; diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 949fdbb072..a2fe0bfcd4 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -50,21 +50,29 @@ Script.include("/~/system/libraries/controllers.js"); return this.hand === RIGHT_HAND ? leftOverlayLaserInput : rightOverlayLaserInput; }; - this.isPointingAtWebEntity = function(controllerData) { + this.isPointingAtTriggerable = function(controllerData, triggerPressed) { + // allow pointing at tablet, unlocked web entities, or web overlays automatically without pressing trigger, + // but for pointing at locked web entities or non-web overlays user must be pressing trigger var intersection = controllerData.rayPicks[this.hand]; - var entityProperty = Entities.getEntityProperties(intersection.objectID); - var entityType = entityProperty.type; - if ((intersection.type === Picks.INTERSECTED_ENTITY && entityType === "Web")) { - return true; + if (intersection.type === Picks.INTERSECTED_OVERLAY) { + var objectID = intersection.objectID; + if ((HMD.tabletID && objectID === HMD.tabletID) || + (HMD.tabletScreenID && objectID === HMD.tabletScreenID) || + (HMD.homeButtonID && objectID === HMD.homeButtonID)) { + return true; + } else { + var overlayType = Overlays.getOverlayType(objectID); + return overlayType === "web3d" || triggerPressed; + } + } else if (intersection.type === Picks.INTERSECTED_ENTITY) { + var entityProperty = Entities.getEntityProperties(intersection.objectID); + var entityType = entityProperty.type; + var isLocked = entityProperty.locked; + return entityType === "Web" && (!isLocked || triggerPressed); } return false; }; - this.isPointingAtOverlay = function(controllerData) { - var intersection = controllerData.rayPicks[this.hand]; - return intersection.type === Picks.INTERSECTED_OVERLAY; - }; - this.deleteContextOverlay = function() { var farGrabModule = getEnabledModuleByName(this.hand === RIGHT_HAND ? "RightFarActionGrabEntity" : "LeftFarActionGrabEntity"); @@ -92,10 +100,10 @@ Script.include("/~/system/libraries/controllers.js"); this.isReady = function(controllerData) { var otherModuleRunning = this.getOtherModule().running; otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. - var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE - && controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; - if ((!otherModuleRunning || isTriggerPressed) - && (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData))) { + var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE && + controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; + var allowThisModule = !otherModuleRunning || isTriggerPressed; + if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed)) { this.updateAllwaysOn(); if (isTriggerPressed) { this.dominantHandOverride = true; // Override dominant hand. @@ -113,9 +121,10 @@ Script.include("/~/system/libraries/controllers.js"); otherModuleRunning = otherModuleRunning && this.getDominantHand() !== this.hand; // Auto-swap to dominant hand. otherModuleRunning = otherModuleRunning || this.getOtherModule().dominantHandOverride; // Override dominant hand. var grabModuleNeedsToRun = this.grabModuleWantsNearbyOverlay(controllerData); - if (!otherModuleRunning && !grabModuleNeedsToRun && (controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE - || this.parameters.handLaser.allwaysOn - && (this.isPointingAtOverlay(controllerData) || this.isPointingAtWebEntity(controllerData)))) { + var allowThisModule = !otherModuleRunning && !grabModuleNeedsToRun; + var isTriggerPressed = controllerData.triggerValues[this.hand] > TRIGGER_OFF_VALUE; + var laserOn = isTriggerPressed || this.parameters.handLaser.allwaysOn; + if (allowThisModule && (laserOn && this.isPointingAtTriggerable(controllerData, isTriggerPressed))) { this.running = true; return makeRunningValues(true, [], []); } diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 50e627fe5d..ce93c6a010 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -14,6 +14,7 @@ var CONTOLLER_SCRIPTS = [ "controllerDisplayManager.js", "grab.js", "toggleAdvancedMovementForHandControllers.js", + "handTouch.js", "controllerDispatcher.js", "controllerModules/nearParentGrabEntity.js", "controllerModules/nearParentGrabOverlay.js", @@ -28,7 +29,6 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/disableOtherModule.js", "controllerModules/farTrigger.js", "controllerModules/teleport.js", - "controllerModules/scaleAvatar.js", "controllerModules/hudOverlayPointer.js", "controllerModules/mouseHMD.js", "controllerModules/scaleEntity.js", diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index a835373e4d..066ef18c97 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -260,7 +260,7 @@ function Grabber() { this.mouseRayOverlays = Picks.createPick(PickType.Ray, { joint: "Mouse", - filter: Picks.PICK_OVERLAYS, + filter: Picks.PICK_OVERLAYS | Picks.PICK_INCLUDE_NONCOLLIDABLE, enabled: true }); var tabletItems = getMainTabletIDs(); @@ -270,7 +270,7 @@ function Grabber() { var renderStates = [{name: "grabbed", end: beacon}]; this.mouseRayEntities = Pointers.createPointer(PickType.Ray, { joint: "Mouse", - filter: Picks.PICK_ENTITIES, + filter: Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE, faceAvatar: true, scaleWithAvatar: true, enabled: true, @@ -315,7 +315,7 @@ Grabber.prototype.pressEvent = function(event) { return; } - if (event.isLeftButton !== true || event.isRightButton === true || event.isMiddleButton === true) { + if (event.button !== "LEFT") { return; } @@ -419,7 +419,7 @@ Grabber.prototype.pressEvent = function(event) { }; Grabber.prototype.releaseEvent = function(event) { - if ((event.isLeftButton!==true || event.isRightButton===true || event.isMiddleButton===true) && !HMD.active) { + if (event.button !== "LEFT" && !HMD.active) { return; } diff --git a/scripts/system/controllers/handTouch.js b/scripts/system/controllers/handTouch.js index 345ba7a1d7..db79aa4a77 100644 --- a/scripts/system/controllers/handTouch.js +++ b/scripts/system/controllers/handTouch.js @@ -14,186 +14,259 @@ /* global Script, Overlays, Controller, Vec3, MyAvatar, Entities */ -(function(){ +(function () { + var handTouchEnabled = true; + var MSECONDS_AFTER_LOAD = 2000; var updateFingerWithIndex = 0; + var untouchableEntities = []; - // Keys to access finger data + // Keys to access finger data var fingerKeys = ["pinky", "ring", "middle", "index", "thumb"]; - - // Additionally close the hands to achieve a grabbing effect - var grabPercent = { left: 0, - right: 0 }; - - // var isGrabbing = false; - + + // Additionally close the hands to achieve a grabbing effect + var grabPercent = { left: 0, right: 0 }; + var Palm = function() { - this.position = {x:0, y:0, z:0}; - this.perpendicular = {x:0, y:0, z:0}; + this.position = {x: 0, y: 0, z: 0}; + this.perpendicular = {x: 0, y: 0, z: 0}; this.distance = 0; this.fingers = { - pinky: {x:0, y:0, z:0}, - middle: {x:0, y:0, z:0}, - ring: {x:0, y:0, z:0}, - thumb: {x:0, y:0, z:0}, - index: {x:0, y:0, z:0} + pinky: {x: 0, y: 0, z: 0}, + middle: {x: 0, y: 0, z: 0}, + ring: {x: 0, y: 0, z: 0}, + thumb: {x: 0, y: 0, z: 0}, + index: {x: 0, y: 0, z: 0} }; this.set = false; }; - + var palmData = { left: new Palm(), right: new Palm() }; var handJointNames = {left: "LeftHand", right: "RightHand"}; - - // Store which fingers are touching - if all false restate the default poses + + // Store which fingers are touching - if all false restate the default poses var isTouching = { left: { - pinky: false, - middle: false, - ring: false, - thumb: false, - index: false + pinky: false, + middle: false, + ring: false, + thumb: false, + index: false }, right: { - pinky: false, - middle: false, - ring: false, - thumb: false, + pinky: false, + middle: false, + ring: false, + thumb: false, index: false } }; - + // frame count for transition to default pose - + var countToDefault = { left: 0, right: 0 }; - - // joint data for opened pose - + + // joint data for open pose var dataOpen = { left: { - pinky:[{x: -0.0066, y:-0.0224, z:-0.2174, w:0.9758},{x: 0.0112, y:0.0001, z:0.0093, w:0.9999},{x: -0.0346, y:0.0003, z:-0.0073, w:0.9994}], - ring:[{x: -0.0029, y:-0.0094, z:-0.1413, w:0.9899},{x: 0.0112, y:0.0001, z:0.0059, w:0.9999},{x: -0.0346, y:0.0002, z:-0.006, w:0.9994}], - middle:[{x: -0.0016, y:0, z:-0.0286, w:0.9996},{x: 0.0112, y:-0.0001, z:-0.0063, w:0.9999},{x: -0.0346, y:-0.0003, z:0.0073, w:0.9994}], - index:[{x: -0.0016, y:0.0001, z:0.0199, w:0.9998},{x: 0.0112, y:0, z:0.0081, w:0.9999},{x: -0.0346, y:0.0008, z:-0.023, w:0.9991}], - thumb:[{x: 0.0354, y:0.0363, z:0.3275, w:0.9435},{x: -0.0945, y:0.0938, z:0.0995, w:0.9861},{x: -0.0952, y:0.0718, z:0.1382, w:0.9832}] + pinky: [ + {x: -0.0066, y: -0.0224, z: -0.2174, w: 0.9758}, + {x: 0.0112, y: 0.0001, z: 0.0093, w: 0.9999}, + {x: -0.0346, y: 0.0003, z: -0.0073, w: 0.9994} + ], + ring: [ + {x: -0.0029, y: -0.0094, z: -0.1413, w: 0.9899}, + {x: 0.0112, y: 0.0001, z: 0.0059, w: 0.9999}, + {x: -0.0346, y: 0.0002, z: -0.006, w: 0.9994} + ], + middle: [ + {x: -0.0016, y: 0, z: -0.0286, w: 0.9996}, + {x: 0.0112, y: -0.0001, z: -0.0063, w: 0.9999}, + {x: -0.0346, y: -0.0003, z: 0.0073, w: 0.9994} + ], + index: [ + {x: -0.0016, y: 0.0001, z: 0.0199, w: 0.9998}, + {x: 0.0112, y: 0, z: 0.0081, w: 0.9999}, + {x: -0.0346, y: 0.0008, z: -0.023, w: 0.9991} + ], + thumb: [ + {x: 0.0354, y: 0.0363, z: 0.3275, w: 0.9435}, + {x: -0.0945, y: 0.0938, z: 0.0995, w: 0.9861}, + {x: -0.0952, y: 0.0718, z: 0.1382, w: 0.9832} + ] }, right: { - pinky:[{x: -0.0034, y:0.023, z:0.1051, w:0.9942},{x: 0.0106, y:-0.0001, z:-0.0091, w:0.9999},{x: -0.0346, y:-0.0003, z:0.0075, w:0.9994}], - ring:[{x: -0.0013, y:0.0097, z:0.0311, w:0.9995},{x: 0.0106, y:-0.0001, z:-0.0056, w:0.9999},{x: -0.0346, y:-0.0002, z:0.0061, w:0.9994}], - middle:[{x: -0.001, y:0, z:0.0285, w:0.9996},{x: 0.0106, y:0.0001, z:0.0062, w:0.9999},{x: -0.0346, y:0.0003, z:-0.0074, w:0.9994}], - index:[{x: -0.001, y:0, z:-0.0199, w:0.9998},{x: 0.0106, y:-0.0001, z:-0.0079, w:0.9999},{x: -0.0346, y:-0.0008, z:0.0229, w:0.9991}], - thumb:[{x: 0.0355, y:-0.0363, z:-0.3263, w:0.9439},{x: -0.0946, y:-0.0938, z:-0.0996, w:0.9861},{x: -0.0952, y:-0.0719, z:-0.1376, w:0.9833}] + pinky: [ + {x: -0.0034, y: 0.023, z: 0.1051, w: 0.9942}, + {x: 0.0106, y: -0.0001, z: -0.0091, w: 0.9999}, + {x: -0.0346, y: -0.0003, z: 0.0075, w: 0.9994} + ], + ring: [ + {x: -0.0013, y: 0.0097, z: 0.0311, w: 0.9995}, + {x: 0.0106, y: -0.0001, z: -0.0056, w: 0.9999}, + {x: -0.0346, y: -0.0002, z: 0.0061, w: 0.9994} + ], + middle: [ + {x: -0.001, y: 0, z: 0.0285, w: 0.9996}, + {x: 0.0106, y: 0.0001, z: 0.0062, w: 0.9999}, + {x: -0.0346, y: 0.0003, z: -0.0074, w: 0.9994} + ], + index: [ + {x: -0.001, y: 0, z: -0.0199, w: 0.9998}, + {x: 0.0106, y: -0.0001, z: -0.0079, w: 0.9999}, + {x: -0.0346, y: -0.0008, z: 0.0229, w: 0.9991} + ], + thumb: [ + {x: 0.0355, y: -0.0363, z: -0.3263, w: 0.9439}, + {x: -0.0946, y: -0.0938, z: -0.0996, w: 0.9861}, + {x: -0.0952, y: -0.0719, z: -0.1376, w: 0.9833} + ] } }; + + // joint data for close pose var dataClose = { left: { - pinky:[{x: 0.5878, y:-0.1735, z:-0.1123, w:0.7821},{x: 0.5704, y:0.0053, z:0.0076, w:0.8213},{x: 0.6069, y:-0.0044, z:-0.0058, w:0.7947}], - ring:[{x: 0.5761, y:-0.0989, z:-0.1025, w:0.8048},{x: 0.5332, y:0.0032, z:0.005, w:0.846},{x: 0.5773, y:-0.0035, z:-0.0049, w:0.8165}], - middle:[{x: 0.543, y:-0.0469, z:-0.0333, w:0.8378},{x: 0.5419, y:-0.0034, z:-0.0053, w:0.8404},{x: 0.5015, y:0.0037, z:0.0063, w:0.8651}], - index:[{x: 0.3051, y:-0.0156, z:-0.014, w:0.9521},{x: 0.6414, y:0.0051, z:0.0063, w:0.7671},{x: 0.5646, y:-0.013, z:-0.019, w:0.8251}], - thumb:[{x: 0.313, y:-0.0348, z:0.3192, w:0.8938},{x: 0, y:0, z:-0.37, w:0.929},{x: 0, y:0, z:-0.2604, w:0.9655}] + pinky: [ + {x: 0.5878, y: -0.1735, z: -0.1123, w: 0.7821}, + {x: 0.5704, y: 0.0053, z: 0.0076, w: 0.8213}, + {x: 0.6069, y: -0.0044, z: -0.0058, w: 0.7947} + ], + ring: [ + {x: 0.5761, y: -0.0989, z: -0.1025, w: 0.8048}, + {x: 0.5332, y: 0.0032, z: 0.005, w: 0.846}, + {x: 0.5773, y: -0.0035, z: -0.0049, w: 0.8165} + ], + middle: [ + {x: 0.543, y: -0.0469, z: -0.0333, w: 0.8378}, + {x: 0.5419, y: -0.0034, z: -0.0053, w: 0.8404}, + {x: 0.5015, y: 0.0037, z: 0.0063, w: 0.8651} + ], + index: [ + {x: 0.3051, y: -0.0156, z: -0.014, w: 0.9521}, + {x: 0.6414, y: 0.0051, z: 0.0063, w: 0.7671}, + {x: 0.5646, y: -0.013, z: -0.019, w: 0.8251} + ], + thumb: [ + {x: 0.313, y: -0.0348, z: 0.3192, w: 0.8938}, + {x: 0, y: 0, z: -0.37, w: 0.929}, + {x: 0, y: 0, z: -0.2604, w: 0.9655} + ] }, right: { - pinky:[{x: 0.5881, y:0.1728, z:0.1114, w:0.7823},{x: 0.5704, y:-0.0052, z:-0.0075, w:0.8213},{x: 0.6069, y:0.0046, z:0.006, w:0.7947}], - ring:[{x: 0.5729, y:0.1181, z:0.0898, w:0.8061},{x: 0.5332, y:-0.003, z:-0.0048, w:0.846},{x: 0.5773, y:0.0035, z:0.005, w:0.8165}], - middle:[{x: 0.543, y:0.0468, z:0.0332, w:0.8378},{x: 0.5419, y:0.0034, z:0.0052, w:0.8404},{x: 0.5047, y:-0.0037, z:-0.0064, w:0.8632}], - index:[{x: 0.306, y:-0.0076, z:-0.0584, w:0.9502},{x: 0.6409, y:-0.005, z:-0.006, w:0.7675},{x: 0.5646, y:0.0129, z:0.0189, w:0.8251}], - thumb:[{x: 0.313, y:0.0352, z:-0.3181, w:0.8942},{x: 0, y:0, z:0.3698, w:0.9291},{x: 0, y:0, z:0.2609, w:0.9654}] + pinky: [ + {x: 0.5881, y: 0.1728, z: 0.1114, w: 0.7823}, + {x: 0.5704, y: -0.0052, z: -0.0075, w: 0.8213}, + {x: 0.6069, y: 0.0046, z: 0.006, w: 0.7947} + ], + ring: [ + {x: 0.5729, y: 0.1181, z: 0.0898, w: 0.8061}, + {x: 0.5332, y: -0.003, z: -0.0048, w: 0.846}, + {x: 0.5773, y: 0.0035, z: 0.005, w: 0.8165} + ], + middle: [ + {x: 0.543, y: 0.0468, z: 0.0332, w: 0.8378}, + {x: 0.5419, y: 0.0034, z: 0.0052, w: 0.8404}, + {x: 0.5047, y: -0.0037, z: -0.0064, w: 0.8632} + ], + index: [ + {x: 0.306, y: -0.0076, z: -0.0584, w: 0.9502}, + {x: 0.6409, y: -0.005, z: -0.006, w: 0.7675}, + {x: 0.5646, y: 0.0129, z: 0.0189, w: 0.8251} + ], + thumb: [ + {x: 0.313, y: 0.0352, z: -0.3181, w: 0.8942}, + {x: 0, y: 0, z: 0.3698, w: 0.9291}, + {x: 0, y: 0, z: 0.2609, w: 0.9654} + ] } }; - + // snapshot for the default pose - var dataDefault = { - left:{ - pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], - middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], - ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], - thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + left: { + pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], set: false }, - right:{ - pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], - middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], - ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], - thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + right: { + pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], set: false } }; - + // joint data for the current frame - var dataCurrent = { - left:{ - pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], - middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], - ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], - thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + left: { + pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}] }, - right:{ - pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], - middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], - ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], - thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + right: { + pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}] } }; - - // interpolated values on joint data to smooth movement - + + // interpolated values on joint data to smooth movement var dataDelta = { - left:{ - pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], - middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], - ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], - thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + left: { + pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}] }, - right:{ - pinky:[{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], - middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], - ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], - thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + right: { + pinky: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + middle: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + ring: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], + thumb: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}], index: [{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0},{x: 0, y: 0, z: 0, w: 0}] } }; - + // Acquire an updated value per hand every 5 frames when finger is touching (faster in) - var touchAnimationSteps = 5; - - // Acquire an updated value per hand every 10 frames when finger is returning to default position (slower out) - + + // Acquire an updated value per hand every 20 frames when finger is returning to default position (slower out) var defaultAnimationSteps = 10; - + // Debugging info - var showSphere = false; var showLines = false; - + // This get setup on creation - var linesCreated = false; var sphereCreated = false; - - // Register object with API Debugger - + + // Register object with API Debugger var varsToDebug = { scriptLoaded: false, - toggleDebugSphere: function(){ + toggleDebugSphere: function() { showSphere = !showSphere; if (showSphere && !sphereCreated) { createDebugSphere(); sphereCreated = true; } }, - toggleDebugLines: function(){ + toggleDebugLines: function() { showLines = !showLines; if (showLines && !linesCreated) { createDebugLines(); @@ -202,17 +275,17 @@ }, fingerPercent: { left: { - pinky: 0.38, - middle: 0.38, - ring: 0.38, - thumb: 0.38, + pinky: 0.38, + middle: 0.38, + ring: 0.38, + thumb: 0.38, index: 0.38 - } , + } , right: { - pinky: 0.38, - middle: 0.38, - ring: 0.38, - thumb: 0.38, + pinky: 0.38, + middle: 0.38, + ring: 0.38, + thumb: 0.38, index: 0.38 } }, @@ -227,17 +300,17 @@ palmData: { left: new Palm(), right: new Palm() - }, - offset: {x:0, y:0, z:0}, + }, + offset: {x: 0, y: 0, z: 0}, avatarLoaded: false }; - - + // Add/Subtract the joint data - per finger joint - function addVals(val1, val2, sign) { var val = []; - if (val1.length != val2.length) return; + if (val1.length !== val2.length) { + return; + } for (var i = 0; i < val1.length; i++) { val.push({x: 0, y: 0, z: 0, w: 0}); val[i].x = val1[i].x + sign*val2[i].x; @@ -247,9 +320,8 @@ } return val; } - + // Multiply/Divide the joint data - per finger joint - function multiplyValsBy(val1, num) { var val = []; for (var i = 0; i < val1.length; i++) { @@ -261,9 +333,8 @@ } return val; } - + // Calculate the finger lengths by adding its joint lengths - function getJointDistances(jointNamesArray) { var result = {distances: [], totalDistance: 0}; for (var i = 1; i < jointNamesArray.length; i++) { @@ -277,30 +348,29 @@ } return result; } - - function dataRelativeToWorld(side, dataIn, dataOut) { + function dataRelativeToWorld(side, dataIn, dataOut) { var handJoint = handJointNames[side]; var jointIndex = MyAvatar.getJointIndex(handJoint); - var worldPosHand = MyAvatar.jointToWorldPoint({x:0, y:0, z:0}, jointIndex); - + var worldPosHand = MyAvatar.jointToWorldPoint({x: 0, y: 0, z: 0}, jointIndex); + dataOut.position = MyAvatar.jointToWorldPoint(dataIn.position, jointIndex); - // dataOut.perpendicular = Vec3.subtract(MyAvatar.jointToWorldPoint(dataIn.perpendicular, jointIndex), worldPosHand); - var localPerpendicular = side == "right" ? {x:0.2, y:0, z:1} : {x:-0.2, y:0, z:1}; - dataOut.perpendicular = Vec3.normalize(Vec3.subtract(MyAvatar.jointToWorldPoint(localPerpendicular, jointIndex), worldPosHand)); + var localPerpendicular = side === "right" ? {x: 0.2, y: 0, z: 1} : {x: -0.2, y: 0, z: 1}; + dataOut.perpendicular = Vec3.normalize( + Vec3.subtract(MyAvatar.jointToWorldPoint(localPerpendicular, jointIndex), worldPosHand) + ); dataOut.distance = dataIn.distance; for (var i = 0; i < fingerKeys.length; i++) { var finger = fingerKeys[i]; dataOut.fingers[finger] = MyAvatar.jointToWorldPoint(dataIn.fingers[finger], jointIndex); - } + } } - - function dataRelativeToHandJoint(side, dataIn, dataOut) { + function dataRelativeToHandJoint(side, dataIn, dataOut) { var handJoint = handJointNames[side]; var jointIndex = MyAvatar.getJointIndex(handJoint); - var worldPosHand = MyAvatar.jointToWorldPoint({x:0, y:0, z:0}, jointIndex); - + var worldPosHand = MyAvatar.jointToWorldPoint({x: 0, y: 0, z: 0}, jointIndex); + dataOut.position = MyAvatar.worldToJointPoint(dataIn.position, jointIndex); dataOut.perpendicular = MyAvatar.worldToJointPoint(Vec3.sum(worldPosHand, dataIn.perpendicular), jointIndex); dataOut.distance = dataIn.distance; @@ -309,147 +379,141 @@ dataOut.fingers[finger] = MyAvatar.worldToJointPoint(dataIn.fingers[finger], jointIndex); } } - - // Calculate the sphere that look up for entities, the center of the palm, perpendicular vector from the palm plane and origin of the the finger rays - + + // Calculate touch field; Sphere at the center of the palm, + // perpendicular vector from the palm plane and origin of the the finger rays function estimatePalmData(side) { // Return data object - var data = new Palm(); - - var jointOffset = { x: 0, y: 0, z: 0 }; - + var data = new Palm(); + + var jointOffset = { x: 0, y: 0, z: 0 }; + var upperSide = side[0].toUpperCase() + side.substring(1); var jointIndexHand = MyAvatar.getJointIndex(upperSide + "Hand"); - + // Store position of the hand joint var worldPosHand = MyAvatar.jointToWorldPoint(jointOffset, jointIndexHand); - var minusWorldPosHand = {x:-worldPosHand.x, y:-worldPosHand.y, z:-worldPosHand.z}; - + var minusWorldPosHand = {x: -worldPosHand.x, y: -worldPosHand.y, z: -worldPosHand.z}; + // Data for finger rays var directions = {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}; var positions = {pinky: undefined, middle: undefined, ring: undefined, thumb: undefined, index: undefined}; - + var thumbLength = 0; var weightCount = 0; - + // Calculate palm center - var handJointWeight = 1; var fingerJointWeight = 2; - - var palmCenter = {x:0, y:0, z:0}; + + var palmCenter = {x: 0, y: 0, z: 0}; palmCenter = Vec3.sum(worldPosHand, palmCenter); - + weightCount += handJointWeight; - + for (var i = 0; i < fingerKeys.length; i++) { var finger = fingerKeys[i]; var jointSuffixes = 4; // Get 4 joint names with suffix numbers (0, 1, 2, 3) var jointNames = getJointNames(side, finger, jointSuffixes); var fingerLength = getJointDistances(jointNames).totalDistance; - + var jointIndex = MyAvatar.getJointIndex(jointNames[0]); positions[finger] = MyAvatar.jointToWorldPoint(jointOffset, jointIndex); directions[finger] = Vec3.normalize(Vec3.sum(positions[finger], minusWorldPosHand)); data.fingers[finger] = Vec3.sum(positions[finger], Vec3.multiply(fingerLength, directions[finger])); - if (finger != "thumb") { + if (finger !== "thumb") { // finger joints have double the weight than the hand joint // This would better position the palm estimation - - palmCenter = Vec3.sum(Vec3.multiply(fingerJointWeight, positions[finger]), palmCenter); + + palmCenter = Vec3.sum(Vec3.multiply(fingerJointWeight, positions[finger]), palmCenter); weightCount += fingerJointWeight; } else { thumbLength = fingerLength; } } - + // perpendicular change direction depending on the side - - data.perpendicular = (side == "right") ? - Vec3.normalize(Vec3.cross(directions.index, directions.pinky)): - Vec3.normalize(Vec3.cross(directions.pinky, directions.index)); - + data.perpendicular = (side === "right") ? + Vec3.normalize(Vec3.cross(directions.index, directions.pinky)): + Vec3.normalize(Vec3.cross(directions.pinky, directions.index)); + data.position = Vec3.multiply(1.0/weightCount, palmCenter); - - if (side == "right") varsToDebug.offset = MyAvatar.worldToJointPoint(worldPosHand, jointIndexHand); - + + if (side === "right") { + varsToDebug.offset = MyAvatar.worldToJointPoint(worldPosHand, jointIndexHand); + } + var palmDistanceMultiplier = 1.55; // 1.55 based on test/error for the sphere radius that best fits the hand - data.distance = palmDistanceMultiplier*Vec3.distance(data.position, positions.index); + data.distance = palmDistanceMultiplier*Vec3.distance(data.position, positions.index); // move back thumb ray origin var thumbBackMultiplier = 0.2; - data.fingers.thumb = Vec3.sum(data.fingers.thumb, Vec3.multiply( -thumbBackMultiplier * thumbLength, data.perpendicular)); - - //return getDataRelativeToHandJoint(side, data); + data.fingers.thumb = Vec3.sum( + data.fingers.thumb, Vec3.multiply( -thumbBackMultiplier * thumbLength, data.perpendicular)); + + // return getDataRelativeToHandJoint(side, data); dataRelativeToHandJoint(side, data, palmData[side]); palmData[side].set = true; - // return palmData[side]; } - + // Register GlobalDebugger for API Debugger Script.registerValue("GlobalDebugger", varsToDebug); - - // store the rays for the fingers - only for debug purposes - - var fingerRays = { - left:{ - pinky: undefined, - middle: undefined, - ring: undefined, - thumb: undefined, + var fingerRays = { + left: { + pinky: undefined, + middle: undefined, + ring: undefined, + thumb: undefined, index: undefined - }, - right:{ - pinky: undefined, - middle: undefined, - ring: undefined, - thumb: undefined, + }, + right: { + pinky: undefined, + middle: undefined, + ring: undefined, + thumb: undefined, index: undefined } }; - + // Create debug overlays - finger rays + palm rays + spheres - var palmRay, sphereHand; - + function createDebugLines() { - for (var i = 0; i < fingerKeys.length; i++) { fingerRays.left[fingerKeys[i]] = Overlays.addOverlay("line3d", { color: { red: 0, green: 0, blue: 255 }, - start: { x:0, y:0, z:0 }, - end: { x:0, y:1, z:0 }, + start: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 1, z: 0 }, visible: showLines }); fingerRays.right[fingerKeys[i]] = Overlays.addOverlay("line3d", { color: { red: 0, green: 0, blue: 255 }, - start: { x:0, y:0, z:0 }, - end: { x:0, y:1, z:0 }, + start: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 1, z: 0 }, visible: showLines }); } - + palmRay = { left: Overlays.addOverlay("line3d", { color: { red: 255, green: 0, blue: 0 }, - start: { x:0, y:0, z:0 }, - end: { x:0, y:1, z:0 }, + start: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 1, z: 0 }, visible: showLines }), right: Overlays.addOverlay("line3d", { color: { red: 255, green: 0, blue: 0 }, - start: { x:0, y:0, z:0 }, - end: { x:0, y:1, z:0 }, + start: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 1, z: 0 }, visible: showLines }) }; linesCreated = true; } - + function createDebugSphere() { - sphereHand = { right: Overlays.addOverlay("sphere", { position: MyAvatar.position, @@ -463,10 +527,10 @@ scale: { x: 0.01, y: 0.01, z: 0.01 }, visible: showSphere }) - }; + }; sphereCreated = true; } - + function acquireDefaultPose(side) { for (var i = 0; i < fingerKeys.length; i++) { var finger = fingerKeys[i]; @@ -480,42 +544,133 @@ } dataDefault[side].set = true; } - - function updateSphereHand(side) { + var rayPicks = { + left: { + pinky: undefined, + middle: undefined, + ring: undefined, + thumb: undefined, + index: undefined + }, + right: { + pinky: undefined, + middle: undefined, + ring: undefined, + thumb: undefined, + index: undefined + } + }; + + var dataFailed = { + left: { + pinky: 0, + middle: 0, + ring: 0, + thumb: 0, + index: 0 + }, + right: { + pinky: 0, + middle: 0, + ring: 0, + thumb: 0, + index: 0 + } + }; + + function clearRayPicks(side) { + for (var i = 0; i < fingerKeys.length; i++) { + var finger = fingerKeys[i]; + if (rayPicks[side][finger] !== undefined) { + RayPick.removeRayPick(rayPicks[side][finger]); + rayPicks[side][finger] = undefined; + } + } + } + + function createRayPicks(side) { + var data = palmData[side]; + clearRayPicks(side); + for (var i = 0; i < fingerKeys.length; i++) { + var finger = fingerKeys[i]; + var LOOKUP_DISTANCE_MULTIPLIER = 1.5; + var dist = LOOKUP_DISTANCE_MULTIPLIER*data.distance; + var checkOffset = { + x: data.perpendicular.x * dist, + y: data.perpendicular.y * dist, + z: data.perpendicular.z * dist + }; + + var checkPoint = Vec3.sum(data.position, Vec3.multiply(2, checkOffset)); + var sensorToWorldScale = MyAvatar.getSensorToWorldScale(); + + var origin = data.fingers[finger]; + + var direction = Vec3.normalize(Vec3.subtract(checkPoint, origin)); + + origin = Vec3.multiply(1/sensorToWorldScale, origin); + + rayPicks[side][finger] = RayPick.createRayPick( + { + "enabled": false, + "joint": handJointNames[side], + "posOffset": origin, + "dirOffset": direction, + "filter": RayPick.PICK_ENTITIES + } + ); + + RayPick.setPrecisionPicking(rayPicks[side][finger], true); + } + } + + function activateNextRay(side, index) { + var nextIndex = (index < fingerKeys.length-1) ? index + 1 : 0; + for (var i = 0; i < fingerKeys.length; i++) { + var finger = fingerKeys[i]; + if (i === nextIndex) { + RayPick.enableRayPick(rayPicks[side][finger]); + } else { + RayPick.disableRayPick(rayPicks[side][finger]); + } + } + } + + function updateSphereHand(side) { var data = new Palm(); dataRelativeToWorld(side, palmData[side], data); varsToDebug.palmData[side] = palmData[side]; - + var palmPoint = data.position; var LOOKUP_DISTANCE_MULTIPLIER = 1.5; var dist = LOOKUP_DISTANCE_MULTIPLIER*data.distance; - - // Situate the debugging overlays - - var checkOffset = { x: data.perpendicular.x * dist, - y: data.perpendicular.y * dist, - z: data.perpendicular.z * dist }; - - + + // Situate the debugging overlays + var checkOffset = { + x: data.perpendicular.x * dist, + y: data.perpendicular.y * dist, + z: data.perpendicular.z * dist + }; + var spherePos = Vec3.sum(palmPoint, checkOffset); var checkPoint = Vec3.sum(palmPoint, Vec3.multiply(2, checkOffset)); - + if (showLines) { Overlays.editOverlay(palmRay[side], { start: palmPoint, end: checkPoint, visible: showLines - }); + }); for (var i = 0; i < fingerKeys.length; i++) { Overlays.editOverlay(fingerRays[side][fingerKeys[i]], { start: data.fingers[fingerKeys[i]], end: checkPoint, visible: showLines }); - } + } } - + if (showSphere) { Overlays.editOverlay(sphereHand[side], { position: spherePos, @@ -525,57 +680,75 @@ z: 2*dist }, visible: showSphere - }); + }); } - + // Update the intersection of only one finger at a time - var finger = fingerKeys[updateFingerWithIndex]; - - var grabbables = Entities.findEntities(spherePos, dist); - var newFingerData = dataDefault[side][finger]; + var nearbyEntities = Entities.findEntities(spherePos, dist); + // Filter the entities that are allowed to be touched + var touchableEntities = nearbyEntities.filter(function (id) { + return untouchableEntities.indexOf(id) == -1; + }); + var intersection; + if (rayPicks[side][finger] !== undefined) { + intersection = RayPick.getPrevRayPickResult(rayPicks[side][finger]); + } + var animationSteps = defaultAnimationSteps; - - if (grabbables.length > 0) { - var origin = data.fingers[finger]; - var direction = Vec3.normalize(Vec3.subtract(checkPoint, origin)); - var intersection = Entities.findRayIntersection({origin: origin, direction: direction}, true, grabbables, [], true, false); + var newFingerData = dataDefault[side][finger]; + var isAbleToGrab = false; + if (touchableEntities.length > 0) { + RayPick.setIncludeItems(rayPicks[side][finger], touchableEntities); + + if (intersection === undefined) { + return; + } + var percent = 0; // Initialize - var isAbleToGrab = intersection.intersects && intersection.distance < LOOKUP_DISTANCE_MULTIPLIER*dist; + isAbleToGrab = intersection.intersects && intersection.distance < LOOKUP_DISTANCE_MULTIPLIER*dist; if (isAbleToGrab && !getTouching(side)) { - acquireDefaultPose(side); // take a snapshot of the default pose before touch starts + acquireDefaultPose(side); // take a snapshot of the default pose before touch starts newFingerData = dataDefault[side][finger]; // assign default pose to finger data } // Store if this finger is touching something isTouching[side][finger] = isAbleToGrab; if (isAbleToGrab) { - // update the open/close percentage for this finger - + // update the open/close percentage for this finger var FINGER_REACT_MULTIPLIER = 2.8; - + percent = intersection.distance/(FINGER_REACT_MULTIPLIER*dist); - + var THUMB_FACTOR = 0.2; var FINGER_FACTOR = 0.05; - - var grabMultiplier = finger === "thumb" ? THUMB_FACTOR : FINGER_FACTOR; // Amount of grab coefficient added to the fingers - thumb is higher + + // Amount of grab coefficient added to the fingers - thumb is higher + var grabMultiplier = finger === "thumb" ? THUMB_FACTOR : FINGER_FACTOR; percent += grabMultiplier * grabPercent[side]; - + // Calculate new interpolation data var totalDistance = addVals(dataClose[side][finger], dataOpen[side][finger], -1); - newFingerData = addVals(dataOpen[side][finger], multiplyValsBy(totalDistance, percent), 1); // assign close/open ratio to finger to simulate touch + // Assign close/open ratio to finger to simulate touch + newFingerData = addVals(dataOpen[side][finger], multiplyValsBy(totalDistance, percent), 1); animationSteps = touchAnimationSteps; - } + } varsToDebug.fingerPercent[side][finger] = percent; - } - - // Calculate animation increments - dataDelta[side][finger] = multiplyValsBy(addVals(newFingerData, dataCurrent[side][finger], -1), 1.0/animationSteps); - + + } + if (!isAbleToGrab) { + dataFailed[side][finger] = dataFailed[side][finger] === 0 ? 1 : 2; + } else { + dataFailed[side][finger] = 0; + } + // If it only fails once it will not update increments + if (dataFailed[side][finger] !== 1) { + // Calculate animation increments + dataDelta[side][finger] = + multiplyValsBy(addVals(newFingerData, dataCurrent[side][finger], -1), 1.0/animationSteps); + } } - + // Recreate the finger joint names - function getJointNames(side, finger, count) { var names = []; for (var i = 1; i < count+1; i++) { @@ -586,30 +759,34 @@ } // Capture the controller values - var leftTriggerPress = function (value) { varsToDebug.triggerValues.leftTriggerValue = value; - // the value for the trigger increments the hand-close percentage + // the value for the trigger increments the hand-close percentage grabPercent.left = value; }; + var leftTriggerClick = function (value) { varsToDebug.triggerValues.leftTriggerClicked = value; }; + var rightTriggerPress = function (value) { varsToDebug.triggerValues.rightTriggerValue = value; - // the value for the trigger increments the hand-close percentage + // the value for the trigger increments the hand-close percentage grabPercent.right = value; }; + var rightTriggerClick = function (value) { varsToDebug.triggerValues.rightTriggerClicked = value; }; + var leftSecondaryPress = function (value) { varsToDebug.triggerValues.leftSecondaryValue = value; }; + var rightSecondaryPress = function (value) { varsToDebug.triggerValues.rightSecondaryValue = value; }; - + var MAPPING_NAME = "com.highfidelity.handTouch"; var mapping = Controller.newMapping(MAPPING_NAME); mapping.from([Controller.Standard.RT]).peek().to(rightTriggerPress); @@ -623,16 +800,17 @@ mapping.from([Controller.Standard.RightGrip]).peek().to(rightSecondaryPress); Controller.enableMapping(MAPPING_NAME); - + if (showLines && !linesCreated) { createDebugLines(); linesCreated = true; } + if (showSphere && !sphereCreated) { createDebugSphere(); sphereCreated = true; } - + function getTouching(side) { var animating = false; for (var i = 0; i < fingerKeys.length; i++) { @@ -641,93 +819,127 @@ } return animating; // return false only if none of the fingers are touching } - + function reEstimatePalmData() { - ["right", "left"].forEach(function(side){ + ["right", "left"].forEach(function(side) { estimatePalmData(side); }); } - - MyAvatar.onLoadComplete.connect(function () { - // Sometimes the rig is not ready when this signal is trigger - console.log("avatar loaded"); - Script.setInterval(function(){ - reEstimatePalmData(); - }, 2000); - }); - - MyAvatar.sensorToWorldScaleChanged.connect(function(){ - reEstimatePalmData(); - }); - - Script.scriptEnding.connect(function () { - ["right", "left"].forEach(function(side){ + + function recreateRayPicks() { + ["right", "left"].forEach(function(side) { + createRayPicks(side); + }); + } + + function cleanUp() { + ["right", "left"].forEach(function (side) { if (linesCreated) { - Overlays.deleteOverlay(palmRay[side]); + Overlays.deleteOverlay(palmRay[side]); } if (sphereCreated) { - Overlays.deleteOverlay(sphereHand[side]); + Overlays.deleteOverlay(sphereHand[side]); } + clearRayPicks(side); for (var i = 0; i < fingerKeys.length; i++) { - var finger = fingerKeys[i]; var jointSuffixes = 3; // We need to clear the joints 0, 1 and 2 joints - var names = getJointNames(side, finger, jointSuffixes); - + var names = getJointNames(side, finger, jointSuffixes); for (var j = 0; j < names.length; j++) { var index = MyAvatar.getJointIndex(names[j]); MyAvatar.clearJointData(index); } - if (linesCreated) { Overlays.deleteOverlay(fingerRays[side][finger]); - } + } } }); + } - - + MyAvatar.shouldDisableHandTouchChanged.connect(function (shouldDisable) { + if (shouldDisable) { + if (handTouchEnabled) { + cleanUp(); + } + } else { + if (!handTouchEnabled) { + reEstimatePalmData(); + recreateRayPicks(); + } + } + handTouchEnabled = !shouldDisable; }); - - Script.update.connect(function(){ - - // index of the finger that needs to be updated this frame - - + MyAvatar.disableHandTouchForIDChanged.connect(function (entityID, disable) { + var entityIndex = untouchableEntities.indexOf(entityID); + if (disable) { + if (entityIndex == -1) { + untouchableEntities.push(entityID); + } + } else { + if (entityIndex != -1) { + untouchableEntities.splice(entityIndex, 1); + } + } + }); + + MyAvatar.onLoadComplete.connect(function () { + // Sometimes the rig is not ready when this signal is trigger + console.log("avatar loaded"); + Script.setTimeout(function() { + reEstimatePalmData(); + recreateRayPicks(); + }, MSECONDS_AFTER_LOAD); + }); + + MyAvatar.sensorToWorldScaleChanged.connect(function() { + reEstimatePalmData(); + }); + + Script.scriptEnding.connect(function () { + cleanUp(); + }); + + Script.update.connect(function () { + + if (!handTouchEnabled) { + return; + } + + // index of the finger that needs to be updated this frame updateFingerWithIndex = (updateFingerWithIndex < fingerKeys.length-1) ? updateFingerWithIndex + 1 : 0; - - - ["right", "left"].forEach(function(side){ - + + ["right", "left"].forEach(function(side) { + if (!palmData[side].set) { reEstimatePalmData(); + recreateRayPicks(); } + // recalculate the base data updateSphereHand(side); - + activateNextRay(side, updateFingerWithIndex); + // this vars manage the transition to default pose var isHandTouching = getTouching(side); countToDefault[side] = isHandTouching ? 0 : countToDefault[side] + 1; - - + for (var i = 0; i < fingerKeys.length; i++) { var finger = fingerKeys[i]; var jointSuffixes = 3; // We need to update rotation of the 0, 1 and 2 joints - var names = getJointNames(side, finger, jointSuffixes); - + var names = getJointNames(side, finger, jointSuffixes); + // Add the animation increments - - dataCurrent[side][finger] = addVals(dataCurrent[side][finger], dataDelta[side][finger], 1); - + dataCurrent[side][finger] = addVals(dataCurrent[side][finger], dataDelta[side][finger], 1); + // update every finger joint - for (var j = 0; j < names.length; j++) { var index = MyAvatar.getJointIndex(names[j]); // if no finger is touching restate the default poses - if (isHandTouching || (dataDefault[side].set && countToDefault[side] < 5*touchAnimationSteps)) { + if (isHandTouching || (dataDefault[side].set && + countToDefault[side] < fingerKeys.length*touchAnimationSteps)) { var quatRot = dataCurrent[side][finger][j]; - MyAvatar.setJointRotation(index, quatRot); + MyAvatar.setJointRotation(index, quatRot); } else { MyAvatar.clearJointData(index); } @@ -735,5 +947,4 @@ } }); }); - }()); diff --git a/scripts/system/controllers/viveControllerConfiguration.js b/scripts/system/controllers/viveControllerConfiguration.js index dc4a5b6bb3..60f0b6b88a 100644 --- a/scripts/system/controllers/viveControllerConfiguration.js +++ b/scripts/system/controllers/viveControllerConfiguration.js @@ -77,6 +77,8 @@ VIVE_CONTROLLER_CONFIGURATION_LEFT = { dimensions: viveNaturalDimensions, parts: { + // DISABLED FOR NOW + /* tips: { type: "static", modelURL: viveTipsModelURL, @@ -103,6 +105,7 @@ VIVE_CONTROLLER_CONFIGURATION_LEFT = { } } }, + */ // The touchpad type draws a dot indicating the current touch/thumb position // and swaps in textures based on the thumb position. @@ -215,6 +218,8 @@ VIVE_CONTROLLER_CONFIGURATION_RIGHT = { }, parts: { + // DISABLED FOR NOW + /* tips: { type: "static", modelURL: viveTipsModelURL, @@ -242,6 +247,7 @@ VIVE_CONTROLLER_CONFIGURATION_RIGHT = { } } }, + */ // The touchpad type draws a dot indicating the current touch/thumb position // and swaps in textures based on the thumb position. diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 05f5e3cb19..4b8abff84b 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -10,17 +10,15 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger, - Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool */ +/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, + Overlays, OverlayWebWindow, UserActivityLogger, Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, + progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool, OverlaySystemWindow */ (function() { // BEGIN LOCAL_SCOPE "use strict"; -var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton"; -var SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system"; -var EDIT_TOOLBAR = "com.highfidelity.interface.toolbar.edit"; Script.include([ "libraries/stringHelpers.js", @@ -36,13 +34,52 @@ Script.include([ "libraries/entityIconOverlayManager.js" ]); +var CreateWindow = Script.require('./modules/createWindow.js'); + +var TITLE_OFFSET = 60; +var CREATE_TOOLS_WIDTH = 490; +var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942; + +var createToolsWindow = new CreateWindow( + Script.resourcesPath() + "qml/hifi/tablet/EditTools.qml", + 'Create Tools', + 'com.highfidelity.create.createToolsWindow', + function () { + var windowHeight = Window.innerHeight - TITLE_OFFSET; + if (windowHeight > MAX_DEFAULT_ENTITY_LIST_HEIGHT) { + windowHeight = MAX_DEFAULT_ENTITY_LIST_HEIGHT; + } + return { + size: { + x: CREATE_TOOLS_WIDTH, + y: windowHeight + }, + position: { + x: Window.x + Window.innerWidth - CREATE_TOOLS_WIDTH, + y: Window.y + TITLE_OFFSET + } + } + }, + false +); + +/** + * @description Returns true in case we should use the tablet version of the CreateApp + * @returns boolean + */ +var shouldUseEditTabletApp = function() { + return HMD.active || (!HMD.active && !Settings.getValue("desktopTabletBecomesToolbar", true)); +}; + + var selectionDisplay = SelectionDisplay; var selectionManager = SelectionManager; var PARTICLE_SYSTEM_URL = Script.resolvePath("assets/images/icon-particles.svg"); var POINT_LIGHT_URL = Script.resolvePath("assets/images/icon-point-light.svg"); var SPOT_LIGHT_URL = Script.resolvePath("assets/images/icon-spot-light.svg"); -entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect'], function(entityID) { + +var entityIconOverlayManager = new EntityIconOverlayManager(['Light', 'ParticleEffect'], function(entityID) { var properties = Entities.getEntityProperties(entityID, ['type', 'isSpotlight']); if (properties.type === 'Light') { return { @@ -59,11 +96,13 @@ var cameraManager = new CameraManager(); var grid = new Grid(); var gridTool = new GridTool({ - horizontalGrid: grid + horizontalGrid: grid, + createToolsWindow: createToolsWindow, + shouldUseEditTabletApp: shouldUseEditTabletApp }); gridTool.setVisible(false); -var entityListTool = new EntityListTool(); +var entityListTool = new EntityListTool(shouldUseEditTabletApp); selectionManager.addEventListener(function () { selectionDisplay.updateHandles(); @@ -207,7 +246,7 @@ function hideMarketplace() { // } function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { - // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original + // Adjust the position such that the bounding box (registration, dimensions and orientation) lies behind the original // position in the given direction. var CORNERS = [ { x: 0, y: 0, z: 0 }, @@ -232,7 +271,6 @@ function adjustPositionPerBoundingBox(position, direction, registration, dimensi return position; } -var TOOLS_PATH = Script.resolvePath("assets/images/tools/"); var GRABBABLE_ENTITIES_MENU_CATEGORY = "Edit"; // Handles any edit mode updates required when domains have switched @@ -260,6 +298,7 @@ var toolBar = (function () { toolBar, activeButton = null, systemToolbar = null, + dialogWindow = null, tablet = null; function createNewEntity(properties) { @@ -353,9 +392,18 @@ var toolBar = (function () { entityListTool.sendUpdate(); selectionManager.setSelections([entityID]); + Window.setFocus(); + return entityID; } + function closeExistingDialogWindow() { + if (dialogWindow) { + dialogWindow.close(); + dialogWindow = null; + } + } + function cleanup() { that.setActive(false); if (tablet) { @@ -438,7 +486,7 @@ var toolBar = (function () { if (materialURL.startsWith("materialData")) { materialData = JSON.stringify({ "materials": {} - }) + }); } var DEFAULT_LAYERED_MATERIAL_PRIORITY = 1; @@ -458,15 +506,23 @@ var toolBar = (function () { var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); tablet.popFromStack(); switch (message.method) { - case "newModelDialogAdd": - handleNewModelDialogResult(message.params); - break; - case "newEntityButtonClicked": - buttonHandlers[message.params.buttonName](); - break; - case "newMaterialDialogAdd": - handleNewMaterialDialogResult(message.params); - break; + case "newModelDialogAdd": + handleNewModelDialogResult(message.params); + closeExistingDialogWindow(); + break; + case "newModelDialogCancel": + closeExistingDialogWindow(); + break; + case "newEntityButtonClicked": + buttonHandlers[message.params.buttonName](); + break; + case "newMaterialDialogAdd": + handleNewMaterialDialogResult(message.params); + closeExistingDialogWindow(); + break; + case "newMaterialDialogCancel": + closeExistingDialogWindow(); + break; } } @@ -475,15 +531,16 @@ var toolBar = (function () { var DELETE_ENTITY_TIMER_TIMEOUT = 100; function checkDeletedEntityAndUpdate(entityID) { - // Allow for multiple entity deletes before updating the entity list. + // Allow for multiple entity deletes before updating the entities selected. entitiesToDelete.push(entityID); if (deletedEntityTimer !== null) { Script.clearTimeout(deletedEntityTimer); } deletedEntityTimer = Script.setTimeout(function () { - selectionManager.removeEntities(entitiesToDelete); - entityListTool.clearEntityList(); - entityListTool.sendUpdate(); + if (entitiesToDelete.length > 0) { + selectionManager.removeEntities(entitiesToDelete); + } + entityListTool.removeEntities(entitiesToDelete, selectionManager.selections); entitiesToDelete = []; deletedEntityTimer = null; }, DELETE_ENTITY_TIMER_TIMEOUT); @@ -500,6 +557,13 @@ var toolBar = (function () { checkEditPermissionsAndUpdate(); }); + HMD.displayModeChanged.connect(function() { + if (isActive) { + tablet.gotoHomeScreen(); + } + that.setActive(false); + }); + Entities.canAdjustLocksChanged.connect(function (canAdjustLocks) { if (isActive && !canAdjustLocks) { that.setActive(false); @@ -526,11 +590,14 @@ var toolBar = (function () { }); createButton = activeButton; tablet.screenChanged.connect(function (type, url) { - if (isActive && (type !== "QML" || url !== "hifi/tablet/Edit.qml")) { - that.setActive(false) + var isGoingToHomescreenOnDesktop = (!shouldUseEditTabletApp() && + (url === 'hifi/tablet/TabletHome.qml' || url === '')); + if (isActive && (type !== "QML" || url !== "hifi/tablet/Edit.qml") && !isGoingToHomescreenOnDesktop) { + that.setActive(false); } }); tablet.fromQml.connect(fromQml); + createToolsWindow.fromQml.addListener(fromQml); createButton.clicked.connect(function() { if ( ! (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()) ) { @@ -549,12 +616,29 @@ var toolBar = (function () { addButton("openAssetBrowserButton", function() { Window.showAssetServer(); }); + function createNewEntityDialogButtonCallback(entityType) { + return function() { + if (shouldUseEditTabletApp()) { + // tablet version of new-model dialog + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + tablet.pushOntoStack("hifi/tablet/New" + entityType + "Dialog.qml"); + } else { + closeExistingDialogWindow(); + var qmlPath = Script.resourcesPath() + "qml/hifi/tablet/New" + entityType + "Window.qml"; + var DIALOG_WINDOW_SIZE = { x: 500, y: 300 }; + dialogWindow = Desktop.createWindow(qmlPath, { + title: "New " + entityType + " Entity", + flags: Desktop.ALWAYS_ON_TOP | Desktop.CLOSE_BUTTON_HIDES, + presentationMode: Desktop.PresentationMode.NATIVE, + size: DIALOG_WINDOW_SIZE, + visible: true + }); + dialogWindow.fromQml.connect(fromQml); + } + }; + }; - addButton("newModelButton", function () { - // tablet version of new-model dialog - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - tablet.pushOntoStack("hifi/tablet/NewModelDialog.qml"); - }); + addButton("newModelButton", createNewEntityDialogButtonCallback("Model")); addButton("newCubeButton", function () { createNewEntity({ @@ -715,11 +799,7 @@ var toolBar = (function () { }); }); - addButton("newMaterialButton", function () { - // tablet version of new material dialog - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - tablet.pushOntoStack("hifi/tablet/NewMaterialDialog.qml"); - }); + addButton("newMaterialButton", createNewEntityDialogButtonCallback("Material")); that.setActive(false); } @@ -742,6 +822,8 @@ var toolBar = (function () { Controller.captureEntityClickEvents(); } else { Controller.releaseEntityClickEvents(); + + closeExistingDialogWindow(); } if (active === isActive) { return; @@ -768,7 +850,12 @@ var toolBar = (function () { selectionDisplay.triggerMapping.disable(); tablet.landscape = false; } else { - tablet.loadQMLSource("hifi/tablet/Edit.qml", true); + if (shouldUseEditTabletApp()) { + tablet.loadQMLSource("hifi/tablet/Edit.qml", true); + } else { + // make other apps inactive while in desktop mode + tablet.gotoHomeScreen(); + } UserActivityLogger.enabledEdit(); entityListTool.setVisible(true); gridTool.setVisible(true); @@ -789,17 +876,6 @@ var toolBar = (function () { return that; })(); - -function isLocked(properties) { - // special case to lock the ground plane model in hq. - if (location.hostname === "hq.highfidelity.io" && - properties.modelURL === HIFI_PUBLIC_BUCKET + "ozan/Terrain_Reduce_forAlpha.fbx") { - return true; - } - return false; -} - - var selectedEntityID; var orientation; var intersection; @@ -1012,15 +1088,19 @@ function mouseReleaseEvent(event) { } } -function wasTabletClicked(event) { +function wasTabletOrEditHandleClicked(event) { var rayPick = Camera.computePickRay(event.x, event.y); - var tabletIDs = getMainTabletIDs(); - if (tabletIDs.length === 0) { - return false; - } else { - var result = Overlays.findRayIntersection(rayPick, true, getMainTabletIDs()); - return result.intersects; + var result = Overlays.findRayIntersection(rayPick, true); + if (result.intersects) { + var overlayID = result.overlayID; + var tabletIDs = getMainTabletIDs(); + if (tabletIDs.indexOf(overlayID) >= 0) { + return true; + } else if (selectionDisplay.isEditHandle(overlayID)) { + return true; + } } + return false; } function mouseClickEvent(event) { @@ -1028,8 +1108,8 @@ function mouseClickEvent(event) { var result, properties, tabletClicked; if (isActive && event.isLeftButton) { result = findClickedEntity(event); - tabletClicked = wasTabletClicked(event); - if (tabletClicked) { + tabletOrEditHandleClicked = wasTabletOrEditHandleClicked(event); + if (tabletOrEditHandleClicked) { return; } @@ -1046,68 +1126,62 @@ function mouseClickEvent(event) { return; } properties = Entities.getEntityProperties(foundEntity); - if (isLocked(properties)) { - if (wantDebug) { - print("Model locked " + properties.id); + var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; + + if (wantDebug) { + print("Checking properties: " + properties.id + " " + " - Half Diagonal:" + halfDiagonal); + } + // P P - Model + // /| A - Palm + // / | d B - unit vector toward tip + // / | X - base of the perpendicular line + // A---X----->B d - distance fom axis + // x x - distance from A + // + // |X-A| = (P-A).B + // X === A + ((P-A).B)B + // d = |P-X| + + var A = pickRay.origin; + var B = Vec3.normalize(pickRay.direction); + var P = properties.position; + + var x = Vec3.dot(Vec3.subtract(P, A), B); + + var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * + 180 / Math.PI; + + var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) && + (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); + + if (0 < x && sizeOK) { + selectedEntityID = foundEntity; + orientation = MyAvatar.orientation; + intersection = rayPlaneIntersection(pickRay, P, Quat.getForward(orientation)); + + if (event.isShifted) { + particleExplorerTool.destroyWebView(); + } + if (properties.type !== "ParticleEffect") { + particleExplorerTool.destroyWebView(); + } + + if (!event.isShifted) { + selectionManager.setSelections([foundEntity]); + } else { + selectionManager.addEntity(foundEntity, true); } - } else { - var halfDiagonal = Vec3.length(properties.dimensions) / 2.0; if (wantDebug) { - print("Checking properties: " + properties.id + " " + " - Half Diagonal:" + halfDiagonal); + print("Model selected: " + foundEntity); } - // P P - Model - // /| A - Palm - // / | d B - unit vector toward tip - // / | X - base of the perpendicular line - // A---X----->B d - distance fom axis - // x x - distance from A - // - // |X-A| = (P-A).B - // X === A + ((P-A).B)B - // d = |P-X| + selectionDisplay.select(selectedEntityID, event); - var A = pickRay.origin; - var B = Vec3.normalize(pickRay.direction); - var P = properties.position; - - var x = Vec3.dot(Vec3.subtract(P, A), B); - - var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * - 180 / Math.PI; - - var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE) && - (allowSmallModels || angularSize > MIN_ANGULAR_SIZE); - - if (0 < x && sizeOK) { - selectedEntityID = foundEntity; - orientation = MyAvatar.orientation; - intersection = rayPlaneIntersection(pickRay, P, Quat.getForward(orientation)); - - if (event.isShifted) { - particleExplorerTool.destroyWebView(); - } - if (properties.type !== "ParticleEffect") { - particleExplorerTool.destroyWebView(); - } - - if (!event.isShifted) { - selectionManager.setSelections([foundEntity]); - } else { - selectionManager.addEntity(foundEntity, true); - } - - if (wantDebug) { - print("Model selected: " + foundEntity); - } - selectionDisplay.select(selectedEntityID, event); - - if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) { - cameraManager.enable(); - cameraManager.focus(selectionManager.worldPosition, - selectionManager.worldDimensions, - Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); - } + if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) { + cameraManager.enable(); + cameraManager.focus(selectionManager.worldPosition, + selectionManager.worldDimensions, + Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); } } } else if (event.isRightButton) { @@ -1367,11 +1441,7 @@ function selectAllEtitiesInCurrentSelectionBox(keepIfTouching) { var localPosition = Vec3.multiplyQbyV(Quat.inverse(selectionManager.localRotation), Vec3.subtract(position, selectionManager.localPosition)); - return insideBox({ - x: 0, - y: 0, - z: 0 - }, selectionManager.localDimensions, localPosition); + return insideBox(Vec3.ZERO, selectionManager.localDimensions, localPosition); }; } for (var i = 0; i < entities.length; ++i) { @@ -1475,7 +1545,7 @@ function parentSelectedEntities() { return; } var parentCheck = false; - var lastEntityId = selectedEntities[selectedEntities.length-1]; + var lastEntityId = selectedEntities[selectedEntities.length - 1]; selectedEntities.forEach(function (id, index) { if (lastEntityId !== id) { var parentId = Entities.getEntityProperties(id, ["parentID"]).parentID; @@ -1488,7 +1558,7 @@ function parentSelectedEntities() { if (parentCheck) { Window.notify("Entities parented"); - }else { + } else { Window.notify("Entities are already parented to last"); } } else { @@ -1520,15 +1590,11 @@ function deleteSelectedEntities() { Entities.deleteEntity(entityID); } } - + if (savedProperties.length > 0) { SelectionManager.clearSelections(); pushCommandForSelections([], savedProperties); - - entityListTool.webView.emitScriptEvent(JSON.stringify({ - type: "deleted", - ids: deletedIDs - })); + entityListTool.deleteEntities(deletedIDs); } } } @@ -1781,9 +1847,9 @@ var keyReleaseEvent = function (event) { // since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items if (event.text === "DELETE") { deleteSelectedEntities(); - } else if (event.text === "ESC") { + } else if (event.text === 'd' && event.isControl) { selectionManager.clearSelections(); - } else if (event.text === "TAB") { + } else if (event.text === "t") { selectionDisplay.toggleSpaceMode(); } else if (event.text === "f") { if (isActive) { @@ -1800,13 +1866,7 @@ var keyReleaseEvent = function (event) { } } else if (event.text === 'g') { if (isActive && selectionManager.hasSelection()) { - var newPosition = selectionManager.worldPosition; - newPosition = Vec3.subtract(newPosition, { - x: 0, - y: selectionManager.worldDimensions.y * 0.5, - z: 0 - }); - grid.setPosition(newPosition); + grid.moveToSelection(); } } else if (event.key === KEY_P && event.isControl && !event.isAutoRepeat ) { if (event.isShifted) { @@ -1864,7 +1924,11 @@ function applyEntityProperties(data) { Entities.deleteEntity(entityID); } - selectionManager.setSelections(selectedEntityIDs); + // We might be getting an undo while edit.js is disabled. If that is the case, don't set + // our selections, causing the edit widgets to display. + if (isActive) { + selectionManager.setSelections(selectedEntityIDs); + } } // For currently selected entities, push a command to the UndoStack that uses the current entity properties for the @@ -1901,8 +1965,6 @@ function pushCommandForSelections(createdEntityData, deletedEntityData) { UndoStack.pushCommand(applyEntityProperties, undoData, applyEntityProperties, redoData); } -var ENTITY_PROPERTIES_URL = Script.resolvePath('html/entityProperties.html'); - var ServerScriptStatusMonitor = function(entityID, statusCallback) { var self = this; @@ -1946,13 +2008,14 @@ var PropertiesTool = function (opts) { var currentSelectedEntityID = null; var statusMonitor = null; - webView.setVisible(visible); - that.setVisible = function (newVisible) { visible = newVisible; - webView.setVisible(visible); + webView.setVisible(shouldUseEditTabletApp() && visible); + createToolsWindow.setVisible(!shouldUseEditTabletApp() && visible); }; + that.setVisible(false); + function updateScriptStatus(info) { info.type = "server_script_status"; webView.emitScriptEvent(JSON.stringify(info)); @@ -1981,7 +2044,7 @@ var PropertiesTool = function (opts) { statusMonitor = null; } currentSelectedEntityID = null; - } else if (currentSelectedEntityID != selectionManager.selections[0]) { + } else if (currentSelectedEntityID !== selectionManager.selections[0]) { if (statusMonitor !== null) { statusMonitor.stop(); } @@ -2007,11 +2070,14 @@ var PropertiesTool = function (opts) { selections.push(entity); } data.selections = selections; + webView.emitScriptEvent(JSON.stringify(data)); + createToolsWindow.emitScriptEvent(JSON.stringify(data)); } selectionManager.addEventListener(updateSelections); - webView.webEventReceived.connect(function (data) { + + var onWebEventReceived = function(data) { try { data = JSON.parse(data); } @@ -2027,26 +2093,14 @@ var PropertiesTool = function (opts) { } else if (data.type === "update") { selectionManager.saveProperties(); if (selectionManager.selections.length > 1) { - properties = { - locked: data.properties.locked, - visible: data.properties.visible - }; for (i = 0; i < selectionManager.selections.length; i++) { - Entities.editEntity(selectionManager.selections[i], properties); + Entities.editEntity(selectionManager.selections[i], data.properties); } } else if (data.properties) { if (data.properties.dynamic === false) { // this object is leaving dynamic, so we zero its velocities - data.properties.velocity = { - x: 0, - y: 0, - z: 0 - }; - data.properties.angularVelocity = { - x: 0, - y: 0, - z: 0 - }; + data.properties.velocity = Vec3.ZERO; + data.properties.angularVelocity = Vec3.ZERO; } if (data.properties.rotation !== undefined) { var rotation = data.properties.rotation; @@ -2174,7 +2228,11 @@ var PropertiesTool = function (opts) { } else if (data.type === "propertiesPageReady") { updateSelections(true); } - }); + }; + + createToolsWindow.webEventReceived.addListener(this, onWebEventReceived); + + webView.webEventReceived.connect(onWebEventReceived); return that; }; @@ -2189,6 +2247,8 @@ var PopupMenu = function () { var overlays = []; var overlayInfo = {}; + var visible = false; + var upColor = { red: 0, green: 0, @@ -2306,8 +2366,6 @@ var PopupMenu = function () { } }; - var visible = false; - self.setVisible = function (newVisible) { if (newVisible !== visible) { visible = newVisible; @@ -2361,7 +2419,7 @@ propertyMenu.onSelectMenuItem = function (name) { var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace"); var propertiesTool = new PropertiesTool(); -var particleExplorerTool = new ParticleExplorerTool(); +var particleExplorerTool = new ParticleExplorerTool(createToolsWindow); var selectedParticleEntityID = null; function selectParticleEntity(entityID) { @@ -2376,14 +2434,18 @@ function selectParticleEntity(entityID) { particleExplorerTool.createWebView(); particleExplorerTool.setActiveParticleEntity(entityID); - particleExplorerTool.setActiveParticleProperties(properties); // Switch to particle explorer - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - tablet.sendToQml({method: 'selectTab', params: {id: 'particle'}}); + var selectTabMethod = { method: 'selectTab', params: { id: 'particle' } }; + if (shouldUseEditTabletApp()) { + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + tablet.sendToQml(selectTabMethod); + } else { + createToolsWindow.sendToQml(selectTabMethod); + } } -entityListTool.webView.webEventReceived.connect(function (data) { +entityListTool.webView.webEventReceived.connect(function(data) { try { data = JSON.parse(data); } catch(e) { diff --git a/scripts/system/emote.js b/scripts/system/emote.js index 139870fd63..d484078b7b 100644 --- a/scripts/system/emote.js +++ b/scripts/system/emote.js @@ -16,9 +16,12 @@ (function() { // BEGIN LOCAL_SCOPE -var EMOTE_ANIMATIONS = ['Crying', 'Surprised', 'Dancing', 'Cheering', 'Waving', 'Fall', 'Pointing', 'Clapping']; +var EMOTE_ANIMATIONS = + ['Crying', 'Surprised', 'Dancing', 'Cheering', 'Waving', 'Fall', 'Pointing', 'Clapping', 'Sit1', 'Sit2', 'Sit3', 'Love']; var ANIMATIONS = Array(); +var eventMappingName = "io.highfidelity.away"; // restoreAnimation on hand controller button events, too +var eventMapping = Controller.newMapping(eventMappingName); EMOTE_ANIMATIONS.forEach(function (name) { var animationURL = Script.resolvePath("assets/animations/" + name + ".fbx"); @@ -31,7 +34,7 @@ EMOTE_ANIMATIONS.forEach(function (name) { var EMOTE_APP_BASE = "html/EmoteApp.html"; var EMOTE_APP_URL = Script.resolvePath(EMOTE_APP_BASE); var EMOTE_LABEL = "EMOTE"; -var EMOTE_APP_SORT_ORDER = 11; +var EMOTE_APP_SORT_ORDER = 12; var FPS = 60; var MSEC_PER_SEC = 1000; var FINISHED = 3; // see ScriptableResource::State @@ -39,8 +42,8 @@ var FINISHED = 3; // see ScriptableResource::State var onEmoteScreen = false; var button; var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); -var activeTimer = false; // used to cancel active timer if a user plays an amimation while another animation is playing -var activeEmote = false; // to keep track of the currently playing emote +var activeTimer = false; // Used to cancel active timer if a user plays an animation while another animation is playing +var activeEmote = false; // To keep track of the currently playing emote button = tablet.addButton({ icon: "icons/tablet-icons/EmoteAppIcon.svg", @@ -58,7 +61,7 @@ function onClicked() { } function onScreenChanged(type, url) { - onEmoteScreen = type === "Web" && (url.indexOf(EMOTE_APP_BASE) == url.length - EMOTE_APP_BASE.length); + onEmoteScreen = type === "Web" && (url.indexOf(EMOTE_APP_BASE) === url.length - EMOTE_APP_BASE.length); button.editProperties({ isActive: onEmoteScreen }); } @@ -71,26 +74,51 @@ function onWebEventReceived(event) { } if (event.type === "click") { + + // Allow for a random sitting animation when a user selects sit + var randSit = Math.floor(Math.random() * 3) + 1; + var emoteName = event.data; - - if (ANIMATIONS[emoteName].resource.state == FINISHED) { + + if (emoteName === "Sit"){ + emoteName = event.data + randSit; // Sit1, Sit2, Sit3 + } + + if (ANIMATIONS[emoteName].resource.state === FINISHED) { + if (activeTimer !== false) { Script.clearTimeout(activeTimer); } - - // if the activeEmote is different from the chosen emote, then play the new emote. Other wise, - // this is a second click on the same emote as the activeEmote, and we will just stop it. + + // If the activeEmote is different from the chosen emote, then play the new emote + // This is a second click on the same emote as the activeEmote, and we will just stop it if (activeEmote !== emoteName) { activeEmote = emoteName; - var frameCount = ANIMATIONS[emoteName].animation.frames.length; - MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount); + + + // Sit is the only animation currently that plays and then ends at the last frame + if (emoteName.match(/^Sit.*$/)) { + + // If user provides input during a sit, the avatar animation state should be restored + Controller.keyPressEvent.connect(restoreAnimation); + Controller.enableMapping(eventMappingName); + MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount); + + } else { + + activeEmote = emoteName; + var frameCount = ANIMATIONS[emoteName].animation.frames.length; + MyAvatar.overrideAnimation(ANIMATIONS[emoteName].url, FPS, false, 0, frameCount); - var timeOut = MSEC_PER_SEC * frameCount / FPS; - activeTimer = Script.setTimeout(function () { - MyAvatar.restoreAnimation(); - activeTimer = false; - activeEmote = false; - }, timeOut); + var timeOut = MSEC_PER_SEC * frameCount / FPS; + activeTimer = Script.setTimeout(function () { + MyAvatar.restoreAnimation(); + activeTimer = false; + activeEmote = false; + }, timeOut); + + } + } else { activeEmote = false; MyAvatar.restoreAnimation(); @@ -99,6 +127,34 @@ function onWebEventReceived(event) { } } +// Restore the navigation animation states (idle, walk, run) +function restoreAnimation() { + MyAvatar.restoreAnimation(); + + // Make sure the input is disconnected after animations are restored so it doesn't affect any emotes other than sit + Controller.keyPressEvent.disconnect(restoreAnimation); + Controller.disableMapping(eventMappingName); +} + +// Note peek() so as to not interfere with other mappings. +eventMapping.from(Controller.Standard.LeftPrimaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RightPrimaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LB).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LS).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RY).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RX).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LY).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LX).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.LeftGrip).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RB).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RS).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.RightGrip).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.Back).peek().to(restoreAnimation); +eventMapping.from(Controller.Standard.Start).peek().to(restoreAnimation); + + button.clicked.connect(onClicked); tablet.screenChanged.connect(onScreenChanged); tablet.webEventReceived.connect(onWebEventReceived); diff --git a/scripts/system/html/EmoteApp.html b/scripts/system/html/EmoteApp.html index 0a423b9b6c..6b42fb8dc8 100644 --- a/scripts/system/html/EmoteApp.html +++ b/scripts/system/html/EmoteApp.html @@ -38,7 +38,7 @@ .content { margin-top: 90px; - padding: 30px; + padding: 10px 30px; } input[type=button] { @@ -47,9 +47,9 @@ font-size: 20px; text-transform: uppercase; vertical-align: top; - height: 105px; + height: 90px; min-width: 190px; - padding: 0px 18px; + padding: 0px 10px; margin-right: 6px; border-radius: 5px; border: none; @@ -97,7 +97,7 @@

Emote App

-

Click an emotion to Emote:

+

Choose an emote:

@@ -106,6 +106,8 @@

+

+

diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 0c3e6199f3..91866605a4 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -668,6 +668,7 @@ window.onload = function () { addImage(element, messageOptions.isLoggedIn, idx === 0 && messageOptions.canShare, idx === 1, false, false, false, true); }); document.getElementById("p1").classList.add("processingGif"); + document.getElementById("snap-button").disabled = true; } else { var gifPath = message.image_data[0].localPath, p1img = document.getElementById('p1img'); @@ -677,14 +678,15 @@ window.onload = function () { shareForUrl("p1"); appendShareBar("p1", messageOptions.isLoggedIn, messageOptions.canShare, true, false, false, messageOptions.canBlast); document.getElementById("p1").classList.remove("processingGif"); + document.getElementById("snap-button").disabled = false; } } else { imageCount = message.image_data.length; message.image_data.forEach(function (element) { addImage(element, messageOptions.isLoggedIn, messageOptions.canShare, false, false, false, false, true); }); + document.getElementById("snap-button").disabled = false; } - document.getElementById("snap-button").disabled = false; break; case 'captureSettings': handleCaptureSetting(message.setting); @@ -701,7 +703,7 @@ window.onload = function () { case 'snapshotUploadComplete': var isGif = fileExtensionMatches(message.image_url, "gif"); updateShareInfo(isGif ? "p1" : "p0", message.story_id); - if (isPrintProcessing()) { + if (isPrintProcessing()) { setPrintButtonEnabled(); } break; @@ -724,11 +726,11 @@ function snapshotSettings() { })); } function takeSnapshot() { + document.getElementById("snap-button").disabled = true; EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", action: "takeSnapshot" })); - document.getElementById("snap-button").disabled = true; } function isPrintDisabled() { @@ -739,14 +741,14 @@ function isPrintDisabled() { document.getElementById('print-button').disabled; } function isPrintProcessing() { - var printElement = document.getElementById('print-icon'); + var printElement = document.getElementById('print-icon'); return printElement.classList.contains("print-icon") && printElement.classList.contains("print-icon-loading") && document.getElementById('print-button').disabled; } function isPrintEnabled() { - var printElement = document.getElementById('print-icon'); + var printElement = document.getElementById('print-icon'); return printElement.classList.contains("print-icon") && printElement.classList.contains("print-icon-default") && @@ -773,8 +775,8 @@ function requestPrintButtonUpdate() { })); } -function printToPolaroid() { - if (isPrintEnabled()) { +function printToPolaroid() { + if (isPrintEnabled()) { EventBridge.emitWebEvent(JSON.stringify({ type: "snapshot", action: "printToPolaroid" diff --git a/scripts/system/html/js/colpick.js b/scripts/system/html/js/colpick.js index 199c624bc5..505dd294d6 100644 --- a/scripts/system/html/js/colpick.js +++ b/scripts/system/html/js/colpick.js @@ -269,6 +269,9 @@ For usage and examples: colpick.com/plugin }, // Show/hide the color picker show = function (ev) { + if ($(this).attr('disabled')) { + return; + } // Prevent the trigger of any direct parent ev.stopPropagation(); var cal = $('#' + $(this).data('colpickId')); diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 88b3ccbf7c..a8c0e22ae6 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -184,17 +184,27 @@ function loaded() { currentElement.onclick = onRowClicked; currentElement.ondblclick = onRowDoubleClicked; }); - - if (refreshEntityListTimer) { - clearTimeout(refreshEntityListTimer); - } - refreshEntityListTimer = setTimeout(refreshEntityListObject, 50); } else { var item = entities[id].item; item.values({ name: name, url: filename, locked: locked, visible: visible }); } } + function removeEntities(deletedIDs) { + for (i = 0, length = deletedIDs.length; i < length; i++) { + delete entities[deletedIDs[i]]; + entityList.remove("id", deletedIDs[i]); + } + } + + function scheduleRefreshEntityList() { + var REFRESH_DELAY = 50; + if (refreshEntityListTimer) { + clearTimeout(refreshEntityListTimer); + } + refreshEntityListTimer = setTimeout(refreshEntityListObject, REFRESH_DELAY); + } + function clearEntities() { entities = {}; entityList.clear(); @@ -346,7 +356,7 @@ function loaded() { if (notFound) { refreshEntities(); } - } else if (data.type == "update") { + } else if (data.type === "update" && data.selectedIDs !== undefined) { var newEntities = data.entities; if (newEntities && newEntities.length == 0) { elNoEntitiesMessage.style.display = "block"; @@ -365,13 +375,15 @@ function loaded() { newEntities[i].hasScript ? SCRIPT_GLYPH : null); } updateSelectedEntities(data.selectedIDs); + scheduleRefreshEntityList(); resize(); } - } else if (data.type === "deleted") { - for (i = 0, length = data.ids.length; i < length; i++) { - delete entities[data.ids[i]]; - entityList.remove("id", data.ids[i]); - } + } else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) { + removeEntities(data.deletedIDs); + updateSelectedEntities(data.selectedIDs); + scheduleRefreshEntityList(); + } else if (data.type === "deleted" && data.ids) { + removeEntities(data.ids); refreshFooter(); } }); diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 4271aa9b09..8b876304b7 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -793,6 +793,7 @@ function loaded() { var elTextTextColorRed = document.getElementById("property-text-text-color-red"); var elTextTextColorGreen = document.getElementById("property-text-text-color-green"); var elTextTextColorBlue = document.getElementById("property-text-text-color-blue"); + var elTextBackgroundColor = document.getElementById("property-text-background-color"); var elTextBackgroundColorRed = document.getElementById("property-text-background-color-red"); var elTextBackgroundColorGreen = document.getElementById("property-text-background-color-green"); var elTextBackgroundColorBlue = document.getElementById("property-text-background-color-blue"); @@ -842,7 +843,7 @@ function loaded() { var elZoneHazeGlareColorGreen = document.getElementById("property-zone-haze-glare-color-green"); var elZoneHazeGlareColorBlue = document.getElementById("property-zone-haze-glare-color-blue"); var elZoneHazeEnableGlare = document.getElementById("property-zone-haze-enable-light-blend"); - var elZonehazeGlareAngle = document.getElementById("property-zone-haze-blend-angle"); + var elZoneHazeGlareAngle = document.getElementById("property-zone-haze-blend-angle"); var elZoneHazeAltitudeEffect = document.getElementById("property-zone-haze-altitude-effect"); var elZoneHazeBaseRef = document.getElementById("property-zone-haze-base"); @@ -893,9 +894,9 @@ function loaded() { } else { elServerScriptStatus.innerText = "Not running"; } - } else if (data.type === "update") { + } else if (data.type === "update" && data.selections) { - if (!data.selections || data.selections.length === 0) { + if (data.selections.length === 0) { if (lastEntityID !== null) { if (editor !== null) { saveJSONUserData(true); @@ -906,12 +907,208 @@ function loaded() { deleteJSONMaterialEditor(); } } + elTypeIcon.style.display = "none"; elType.innerHTML = "No selection"; - elID.value = ""; elPropertiesList.className = ''; + + elID.value = ""; + elName.value = ""; + elLocked.checked = false; + elVisible.checked = false; + + elParentID.value = ""; + elParentJointIndex.value = ""; + + elColorRed.value = ""; + elColorGreen.value = ""; + elColorBlue.value = ""; + elColorControlVariant2.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + + elPositionX.value = ""; + elPositionY.value = ""; + elPositionZ.value = ""; + + elRotationX.value = ""; + elRotationY.value = ""; + elRotationZ.value = ""; + + elDimensionsX.value = ""; + elDimensionsY.value = ""; + elDimensionsZ.value = ""; + + elRegistrationX.value = ""; + elRegistrationY.value = ""; + elRegistrationZ.value = ""; + + elLinearVelocityX.value = ""; + elLinearVelocityY.value = ""; + elLinearVelocityZ.value = ""; + elLinearDamping.value = ""; + + elAngularVelocityX.value = ""; + elAngularVelocityY.value = ""; + elAngularVelocityZ.value = ""; + elAngularDamping.value = ""; + + elGravityX.value = ""; + elGravityY.value = ""; + elGravityZ.value = ""; + + elAccelerationX.value = ""; + elAccelerationY.value = ""; + elAccelerationZ.value = ""; + + elRestitution.value = ""; + elFriction.value = ""; + elDensity.value = ""; + + elCollisionless.checked = false; + elDynamic.checked = false; + + elCollideStatic.checked = false; + elCollideKinematic.checked = false; + elCollideDynamic.checked = false; + elCollideMyAvatar.checked = false; + elCollideOtherAvatar.checked = false; + + elGrabbable.checked = false; + elWantsTrigger.checked = false; + elIgnoreIK.checked = false; + + elCloneable.checked = false; + elCloneableDynamic.checked = false; + elCloneableAvatarEntity.checked = false; + elCloneableGroup.style.display = "none"; + elCloneableLimit.value = ""; + elCloneableLifetime.value = ""; + + showElements(document.getElementsByClassName('can-cast-shadow-section'), true); + elCanCastShadow.checked = false; + + elCollisionSoundURL.value = ""; + elLifetime.value = ""; + elScriptURL.value = ""; + elServerScripts.value = ""; + elHyperlinkHref.value = ""; + elDescription.value = ""; + + deleteJSONEditor(); + elUserData.value = ""; + showUserDataTextArea(); + showSaveUserDataButton(); + showNewJSONEditorButton(); + + // Shape Properties + elShape.value = "Cube"; + setDropdownText(elShape); + + // Light Properties + elLightSpotLight.checked = false; + elLightColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elLightColorRed.value = ""; + elLightColorGreen.value = ""; + elLightColorBlue.value = ""; + elLightIntensity.value = ""; + elLightFalloffRadius.value = ""; + elLightExponent.value = ""; + elLightCutoff.value = ""; + + // Model Properties + elModelURL.value = ""; + elCompoundShapeURL.value = ""; + elShapeType.value = "none"; + setDropdownText(elShapeType); + elModelAnimationURL.value = "" + elModelAnimationPlaying.checked = false; + elModelAnimationFPS.value = ""; + elModelAnimationFrame.value = ""; + elModelAnimationFirstFrame.value = ""; + elModelAnimationLastFrame.value = ""; + elModelAnimationLoop.checked = false; + elModelAnimationHold.checked = false; + elModelAnimationAllowTranslation.checked = false; + elModelTextures.value = ""; + elModelOriginalTextures.value = ""; + + // Zone Properties + elZoneFlyingAllowed.checked = false; + elZoneGhostingAllowed.checked = false; + elZoneFilterURL.value = ""; + elZoneKeyLightColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elZoneKeyLightColorRed.value = ""; + elZoneKeyLightColorGreen.value = ""; + elZoneKeyLightColorBlue.value = ""; + elZoneKeyLightIntensity.value = ""; + elZoneKeyLightDirectionX.value = ""; + elZoneKeyLightDirectionY.value = ""; + elZoneKeyLightCastShadows.checked = false; + elZoneAmbientLightIntensity.value = ""; + elZoneAmbientLightURL.value = ""; + elZoneHazeRange.value = ""; + elZoneHazeColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elZoneHazeColorRed.value = ""; + elZoneHazeColorGreen.value = ""; + elZoneHazeColorBlue.value = ""; + elZoneHazeBackgroundBlend.value = 0; + elZoneHazeGlareColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elZoneHazeGlareColorRed.value = ""; + elZoneHazeGlareColorGreen.value = ""; + elZoneHazeGlareColorBlue.value = ""; + elZoneHazeEnableGlare.checked = false; + elZoneHazeGlareAngle.value = ""; + elZoneHazeAltitudeEffect.checked = false; + elZoneHazeBaseRef.value = ""; + elZoneHazeCeiling.value = ""; + elZoneSkyboxColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elZoneSkyboxColorRed.value = ""; + elZoneSkyboxColorGreen.value = ""; + elZoneSkyboxColorBlue.value = ""; + elZoneSkyboxURL.value = ""; + showElements(document.getElementsByClassName('keylight-section'), true); + showElements(document.getElementsByClassName('skybox-section'), true); + showElements(document.getElementsByClassName('ambient-section'), true); + showElements(document.getElementsByClassName('haze-section'), true); + + // Text Properties + elTextText.value = ""; + elTextLineHeight.value = ""; + elTextFaceCamera.checked = false; + elTextTextColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elTextTextColorRed.value = ""; + elTextTextColorGreen.value = ""; + elTextTextColorBlue.value = ""; + elTextBackgroundColor.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; + elTextBackgroundColorRed.value = ""; + elTextBackgroundColorGreen.value = ""; + elTextBackgroundColorBlue.value = ""; + + // Image Properties + elImageURL.value = ""; + + // Web Properties + elWebSourceURL.value = ""; + elWebDPI.value = ""; + + // Material Properties + elMaterialURL.value = ""; + elParentMaterialNameNumber.value = ""; + elParentMaterialNameCheckbox.checked = false; + elPriority.value = ""; + elMaterialMappingPosX.value = ""; + elMaterialMappingPosY.value = ""; + elMaterialMappingScaleX.value = ""; + elMaterialMappingScaleY.value = ""; + elMaterialMappingRot.value = ""; + + deleteJSONMaterialEditor(); + elMaterialData.value = ""; + showMaterialDataTextArea(); + showSaveMaterialDataButton(); + showNewJSONMaterialEditorButton(); + disableProperties(); - } else if (data.selections && data.selections.length > 1) { + } else if (data.selections.length > 1) { deleteJSONEditor(); deleteJSONMaterialEditor(); var selections = data.selections; @@ -1184,10 +1381,14 @@ function loaded() { elTextLineHeight.value = properties.lineHeight.toFixed(4); elTextFaceCamera.checked = properties.faceCamera; elTextTextColor.style.backgroundColor = "rgb(" + properties.textColor.red + "," + - properties.textColor.green + "," + properties.textColor.blue + ")"; + properties.textColor.green + "," + + properties.textColor.blue + ")"; elTextTextColorRed.value = properties.textColor.red; elTextTextColorGreen.value = properties.textColor.green; elTextTextColorBlue.value = properties.textColor.blue; + elTextBackgroundColor.style.backgroundColor = "rgb(" + properties.backgroundColor.red + "," + + properties.backgroundColor.green + "," + + properties.backgroundColor.blue + ")"; elTextBackgroundColorRed.value = properties.backgroundColor.red; elTextBackgroundColorGreen.value = properties.backgroundColor.green; elTextBackgroundColorBlue.value = properties.backgroundColor.blue; @@ -1260,13 +1461,12 @@ function loaded() { elZoneHazeGlareColorBlue.value = properties.haze.hazeGlareColor.blue; elZoneHazeEnableGlare.checked = properties.haze.hazeEnableGlare; - elZonehazeGlareAngle.value = properties.haze.hazeGlareAngle.toFixed(0); + elZoneHazeGlareAngle.value = properties.haze.hazeGlareAngle.toFixed(0); elZoneHazeAltitudeEffect.checked = properties.haze.hazeAltitudeEffect; elZoneHazeBaseRef.value = properties.haze.hazeBaseRef.toFixed(0); elZoneHazeCeiling.value = properties.haze.hazeCeiling.toFixed(0); - elZoneHazeBackgroundBlend.value = properties.haze.hazeBackgroundBlend.toFixed(2); elShapeType.value = properties.shapeType; elCompoundShapeURL.value = properties.compoundShapeURL; @@ -1848,7 +2048,7 @@ function loaded() { elZoneHazeEnableGlare.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeEnableGlare')); - elZonehazeGlareAngle.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeGlareAngle')); + elZoneHazeGlareAngle.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeGlareAngle')); elZoneHazeAltitudeEffect.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeAltitudeEffect')); diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 799a974fd6..9b91d06d41 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -306,13 +306,21 @@ // change pricing to GET/BUY on button hover $('body').on('mouseenter', '#price-or-edit .price', function () { var $this = $(this); + var buyString = "BUY"; + var getString = "GET"; + // Protection against the button getting stuck in the "BUY"/"GET" state. + // That happens when the browser gets two MOUSEENTER events before getting a + // MOUSELEAVE event. + if ($this.text() === buyString || $this.text() === getString) { + return; + } $this.data('initialHtml', $this.html()); var cost = $(this).parent().siblings().text(); if (parseInt(cost) > 0) { - $this.text('BUY'); + $this.text(buyString); } else { - $this.text('GET'); + $this.text(getString); } }); diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index fade6f6b26..f83f961438 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -15,6 +15,7 @@ Script.include(Script.resolvePath("../libraries/controllers.js")); Script.include(Script.resolvePath("../libraries/Xform.js")); var Y_AXIS = {x: 0, y: 1, z: 0}; +var X_AXIS = {x: 1, y: 0, z: 0}; var DEFAULT_DPI = 31; var DEFAULT_WIDTH = 0.4375; var DEFAULT_VERTICAL_FIELD_OF_VIEW = 45; // degrees @@ -29,14 +30,22 @@ var INCHES_TO_METERS = 1 / 39.3701; var NO_HANDS = -1; var DELAY_FOR_30HZ = 33; // milliseconds +var TABLET_MATERIAL_ENTITY_NAME = 'Tablet-Material-Entity'; + // will need to be recaclulated if dimensions of fbx model change. var TABLET_NATURAL_DIMENSIONS = {x: 32.083, y: 48.553, z: 2.269}; -var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png"; +var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "images/button-close.png"; // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png"; // var TABLET_MODEL_PATH = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx"; +var LOCAL_BEZEL_HIGHLIGHT = Script.resourcesPath() + "images/buttonBezel_highlight.png"; +var LOCAL_NORMAL_BEZEL = Script.resourcesPath() + "images/buttonBezel.png"; + var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-button-small-bezel.fbx"; +var HIGH_PRIORITY = 1; +var LOW_PRIORITY = 0; +var SUBMESH = 2; // returns object with two fields: // * position - position in front of the user @@ -72,6 +81,19 @@ function calcSpawnInfo(hand, landscape) { }; } + +cleanUpOldMaterialEntities = function() { + var avatarEntityData = MyAvatar.getAvatarEntityData(); + for (var entityID in avatarEntityData) { + var entityName = Entities.getEntityProperties(entityID, ["name"]).name; + + if (entityName === TABLET_MATERIAL_ENTITY_NAME && entityID !== HMD.homeButtonHighlightMaterialID && + entityID !== HMD.homeButtonUnhighlightMaterialID) { + Entities.deleteEntity(entityID); + } + } +}; + /** * WebTablet * @param url [string] url of content to show on the tablet. @@ -127,6 +149,7 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { } this.cleanUpOldTablets(); + cleanUpOldMaterialEntities(); this.tabletEntityID = Overlays.addOverlay("model", tabletProperties); @@ -134,11 +157,10 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { Overlays.deleteOverlay(this.webOverlayID); } - var RAYPICK_OFFSET = 0.0007; // Sufficient for raypick to reliably intersect tablet screen before tablet model. - var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) / sensorScaleFactor + RAYPICK_OFFSET; - var WEB_ENTITY_Y_OFFSET = 1 * tabletScaleFactor; - var screenWidth = 0.9275 * tabletWidth; - var screenHeight = 0.8983 * tabletHeight; + var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.5) * sensorScaleFactor; + var WEB_ENTITY_Y_OFFSET = 1.25 * tabletScaleFactor; + var screenWidth = 0.9367 * tabletWidth; + var screenHeight = 0.9000 * tabletHeight; this.webOverlayID = Overlays.addOverlay("web3d", { name: "WebTablet Web", url: url, @@ -154,12 +176,14 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { visible: visible }); - var HOME_BUTTON_Y_OFFSET = (tabletHeight / 2) - (tabletHeight / 20) + 0.003 * sensorScaleFactor; // FIXME: Circle3D overlays currently at the wrong dimensions, so we need to account for that here var homeButtonDim = 4.0 * tabletScaleFactor / 3.0; + var HOME_BUTTON_X_OFFSET = 0.00079 * sensorScaleFactor; + var HOME_BUTTON_Y_OFFSET = -1 * ((tabletHeight / 2) - (4.0 * tabletScaleFactor / 2)); + var HOME_BUTTON_Z_OFFSET = (tabletDepth / 1.9) * sensorScaleFactor; this.homeButtonID = Overlays.addOverlay("circle3d", { name: "homeButton", - localPosition: { x: 0.0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, + localPosition: { x: HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET }, localRotation: { x: 0, y: 1, z: 0, w: 0}, dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }, solid: true, @@ -170,24 +194,48 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { parentJointIndex: -1 }); - this.homeButtonHighlightID = Overlays.addOverlay("circle3d", { - name: "homeButtonHighlight", - localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, - localRotation: { x: 0, y: 1, z: 0, w: 0 }, - dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }, - color: { red: 255, green: 255, blue: 255 }, - solid: true, - innerRadius: 0.9, - ignoreIntersection: true, - alpha: 1.0, - visible: visible, - drawInFront: false, - parentID: this.tabletEntityID, - parentJointIndex: -1 - }); + this.homeButtonUnhighlightMaterial = Entities.addEntity({ + type: "Material", + name: TABLET_MATERIAL_ENTITY_NAME, + materialURL: "materialData", + localPosition: { x: 0.0, y: 0.0, z: 0.0 }, + priority: HIGH_PRIORITY, + materialData: JSON.stringify({ + materials: { + albedoMap: LOCAL_NORMAL_BEZEL + } + + }), + userData: JSON.stringify({ + "grabbableKey": {"grabbable": false} + }), + visible: false, + parentMaterialName: SUBMESH, + parentID: this.tabletEntityID + }, true); + + this.homeButtonHighlightMaterial = Entities.addEntity({ + type: "Material", + name: TABLET_MATERIAL_ENTITY_NAME, + materialURL: "materialData", + localPosition: { x: 0.0, y: 0.0, z: 0.0 }, + priority: LOW_PRIORITY, + visible: false, + materialData: JSON.stringify({ + materials: { + emissiveMap: LOCAL_BEZEL_HIGHLIGHT + } + + }), + userData: JSON.stringify({ + "grabbableKey": {"grabbable": false} + }), + parentMaterialName: SUBMESH, + parentID: this.tabletEntityID + }, true); this.receive = function (channel, senderID, senderUUID, localOnly) { - if (_this.homeButtonID == senderID) { + if (_this.homeButtonID === senderID) { var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var onHomeScreen = tablet.onHomeScreen(); var isMessageOpen; @@ -339,7 +387,8 @@ WebTablet.prototype.destroy = function () { Overlays.deleteOverlay(this.webOverlayID); Overlays.deleteOverlay(this.tabletEntityID); Overlays.deleteOverlay(this.homeButtonID); - Overlays.deleteOverlay(this.homeButtonHighlightID); + Entities.deleteEntity(this.homeButtonUnhighlightMaterial); + Entities.deleteEntity(this.homeButtonHighlightMaterial); HMD.displayModeChanged.disconnect(this.myOnHmdChanged); Controller.mousePressEvent.disconnect(this.myMousePressEvent); @@ -433,21 +482,24 @@ WebTablet.prototype.calculateWorldAttitudeRelativeToCamera = function (windowPos WebTablet.prototype.onHoverEnterOverlay = function (overlayID, pointerEvent) { if (overlayID === this.homeButtonID) { - Overlays.editOverlay(this.homeButtonHighlightID, { alpha: 1.0 }); + Entities.editEntity(this.homeButtonUnhighlightMaterial, {priority: LOW_PRIORITY}); + Entities.editEntity(this.homeButtonHighlightMaterial, {priority: HIGH_PRIORITY}); } -} +}; WebTablet.prototype.onHoverOverOverlay = function (overlayID, pointerEvent) { if (overlayID !== this.homeButtonID) { - Overlays.editOverlay(this.homeButtonHighlightID, { alpha: 0.0 }); + Entities.editEntity(this.homeButtonUnhighlightMaterial, {priority: HIGH_PRIORITY}); + Entities.editEntity(this.homeButtonHighlightMaterial, {priority: LOW_PRIORITY}); } -} +}; WebTablet.prototype.onHoverLeaveOverlay = function (overlayID, pointerEvent) { if (overlayID === this.homeButtonID) { - Overlays.editOverlay(this.homeButtonHighlightID, { alpha: 0.0 }); + Entities.editEntity(this.homeButtonUnhighlightMaterial, {priority: HIGH_PRIORITY}); + Entities.editEntity(this.homeButtonHighlightMaterial, {priority: LOW_PRIORITY}); } -} +}; // compute position, rotation & parentJointIndex of the tablet WebTablet.prototype.calculateTabletAttachmentProperties = function (hand, useMouse, tabletProperties) { @@ -575,22 +627,6 @@ WebTablet.prototype.scheduleMouseMoveProcessor = function() { } }; -WebTablet.prototype.handleHomeButtonHover = function(x, y) { - var pickRay = Camera.computePickRay(x, y); - var entityPickResults; - var homebuttonHovered = false; - entityPickResults = Overlays.findRayIntersection(pickRay, true, [this.tabletEntityID]); - if (entityPickResults.intersects && (entityPickResults.entityID === this.tabletEntityID || - entityPickResults.overlayID === this.tabletEntityID)) { - var overlayPickResults = Overlays.findRayIntersection(pickRay, true, [this.homeButtonID], []); - if (overlayPickResults.intersects && overlayPickResults.overlayID === this.homeButtonID) { - homebuttonHovered = true; - } - } - - Overlays.editOverlay(this.homeButtonHighlightID, { alpha: homebuttonHovered ? 1.0 : 0.0 }); -} - WebTablet.prototype.mouseMoveEvent = function (event) { if (this.dragging) { this.currentMouse = { @@ -598,8 +634,6 @@ WebTablet.prototype.mouseMoveEvent = function (event) { y: event.y }; this.scheduleMouseMoveProcessor(); - } else { - this.handleHomeButtonHover(event.x, event.y); } }; @@ -626,8 +660,6 @@ WebTablet.prototype.mouseMoveProcessor = function () { }); } this.scheduleMouseMoveProcessor(); - } else { - this.handleHomeButtonHover(this.currentMouse.x, this.currentMouse.y); } }; diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 1506ce17b2..e817bb4ee1 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -59,6 +59,7 @@ highlightTargetEntity:true, clearHighlightedEntities:true, unhighlightTargetEntity:true + distanceBetweenEntityLocalPositionAndBoundingBox: true */ MSECS_PER_SEC = 1000.0; @@ -129,7 +130,10 @@ DISPATCHER_PROPERTIES = [ "userData", "type", "href", - "cloneable" + "cloneable", + "cloneDynamic", + "localPosition", + "localRotation" ]; // priority -- a lower priority means the module will be asked sooner than one with a higher priority in a given update step @@ -412,6 +416,25 @@ findHandChildEntities = function(hand) { }); }; +distanceBetweenEntityLocalPositionAndBoundingBox = function(entityProps) { + var localPoint = entityProps.localPosition; + var entityXform = new Xform(entityProps.rotation, entityProps.position); + var minOffset = Vec3.multiplyVbyV(entityProps.registrationPoint, entityProps.dimensions); + var maxOffset = Vec3.multiplyVbyV(Vec3.subtract(ONE_VEC, entityProps.registrationPoint), entityProps.dimensions); + var localMin = Vec3.subtract(entityXform.trans, minOffset); + var localMax = Vec3.sum(entityXform.trans, maxOffset); + + var v = {x: localPoint.x, y: localPoint.y, z: localPoint.z}; + v.x = Math.max(v.x, localMin.x); + v.x = Math.min(v.x, localMax.x); + v.y = Math.max(v.y, localMin.y); + v.y = Math.min(v.y, localMax.y); + v.z = Math.max(v.z, localMin.z); + v.z = Math.min(v.z, localMax.z); + + return Vec3.distance(v, localPoint); +}; + distanceBetweenPointAndEntityBoundingBox = function(point, entityProps) { var entityXform = new Xform(entityProps.rotation, entityProps.position); var localPoint = entityXform.inv().xformPoint(point); diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 611bd4d84c..24b90e3a44 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -11,27 +11,64 @@ /* global EntityListTool, Tablet, selectionManager, Entities, Camera, MyAvatar, Vec3, Menu, Messages, cameraManager, MENU_EASE_ON_FOCUS, deleteSelectedEntities, toggleSelectedEntitiesLocked, toggleSelectedEntitiesVisible */ -EntityListTool = function(opts) { +EntityListTool = function(shouldUseEditTabletApp) { var that = {}; + var CreateWindow = Script.require('../modules/createWindow.js'); + + var TITLE_OFFSET = 60; + var ENTITY_LIST_WIDTH = 495; + var MAX_DEFAULT_CREATE_TOOLS_HEIGHT = 778; + var entityListWindow = new CreateWindow( + Script.resourcesPath() + "qml/hifi/tablet/EditEntityList.qml", + 'Entity List', + 'com.highfidelity.create.entityListWindow', + function () { + var windowHeight = Window.innerHeight - TITLE_OFFSET; + if (windowHeight > MAX_DEFAULT_CREATE_TOOLS_HEIGHT) { + windowHeight = MAX_DEFAULT_CREATE_TOOLS_HEIGHT; + } + return { + size: { + x: ENTITY_LIST_WIDTH, + y: windowHeight + }, + position: { + x: Window.x, + y: Window.y + TITLE_OFFSET + } + }; + }, + false + ); + var webView = null; webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - webView.setVisible = function(value) {}; + webView.setVisible = function(value){ }; var filterInView = false; var searchRadius = 100; var visible = false; - webView.setVisible(visible); - that.webView = webView; that.setVisible = function(newVisible) { visible = newVisible; - webView.setVisible(visible); + webView.setVisible(shouldUseEditTabletApp() && visible); + entityListWindow.setVisible(!shouldUseEditTabletApp() && visible); }; + that.setVisible(false); + + function emitJSONScriptEvent(data) { + var dataString = JSON.stringify(data); + webView.emitScriptEvent(dataString); + if (entityListWindow.window) { + entityListWindow.window.emitScriptEvent(dataString); + } + } + that.toggleVisible = function() { that.setVisible(!visible); }; @@ -43,24 +80,42 @@ EntityListTool = function(opts) { selectedIDs.push(selectionManager.selections[i]); } - var data = { + emitJSONScriptEvent({ type: 'selectionUpdate', - selectedIDs: selectedIDs, - }; - webView.emitScriptEvent(JSON.stringify(data)); + selectedIDs: selectedIDs + }); }); - that.clearEntityList = function () { - var data = { + that.clearEntityList = function() { + emitJSONScriptEvent({ type: 'clearEntityList' - }; - webView.emitScriptEvent(JSON.stringify(data)); + }); + }; + + that.removeEntities = function (deletedIDs, selectedIDs) { + emitJSONScriptEvent({ + type: 'removeEntities', + deletedIDs: deletedIDs, + selectedIDs: selectedIDs + }); + }; + + that.deleteEntities = function (deletedIDs) { + emitJSONScriptEvent({ + type: "deleted", + ids: deletedIDs + }); }; function valueIfDefined(value) { return value !== undefined ? value : ""; } + function filterEntity(entityID) { + return ((entityID === HMD.homeButtonHighlightMaterialID) || + (entityID === HMD.homeButtonUnhighlightMaterialID)); + } + that.sendUpdate = function() { var entities = []; @@ -71,6 +126,10 @@ EntityListTool = function(opts) { ids = Entities.findEntities(MyAvatar.position, searchRadius); } + ids = ids.filter(function(id) { + return !filterEntity(id); + }); + var cameraPosition = Camera.position; for (var i = 0; i < ids.length; i++) { var id = ids[i]; @@ -78,9 +137,9 @@ EntityListTool = function(opts) { if (!filterInView || Vec3.distance(properties.position, cameraPosition) <= searchRadius) { var url = ""; - if (properties.type == "Model") { + if (properties.type === "Model") { url = properties.modelURL; - } else if (properties.type == "Material") { + } else if (properties.type === "Material") { url = properties.materialURL; } entities.push({ @@ -90,12 +149,17 @@ EntityListTool = function(opts) { url: url, locked: properties.locked, visible: properties.visible, - verticesCount: valueIfDefined(properties.renderInfo.verticesCount), - texturesCount: valueIfDefined(properties.renderInfo.texturesCount), - texturesSize: valueIfDefined(properties.renderInfo.texturesSize), - hasTransparent: valueIfDefined(properties.renderInfo.hasTransparent), - isBaked: properties.type == "Model" ? url.toLowerCase().endsWith(".baked.fbx") : false, - drawCalls: valueIfDefined(properties.renderInfo.drawCalls), + verticesCount: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.verticesCount) : ""), + texturesCount: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.texturesCount) : ""), + texturesSize: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.texturesSize) : ""), + hasTransparent: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.hasTransparent) : ""), + isBaked: properties.type === "Model" ? url.toLowerCase().endsWith(".baked.fbx") : false, + drawCalls: (properties.renderInfo !== undefined ? + valueIfDefined(properties.renderInfo.drawCalls) : ""), hasScript: properties.script !== "" }); } @@ -106,12 +170,11 @@ EntityListTool = function(opts) { selectedIDs.push(selectionManager.selections[j]); } - var data = { + emitJSONScriptEvent({ type: "update", entities: entities, selectedIDs: selectedIDs, - }; - webView.emitScriptEvent(JSON.stringify(data)); + }); }; function onFileSaveChanged(filename) { @@ -124,15 +187,15 @@ EntityListTool = function(opts) { } } - webView.webEventReceived.connect(function(data) { + var onWebEventReceived = function(data) { try { data = JSON.parse(data); } catch(e) { - print("entityList.js: Error parsing JSON: " + e.name + " data " + data) + print("entityList.js: Error parsing JSON: " + e.name + " data " + data); return; } - if (data.type == "selectionUpdate") { + if (data.type === "selectionUpdate") { var ids = data.entityIds; var entityIDs = []; for (var i = 0; i < ids.length; i++) { @@ -145,20 +208,20 @@ EntityListTool = function(opts) { selectionManager.worldDimensions, Menu.isOptionChecked(MENU_EASE_ON_FOCUS)); } - } else if (data.type == "refresh") { + } else if (data.type === "refresh") { that.sendUpdate(); - } else if (data.type == "teleport") { + } else if (data.type === "teleport") { if (selectionManager.hasSelection()) { MyAvatar.position = selectionManager.worldPosition; } - } else if (data.type == "export") { + } else if (data.type === "export") { if (!selectionManager.hasSelection()) { Window.notifyEditError("No entities have been selected."); } else { Window.saveFileChanged.connect(onFileSaveChanged); Window.saveAsync("Select Where to Save", "", "*.json"); } - } else if (data.type == "pal") { + } else if (data.type === "pal") { var sessionIds = {}; // Collect the sessionsIds of all selected entitities, w/o duplicates. selectionManager.selections.forEach(function (id) { var lastEditedBy = Entities.getEntityProperties(id, 'lastEditedBy').lastEditedBy; @@ -175,24 +238,21 @@ EntityListTool = function(opts) { // No need to subscribe if we're just sending. Messages.sendMessage('com.highfidelity.pal', JSON.stringify({method: 'select', params: [dedupped, true, false]}), 'local'); } - } else if (data.type == "delete") { + } else if (data.type === "delete") { deleteSelectedEntities(); - } else if (data.type == "toggleLocked") { + } else if (data.type === "toggleLocked") { toggleSelectedEntitiesLocked(); - } else if (data.type == "toggleVisible") { + } else if (data.type === "toggleVisible") { toggleSelectedEntitiesVisible(); } else if (data.type === "filterInView") { filterInView = data.filterInView === true; } else if (data.type === "radius") { searchRadius = data.radius; } - }); + }; - // webView.visibleChanged.connect(function () { - // if (webView.visible) { - // that.sendUpdate(); - // } - // }); + webView.webEventReceived.connect(onWebEventReceived); + entityListWindow.webEventReceived.addListener(onWebEventReceived); return that; }; diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 1b41559160..091d431502 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -13,11 +13,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global SPACE_LOCAL, SelectionManager */ +/* global SelectionManager, SelectionDisplay, grid, rayPlaneIntersection, rayPlaneIntersection2, pushCommandForSelections, + getMainTabletIDs, getControllerWorldLocation */ -SPACE_LOCAL = "local"; -SPACE_WORLD = "world"; -HIGHLIGHT_LIST_NAME = "editHandleHighlightList"; +var SPACE_LOCAL = "local"; +var SPACE_WORLD = "world"; +var HIGHLIGHT_LIST_NAME = "editHandleHighlightList"; Script.include([ "./controllers.js", @@ -58,12 +59,22 @@ SelectionManager = (function() { that.setSelections([messageParsed.entityID]); } else if (messageParsed.method === "clearSelection") { that.clearSelections(); + } else if (messageParsed.method === "pointingAt") { + if (messageParsed.rightHand) { + that.pointingAtDesktopWindowRight = messageParsed.desktopWindow; + that.pointingAtTabletRight = messageParsed.tablet; + } else { + that.pointingAtDesktopWindowLeft = messageParsed.desktopWindow; + that.pointingAtTabletLeft = messageParsed.tablet; + } } } subscribeToUpdateMessages(); - var COLOR_ORANGE_HIGHLIGHT = { red: 255, green: 99, blue: 9 } + // disabling this for now as it is causing rendering issues with the other handle overlays + /* + var COLOR_ORANGE_HIGHLIGHT = { red: 255, green: 99, blue: 9 }; var editHandleOutlineStyle = { outlineUnoccludedColor: COLOR_ORANGE_HIGHLIGHT, outlineOccludedColor: COLOR_ORANGE_HIGHLIGHT, @@ -76,8 +87,8 @@ SelectionManager = (function() { outlineWidth: 3, isOutlineSmooth: true }; - //disabling this for now as it is causing rendering issues with the other handle overlays - //Selection.enableListHighlight(HIGHLIGHT_LIST_NAME, editHandleOutlineStyle); + Selection.enableListHighlight(HIGHLIGHT_LIST_NAME, editHandleOutlineStyle); + */ that.savedProperties = {}; that.selections = []; @@ -93,6 +104,11 @@ SelectionManager = (function() { that.worldDimensions = Vec3.ZERO; that.worldRegistrationPoint = Vec3.HALF; that.centerPosition = Vec3.ZERO; + + that.pointingAtDesktopWindowLeft = false; + that.pointingAtDesktopWindowRight = false; + that.pointingAtTabletLeft = false; + that.pointingAtTabletRight = false; that.saveProperties = function() { that.savedProperties = {}; @@ -166,20 +182,69 @@ SelectionManager = (function() { that.selections = []; that._update(true); }; + + that.addChildrenEntities = function(parentEntityID, entityList) { + var children = Entities.getChildrenIDs(parentEntityID); + for (var i = 0; i < children.length; i++) { + var childID = children[i]; + if (entityList.indexOf(childID) < 0) { + entityList.push(childID); + } + that.addChildrenEntities(childID, entityList); + } + }; that.duplicateSelection = function() { + var entitiesToDuplicate = []; var duplicatedEntityIDs = []; - Object.keys(that.savedProperties).forEach(function(otherEntityID) { - var properties = that.savedProperties[otherEntityID]; + var duplicatedChildrenWithOldParents = []; + var originalEntityToNewEntityID = []; + + // build list of entities to duplicate by including any unselected children of selected parent entities + Object.keys(that.savedProperties).forEach(function(originalEntityID) { + if (entitiesToDuplicate.indexOf(originalEntityID) < 0) { + entitiesToDuplicate.push(originalEntityID); + } + that.addChildrenEntities(originalEntityID, entitiesToDuplicate); + }); + + // duplicate entities from above and store their original to new entity mappings and children needing re-parenting + for (var i = 0; i < entitiesToDuplicate.length; i++) { + var originalEntityID = entitiesToDuplicate[i]; + var properties = that.savedProperties[originalEntityID]; + if (properties === undefined) { + properties = Entities.getEntityProperties(originalEntityID); + } if (!properties.locked && (!properties.clientOnly || properties.owningAvatarID === MyAvatar.sessionUUID)) { + var newEntityID = Entities.addEntity(properties); duplicatedEntityIDs.push({ - entityID: Entities.addEntity(properties), + entityID: newEntityID, properties: properties }); + if (properties.parentID !== Uuid.NULL) { + duplicatedChildrenWithOldParents[newEntityID] = properties.parentID; + } + originalEntityToNewEntityID[originalEntityID] = newEntityID; + } + } + + // re-parent duplicated children to the duplicate entities of their original parents (if they were duplicated) + Object.keys(duplicatedChildrenWithOldParents).forEach(function(childIDNeedingNewParent) { + var originalParentID = duplicatedChildrenWithOldParents[childIDNeedingNewParent]; + var newParentID = originalEntityToNewEntityID[originalParentID]; + if (newParentID) { + Entities.editEntity(childIDNeedingNewParent, { parentID: newParentID }); + for (var i = 0; i < duplicatedEntityIDs.length; i++) { + var duplicatedEntity = duplicatedEntityIDs[i]; + if (duplicatedEntity.entityID === childIDNeedingNewParent) { + duplicatedEntity.properties.parentID = newParentID; + } + } } }); + return duplicatedEntityIDs; - } + }; that._update = function(selectionUpdated) { var properties = null; @@ -198,16 +263,14 @@ SelectionManager = (function() { that.worldDimensions = properties.boundingBox.dimensions; that.worldPosition = properties.boundingBox.center; - that.worldRotation = properties.boundingBox.rotation; + that.worldRotation = Quat.IDENTITY; that.entityType = properties.type; - - SelectionDisplay.setSpaceMode(SPACE_LOCAL); + + if (selectionUpdated) { + SelectionDisplay.setSpaceMode(SPACE_LOCAL); + } } else { - that.localRotation = null; - that.localDimensions = null; - that.localPosition = null; - properties = Entities.getEntityProperties(that.selections[0]); that.entityType = properties.type; @@ -226,6 +289,7 @@ SelectionManager = (function() { tfl.z = Math.max(bb.tfl.z, tfl.z); } + that.localRotation = null; that.localDimensions = null; that.localPosition = null; that.worldDimensions = { @@ -233,6 +297,7 @@ SelectionManager = (function() { y: tfl.y - brn.y, z: tfl.z - brn.z }; + that.worldRotation = Quat.IDENTITY; that.worldPosition = { x: brn.x + (that.worldDimensions.x / 2), y: brn.y + (that.worldDimensions.y / 2), @@ -257,11 +322,12 @@ SelectionManager = (function() { // Normalize degrees to be in the range (-180, 180) function normalizeDegrees(degrees) { - degrees = ((degrees + 180) % 360) - 180; - if (degrees <= -180) { - degrees += 360; + var maxDegrees = 360; + var halfMaxDegrees = maxDegrees / 2; + degrees = ((degrees + halfMaxDegrees) % maxDegrees) - halfMaxDegrees; + if (degrees <= -halfMaxDegrees) { + degrees += maxDegrees; } - return degrees; } @@ -271,14 +337,14 @@ SelectionDisplay = (function() { var NEGATE_VECTOR = -1; - var COLOR_GREEN = { red:31, green:198, blue:166 }; - var COLOR_BLUE = { red:0, green:147, blue:197 }; - var COLOR_RED = { red:226, green:51, blue:77 }; - var COLOR_HOVER = { red:227, green:227, blue:227 }; + var COLOR_GREEN = { red: 31, green: 198, blue: 166 }; + var COLOR_BLUE = { red: 0, green: 147, blue: 197 }; + var COLOR_RED = { red: 226, green: 51, blue: 77 }; + var COLOR_HOVER = { red: 227, green: 227, blue: 227 }; var COLOR_ROTATE_CURRENT_RING = { red: 255, green: 99, blue: 9 }; - var COLOR_SCALE_EDGE = { red:87, green:87, blue:87 }; - var COLOR_SCALE_CUBE = { red:106, green:106, blue:106 }; - var COLOR_SCALE_CUBE_SELECTED = { red:18, green:18, blue:18 }; + var COLOR_SCALE_EDGE = { red: 87, green: 87, blue: 87 }; + var COLOR_SCALE_CUBE = { red: 106, green: 106, blue: 106 }; + var COLOR_SCALE_CUBE_SELECTED = { red: 18, green: 18, blue: 18 }; var TRANSLATE_ARROW_CYLINDER_OFFSET = 0.1; var TRANSLATE_ARROW_CYLINDER_CAMERA_DISTANCE_MULTIPLE = 0.005; @@ -307,48 +373,46 @@ SelectionDisplay = (function() { var STRETCH_PANEL_WIDTH = 0.01; var SCALE_CUBE_OFFSET = 0.5; - var SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.015; + var SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.0125; - var CLONER_OFFSET = { x:0.9, y:-0.9, z:0.9 }; + var CLONER_OFFSET = { x: 0.9, y: -0.9, z: 0.9 }; var CTRL_KEY_CODE = 16777249; - var AVATAR_COLLISIONS_OPTION = "Enable Avatar Collisions"; + var RAIL_AXIS_LENGTH = 10000; var TRANSLATE_DIRECTION = { - X : 0, - Y : 1, - Z : 2 - } + X: 0, + Y: 1, + Z: 2 + }; var STRETCH_DIRECTION = { - X : 0, - Y : 1, - Z : 2, - ALL : 3 - } + X: 0, + Y: 1, + Z: 2, + ALL: 3 + }; var SCALE_DIRECTION = { - LBN : 0, - RBN : 1, - LBF : 2, - RBF : 3, - LTN : 4, - RTN : 5, - LTF : 6, - RTF : 7 - } + LBN: 0, + RBN: 1, + LBF: 2, + RBF: 3, + LTN: 4, + RTN: 5, + LTF: 6, + RTF: 7 + }; var ROTATE_DIRECTION = { - PITCH : 0, - YAW : 1, - ROLL : 2 - } + PITCH: 0, + YAW: 1, + ROLL: 2 + }; var spaceMode = SPACE_LOCAL; var overlayNames = []; - var lastCameraPosition = Camera.getPosition(); - var lastCameraOrientation = Camera.getOrientation(); var lastControllerPoses = [ getControllerWorldLocation(Controller.Standard.LeftHand, true), getControllerWorldLocation(Controller.Standard.RightHand, true) @@ -368,7 +432,7 @@ SelectionDisplay = (function() { var ctrlPressed = false; - var handleStretchCollisionOverride = false; + that.replaceCollisionsAfterStretch = false; var handlePropertiesTranslateArrowCones = { shape: "Cone", @@ -386,16 +450,16 @@ SelectionDisplay = (function() { }; var handleTranslateXCone = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCones); var handleTranslateXCylinder = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCylinders); - Overlays.editOverlay(handleTranslateXCone, { color : COLOR_RED }); - Overlays.editOverlay(handleTranslateXCylinder, { color : COLOR_RED }); + Overlays.editOverlay(handleTranslateXCone, { color: COLOR_RED }); + Overlays.editOverlay(handleTranslateXCylinder, { color: COLOR_RED }); var handleTranslateYCone = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCones); var handleTranslateYCylinder = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCylinders); - Overlays.editOverlay(handleTranslateYCone, { color : COLOR_GREEN }); - Overlays.editOverlay(handleTranslateYCylinder, { color : COLOR_GREEN }); + Overlays.editOverlay(handleTranslateYCone, { color: COLOR_GREEN }); + Overlays.editOverlay(handleTranslateYCylinder, { color: COLOR_GREEN }); var handleTranslateZCone = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCones); var handleTranslateZCylinder = Overlays.addOverlay("shape", handlePropertiesTranslateArrowCylinders); - Overlays.editOverlay(handleTranslateZCone, { color : COLOR_BLUE }); - Overlays.editOverlay(handleTranslateZCylinder, { color : COLOR_BLUE }); + Overlays.editOverlay(handleTranslateZCone, { color: COLOR_BLUE }); + Overlays.editOverlay(handleTranslateZCylinder, { color: COLOR_BLUE }); var handlePropertiesRotateRings = { alpha: 1, @@ -411,18 +475,18 @@ SelectionDisplay = (function() { }; var handleRotatePitchRing = Overlays.addOverlay("circle3d", handlePropertiesRotateRings); Overlays.editOverlay(handleRotatePitchRing, { - color : COLOR_RED, - majorTickMarksColor: COLOR_RED, + color: COLOR_RED, + majorTickMarksColor: COLOR_RED }); var handleRotateYawRing = Overlays.addOverlay("circle3d", handlePropertiesRotateRings); Overlays.editOverlay(handleRotateYawRing, { - color : COLOR_GREEN, - majorTickMarksColor: COLOR_GREEN, + color: COLOR_GREEN, + majorTickMarksColor: COLOR_GREEN }); var handleRotateRollRing = Overlays.addOverlay("circle3d", handlePropertiesRotateRings); Overlays.editOverlay(handleRotateRollRing, { - color : COLOR_BLUE, - majorTickMarksColor: COLOR_BLUE, + color: COLOR_BLUE, + majorTickMarksColor: COLOR_BLUE }); var handleRotateCurrentRing = Overlays.addOverlay("circle3d", { @@ -461,11 +525,11 @@ SelectionDisplay = (function() { drawInFront: true }; var handleStretchXSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres); - Overlays.editOverlay(handleStretchXSphere, { color : COLOR_RED }); + Overlays.editOverlay(handleStretchXSphere, { color: COLOR_RED }); var handleStretchYSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres); - Overlays.editOverlay(handleStretchYSphere, { color : COLOR_GREEN }); + Overlays.editOverlay(handleStretchYSphere, { color: COLOR_GREEN }); var handleStretchZSphere = Overlays.addOverlay("shape", handlePropertiesStretchSpheres); - Overlays.editOverlay(handleStretchZSphere, { color : COLOR_BLUE }); + Overlays.editOverlay(handleStretchZSphere, { color: COLOR_BLUE }); var handlePropertiesStretchPanel = { shape: "Quad", @@ -474,13 +538,13 @@ SelectionDisplay = (function() { visible: false, ignoreRayIntersection: true, drawInFront: true - } + }; var handleStretchXPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel); - Overlays.editOverlay(handleStretchXPanel, { color : COLOR_RED }); + Overlays.editOverlay(handleStretchXPanel, { color: COLOR_RED }); var handleStretchYPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel); - Overlays.editOverlay(handleStretchYPanel, { color : COLOR_GREEN }); + Overlays.editOverlay(handleStretchYPanel, { color: COLOR_GREEN }); var handleStretchZPanel = Overlays.addOverlay("shape", handlePropertiesStretchPanel); - Overlays.editOverlay(handleStretchZPanel, { color : COLOR_BLUE }); + Overlays.editOverlay(handleStretchZPanel, { color: COLOR_BLUE }); var handlePropertiesScaleCubes = { size: 0.025, @@ -506,7 +570,7 @@ SelectionDisplay = (function() { ignoreRayIntersection: true, drawInFront: true, lineWidth: 0.2 - } + }; var handleScaleTREdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); var handleScaleTLEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); var handleScaleTFEdge = Overlays.addOverlay("line3d", handlePropertiesScaleEdge); @@ -552,6 +616,40 @@ SelectionDisplay = (function() { dashed: false }); + var xRailOverlay = Overlays.addOverlay("line3d", { + visible: false, + start: Vec3.ZERO, + end: Vec3.ZERO, + color: { + red: 255, + green: 0, + blue: 0 + }, + ignoreRayIntersection: true // always ignore this + }); + var yRailOverlay = Overlays.addOverlay("line3d", { + visible: false, + start: Vec3.ZERO, + end: Vec3.ZERO, + color: { + red: 0, + green: 255, + blue: 0 + }, + ignoreRayIntersection: true // always ignore this + }); + var zRailOverlay = Overlays.addOverlay("line3d", { + visible: false, + start: Vec3.ZERO, + end: Vec3.ZERO, + color: { + red: 0, + green: 0, + blue: 255 + }, + ignoreRayIntersection: true // always ignore this +}); + var allOverlays = [ handleTranslateXCone, handleTranslateXCylinder, @@ -592,8 +690,13 @@ SelectionDisplay = (function() { handleScaleFLEdge, handleCloner, selectionBox, - iconSelectionBox + iconSelectionBox, + xRailOverlay, + yRailOverlay, + zRailOverlay + ]; + var maximumHandleInAllOverlays = handleCloner; overlayNames[handleTranslateXCone] = "handleTranslateXCone"; overlayNames[handleTranslateXCylinder] = "handleTranslateXCylinder"; @@ -644,11 +747,6 @@ SelectionDisplay = (function() { var activeTool = null; var handleTools = {}; - that.shutdown = function() { - that.restoreAvatarCollisionsFromStretch(); - } - Script.scriptEnding.connect(that.shutdown); - // We get mouseMoveEvents from the handControllers, via handControllerPointer. // But we dont' get mousePressEvents. that.triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); @@ -667,7 +765,13 @@ SelectionDisplay = (function() { activeHand = (activeHand === Controller.Standard.RightHand) ? Controller.Standard.LeftHand : Controller.Standard.RightHand; } - if (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(Reticle.position)) { + var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand && + SelectionManager.pointingAtDesktopWindowRight) || + (hand === Controller.Standard.LeftHand && + SelectionManager.pointingAtDesktopWindowLeft); + var pointingAtTablet = (hand === Controller.Standard.RightHand && SelectionManager.pointingAtTabletRight) || + (hand === Controller.Standard.LeftHand && SelectionManager.pointingAtTabletLeft); + if (pointingAtDesktopWindow || pointingAtTablet) { return; } that.mousePressEvent({}); @@ -716,6 +820,12 @@ SelectionDisplay = (function() { return Math.abs(position.x) <= box.dimensions.x / 2 && Math.abs(position.y) <= box.dimensions.y / 2 && Math.abs(position.z) <= box.dimensions.z / 2; } + + that.isEditHandle = function(overlayID) { + var overlayIndex = allOverlays.indexOf(overlayID); + var maxHandleIndex = allOverlays.indexOf(maximumHandleInAllOverlays); + return overlayIndex >= 0 && overlayIndex <= maxHandleIndex; + }; // FUNCTION: MOUSE PRESS EVENT that.mousePressEvent = function (event) { @@ -773,11 +883,11 @@ SelectionDisplay = (function() { }; that.resetPreviousHandleColor = function() { - if (previousHandle != null) { + if (previousHandle !== null) { Overlays.editOverlay(previousHandle, { color: previousHandleColor }); previousHandle = null; } - if (previousHandleHelper != null) { + if (previousHandleHelper !== null) { Overlays.editOverlay(previousHandleHelper, { color: previousHandleColor }); previousHandleHelper = null; } @@ -801,11 +911,13 @@ SelectionDisplay = (function() { }; // FUNCTION: MOUSE MOVE EVENT + var lastMouseEvent = null; that.mouseMoveEvent = function(event) { var wantDebug = false; if (wantDebug) { print("=============== eST::MouseMoveEvent BEG ======================="); } + lastMouseEvent = event; if (activeTool) { if (wantDebug) { print(" Trigger ActiveTool(" + activeTool.mode + ")'s onMove"); @@ -874,7 +986,7 @@ SelectionDisplay = (function() { Overlays.editOverlay(result.overlayID, { color: COLOR_HOVER }); previousHandle = result.overlayID; previousHandleHelper = that.getHandleHelper(result.overlayID); - if (previousHandleHelper != null) { + if (previousHandleHelper !== null) { Overlays.editOverlay(previousHandleHelper, { color: COLOR_HOVER }); } previousHandleColor = pickedColor; @@ -927,20 +1039,36 @@ SelectionDisplay = (function() { }; // Control key remains active only while key is held down - that.keyReleaseEvent = function(key) { - if (key.key === CTRL_KEY_CODE) { + that.keyReleaseEvent = function(event) { + if (event.key === CTRL_KEY_CODE) { ctrlPressed = false; that.updateActiveRotateRing(); } - } + if (activeTool && lastMouseEvent !== null) { + lastMouseEvent.isShifted = event.isShifted; + lastMouseEvent.isMeta = event.isMeta; + lastMouseEvent.isControl = event.isControl; + lastMouseEvent.isAlt = event.isAlt; + activeTool.onMove(lastMouseEvent); + SelectionManager._update(); + } + }; // Triggers notification on specific key driven events - that.keyPressEvent = function(key) { - if (key.key === CTRL_KEY_CODE) { + that.keyPressEvent = function(event) { + if (event.key === CTRL_KEY_CODE) { ctrlPressed = true; that.updateActiveRotateRing(); } - } + if (activeTool && lastMouseEvent !== null) { + lastMouseEvent.isShifted = event.isShifted; + lastMouseEvent.isMeta = event.isMeta; + lastMouseEvent.isControl = event.isControl; + lastMouseEvent.isAlt = event.isAlt; + activeTool.onMove(lastMouseEvent); + SelectionManager._update(); + } + }; // NOTE: mousePressEvent and mouseMoveEvent from the main script should call us., so we don't hook these: // Controller.mousePressEvent.connect(that.mousePressEvent); @@ -953,7 +1081,7 @@ SelectionDisplay = (function() { if (SelectionManager.hasSelection()) { var controllerPose = getControllerWorldLocation(activeHand, true); var hand = (activeHand === Controller.Standard.LeftHand) ? 0 : 1; - if (controllerPose.valid && lastControllerPoses[hand].valid) { + if (controllerPose.valid && lastControllerPoses[hand].valid && that.triggered) { if (!Vec3.equal(controllerPose.position, lastControllerPoses[hand].position) || !Vec3.equal(controllerPose.rotation, lastControllerPoses[hand].rotation)) { that.mouseMoveEvent({}); @@ -982,6 +1110,11 @@ SelectionDisplay = (function() { var toCameraDistance = Vec3.length(Vec3.subtract(cameraPosition, position)); return toCameraDistance; } + + function usePreviousPickRay(pickRayDirection, previousPickRayDirection, normal) { + return (Vec3.dot(pickRayDirection, normal) > 0 && Vec3.dot(previousPickRayDirection, normal) < 0) || + (Vec3.dot(pickRayDirection, normal) < 0 && Vec3.dot(previousPickRayDirection, normal) > 0); + } // @return string - The mode of the currently active tool; // otherwise, "UNKNOWN" if there's no active tool. @@ -998,12 +1131,7 @@ SelectionDisplay = (function() { that.select = function(entityID, event) { var properties = Entities.getEntityProperties(SelectionManager.selections[0]); - lastCameraPosition = Camera.getPosition(); - lastCameraOrientation = Camera.getOrientation(); - if (event !== false) { - pickRay = generalComputePickRay(event.x, event.y); - var wantDebug = false; if (wantDebug) { print("select() with EVENT...... "); @@ -1029,7 +1157,8 @@ SelectionDisplay = (function() { spaceMode = newSpaceMode; that.updateHandles(); } else if (wantDebug) { - print("WARNING: entitySelectionTool.setSpaceMode - Can't update SpaceMode. CurrentMode: " + spaceMode + " DesiredMode: " + newSpaceMode); + print("WARNING: entitySelectionTool.setSpaceMode - Can't update SpaceMode. CurrentMode: " + + spaceMode + " DesiredMode: " + newSpaceMode); } if (wantDebug) { print("====== SetSpaceMode called. <========"); @@ -1079,7 +1208,8 @@ SelectionDisplay = (function() { } if (!handleTools.hasOwnProperty(toolHandle)) { - print("WARNING: entitySelectionTool.isActiveTool - Encountered unknown grabberToolHandle: " + toolHandle + ". Tools should be registered via addHandleTool."); + print("WARNING: entitySelectionTool.isActiveTool - Encountered unknown grabberToolHandle: " + + toolHandle + ". Tools should be registered via addHandleTool."); // EARLY EXIT return false; } @@ -1109,26 +1239,34 @@ SelectionDisplay = (function() { var rotationInverse = Quat.inverse(rotation); var toCameraDistance = getDistanceToCamera(position); - var localRotationX = Quat.fromPitchYawRollDegrees(0, 0, -90); + var rotationDegrees = 90; + var localRotationX = Quat.fromPitchYawRollDegrees(0, 0, -rotationDegrees); var rotationX = Quat.multiply(rotation, localRotationX); worldRotationX = rotationX; - var localRotationY = Quat.fromPitchYawRollDegrees(0, 90, 0); + var localRotationY = Quat.fromPitchYawRollDegrees(0, rotationDegrees, 0); var rotationY = Quat.multiply(rotation, localRotationY); worldRotationY = rotationY; - var localRotationZ = Quat.fromPitchYawRollDegrees(90, 0, 0); + var localRotationZ = Quat.fromPitchYawRollDegrees(rotationDegrees, 0, 0); var rotationZ = Quat.multiply(rotation, localRotationZ); worldRotationZ = rotationZ; + + var selectionBoxGeometry = { + position: position, + rotation: rotation, + dimensions: dimensions + }; + var isCameraInsideBox = isPointInsideBox(Camera.position, selectionBoxGeometry); - // in HMD we clamp the overlays to the bounding box for now so lasers can hit them + // in HMD if outside the bounding box clamp the overlays to the bounding box for now so lasers can hit them var maxHandleDimension = 0; - if (HMD.active) { + if (HMD.active && !isCameraInsideBox) { maxHandleDimension = Math.max(dimensions.x, dimensions.y, dimensions.z); } // UPDATE ROTATION RINGS // rotateDimension is used as the base dimension for all overlays var rotateDimension = Math.max(maxHandleDimension, toCameraDistance * ROTATE_RING_CAMERA_DISTANCE_MULTIPLE); - var rotateDimensions = { x:rotateDimension, y:rotateDimension, z:rotateDimension }; + var rotateDimensions = { x: rotateDimension, y: rotateDimension, z: rotateDimension }; if (!isActiveTool(handleRotatePitchRing)) { Overlays.editOverlay(handleRotatePitchRing, { position: position, @@ -1160,16 +1298,16 @@ SelectionDisplay = (function() { var arrowCylinderDimension = rotateDimension * TRANSLATE_ARROW_CYLINDER_CAMERA_DISTANCE_MULTIPLE / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; var arrowCylinderDimensions = { - x:arrowCylinderDimension, - y:arrowCylinderDimension * TRANSLATE_ARROW_CYLINDER_Y_MULTIPLE, - z:arrowCylinderDimension + x: arrowCylinderDimension, + y: arrowCylinderDimension * TRANSLATE_ARROW_CYLINDER_Y_MULTIPLE, + z: arrowCylinderDimension }; var arrowConeDimension = rotateDimension * TRANSLATE_ARROW_CONE_CAMERA_DISTANCE_MULTIPLE / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; - var arrowConeDimensions = { x:arrowConeDimension, y:arrowConeDimension, z:arrowConeDimension }; + var arrowConeDimensions = { x: arrowConeDimension, y: arrowConeDimension, z: arrowConeDimension }; var arrowCylinderOffset = rotateDimension * TRANSLATE_ARROW_CYLINDER_OFFSET / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; var arrowConeOffset = arrowCylinderDimensions.y * TRANSLATE_ARROW_CONE_OFFSET_CYLINDER_DIMENSION_MULTIPLE; - var cylinderXPosition = { x:arrowCylinderOffset, y:0, z:0 }; + var cylinderXPosition = { x: arrowCylinderOffset, y: 0, z: 0 }; cylinderXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, cylinderXPosition)); Overlays.editOverlay(handleTranslateXCylinder, { position: cylinderXPosition, @@ -1183,7 +1321,7 @@ SelectionDisplay = (function() { rotation: rotationX, dimensions: arrowConeDimensions }); - var cylinderYPosition = { x:0, y:arrowCylinderOffset, z:0 }; + var cylinderYPosition = { x: 0, y: arrowCylinderOffset, z: 0 }; cylinderYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, cylinderYPosition)); Overlays.editOverlay(handleTranslateYCylinder, { position: cylinderYPosition, @@ -1197,7 +1335,7 @@ SelectionDisplay = (function() { rotation: rotationY, dimensions: arrowConeDimensions }); - var cylinderZPosition = { x:0, y:0, z:arrowCylinderOffset }; + var cylinderZPosition = { x: 0, y: 0, z: arrowCylinderOffset }; cylinderZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, cylinderZPosition)); Overlays.editOverlay(handleTranslateZCylinder, { position: cylinderZPosition, @@ -1216,61 +1354,73 @@ SelectionDisplay = (function() { var scaleCubeOffsetX = SCALE_CUBE_OFFSET * dimensions.x; var scaleCubeOffsetY = SCALE_CUBE_OFFSET * dimensions.y; var scaleCubeOffsetZ = SCALE_CUBE_OFFSET * dimensions.z; - var scaleCubeDimension = rotateDimension * SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE / - ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; - var scaleCubeDimensions = { x:scaleCubeDimension, y:scaleCubeDimension, z:scaleCubeDimension }; var scaleCubeRotation = spaceMode === SPACE_LOCAL ? rotation : Quat.IDENTITY; - var scaleLBNCubePosition = { x:-scaleCubeOffsetX, y:-scaleCubeOffsetY, z:-scaleCubeOffsetZ }; + var scaleLBNCubePosition = { x: -scaleCubeOffsetX, y: -scaleCubeOffsetY, z: -scaleCubeOffsetZ }; scaleLBNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLBNCubePosition)); + var scaleLBNCubeToCamera = getDistanceToCamera(scaleLBNCubePosition); + var scaleRBNCubePosition = { x: scaleCubeOffsetX, y: -scaleCubeOffsetY, z: -scaleCubeOffsetZ }; + scaleRBNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRBNCubePosition)); + var scaleRBNCubeToCamera = getDistanceToCamera(scaleRBNCubePosition); + var scaleLBFCubePosition = { x: -scaleCubeOffsetX, y: -scaleCubeOffsetY, z: scaleCubeOffsetZ }; + scaleLBFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLBFCubePosition)); + var scaleLBFCubeToCamera = getDistanceToCamera(scaleLBFCubePosition); + var scaleRBFCubePosition = { x: scaleCubeOffsetX, y: -scaleCubeOffsetY, z: scaleCubeOffsetZ }; + scaleRBFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRBFCubePosition)); + var scaleRBFCubeToCamera = getDistanceToCamera(scaleRBFCubePosition); + var scaleLTNCubePosition = { x: -scaleCubeOffsetX, y: scaleCubeOffsetY, z: -scaleCubeOffsetZ }; + scaleLTNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLTNCubePosition)); + var scaleLTNCubeToCamera = getDistanceToCamera(scaleLTNCubePosition); + var scaleRTNCubePosition = { x: scaleCubeOffsetX, y: scaleCubeOffsetY, z: -scaleCubeOffsetZ }; + scaleRTNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRTNCubePosition)); + var scaleRTNCubeToCamera = getDistanceToCamera(scaleRTNCubePosition); + var scaleLTFCubePosition = { x: -scaleCubeOffsetX, y: scaleCubeOffsetY, z: scaleCubeOffsetZ }; + scaleLTFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLTFCubePosition)); + var scaleLTFCubeToCamera = getDistanceToCamera(scaleLTFCubePosition); + var scaleRTFCubePosition = { x: scaleCubeOffsetX, y: scaleCubeOffsetY, z: scaleCubeOffsetZ }; + scaleRTFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRTFCubePosition)); + var scaleRTFCubeToCamera = getDistanceToCamera(scaleRTFCubePosition); + + var scaleCubeToCamera = Math.min(scaleLBNCubeToCamera, scaleRBNCubeToCamera, scaleLBFCubeToCamera, + scaleRBFCubeToCamera, scaleLTNCubeToCamera, scaleRTNCubeToCamera, + scaleLTFCubeToCamera, scaleRTFCubeToCamera); + var scaleCubeDimension = scaleCubeToCamera * SCALE_CUBE_CAMERA_DISTANCE_MULTIPLE; + var scaleCubeDimensions = { x: scaleCubeDimension, y: scaleCubeDimension, z: scaleCubeDimension }; + Overlays.editOverlay(handleScaleLBNCube, { position: scaleLBNCubePosition, rotation: scaleCubeRotation, dimensions: scaleCubeDimensions }); - var scaleRBNCubePosition = { x:scaleCubeOffsetX, y:-scaleCubeOffsetY, z:-scaleCubeOffsetZ }; - scaleRBNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRBNCubePosition)); Overlays.editOverlay(handleScaleRBNCube, { position: scaleRBNCubePosition, rotation: scaleCubeRotation, dimensions: scaleCubeDimensions }); - var scaleLBFCubePosition = { x:-scaleCubeOffsetX, y:-scaleCubeOffsetY, z:scaleCubeOffsetZ }; - scaleLBFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLBFCubePosition)); Overlays.editOverlay(handleScaleLBFCube, { position: scaleLBFCubePosition, rotation: scaleCubeRotation, dimensions: scaleCubeDimensions }); - var scaleRBFCubePosition = { x:scaleCubeOffsetX, y:-scaleCubeOffsetY, z:scaleCubeOffsetZ }; - scaleRBFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRBFCubePosition)); Overlays.editOverlay(handleScaleRBFCube, { position: scaleRBFCubePosition, rotation: scaleCubeRotation, dimensions: scaleCubeDimensions }); - var scaleLTNCubePosition = { x:-scaleCubeOffsetX, y:scaleCubeOffsetY, z:-scaleCubeOffsetZ }; - scaleLTNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLTNCubePosition)); Overlays.editOverlay(handleScaleLTNCube, { position: scaleLTNCubePosition, rotation: scaleCubeRotation, dimensions: scaleCubeDimensions }); - var scaleRTNCubePosition = { x:scaleCubeOffsetX, y:scaleCubeOffsetY, z:-scaleCubeOffsetZ }; - scaleRTNCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRTNCubePosition)); Overlays.editOverlay(handleScaleRTNCube, { position: scaleRTNCubePosition, rotation: scaleCubeRotation, dimensions: scaleCubeDimensions }); - var scaleLTFCubePosition = { x:-scaleCubeOffsetX, y:scaleCubeOffsetY, z:scaleCubeOffsetZ }; - scaleLTFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleLTFCubePosition)); Overlays.editOverlay(handleScaleLTFCube, { position: scaleLTFCubePosition, rotation: scaleCubeRotation, dimensions: scaleCubeDimensions }); - var scaleRTFCubePosition = { x:scaleCubeOffsetX, y:scaleCubeOffsetY, z:scaleCubeOffsetZ }; - scaleRTFCubePosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, scaleRTFCubePosition)); Overlays.editOverlay(handleScaleRTFCube, { position: scaleRTFCubePosition, rotation: scaleCubeRotation, @@ -1294,21 +1444,21 @@ SelectionDisplay = (function() { // UPDATE STRETCH SPHERES var stretchSphereDimension = rotateDimension * STRETCH_SPHERE_CAMERA_DISTANCE_MULTIPLE / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; - var stretchSphereDimensions = { x:stretchSphereDimension, y:stretchSphereDimension, z:stretchSphereDimension }; + var stretchSphereDimensions = { x: stretchSphereDimension, y: stretchSphereDimension, z: stretchSphereDimension }; var stretchSphereOffset = rotateDimension * STRETCH_SPHERE_OFFSET / ROTATE_RING_CAMERA_DISTANCE_MULTIPLE; - var stretchXPosition = { x:stretchSphereOffset, y:0, z:0 }; + var stretchXPosition = { x: stretchSphereOffset, y: 0, z: 0 }; stretchXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchXPosition)); Overlays.editOverlay(handleStretchXSphere, { position: stretchXPosition, dimensions: stretchSphereDimensions }); - var stretchYPosition = { x:0, y:stretchSphereOffset, z:0 }; + var stretchYPosition = { x: 0, y: stretchSphereOffset, z: 0 }; stretchYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchYPosition)); Overlays.editOverlay(handleStretchYSphere, { position: stretchYPosition, dimensions: stretchSphereDimensions }); - var stretchZPosition = { x:0, y:0, z:stretchSphereOffset }; + var stretchZPosition = { x: 0, y: 0, z: stretchSphereOffset }; stretchZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, stretchZPosition)); Overlays.editOverlay(handleStretchZSphere, { position: stretchZPosition, @@ -1325,27 +1475,29 @@ SelectionDisplay = (function() { stretchPanelXDimensions.x = STRETCH_PANEL_WIDTH; stretchPanelXDimensions.y = Math.abs(stretchPanelXDimensions.z); stretchPanelXDimensions.z = tempY; - var stretchPanelXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:dimensions.x / 2, y:0, z:0 })); + var stretchPanelXPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x: dimensions.x / 2, y: 0, z: 0 })); Overlays.editOverlay(handleStretchXPanel, { position: stretchPanelXPosition, rotation: rotationZ, dimensions: stretchPanelXDimensions }); var stretchPanelYDimensions = Vec3.subtract(scaleLTNCubePositionRotated, scaleRTFCubePositionRotated); + var tempX = Math.abs(stretchPanelYDimensions.x); stretchPanelYDimensions.x = Math.abs(stretchPanelYDimensions.z); stretchPanelYDimensions.y = STRETCH_PANEL_WIDTH; - stretchPanelYDimensions.z = Math.abs(stretchPanelYDimensions.x); - var stretchPanelYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:dimensions.y / 2, z:0 })); + stretchPanelYDimensions.z = tempX; + var stretchPanelYPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x: 0, y: dimensions.y / 2, z: 0 })); Overlays.editOverlay(handleStretchYPanel, { position: stretchPanelYPosition, rotation: rotationY, dimensions: stretchPanelYDimensions }); var stretchPanelZDimensions = Vec3.subtract(scaleLTNCubePositionRotated, scaleRBFCubePositionRotated); + tempX = Math.abs(stretchPanelZDimensions.x); stretchPanelZDimensions.x = Math.abs(stretchPanelZDimensions.y); - stretchPanelZDimensions.y = Math.abs(stretchPanelZDimensions.x); + stretchPanelZDimensions.y = tempX; stretchPanelZDimensions.z = STRETCH_PANEL_WIDTH; - var stretchPanelZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x:0, y:0, z:dimensions.z / 2 })); + var stretchPanelZPosition = Vec3.sum(position, Vec3.multiplyQbyV(rotation, { x: 0, y: 0, z: dimensions.z / 2 })); Overlays.editOverlay(handleStretchZPanel, { position: stretchPanelZPosition, rotation: rotationX, @@ -1356,12 +1508,6 @@ SelectionDisplay = (function() { var inModeRotate = isActiveTool(handleRotatePitchRing) || isActiveTool(handleRotateYawRing) || isActiveTool(handleRotateRollRing); - var selectionBoxGeometry = { - position: position, - rotation: rotation, - dimensions: dimensions - }; - var isCameraInsideBox = isPointInsideBox(Camera.position, selectionBoxGeometry); selectionBoxGeometry.visible = !inModeRotate && !isCameraInsideBox; Overlays.editOverlay(selectionBox, selectionBoxGeometry); @@ -1378,10 +1524,10 @@ SelectionDisplay = (function() { } // UPDATE CLONER (CURRENTLY HIDDEN FOR NOW) - var handleClonerOffset = { - x:CLONER_OFFSET.x * dimensions.x, - y:CLONER_OFFSET.y * dimensions.y, - z:CLONER_OFFSET.z * dimensions.z + var handleClonerOffset = { + x: CLONER_OFFSET.x * dimensions.x, + y: CLONER_OFFSET.y * dimensions.y, + z: CLONER_OFFSET.z * dimensions.z }; var handleClonerPos = Vec3.sum(position, Vec3.multiplyQbyV(rotation, handleClonerOffset)); Overlays.editOverlay(handleCloner, { @@ -1401,7 +1547,7 @@ SelectionDisplay = (function() { that.setHandleRotateYawVisible(!activeTool || isActiveTool(handleRotateYawRing)); that.setHandleRotateRollVisible(!activeTool || isActiveTool(handleRotateRollRing)); - var showScaleStretch = !activeTool && SelectionManager.selections.length === 1; + var showScaleStretch = !activeTool && SelectionManager.selections.length === 1 && spaceMode === SPACE_LOCAL; that.setHandleStretchXVisible(showScaleStretch || isActiveTool(handleStretchXSphere)); that.setHandleStretchYVisible(showScaleStretch || isActiveTool(handleStretchYSphere)); that.setHandleStretchZVisible(showScaleStretch || isActiveTool(handleStretchZSphere)); @@ -1419,9 +1565,9 @@ SelectionDisplay = (function() { !isActiveTool(handleRotateYawRing) && !isActiveTool(handleRotateRollRing))); - //keep cloner always hidden for now since you can hold Alt to clone while - //translating an entity - we may bring cloner back for HMD only later - //that.setHandleClonerVisible(!activeTool || isActiveTool(handleCloner)); + // keep cloner always hidden for now since you can hold Alt to clone while + // translating an entity - we may bring cloner back for HMD only later + // that.setHandleClonerVisible(!activeTool || isActiveTool(handleCloner)); if (wantDebug) { print("====== Update Handles <======="); @@ -1439,8 +1585,8 @@ SelectionDisplay = (function() { } else if (isActiveTool(handleRotateRollRing)) { activeRotateRing = handleRotateRollRing; } - if (activeRotateRing != null) { - var tickMarksAngle = ctrlPressed ? ROTATE_CTRL_SNAP_ANGLE : ROTATE_DEFAULT_TICK_MARKS_ANGLE; + if (activeRotateRing !== null) { + var tickMarksAngle = ctrlPressed ? ROTATE_CTRL_SNAP_ANGLE : ROTATE_DEFAULT_TICK_MARKS_ANGLE; Overlays.editOverlay(activeRotateRing, { majorTickMarksAngle: tickMarksAngle }); } }; @@ -1583,7 +1729,7 @@ SelectionDisplay = (function() { translateXZTool.pickPlanePosition = pickResult.intersection; translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, - SelectionManager.worldDimensions.y), + SelectionManager.worldDimensions.y), SelectionManager.worldDimensions.z); translateXZTool.startingDistance = Vec3.distance(pickRay.origin, SelectionManager.position); translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition); @@ -1615,13 +1761,21 @@ SelectionDisplay = (function() { }, onEnd: function(event, reason) { pushCommandForSelections(duplicatedEntityIDs); + if (isConstrained) { + Overlays.editOverlay(xRailOverlay, { + visible: false + }); + Overlays.editOverlay(zRailOverlay, { + visible: false + }); + } }, elevation: function(origin, intersection) { return (origin.y - intersection.y) / Vec3.distance(origin, intersection); }, onMove: function(event) { var wantDebug = false; - pickRay = generalComputePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); var pick = rayPlaneIntersection2(pickRay, translateXZTool.pickPlanePosition, { x: 0, @@ -1678,16 +1832,53 @@ SelectionDisplay = (function() { vector.x = 0; } if (!isConstrained) { + var xStart = Vec3.sum(startPosition, { + x: -RAIL_AXIS_LENGTH, + y: 0, + z: 0 + }); + var xEnd = Vec3.sum(startPosition, { + x: RAIL_AXIS_LENGTH, + y: 0, + z: 0 + }); + var zStart = Vec3.sum(startPosition, { + x: 0, + y: 0, + z: -RAIL_AXIS_LENGTH + }); + var zEnd = Vec3.sum(startPosition, { + x: 0, + y: 0, + z: RAIL_AXIS_LENGTH + }); + Overlays.editOverlay(xRailOverlay, { + start: xStart, + end: xEnd, + visible: true + }); + Overlays.editOverlay(zRailOverlay, { + start: zStart, + end: zEnd, + visible: true + }); isConstrained = true; } } else { if (isConstrained) { + Overlays.editOverlay(xRailOverlay, { + visible: false + }); + Overlays.editOverlay(zRailOverlay, { + visible: false + }); isConstrained = false; } } constrainMajorOnly = event.isControl; - var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(-0.5, SelectionManager.worldDimensions)); + var negateAndHalve = -0.5; + var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(negateAndHalve, SelectionManager.worldDimensions)); vector = Vec3.subtract( grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), cornerPosition); @@ -1732,22 +1923,27 @@ SelectionDisplay = (function() { function addHandleTranslateTool(overlay, mode, direction) { var pickNormal = null; var lastPick = null; + var initialPosition = null; var projectionVector = null; + var previousPickRay = null; addHandleTool(overlay, { mode: mode, onBegin: function(event, pickRay, pickResult) { + var axisVector; if (direction === TRANSLATE_DIRECTION.X) { - pickNormal = { x:0, y:1, z:1 }; + axisVector = { x: 1, y: 0, z: 0 }; } else if (direction === TRANSLATE_DIRECTION.Y) { - pickNormal = { x:1, y:0, z:1 }; + axisVector = { x: 0, y: 1, z: 0 }; } else if (direction === TRANSLATE_DIRECTION.Z) { - pickNormal = { x:1, y:1, z:0 }; + axisVector = { x: 0, y: 0, z: 1 }; } var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; - pickNormal = Vec3.multiplyQbyV(rotation, pickNormal); + axisVector = Vec3.multiplyQbyV(rotation, axisVector); + pickNormal = Vec3.cross(Vec3.cross(pickRay.direction, axisVector), axisVector); lastPick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal); + initialPosition = SelectionManager.worldPosition; SelectionManager.saveProperties(); that.resetPreviousHandleColor(); @@ -1768,22 +1964,29 @@ SelectionDisplay = (function() { } else { duplicatedEntityIDs = null; } + + previousPickRay = pickRay; }, onEnd: function(event, reason) { pushCommandForSelections(duplicatedEntityIDs); }, onMove: function(event) { - pickRay = generalComputePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); + + // Use previousPickRay if new pickRay will cause resulting rayPlaneIntersection values to wrap around + if (usePreviousPickRay(pickRay.direction, previousPickRay.direction, pickNormal)) { + pickRay = previousPickRay; + } - var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, pickNormal); + var newIntersection = rayPlaneIntersection(pickRay, initialPosition, pickNormal); var vector = Vec3.subtract(newIntersection, lastPick); if (direction === TRANSLATE_DIRECTION.X) { - projectionVector = { x:1, y:0, z:0 }; + projectionVector = { x: 1, y: 0, z: 0 }; } else if (direction === TRANSLATE_DIRECTION.Y) { - projectionVector = { x:0, y:1, z:0 }; + projectionVector = { x: 0, y: 1, z: 0 }; } else if (direction === TRANSLATE_DIRECTION.Z) { - projectionVector = { x:0, y:0, z:1 }; + projectionVector = { x: 0, y: 0, z: 1 }; } var rotation = spaceMode === SPACE_LOCAL ? SelectionManager.localRotation : SelectionManager.worldRotation; @@ -1791,7 +1994,8 @@ SelectionDisplay = (function() { var dotVector = Vec3.dot(vector, projectionVector); vector = Vec3.multiply(dotVector, projectionVector); - vector = grid.snapToGrid(vector); + var gridOrigin = grid.getOrigin(); + vector = Vec3.subtract(grid.snapToGrid(Vec3.sum(vector, gridOrigin)), gridOrigin); var wantDebug = false; if (wantDebug) { @@ -1817,6 +2021,8 @@ SelectionDisplay = (function() { var newPosition = Vec3.sum(properties.position, vector); Entities.editEntity(id, { position: newPosition }); } + + previousPickRay = pickRay; SelectionManager._update(); } @@ -1832,13 +2038,6 @@ SelectionDisplay = (function() { }; }; - that.restoreAvatarCollisionsFromStretch = function() { - if (handleStretchCollisionOverride) { - Menu.setIsOptionChecked(AVATAR_COLLISIONS_OPTION, true); - handleStretchCollisionOverride = false; - } - } - // TOOL DEFINITION: HANDLE STRETCH TOOL function makeStretchTool(stretchMode, directionEnum, directionVec, pivot, offset, stretchPanel, scaleHandle) { var directionFor3DStretch = directionVec; @@ -1864,7 +2063,6 @@ SelectionDisplay = (function() { var lastPick3D = null; var initialPosition = null; var initialDimensions = null; - var initialIntersection = null; var initialProperties = null; var registrationPoint = null; var deltaPivot = null; @@ -1872,6 +2070,7 @@ SelectionDisplay = (function() { var pickRayPosition = null; var pickRayPosition3D = null; var rotation = null; + var previousPickRay = null; var onBegin = function(event, pickRay, pickResult) { var properties = Entities.getEntityProperties(SelectionManager.selections[0]); @@ -1904,7 +2103,7 @@ SelectionDisplay = (function() { var scaledOffset = Vec3.multiply(0.5, offset); // Offset from the registration point - offsetRP = Vec3.subtract(scaledOffset, centeredRP); + var offsetRP = Vec3.subtract(scaledOffset, centeredRP); // Scaled offset in world coordinates var scaledOffsetWorld = vec3Mult(initialDimensions, offsetRP); @@ -1914,57 +2113,10 @@ SelectionDisplay = (function() { if (directionFor3DStretch) { // pivot, offset and pickPlanePosition for 3D manipulation var scaledPivot3D = Vec3.multiply(0.5, Vec3.multiply(1.0, directionFor3DStretch)); - deltaPivot3D = Vec3.subtract(centeredRP, scaledPivot3D); - - var scaledOffsetWorld3D = vec3Mult(initialDimensions, - Vec3.subtract(Vec3.multiply(0.5, Vec3.multiply(-1.0, directionFor3DStretch)), centeredRP)); - + deltaPivot3D = Vec3.subtract(centeredRP, scaledPivot3D); pickRayPosition3D = Vec3.sum(initialPosition, Vec3.multiplyQbyV(rotation, scaledOffsetWorld)); } - var start = null; - var end = null; - if ((numDimensions === 1) && mask.x) { - start = Vec3.multiplyQbyV(rotation, { - x: -10000, - y: 0, - z: 0 - }); - start = Vec3.sum(start, properties.position); - end = Vec3.multiplyQbyV(rotation, { - x: 10000, - y: 0, - z: 0 - }); - end = Vec3.sum(end, properties.position); - } - if ((numDimensions === 1) && mask.y) { - start = Vec3.multiplyQbyV(rotation, { - x: 0, - y: -10000, - z: 0 - }); - start = Vec3.sum(start, properties.position); - end = Vec3.multiplyQbyV(rotation, { - x: 0, - y: 10000, - z: 0 - }); - end = Vec3.sum(end, properties.position); - } - if ((numDimensions === 1) && mask.z) { - start = Vec3.multiplyQbyV(rotation, { - x: 0, - y: 0, - z: -10000 - }); - start = Vec3.sum(start, properties.position); - end = Vec3.multiplyQbyV(rotation, { - x: 0, - y: 0, - z: 10000 - }); - end = Vec3.sum(end, properties.position); - } + if (numDimensions === 1) { if (mask.x === 1) { planeNormal = { @@ -2035,40 +2187,49 @@ SelectionDisplay = (function() { SelectionManager.saveProperties(); that.resetPreviousHandleColor(); - if (stretchPanel != null) { + if (stretchPanel !== null) { Overlays.editOverlay(stretchPanel, { visible: true }); } - if (scaleHandle != null) { + if (scaleHandle !== null) { Overlays.editOverlay(scaleHandle, { color: COLOR_SCALE_CUBE_SELECTED }); } - if (Menu.isOptionChecked(AVATAR_COLLISIONS_OPTION)) { - Menu.setIsOptionChecked(AVATAR_COLLISIONS_OPTION, false); - handleStretchCollisionOverride = true; + + var collisionToRemove = "myAvatar"; + if (properties.collidesWith.indexOf(collisionToRemove) > -1) { + var newCollidesWith = properties.collidesWith.replace(collisionToRemove, ""); + Entities.editEntity(SelectionManager.selections[0], {collidesWith: newCollidesWith}); + that.replaceCollisionsAfterStretch = true; } + + previousPickRay = pickRay; }; var onEnd = function(event, reason) { - if (stretchPanel != null) { + if (stretchPanel !== null) { Overlays.editOverlay(stretchPanel, { visible: false }); } - if (scaleHandle != null) { + if (scaleHandle !== null) { Overlays.editOverlay(scaleHandle, { color: COLOR_SCALE_CUBE }); } - that.restoreAvatarCollisionsFromStretch(); + + if (that.replaceCollisionsAfterStretch) { + var newCollidesWith = SelectionManager.savedProperties[SelectionManager.selections[0]].collidesWith; + Entities.editEntity(SelectionManager.selections[0], {collidesWith: newCollidesWith}); + that.replaceCollisionsAfterStretch = false; + } + pushCommandForSelections(); }; var onMove = function(event) { - var proportional = (spaceMode === SPACE_WORLD) || directionEnum === STRETCH_DIRECTION.ALL; + var proportional = directionEnum === STRETCH_DIRECTION.ALL; - var position, dimensions, rotation; + var position, rotation; if (spaceMode === SPACE_LOCAL) { position = SelectionManager.localPosition; - dimensions = SelectionManager.localDimensions; rotation = SelectionManager.localRotation; } else { position = SelectionManager.worldPosition; - dimensions = SelectionManager.worldDimensions; rotation = SelectionManager.worldRotation; } @@ -2076,9 +2237,15 @@ SelectionDisplay = (function() { var localSigns = signs; var pickRay = generalComputePickRay(event.x, event.y); + // Use previousPickRay if new pickRay will cause resulting rayPlaneIntersection values to wrap around + if (usePreviousPickRay(pickRay.direction, previousPickRay.direction, planeNormal)) { + pickRay = previousPickRay; + } + // Are we using handControllers or Mouse - only relevant for 3D tools var controllerPose = getControllerWorldLocation(activeHand, true); var vector = null; + var newPick = null; if (HMD.isHMDAvailable() && HMD.isHandControllerAvailable() && controllerPose.valid && that.triggered && directionFor3DStretch) { localDeltaPivot = deltaPivot3D; @@ -2128,24 +2295,31 @@ SelectionDisplay = (function() { newDimensions = Vec3.sum(initialDimensions, changeInDimensions); } - var minimumDimension = directionEnum === STRETCH_DIRECTION.ALL ? STRETCH_ALL_MINIMUM_DIMENSION : - STRETCH_MINIMUM_DIMENSION; - newDimensions.x = Math.max(newDimensions.x, minimumDimension); - newDimensions.y = Math.max(newDimensions.y, minimumDimension); - newDimensions.z = Math.max(newDimensions.z, minimumDimension); + var minimumDimension = directionEnum === + STRETCH_DIRECTION.ALL ? STRETCH_ALL_MINIMUM_DIMENSION : STRETCH_MINIMUM_DIMENSION; + if (newDimensions.x < minimumDimension) { + newDimensions.x = minimumDimension; + changeInDimensions.x = minimumDimension - initialDimensions.x; + } + if (newDimensions.y < minimumDimension) { + newDimensions.y = minimumDimension; + changeInDimensions.y = minimumDimension - initialDimensions.y; + } + if (newDimensions.z < minimumDimension) { + newDimensions.z = minimumDimension; + changeInDimensions.z = minimumDimension - initialDimensions.z; + } var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(localDeltaPivot, changeInDimensions)); if (directionEnum === STRETCH_DIRECTION.ALL) { - changeInPosition = { x:0, y:0, z:0 }; + changeInPosition = { x: 0, y: 0, z: 0 }; } var newPosition = Vec3.sum(initialPosition, changeInPosition); - for (var i = 0; i < SelectionManager.selections.length; i++) { - Entities.editEntity(SelectionManager.selections[i], { - position: newPosition, - dimensions: newDimensions - }); - } + Entities.editEntity(SelectionManager.selections[0], { + position: newPosition, + dimensions: newDimensions + }); var wantDebug = false; if (wantDebug) { @@ -2156,6 +2330,8 @@ SelectionDisplay = (function() { Vec3.print(" changeInPosition:", changeInPosition); Vec3.print(" newPosition:", newPosition); } + + previousPickRay = pickRay; SelectionManager._update(); };// End of onMove def @@ -2172,13 +2348,13 @@ SelectionDisplay = (function() { var directionVector, offset, stretchPanel; if (directionEnum === STRETCH_DIRECTION.X) { stretchPanel = handleStretchXPanel; - directionVector = { x:-1, y:0, z:0 }; + directionVector = { x: -1, y: 0, z: 0 }; } else if (directionEnum === STRETCH_DIRECTION.Y) { stretchPanel = handleStretchYPanel; - directionVector = { x:0, y:-1, z:0 }; + directionVector = { x: 0, y: -1, z: 0 }; } else if (directionEnum === STRETCH_DIRECTION.Z) { - stretchPanel = handleStretchZPanel - directionVector = { x:0, y:0, z:-1 }; + stretchPanel = handleStretchZPanel; + directionVector = { x: 0, y: 0, z: -1 }; } offset = Vec3.multiply(directionVector, NEGATE_VECTOR); var tool = makeStretchTool(mode, directionEnum, directionVector, directionVector, offset, stretchPanel, null); @@ -2189,39 +2365,37 @@ SelectionDisplay = (function() { function addHandleScaleTool(overlay, mode, directionEnum) { var directionVector, offset, selectedHandle; if (directionEnum === SCALE_DIRECTION.LBN) { - directionVector = { x:1, y:1, z:1 }; + directionVector = { x: 1, y: 1, z: 1 }; selectedHandle = handleScaleLBNCube; } else if (directionEnum === SCALE_DIRECTION.RBN) { - directionVector = { x:-1, y:1, z:1 }; + directionVector = { x: -1, y: 1, z: 1 }; selectedHandle = handleScaleRBNCube; } else if (directionEnum === SCALE_DIRECTION.LBF) { - directionVector = { x:1, y:1, z:-1 }; + directionVector = { x: 1, y: 1, z: -1 }; selectedHandle = handleScaleLBFCube; } else if (directionEnum === SCALE_DIRECTION.RBF) { - directionVector = { x:-1, y:1, z:-1 }; + directionVector = { x: -1, y: 1, z: -1 }; selectedHandle = handleScaleRBFCube; } else if (directionEnum === SCALE_DIRECTION.LTN) { - directionVector = { x:1, y:-1, z:1 }; + directionVector = { x: 1, y: -1, z: 1 }; selectedHandle = handleScaleLTNCube; } else if (directionEnum === SCALE_DIRECTION.RTN) { - directionVector = { x:-1, y:-1, z:1 }; + directionVector = { x: -1, y: -1, z: 1 }; selectedHandle = handleScaleRTNCube; } else if (directionEnum === SCALE_DIRECTION.LTF) { - directionVector = { x:1, y:-1, z:-1 }; + directionVector = { x: 1, y: -1, z: -1 }; selectedHandle = handleScaleLTFCube; } else if (directionEnum === SCALE_DIRECTION.RTF) { - directionVector = { x:-1, y:-1, z:-1 }; + directionVector = { x: -1, y: -1, z: -1 }; selectedHandle = handleScaleRTFCube; } offset = Vec3.multiply(directionVector, NEGATE_VECTOR); - var tool = makeStretchTool(mode, STRETCH_DIRECTION.ALL, directionVector, - directionVector, offset, null, selectedHandle); + var tool = makeStretchTool(mode, STRETCH_DIRECTION.ALL, directionVector, directionVector, offset, null, selectedHandle); return addHandleTool(overlay, tool); } // FUNCTION: UPDATE ROTATION DEGREES OVERLAY function updateRotationDegreesOverlay(angleFromZero, position) { - var angle = angleFromZero * (Math.PI / 180); var toCameraDistance = getDistanceToCamera(position); var overlayProps = { position: position, @@ -2412,9 +2586,10 @@ SelectionDisplay = (function() { var startAtCurrent = 0; var endAtCurrent = angleFromZero; + var maxDegrees = 360; if (angleFromZero < 0) { - startAtCurrent = 360 + angleFromZero; - endAtCurrent = 360; + startAtCurrent = maxDegrees + angleFromZero; + endAtCurrent = maxDegrees; } Overlays.editOverlay(handleRotateCurrentRing, { startAt: startAtCurrent, @@ -2426,8 +2601,9 @@ SelectionDisplay = (function() { if (spaceMode === SPACE_LOCAL) { Overlays.editOverlay(handleRotateCurrentRing, { rotation: worldRotationZ }); } else { + var rotationDegrees = 90; Overlays.editOverlay(handleRotateCurrentRing, { - rotation: Quat.fromPitchYawRollDegrees(-90, 0, 0) + rotation: Quat.fromPitchYawRollDegrees(-rotationDegrees, 0, 0) }); } } diff --git a/scripts/system/libraries/gridTool.js b/scripts/system/libraries/gridTool.js index 3be6ac0b00..3a114f23c7 100644 --- a/scripts/system/libraries/gridTool.js +++ b/scripts/system/libraries/gridTool.js @@ -1,6 +1,6 @@ var GRID_CONTROLS_HTML_URL = Script.resolvePath('../html/gridControls.html'); -Grid = function(opts) { +Grid = function() { var that = {}; var gridColor = { red: 0, green: 0, blue: 0 }; var gridAlpha = 0.6; @@ -154,6 +154,12 @@ Grid = function(opts) { that.emitUpdate(); } }; + + that.moveToSelection = function() { + var newPosition = SelectionManager.worldPosition; + newPosition = Vec3.subtract(newPosition, { x: 0, y: SelectionManager.worldDimensions.y * 0.5, z: 0 }); + that.setPosition(newPosition); + }; that.emitUpdate = function() { if (that.onUpdate) { @@ -240,6 +246,8 @@ GridTool = function(opts) { var horizontalGrid = opts.horizontalGrid; var verticalGrid = opts.verticalGrid; + var createToolsWindow = opts.createToolsWindow; + var shouldUseEditTabletApp = opts.shouldUseEditTabletApp; var listeners = []; var webView = null; @@ -247,13 +255,15 @@ GridTool = function(opts) { webView.setVisible = function(value) { }; horizontalGrid.addListener(function(data) { - webView.emitScriptEvent(JSON.stringify(data)); + var dataString = JSON.stringify(data); + webView.emitScriptEvent(dataString); + createToolsWindow.emitScriptEvent(dataString); if (selectionDisplay) { selectionDisplay.updateHandles(); } }); - webView.webEventReceived.connect(function(data) { + var webEventReceived = function(data) { try { data = JSON.parse(data); } catch (e) { @@ -277,19 +287,20 @@ GridTool = function(opts) { } horizontalGrid.setPosition(position); } else if (action == "moveToSelection") { - var newPosition = selectionManager.worldPosition; - newPosition = Vec3.subtract(newPosition, { x: 0, y: selectionManager.worldDimensions.y * 0.5, z: 0 }); - grid.setPosition(newPosition); + horizontalGrid.moveToSelection(); } } - }); + }; + + webView.webEventReceived.connect(webEventReceived); + createToolsWindow.webEventReceived.addListener(webEventReceived); that.addListener = function(callback) { listeners.push(callback); }; that.setVisible = function(visible) { - webView.setVisible(visible); + webView.setVisible(shouldUseEditTabletApp() && visible); }; return that; diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index 7e9e1d7e6a..220ecd1959 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -32,7 +32,7 @@ if (!Function.prototype.bind) { if (this.prototype) { // Function.prototype doesn't have a prototype property - fNOP.prototype = this.prototype; + fNOP.prototype = this.prototype; } fBound.prototype = new fNOP(); @@ -370,7 +370,7 @@ getTabletWidthFromSettings = function () { resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) { - if (!HMD.tabletID || !HMD.tabletScreenID || !HMD.homeButtonID || !HMD.homeButtonHighlightID) { + if (!HMD.tabletID || !HMD.tabletScreenID || !HMD.homeButtonID) { return; } var sensorScaleFactor = sensorToWorldScaleOverride || MyAvatar.sensorToWorldScale; @@ -381,6 +381,7 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) sensorScaleOffsetOverride = 1 / sensorScaleFactor; } + // will need to be recaclulated if dimensions of fbx model change. var TABLET_NATURAL_DIMENSIONS = {x: 32.083, y: 48.553, z: 2.269}; var DEFAULT_DPI = 31; @@ -399,32 +400,26 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) }); // update webOverlay - var RAYPICK_OFFSET = 0.0007; // Sufficient for raypick to reliably intersect tablet screen before tablet model. - var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) * sensorScaleOffsetOverride + RAYPICK_OFFSET; - var WEB_ENTITY_Y_OFFSET = 1 * tabletScaleFactor; - print(WEB_ENTITY_Y_OFFSET); - var screenWidth = 0.9275 * tabletWidth; - var screenHeight = 0.8983 * tabletHeight; + var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.5) * sensorScaleOffsetOverride; + var WEB_ENTITY_Y_OFFSET = 1.25 * tabletScaleFactor * sensorScaleOffsetOverride; + var screenWidth = 0.9367 * tabletWidth; + var screenHeight = 0.9000 * tabletHeight; var landscape = Tablet.getTablet("com.highfidelity.interface.tablet.system").landscape; Overlays.editOverlay(HMD.tabletScreenID, { - localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, + localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET}, dimensions: {x: landscape ? screenHeight : screenWidth, y: landscape ? screenWidth : screenHeight, z: 0.1}, dpi: tabletDpi }); // update homeButton - var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20) + 0.003 * sensorScaleFactor) * sensorScaleOffsetOverride; // FIXME: Circle3D overlays currently at the wrong dimensions, so we need to account for that here var homeButtonDim = 4.0 * tabletScaleFactor / 3.0; + var HOME_BUTTON_X_OFFSET = 0.00079 * sensorScaleOffsetOverride * sensorScaleFactor; + var HOME_BUTTON_Y_OFFSET = -1 * ((tabletHeight / 2) - (4.0 * tabletScaleFactor / 2)) * sensorScaleOffsetOverride; + var HOME_BUTTON_Z_OFFSET = (tabletDepth / 1.9) * sensorScaleOffsetOverride; Overlays.editOverlay(HMD.homeButtonID, { - localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, - localRotation: Quat.angleAxis(180, Vec3.UNIT_Y), - dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } - }); - - Overlays.editOverlay(HMD.homeButtonHighlightID, { - localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, - localRotation: Quat.angleAxis(180, Vec3.UNIT_Y), + localPosition: { x: HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET }, + localRotation: { x: 0, y: 1, z: 0, w: 0 }, dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } }); }; diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index d9003ffeaa..8642fc5ce6 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -88,8 +88,8 @@ "polarFinish": 1, "radiusFinish": 0.3, "radiusStart": 0.04, - "speedSpread": 0.01, - "radiusSpread": 0.9, + "speedSpread": 0.00, + "radiusSpread": 0.0, "textures": "http://hifi-content.s3.amazonaws.com/alan/dev/Particles/Bokeh-Particle.png", "color": {"red": 200, "green": 170, "blue": 255}, "colorFinish": {"red": 0, "green": 134, "blue": 255}, diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 208e64fd5e..fd7b9c703a 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -114,7 +114,6 @@ var selectionDisplay = null; // for gridTool.js to ignore Overlays.editOverlay(HMD.tabletID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); Overlays.editOverlay(HMD.homeButtonID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); - Overlays.editOverlay(HMD.homeButtonHighlightIDtabletID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); Overlays.editOverlay(HMD.tabletScreenID, { isVisibleInSecondaryCamera : visibleInSecondaryCam }); } diff --git a/scripts/system/modules/createWindow.js b/scripts/system/modules/createWindow.js new file mode 100644 index 0000000000..185991d2ef --- /dev/null +++ b/scripts/system/modules/createWindow.js @@ -0,0 +1,151 @@ +"use strict"; + +// createWindow.js +// +// Created by Thijs Wenker on 6/1/18 +// +// Copyright 2018 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 +// + +var getWindowRect = function(settingsKey, defaultRect) { + var windowRect = Settings.getValue(settingsKey, defaultRect); + return windowRect; +}; + +var setWindowRect = function(settingsKey, position, size) { + Settings.setValue(settingsKey, { + position: position, + size: size + }); +}; + +var CallableEvent = (function() { + function CallableEvent() { + this.callbacks = []; + } + + CallableEvent.prototype = { + callbacks: null, + call: function () { + var callArguments = arguments; + this.callbacks.forEach(function(callbackObject) { + try { + callbackObject.callback.apply(callbackObject.context ? callbackObject.context : this, callArguments); + } catch (e) { + console.error('Call to CallableEvent callback failed!'); + } + }); + }, + addListener: function(contextOrCallback, callback) { + if (callback) { + this.callbacks.push({ + context: contextOrCallback, + callback: callback + }); + } else { + this.callbacks.push({ + callback: contextOrCallback + }); + } + }, + removeListener: function(callback) { + var foundIndex = -1; + this.callbacks.forEach(function (callbackObject, index) { + if (callbackObject.callback === callback) { + foundIndex = index; + } + }); + + if (foundIndex !== -1) { + this.callbacks.splice(foundIndex, 1); + } + } + }; + + return CallableEvent; +})(); + +module.exports = (function() { + function CreateWindow(qmlPath, title, settingsKey, defaultRect, createOnStartup) { + this.qmlPath = qmlPath; + this.title = title; + this.settingsKey = settingsKey; + this.defaultRect = defaultRect; + this.webEventReceived = new CallableEvent(); + this.fromQml = new CallableEvent(); + if (createOnStartup) { + this.createWindow(); + } + } + + CreateWindow.prototype = { + window: null, + createWindow: function() { + var defaultRect = this.defaultRect; + if (typeof this.defaultRect === "function") { + defaultRect = this.defaultRect(); + } + + var windowRect = getWindowRect(this.settingsKey, defaultRect); + this.window = Desktop.createWindow(this.qmlPath, { + title: this.title, + flags: Desktop.ALWAYS_ON_TOP | Desktop.CLOSE_BUTTON_HIDES, + presentationMode: Desktop.PresentationMode.NATIVE, + size: windowRect.size, + visible: true, + position: windowRect.position + }); + + var windowRectChanged = function () { + if (this.window.visible) { + setWindowRect(this.settingsKey, this.window.position, this.window.size); + } + }; + + this.window.sizeChanged.connect(this, windowRectChanged); + this.window.positionChanged.connect(this, windowRectChanged); + + this.window.webEventReceived.connect(this, function (data) { + this.webEventReceived.call(data); + }); + + this.window.fromQml.connect(this, function (data) { + this.fromQml.call(data); + }); + + Script.scriptEnding.connect(this, function() { + this.window.close(); + }); + }, + setVisible: function(visible) { + if (visible && !this.window) { + this.createWindow(); + } + + if (this.window) { + if (visible) { + this.window.show(); + } else { + this.window.visible = false; + } + } + }, + emitScriptEvent: function(data) { + if (this.window) { + this.window.emitScriptEvent(data); + } + }, + sendToQml: function(data) { + if (this.window) { + this.window.sendToQml(data); + } + }, + webEventReceived: null, + fromQml: null + }; + + return CreateWindow; +})(); diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 7175685b4f..ebb45130e5 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -12,14 +12,15 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -(function() { // BEGIN LOCAL_SCOPE +(function () { // BEGIN LOCAL_SCOPE - var request = Script.require('request').request; +var request = Script.require('request').request; +var AppUi = Script.require('appUi'); var populateNearbyUserList, color, textures, removeOverlays, - controllerComputePickRay, onTabletButtonClicked, onTabletScreenChanged, + controllerComputePickRay, off, receiveMessage, avatarDisconnected, clearLocalQMLDataAndClosePAL, - createAudioInterval, tablet, CHANNEL, getConnectionData, findableByChanged, + CHANNEL, getConnectionData, findableByChanged, avatarAdded, avatarRemoved, avatarSessionChanged; // forward references; // hardcoding these as it appears we cannot traverse the originalTextures in overlays??? Maybe I've missed @@ -40,6 +41,7 @@ var HOVER_TEXTURES = { var UNSELECTED_COLOR = { red: 0x1F, green: 0xC6, blue: 0xA6}; var SELECTED_COLOR = {red: 0xF3, green: 0x91, blue: 0x29}; var HOVER_COLOR = {red: 0xD0, green: 0xD0, blue: 0xD0}; // almost white for now +var METAVERSE_BASE = Account.metaverseServerURL; Script.include("/~/system/libraries/controllers.js"); @@ -221,7 +223,7 @@ function convertDbToLinear(decibels) { return Math.pow(2, decibels / 10.0); } function fromQml(message) { // messages are {method, params}, like json-rpc. See also sendToQml. - var data; + var data, connectionUserName, friendUserName; switch (message.method) { case 'selected': selectedIds = message.params; @@ -266,7 +268,6 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See break; case 'refreshConnections': print('Refreshing Connections...'); - getConnectionData(false); UserActivityLogger.palAction("refresh_connections", ""); break; case 'removeConnection': @@ -279,9 +280,9 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See print("Error: unable to remove connection", connectionUserName, error || response.status); return; } - getConnectionData(false); + sendToQml({ method: 'refreshConnections' }); }); - break + break; case 'removeFriend': friendUserName = message.params; @@ -296,7 +297,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See } getConnectionData(friendUserName); }); - break + break; case 'addFriend': friendUserName = message.params; print("Adding " + friendUserName + " to friends."); @@ -307,24 +308,23 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See body: { username: friendUserName, } - }, function (error, response) { - if (error || (response.status !== 'success')) { - print("Error: unable to friend " + friendUserName, error || response.status); - return; - } - getConnectionData(friendUserName); + }, function (error, response) { + if (error || (response.status !== 'success')) { + print("Error: unable to friend " + friendUserName, error || response.status); + return; } - ); + getConnectionData(friendUserName); + }); break; case 'http.request': - break; // Handled by request-service. + break; // Handled by request-service. default: print('Unrecognized message from Pal.qml:', JSON.stringify(message)); } } function sendToQml(message) { - tablet.sendToQml(message); + ui.sendMessage(message); } function updateUser(data) { print('PAL update:', JSON.stringify(data)); @@ -334,7 +334,6 @@ function updateUser(data) { // User management services // // These are prototype versions that will be changed when the back end changes. -var METAVERSE_BASE = Account.metaverseServerURL; function requestJSON(url, callback) { // callback(data) if successfull. Logs otherwise. request({ @@ -361,8 +360,9 @@ function getProfilePicture(username, callback) { // callback(url) if successfull callback(matched[1]); }); } +var SAFETY_LIMIT = 400; function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successfull. (Logs otherwise) - url = METAVERSE_BASE + '/api/v1/users?per_page=400&' + var url = METAVERSE_BASE + '/api/v1/users?per_page=' + SAFETY_LIMIT + '&'; if (domain) { url += 'status=' + domain.slice(1, -1); // without curly braces } else { @@ -373,8 +373,10 @@ function getAvailableConnections(domain, callback) { // callback([{usename, loca }); } function getInfoAboutUser(specificUsername, callback) { - url = METAVERSE_BASE + '/api/v1/users?filter=connections' + var url = METAVERSE_BASE + '/api/v1/users?filter=connections&per_page=' + SAFETY_LIMIT + '&search=' + encodeURIComponent(specificUsername); requestJSON(url, function (connectionsData) { + // You could have (up to SAFETY_LIMIT connections whose usernames contain the specificUsername. + // Search returns all such matches. for (user in connectionsData.users) { if (connectionsData.users[user].username === specificUsername) { callback(connectionsData.users[user]); @@ -406,16 +408,14 @@ function getConnectionData(specificUsername, domain) { // Update all the usernam print('Error: Unable to find information about ' + specificUsername + ' in connectionsData!'); } }); - } else { + } else if (domain) { getAvailableConnections(domain, function (users) { - if (domain) { - users.forEach(function (user) { - updateUser(frob(user)); - }); - } else { - sendToQml({ method: 'connections', params: users.map(frob) }); - } + users.forEach(function (user) { + updateUser(frob(user)); + }); }); + } else { + print("Error: unrecognized getConnectionData()"); } } @@ -447,21 +447,24 @@ function populateNearbyUserList(selectData, oldAudioData) { verticalAngleNormal = filter && Quat.getRight(orientation), horizontalAngleNormal = filter && Quat.getUp(orientation); avatarsOfInterest = {}; - avatars.forEach(function (id) { - var avatar = AvatarList.getAvatar(id); - var name = avatar.sessionDisplayName; + + var avatarData = AvatarList.getPalData().data; + + avatarData.forEach(function (currentAvatarData) { + var id = currentAvatarData.sessionUUID; + var name = currentAvatarData.sessionDisplayName; if (!name) { // Either we got a data packet but no identity yet, or something is really messed up. In any case, // we won't be able to do anything with this user, so don't include them. // In normal circumstances, a refresh will bring in the new user, but if we're very heavily loaded, // we could be losing and gaining people randomly. - print('No avatar identity data for', id); + print('No avatar identity data for', currentAvatarData.sessionUUID); return; } - if (id && myPosition && (Vec3.distance(avatar.position, myPosition) > filter.distance)) { + if (id && myPosition && (Vec3.distance(currentAvatarData.position, myPosition) > filter.distance)) { return; } - var normal = id && filter && Vec3.normalize(Vec3.subtract(avatar.position, myPosition)); + var normal = id && filter && Vec3.normalize(Vec3.subtract(currentAvatarData.position, myPosition)); var horizontal = normal && angleBetweenVectorsInPlane(normal, forward, horizontalAngleNormal); var vertical = normal && angleBetweenVectorsInPlane(normal, forward, verticalAngleNormal); if (id && filter && ((Math.abs(horizontal) > horizontalHalfAngle) || (Math.abs(vertical) > verticalHalfAngle))) { @@ -480,11 +483,11 @@ function populateNearbyUserList(selectData, oldAudioData) { personalMute: !!id && Users.getPersonalMuteStatus(id), // expects proper boolean, not null ignore: !!id && Users.getIgnoreStatus(id), // ditto isPresent: true, - isReplicated: avatar.isReplicated + isReplicated: currentAvatarData.isReplicated }; // Everyone needs to see admin status. Username and fingerprint returns default constructor output if the requesting user isn't an admin. Users.requestUsernameFromID(id); - if (id) { + if (id !== "") { addAvatarNode(id); // No overlay for ourselves avatarsOfInterest[id] = true; } else { @@ -515,30 +518,63 @@ function usernameFromIDReply(id, username, machineFingerprint, isAdmin) { updateUser(data); } +function updateAudioLevel(avatarData) { + // the VU meter should work similarly to the one in AvatarInputs: log scale, exponentially averaged + // But of course it gets the data at a different rate, so we tweak the averaging ratio and frequency + // of updating (the latter for efficiency too). + var audioLevel = 0.0; + var avgAudioLevel = 0.0; + + var data = avatarData.sessionUUID === "" ? myData : ExtendedOverlay.get(avatarData.sessionUUID); + + if (data) { + // we will do exponential moving average by taking some the last loudness and averaging + data.accumulatedLevel = AVERAGING_RATIO * (data.accumulatedLevel || 0) + (1 - AVERAGING_RATIO) * (avatarData.audioLoudness); + + // add 1 to insure we don't go log() and hit -infinity. Math.log is + // natural log, so to get log base 2, just divide by ln(2). + audioLevel = scaleAudio(Math.log(data.accumulatedLevel + 1) / LOG2); + + // decay avgAudioLevel + avgAudioLevel = Math.max((1 - AUDIO_PEAK_DECAY) * (data.avgAudioLevel || 0), audioLevel); + + data.avgAudioLevel = avgAudioLevel; + data.audioLevel = audioLevel; + + // now scale for the gain. Also, asked to boost the low end, so one simple way is + // to take sqrt of the value. Lets try that, see how it feels. + avgAudioLevel = Math.min(1.0, Math.sqrt(avgAudioLevel * (sessionGains[avatarData.sessionUUID] || 0.75))); + } + + var param = {}; + var level = [audioLevel, avgAudioLevel]; + var userId = avatarData.sessionUUID; + param[userId] = level; + sendToQml({ method: 'updateAudioLevel', params: param }); +} + var pingPong = true; function updateOverlays() { var eye = Camera.position; - AvatarList.getAvatarIdentifiers().forEach(function (id) { - if (!id || !avatarsOfInterest[id]) { + + var avatarData = AvatarList.getPalData().data; + + avatarData.forEach(function (currentAvatarData) { + + if (currentAvatarData.sessionUUID === "" || !avatarsOfInterest[currentAvatarData.sessionUUID]) { return; // don't update ourself, or avatars we're not interested in } - var avatar = AvatarList.getAvatar(id); - if (!avatar) { - return; // will be deleted below if there had been an overlay. - } - var overlay = ExtendedOverlay.get(id); + updateAudioLevel(currentAvatarData); + var overlay = ExtendedOverlay.get(currentAvatarData.sessionUUID); if (!overlay) { // For now, we're treating this as a temporary loss, as from the personal space bubble. Add it back. - print('Adding non-PAL avatar node', id); - overlay = addAvatarNode(id); + print('Adding non-PAL avatar node', currentAvatarData.sessionUUID); + overlay = addAvatarNode(currentAvatarData.sessionUUID); } - var target = avatar.position; + + var target = currentAvatarData.position; var distance = Vec3.distance(target, eye); - var offset = 0.2; + var offset = currentAvatarData.palOrbOffset; var diff = Vec3.subtract(target, eye); // get diff between target and eye (a vector pointing to the eye from avatar position) - var headIndex = avatar.getJointIndex("Head"); // base offset on 1/2 distance from hips to head if we can - if (headIndex > 0) { - offset = avatar.getAbsoluteJointTranslationInObjectFrame(headIndex).y / 2; - } // move a bit in front, towards the camera target = Vec3.subtract(target, Vec3.multiply(Vec3.normalize(diff), offset)); @@ -548,7 +584,7 @@ function updateOverlays() { overlay.ping = pingPong; overlay.editOverlay({ - color: color(ExtendedOverlay.isSelected(id), overlay.hovering, overlay.audioLevel), + color: color(ExtendedOverlay.isSelected(currentAvatarData.sessionUUID), overlay.hovering, overlay.audioLevel), position: target, dimensions: 0.032 * distance }); @@ -669,68 +705,41 @@ triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Cont triggerPressMapping.from(Controller.Standard.RT).peek().to(makePressHandler(Controller.Standard.RightHand)); triggerPressMapping.from(Controller.Standard.LT).peek().to(makePressHandler(Controller.Standard.LeftHand)); +var ui; +// Most apps can have people toggle the tablet closed and open again, and the app should remain "open" even while +// the tablet is not shown. However, for the pal, we explicitly close the app and return the tablet to it's +// home screen (so that the avatar highlighting goes away). function tabletVisibilityChanged() { - if (!tablet.tabletShown) { - ContextOverlay.enabled = true; - tablet.gotoHomeScreen(); + if (!ui.tablet.tabletShown && ui.isOpen) { + ui.close(); } + } + +var UPDATE_INTERVAL_MS = 100; +var updateInterval; +function createUpdateInterval() { + return Script.setInterval(function () { + updateOverlays(); + }, UPDATE_INTERVAL_MS); } -var onPalScreen = false; -var PAL_QML_SOURCE = "hifi/Pal.qml"; -function onTabletButtonClicked() { - if (!tablet) { - print("Warning in onTabletButtonClicked(): 'tablet' undefined!"); - return; - } - if (onPalScreen) { - // In Toolbar Mode, `gotoHomeScreen` will close the app window. - tablet.gotoHomeScreen(); - } else { - tablet.loadQMLSource(PAL_QML_SOURCE); - } -} -var hasEventBridge = false; -function wireEventBridge(on) { - if (on) { - if (!hasEventBridge) { - tablet.fromQml.connect(fromQml); - hasEventBridge = true; - } - } else { - if (hasEventBridge) { - tablet.fromQml.disconnect(fromQml); - hasEventBridge = false; - } - } -} +var previousContextOverlay = ContextOverlay.enabled; +var previousRequestsDomainListData = Users.requestsDomainListData; +function on() { -function onTabletScreenChanged(type, url) { - onPalScreen = (type === "QML" && url === PAL_QML_SOURCE); - wireEventBridge(onPalScreen); - // for toolbar mode: change button to active when window is first openend, false otherwise. - button.editProperties({isActive: onPalScreen}); + previousContextOverlay = ContextOverlay.enabled; + previousRequestsDomainListData = Users.requestsDomainListData + ContextOverlay.enabled = false; + Users.requestsDomainListData = true; - if (onPalScreen) { - isWired = true; - - ContextOverlay.enabled = false; - Users.requestsDomainListData = true; - - audioTimer = createAudioInterval(AUDIO_LEVEL_UPDATE_INTERVAL_MS); - - tablet.tabletShownChanged.connect(tabletVisibilityChanged); - Script.update.connect(updateOverlays); - Controller.mousePressEvent.connect(handleMouseEvent); - Controller.mouseMoveEvent.connect(handleMouseMoveEvent); - Users.usernameFromIDReply.connect(usernameFromIDReply); - triggerMapping.enable(); - triggerPressMapping.enable(); - populateNearbyUserList(); - } else { - off(); - ContextOverlay.enabled = true; - } + ui.tablet.tabletShownChanged.connect(tabletVisibilityChanged); + updateInterval = createUpdateInterval(); + Controller.mousePressEvent.connect(handleMouseEvent); + Controller.mouseMoveEvent.connect(handleMouseMoveEvent); + Users.usernameFromIDReply.connect(usernameFromIDReply); + triggerMapping.enable(); + triggerPressMapping.enable(); + populateNearbyUserList(); } // @@ -744,8 +753,8 @@ function receiveMessage(channel, messageString, senderID) { var message = JSON.parse(messageString); switch (message.method) { case 'select': - if (!onPalScreen) { - tablet.loadQMLSource(PAL_QML_SOURCE); + if (!ui.isOpen) { + ui.open(); Script.setTimeout(function () { sendToQml(message); }, 1000); } else { sendToQml(message); // Accepts objects, not just strings. @@ -774,50 +783,6 @@ function scaleAudio(val) { return audioLevel; } -function getAudioLevel(id) { - // the VU meter should work similarly to the one in AvatarInputs: log scale, exponentially averaged - // But of course it gets the data at a different rate, so we tweak the averaging ratio and frequency - // of updating (the latter for efficiency too). - var avatar = AvatarList.getAvatar(id); - var audioLevel = 0.0; - var avgAudioLevel = 0.0; - var data = id ? ExtendedOverlay.get(id) : myData; - if (data) { - - // we will do exponential moving average by taking some the last loudness and averaging - data.accumulatedLevel = AVERAGING_RATIO * (data.accumulatedLevel || 0) + (1 - AVERAGING_RATIO) * (avatar.audioLoudness); - - // add 1 to insure we don't go log() and hit -infinity. Math.log is - // natural log, so to get log base 2, just divide by ln(2). - audioLevel = scaleAudio(Math.log(data.accumulatedLevel + 1) / LOG2); - - // decay avgAudioLevel - avgAudioLevel = Math.max((1 - AUDIO_PEAK_DECAY) * (data.avgAudioLevel || 0), audioLevel); - - data.avgAudioLevel = avgAudioLevel; - data.audioLevel = audioLevel; - - // now scale for the gain. Also, asked to boost the low end, so one simple way is - // to take sqrt of the value. Lets try that, see how it feels. - avgAudioLevel = Math.min(1.0, Math.sqrt(avgAudioLevel * (sessionGains[id] || 0.75))); - } - return [audioLevel, avgAudioLevel]; -} - -function createAudioInterval(interval) { - // we will update the audioLevels periodically - // TODO: tune for efficiency - expecially with large numbers of avatars - return Script.setInterval(function () { - var param = {}; - AvatarList.getAvatarIdentifiers().forEach(function (id) { - var level = getAudioLevel(id), - userId = id || 0; // qml didn't like an object with null/empty string for a key, so... - param[userId] = level; - }); - sendToQml({method: 'updateAudioLevel', params: param}); - }, interval); -} - function avatarDisconnected(nodeID) { // remove from the pal list sendToQml({method: 'avatarDisconnected', params: [nodeID]}); @@ -825,9 +790,8 @@ function avatarDisconnected(nodeID) { function clearLocalQMLDataAndClosePAL() { sendToQml({ method: 'clearLocalQMLData' }); - if (onPalScreen) { - ContextOverlay.enabled = true; - tablet.gotoHomeScreen(); + if (ui.isOpen) { + ui.close(); } } @@ -843,20 +807,15 @@ function avatarSessionChanged(avatarID) { sendToQml({ method: 'palIsStale', params: [avatarID, 'avatarSessionChanged'] }); } - -var button; -var buttonName = "PEOPLE"; -var tablet = null; function startup() { - tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - button = tablet.addButton({ - text: buttonName, - icon: "icons/tablet-icons/people-i.svg", - activeIcon: "icons/tablet-icons/people-a.svg", - sortOrder: 7 + ui = new AppUi({ + buttonName: "PEOPLE", + sortOrder: 7, + home: "hifi/Pal.qml", + onOpened: on, + onClosed: off, + onMessage: fromQml }); - button.clicked.connect(onTabletButtonClicked); - tablet.screenChanged.connect(onTabletScreenChanged); Window.domainChanged.connect(clearLocalQMLDataAndClosePAL); Window.domainConnectionRefused.connect(clearLocalQMLDataAndClosePAL); Messages.subscribe(CHANNEL); @@ -868,39 +827,25 @@ function startup() { } startup(); - -var isWired = false; -var audioTimer; -var AUDIO_LEVEL_UPDATE_INTERVAL_MS = 100; // 10hz for now (change this and change the AVERAGING_RATIO too) function off() { - if (isWired) { - Script.update.disconnect(updateOverlays); + if (ui.isOpen) { // i.e., only when connected + if (updateInterval) { + Script.clearInterval(updateInterval); + } Controller.mousePressEvent.disconnect(handleMouseEvent); Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent); - tablet.tabletShownChanged.disconnect(tabletVisibilityChanged); + ui.tablet.tabletShownChanged.disconnect(tabletVisibilityChanged); Users.usernameFromIDReply.disconnect(usernameFromIDReply); - ContextOverlay.enabled = true triggerMapping.disable(); triggerPressMapping.disable(); - Users.requestsDomainListData = false; - - isWired = false; - - if (audioTimer) { - Script.clearInterval(audioTimer); - } } removeOverlays(); + ContextOverlay.enabled = previousContextOverlay; + Users.requestsDomainListData = previousRequestsDomainListData; } function shutdown() { - if (onPalScreen) { - tablet.gotoHomeScreen(); - } - button.clicked.disconnect(onTabletButtonClicked); - tablet.removeButton(button); - tablet.screenChanged.disconnect(onTabletScreenChanged); Window.domainChanged.disconnect(clearLocalQMLDataAndClosePAL); Window.domainConnectionRefused.disconnect(clearLocalQMLDataAndClosePAL); Messages.subscribe(CHANNEL); diff --git a/scripts/system/particle_explorer/hifi-entity-ui.js b/scripts/system/particle_explorer/hifi-entity-ui.js index 05b6ba6f75..62a0aadc86 100644 --- a/scripts/system/particle_explorer/hifi-entity-ui.js +++ b/scripts/system/particle_explorer/hifi-entity-ui.js @@ -44,6 +44,7 @@ and If there is any changes to either the Entities or properties of **/ var RADIANS_PER_DEGREE = Math.PI / 180; +var DEBOUNCE_TIMEOUT = 125; var roundFloat = function (input, round) { round = round ? round : 1000; @@ -60,13 +61,20 @@ function HifiEntityUI(parent) { this.parent = parent; var self = this; - this.webBridgeSync = _.debounce(function (id, val) { - if (self.EventBridge) { - var sendPackage = {}; - sendPackage[id] = val; - self.submitChanges(sendPackage); + this.sendPackage = {}; + this.settingsUpdateLock = false; + this.webBridgeSync = function(id, val) { + if (!this.settingsUpdateLock) { + this.sendPackage[id] = val; + this.webBridgeSyncDebounce(); } - }, 125); + }; + this.webBridgeSyncDebounce = _.debounce(function () { + if (self.EventBridge) { + self.submitChanges(self.sendPackage); + self.sendPackage = {}; + } + }, DEBOUNCE_TIMEOUT); } HifiEntityUI.prototype = { @@ -112,7 +120,6 @@ HifiEntityUI.prototype = { var self = this; var json = {}; var keys = Object.keys(self.builtRows); - for (var i = 0; i < keys.length; i++) { var key = keys[i]; var el = self.builtRows[key]; @@ -144,12 +151,14 @@ HifiEntityUI.prototype = { vector.z = z.value; } json[key] = vector; + } else if (el.className.indexOf("radian") !== -1) { + json[key] = document.getElementById(key).value * RADIANS_PER_DEGREE; } else if (el.className.length > 0) { - json[key] = document.getElementById(key) - .value; + json[key] = document.getElementById(key).value; } } + return json; }, fillFields: function (currentProperties) { @@ -159,8 +168,13 @@ HifiEntityUI.prototype = { if (!currentProperties.locked) { for (var i = 0; i < fields.length; i++) { fields[i].removeAttribute("disabled"); + if (fields[i].hasAttribute("data-max")) { + // Reset Max to original max + fields[i].setAttribute("max", fields[i].getAttribute("data-max")); + } } } + if (self.onSelect) { self.onSelect(); } @@ -170,7 +184,7 @@ HifiEntityUI.prototype = { for (var e in keys) { if (keys.hasOwnProperty(e)) { var value = keys[e]; - + var property = currentProperties[value]; var field = self.builtRows[value]; if (field) { @@ -240,9 +254,9 @@ HifiEntityUI.prototype = { data = JSON.parse(data); if (data.messageType === 'particle_settings') { - // Update settings - var currentProperties = data.currentProperties; - self.fillFields(currentProperties); + self.settingsUpdateLock = true; + self.fillFields(data.currentProperties); + self.settingsUpdateLock = false; // Do expected property match with structure; } else if (data.messageType === 'particle_close') { self.disableFields(); @@ -272,7 +286,7 @@ HifiEntityUI.prototype = { title.innerHTML = section; title.appendChild(dropDown); sectionDivHeader.appendChild(title); - + var collapsed = index !== 0; dropDown.innerHTML = collapsed ? "L" : "M"; @@ -505,13 +519,13 @@ HifiEntityUI.prototype = { textureImage.classList.remove("no-preview"); textureImage.classList.add("no-texture"); } - self.webBridgeSync(group.id, url); - }, 250); + }, DEBOUNCE_TIMEOUT * 2); textureUrl.oninput = function (event) { // Add throttle var url = event.target.value; imageLoad(url); + self.webBridgeSync(group.id, url); }; textureUrl.onchange = textureUrl.oninput; textureImage.appendChild(image); @@ -540,21 +554,21 @@ HifiEntityUI.prototype = { slider.setAttribute("min", group.min !== undefined ? group.min : 0); slider.setAttribute("max", group.max !== undefined ? group.max : 10000); + slider.setAttribute("data-max", group.max !== undefined ? group.max : 10000); slider.setAttribute("step", 1); inputField.oninput = function (event) { - + // TODO: Remove this functionality? Alan finds it confusing if (parseInt(event.target.value) > parseInt(slider.getAttribute("max")) && group.max !== 1) { slider.setAttribute("max", event.target.value); } slider.value = event.target.value; - self.webBridgeSync(group.id, slider.value); }; inputField.onchange = inputField.oninput; slider.oninput = function (event) { inputField.value = event.target.value; - self.webBridgeSync(group.id, slider.value); + self.webBridgeSync(group.id, inputField.value); }; inputField.id = group.id; @@ -579,7 +593,7 @@ HifiEntityUI.prototype = { } else { inputField.value = Math.ceil(event.target.value); } - self.webBridgeSync(group.id, slider.value * RADIANS_PER_DEGREE); + self.webBridgeSync(group.id, inputField.value * RADIANS_PER_DEGREE); }; var degrees = document.createElement("label"); degrees.innerHTML = "°"; @@ -596,9 +610,11 @@ HifiEntityUI.prototype = { slider.setAttribute("min", group.min !== undefined ? group.min : 0); slider.setAttribute("max", group.max !== undefined ? group.max : 1); + slider.setAttribute("data-max", group.max !== undefined ? group.max : 1); slider.setAttribute("step", 0.01); inputField.oninput = function (event) { + // TODO: Remove this functionality? Alan finds it confusing if (parseFloat(event.target.value) > parseFloat(slider.getAttribute("max")) && group.max !== 1) { slider.setAttribute("max", event.target.value); } @@ -690,4 +706,4 @@ HifiEntityUI.prototype = { } return row; } -}; +}; \ No newline at end of file diff --git a/scripts/system/particle_explorer/particleExplorer.js b/scripts/system/particle_explorer/particleExplorer.js index 099c4be9e3..f1b7c8600f 100644 --- a/scripts/system/particle_explorer/particleExplorer.js +++ b/scripts/system/particle_explorer/particleExplorer.js @@ -152,6 +152,12 @@ type: "SliderFloat", max: 5 }, + { + id: "speedSpread", + name: "Speed Spread", + type: "SliderFloat", + max: 5 + }, { type: "Row" }, @@ -355,6 +361,55 @@ type: "Row" } ], + Spin: [ + { + id: "particleSpin", + name: "Particle Spin", + type: "SliderRadian", + min: -360.0, + max: 360.0 + }, + { + type: "Row" + }, + { + id: "spinSpread", + name: "Spin Spread", + type: "SliderRadian", + max: 360.0 + }, + { + type: "Row" + }, + { + id: "spinStart", + name: "Spin Start", + type: "SliderRadian", + min: -360.0, + max: 360.0 + }, + { + type: "Row" + }, + { + id: "spinFinish", + name: "Spin Finish", + type: "SliderRadian", + min: -360.0, + max: 360.0 + }, + { + type: "Row" + }, + { + id: "rotateWithEntity", + name: "Rotate with Entity", + type: "Boolean" + }, + { + type: "Row" + } + ], Polar: [ { id: "polarStart", diff --git a/scripts/system/particle_explorer/particleExplorerTool.js b/scripts/system/particle_explorer/particleExplorerTool.js index de2cb0bd8b..a3be004329 100644 --- a/scripts/system/particle_explorer/particleExplorerTool.js +++ b/scripts/system/particle_explorer/particleExplorerTool.js @@ -9,56 +9,127 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global window, alert, ParticleExplorerTool, EventBridge, dat, listenForSettingsUpdates,createVec3Folder,createQuatFolder,writeVec3ToInterface,writeDataToInterface*/ +/* global ParticleExplorerTool */ var PARTICLE_EXPLORER_HTML_URL = Script.resolvePath('particleExplorer.html'); -ParticleExplorerTool = function() { +ParticleExplorerTool = function(createToolsWindow) { var that = {}; that.activeParticleEntity = 0; - that.activeParticleProperties = {}; + that.updatedActiveParticleProperties = {}; that.createWebView = function() { that.webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); that.webView.setVisible = function(value) {}; that.webView.webEventReceived.connect(that.webEventReceived); + createToolsWindow.webEventReceived.addListener(this, that.webEventReceived); }; + function emitScriptEvent(data) { + var messageData = JSON.stringify(data); + that.webView.emitScriptEvent(messageData); + createToolsWindow.emitScriptEvent(messageData); + } + that.destroyWebView = function() { if (!that.webView) { return; } that.activeParticleEntity = 0; - that.activeParticleProperties = {}; + that.updatedActiveParticleProperties = {}; - var messageData = { + emitScriptEvent({ messageType: "particle_close" - }; - that.webView.emitScriptEvent(JSON.stringify(messageData)); + }); }; - function sendActiveParticleProperties() { - that.webView.emitScriptEvent(JSON.stringify({ + function sendParticleProperties(properties) { + emitScriptEvent({ messageType: "particle_settings", - currentProperties: that.activeParticleProperties - })); + currentProperties: properties + }); + } + + function sendActiveParticleProperties() { + var properties = Entities.getEntityProperties(that.activeParticleEntity); + if (properties.emitOrientation) { + properties.emitOrientation = Quat.safeEulerAngles(properties.emitOrientation); + } + // Update uninitialized variables + if (isNaN(properties.alphaStart)) { + properties.alphaStart = properties.alpha; + } + if (isNaN(properties.alphaFinish)) { + properties.alphaFinish = properties.alpha; + } + if (isNaN(properties.radiusStart)) { + properties.radiusStart = properties.particleRadius; + } + if (isNaN(properties.radiusFinish)) { + properties.radiusFinish = properties.particleRadius; + } + if (isNaN(properties.colorStart.red)) { + properties.colorStart = properties.color; + } + if (isNaN(properties.colorFinish.red)) { + properties.colorFinish = properties.color; + } + if (isNaN(properties.spinStart)) { + properties.spinStart = properties.particleSpin; + } + if (isNaN(properties.spinFinish)) { + properties.spinFinish = properties.particleSpin; + } + sendParticleProperties(properties); + } + + function sendUpdatedActiveParticleProperties() { + sendParticleProperties(that.updatedActiveParticleProperties); + that.updatedActiveParticleProperties = {}; } that.webEventReceived = function(message) { var data = JSON.parse(message); if (data.messageType === "settings_update") { - if (data.updatedSettings.emitOrientation) { - data.updatedSettings.emitOrientation = Quat.fromVec3Degrees(data.updatedSettings.emitOrientation); - } - Entities.editEntity(that.activeParticleEntity, data.updatedSettings); + var updatedSettings = data.updatedSettings; - for (var key in data.updatedSettings) { - if (that.activeParticleProperties.hasOwnProperty(key)) { - that.activeParticleProperties[key] = data.updatedSettings[key]; + var optionalProps = ["alphaStart", "alphaFinish", "radiusStart", "radiusFinish", "colorStart", "colorFinish", "spinStart", "spinFinish"]; + var fallbackProps = ["alpha", "particleRadius", "color", "particleSpin"]; + for (var i = 0; i < optionalProps.length; i++) { + var fallbackProp = fallbackProps[Math.floor(i / 2)]; + var optionalValue = updatedSettings[optionalProps[i]]; + var fallbackValue = updatedSettings[fallbackProp]; + if (optionalValue && fallbackValue) { + delete updatedSettings[optionalProps[i]]; + } + } + + if (updatedSettings.emitOrientation) { + updatedSettings.emitOrientation = Quat.fromVec3Degrees(updatedSettings.emitOrientation); + } + + Entities.editEntity(that.activeParticleEntity, updatedSettings); + + var entityProps = Entities.getEntityProperties(that.activeParticleEntity, optionalProps); + + var needsUpdate = false; + for (var i = 0; i < optionalProps.length; i++) { + var fallbackProp = fallbackProps[Math.floor(i / 2)]; + var fallbackValue = updatedSettings[fallbackProp]; + if (fallbackValue) { + var optionalProp = optionalProps[i]; + if ((fallbackProp !== "color" && isNaN(entityProps[optionalProp])) || (fallbackProp === "color" && isNaN(entityProps[optionalProp].red))) { + that.updatedActiveParticleProperties[optionalProp] = fallbackValue; + needsUpdate = true; + } } } + if (needsUpdate) { + sendUpdatedActiveParticleProperties(); + } + } else if (data.messageType === "page_loaded") { sendActiveParticleProperties(); } @@ -66,12 +137,8 @@ ParticleExplorerTool = function() { that.setActiveParticleEntity = function(id) { that.activeParticleEntity = id; - }; - - that.setActiveParticleProperties = function(properties) { - that.activeParticleProperties = properties; sendActiveParticleProperties(); }; - + return that; }; diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index f7e590c6f9..3e58bc61ad 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -17,7 +17,6 @@ var SNAPSHOT_DELAY = 500; // 500ms var FINISH_SOUND_DELAY = 350; var resetOverlays; var reticleVisible; -var clearOverlayWhenMoving; var buttonName = "SNAP"; var buttonConnected = false; @@ -37,8 +36,8 @@ var shareAfterLogin = false; var snapshotToShareAfterLogin = []; var METAVERSE_BASE = Account.metaverseServerURL; var isLoggedIn; -var numGifSnapshotUploadsPending = 0; -var numStillSnapshotUploadsPending = 0; +var mostRecentGifSnapshotFilename = ""; +var mostRecentStillSnapshotFilename = ""; // It's totally unnecessary to return to C++ to perform many of these requests, such as DELETEing an old story, // POSTING a new one, PUTTING a new audience, or GETTING story data. It's far more efficient to do all of that within JS @@ -64,6 +63,10 @@ function fileExtensionMatches(filePath, extension) { return filePath.split('.').pop().toLowerCase() === extension; } +function getFilenameFromPath(str) { + return str.split('\\').pop().split('/').pop(); +} + function onMessage(message) { // Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following: // 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.) @@ -147,9 +150,9 @@ function onMessage(message) { print('Sharing snapshot with audience "for_url":', message.data); Window.shareSnapshot(message.data, Settings.getValue("previousSnapshotHref")); if (isGif) { - numGifSnapshotUploadsPending++; + mostRecentGifSnapshotFilename = getFilenameFromPath(message.data); } else { - numStillSnapshotUploadsPending++; + mostRecentStillSnapshotFilename = getFilenameFromPath(message.data); } } else { shareAfterLogin = true; @@ -276,12 +279,25 @@ function onMessage(message) { } var POLAROID_PRINT_SOUND = SoundCache.getSound(Script.resourcesPath() + "sounds/snapshot/sound-print-photo.wav"); -var POLAROID_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/alan/dev/Test/snapshot.fbx'; +var POLAROID_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/alan/dev/Test/snapshot.fbx'; +var POLAROID_RATE_LIMIT_MS = 1000; +var polaroidPrintingIsRateLimited = false; function printToPolaroid(image_url) { + + // Rate-limit printing + if (polaroidPrintingIsRateLimited) { + return; + } + polaroidPrintingIsRateLimited = true; + Script.setTimeout(function () { + polaroidPrintingIsRateLimited = false; + }, POLAROID_RATE_LIMIT_MS); + var polaroid_url = image_url; var model_pos = Vec3.sum(MyAvatar.position, Vec3.multiply(1.25, Quat.getForward(MyAvatar.orientation))); + model_pos.y += 0.39; // Print a bit closer to the head var model_q1 = MyAvatar.orientation; var model_q2 = Quat.angleAxis(90, Quat.getRight(model_q1)); @@ -291,11 +307,11 @@ function printToPolaroid(image_url) { "type": 'Model', "shapeType": 'box', - "name": "New Snapshot", - "description": "Printed from Snaps", + "name": "Snapshot by " + MyAvatar.sessionDisplayName, + "description": "Printed from SNAP app", "modelURL": POLAROID_MODEL_URL, - "dimensions": { "x": 0.5667, "y": 0.0212, "z": 0.4176 }, + "dimensions": { "x": 0.5667, "y": 0.042, "z": 0.4176 }, "position": model_pos, "rotation": model_rot, @@ -303,10 +319,8 @@ function printToPolaroid(image_url) { "density": 200, "restitution": 0.15, - "gravity": { "x": 0, "y": -4.5, "z": 0 }, - - "velocity": { "x": 0, "y": 3.5, "z": 0 }, - "angularVelocity": { "x": -1.0, "y": 0, "z": -1.3 }, + "gravity": { "x": 0, "y": -2.0, "z": 0 }, + "damping": 0.45, "dynamic": true, "collisionsWillMove": true, @@ -385,13 +399,11 @@ function snapshotUploaded(isError, reply) { ignoreStillSnapshotData = false; storyIDsToMaybeDelete.push(storyID); if (isGif) { - numGifSnapshotUploadsPending--; - if (numGifSnapshotUploadsPending !== 0) { + if (mostRecentGifSnapshotFilename !== replyJson.user_story.details.original_image_file_name) { ignoreGifSnapshotData = true; } } else { - numStillSnapshotUploadsPending--; - if (numStillSnapshotUploadsPending !== 0) { + if (mostRecentStillSnapshotFilename !== replyJson.user_story.details.original_image_file_name) { ignoreStillSnapshotData = true; } } @@ -436,11 +448,6 @@ function takeSnapshot() { isUploadingPrintableStill = true; updatePrintPermissions(); - // Raising the desktop for the share dialog at end will interact badly with clearOverlayWhenMoving. - // Turn it off now, before we start futzing with things (and possibly moving). - clearOverlayWhenMoving = MyAvatar.getClearOverlayWhenMoving(); // Do not use Settings. MyAvatar keeps a separate copy. - MyAvatar.setClearOverlayWhenMoving(false); - // We will record snapshots based on the starting location. That could change, e.g., when recording a .gif. // Even the domainID could change (e.g., if the user falls into a teleporter while recording). href = location.href; @@ -542,9 +549,6 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { // last element in data array tells dialog whether we can share or not Settings.setValue("previousStillSnapPath", pathStillSnapshot); - if (clearOverlayWhenMoving) { - MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog - } HMD.openTablet(); isDomainOpen(domainID, function (canShare) { @@ -588,9 +592,6 @@ function processingGifStarted(pathStillSnapshot) { } Settings.setValue("previousStillSnapPath", pathStillSnapshot); - if (clearOverlayWhenMoving) { - MyAvatar.setClearOverlayWhenMoving(true); // not until after the share dialog - } HMD.openTablet(); isDomainOpen(domainID, function (canShare) { @@ -686,9 +687,9 @@ function onUsernameChanged() { Window.shareSnapshot(element.path, element.href); var isGif = fileExtensionMatches(element.path, "gif"); if (isGif) { - numGifSnapshotUploadsPending++; + mostRecentGifSnapshotFilename = getFilenameFromPath(element.path); } else { - numStillSnapshotUploadsPending++; + mostRecentStillSnapshotFilename = getFilenameFromPath(element.path); } }); } diff --git a/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index ee3dab7308..bd6a9c69d5 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -13,7 +13,7 @@ // /* global Script, HMD, WebTablet, UIWebTablet, UserActivityLogger, Settings, Entities, Messages, Tablet, Overlays, - MyAvatar, Menu, AvatarInputs, Vec3 */ + MyAvatar, Menu, AvatarInputs, Vec3, cleanUpOldMaterialEntities */ (function() { // BEGIN LOCAL_SCOPE var tabletRezzed = false; @@ -24,13 +24,21 @@ var validCheckTime = Date.now(); var debugTablet = false; var tabletScalePercentage = 70.0; - UIWebTablet = null; + var UIWebTablet = null; var MSECS_PER_SEC = 1000.0; var MUTE_MICROPHONE_MENU_ITEM = "Mute Microphone"; var gTablet = null; Script.include("../libraries/WebTablet.js"); + function cleanupMaterialEntities() { + if (Window.isPhysicsEnabled()) { + cleanUpOldMaterialEntities(); + return; + } + Script.setTimeout(cleanupMaterialEntities, 100); + } + function checkTablet() { if (gTablet === null) { gTablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); @@ -103,8 +111,9 @@ UIWebTablet.register(); HMD.tabletID = UIWebTablet.tabletEntityID; HMD.homeButtonID = UIWebTablet.homeButtonID; - HMD.homeButtonHighlightID = UIWebTablet.homeButtonHighlightID; HMD.tabletScreenID = UIWebTablet.webOverlayID; + HMD.homeButtonHighlightMaterialID = UIWebTablet.homeButtonHighlightMaterial; + HMD.homeButtonUnhighlightMaterialID = UIWebTablet.homeButtonUnhighlightMaterial; HMD.displayModeChanged.connect(onHmdChanged); MyAvatar.sensorToWorldScaleChanged.connect(onSensorToWorldScaleChanged); @@ -130,7 +139,6 @@ tabletProperties.visible = true; Overlays.editOverlay(HMD.tabletID, tabletProperties); Overlays.editOverlay(HMD.homeButtonID, { visible: true }); - Overlays.editOverlay(HMD.homeButtonHighlightID, { visible: true }); Overlays.editOverlay(HMD.tabletScreenID, { visible: true }); Overlays.editOverlay(HMD.tabletScreenID, { maxFPS: 90 }); updateTabletWidthFromSettings(true); @@ -151,7 +159,6 @@ Overlays.editOverlay(HMD.tabletID, { visible: false }); Overlays.editOverlay(HMD.homeButtonID, { visible: false }); - Overlays.editOverlay(HMD.homeButtonHighlightID, { visible: false }); Overlays.editOverlay(HMD.tabletScreenID, { visible: false }); Overlays.editOverlay(HMD.tabletScreenID, { maxFPS: 1 }); } @@ -172,7 +179,6 @@ UIWebTablet = null; HMD.tabletID = null; HMD.homeButtonID = null; - HMD.homeButtonHighlightID = null; HMD.tabletScreenID = null; } else if (debugTablet) { print("TABLET closeTabletUI, UIWebTablet is null"); @@ -325,7 +331,9 @@ Overlays.deleteOverlay(tabletID); HMD.tabletID = null; HMD.homeButtonID = null; - HMD.homeButtonHighlightID = null; HMD.tabletScreenID = null; + HMD.homeButtonHighlightMaterialID = null; + HMD.homeButtonUnhighlightMaterialID = null; }); + Script.setTimeout(cleanupMaterialEntities, 100); }()); // END LOCAL_SCOPE diff --git a/server-console/package.json b/server-console/package.json index 6dd39ea6f8..565658702b 100644 --- a/server-console/package.json +++ b/server-console/package.json @@ -27,7 +27,7 @@ "cheerio": "^0.19.0", "electron-log": "1.1.1", "extend": "^3.0.0", - "fs-extra": "^1.0.0", + "fs-extra": "^6.0.0", "node-notifier": "^5.2.1", "os-homedir": "^1.0.1", "request": "^2.85.0", diff --git a/server-console/src/main.js b/server-console/src/main.js index d4447c432c..92ebdbf36c 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -60,7 +60,14 @@ function getBuildInfo() { } } - const DEFAULT_BUILD_INFO = { releaseType: "", buildIdentifier: "dev" }; + const DEFAULT_BUILD_INFO = { + releaseType: "", + buildIdentifier: "dev", + buildNumber: "0", + stableBuild: "0", + organization: "High Fidelity - dev", + appUserModelId: "com.highfidelity.sandbox-dev" + }; var buildInfo = DEFAULT_BUILD_INFO; if (buildInfoPath) { @@ -108,17 +115,43 @@ const UPDATER_LOCK_FULL_PATH = getRootHifiDataDirectory() + "/" + UPDATER_LOCK_F // Configure log global.log = require('electron-log'); -const logFile = getApplicationDataDirectory(true) + '/log.txt'; +const oldLogFile = path.join(getApplicationDataDirectory(), '/log.txt'); +const logFile = path.join(getApplicationDataDirectory(true), '/log.txt'); +if (oldLogFile != logFile && fs.existsSync(oldLogFile)) { + if (!fs.existsSync(oldLogFile)) { + fs.moveSync(oldLogFile, logFile); + } else { + fs.remove(oldLogFile); + } +} fs.ensureFileSync(logFile); // Ensure file exists log.transports.file.maxSize = 5 * 1024 * 1024; log.transports.file.file = logFile; log.debug("build info", buildInfo); log.debug("Root hifi directory is: ", getRootHifiDataDirectory()); +log.debug("App Data directory:", getApplicationDataDirectory()); +fs.ensureDirSync(getApplicationDataDirectory()); + +var oldLogPath = path.join(getApplicationDataDirectory(), '/logs'); +var logPath = path.join(getApplicationDataDirectory(true), '/logs'); +if (oldLogPath != logPath && fs.existsSync(oldLogPath)) { + if (!fs.existsSync(oldLogPath)) { + fs.moveSync(oldLogPath, logPath); + } else { + fs.remove(oldLogPath); + } +} +fs.ensureDirSync(logPath); +log.debug("Log directory:", logPath); + +const configPath = path.join(getApplicationDataDirectory(), 'config.json'); +var userConfig = new Config(); +userConfig.load(configPath); + const ipcMain = electron.ipcMain; - var isShuttingDown = false; function shutdown() { log.debug("Normal shutdown (isShuttingDown: " + isShuttingDown + ")"); @@ -225,26 +258,7 @@ function deleteOldFiles(directoryPath, maxAgeInSeconds, filenameRegex) { } } -var oldLogPath = path.join(getApplicationDataDirectory(), '/logs'); -var logPath = path.join(getApplicationDataDirectory(true), '/logs'); - -if (oldLogPath != logPath) { - console.log("Migrating old logs from " + oldLogPath + " to " + logPath); - fs.copy(oldLogPath, logPath, err => { - if (err) { - console.error(err); - } else { - console.log('success!'); - } - }) -} - -log.debug("Log directory:", logPath); -log.debug("Data directory:", getRootHifiDataDirectory()); - -const configPath = path.join(getApplicationDataDirectory(), 'config.json'); -var userConfig = new Config(); -userConfig.load(configPath); +app.setAppUserModelId(buildInfo.appUserModelId); // print out uncaught exceptions in the console process.on('uncaughtException', function(err) { @@ -391,7 +405,7 @@ LogWindow.prototype = { } }; -function goHomeClicked() { +function visitSandboxClicked() { if (interfacePath) { startInterface('hifi://localhost'); } else { @@ -425,8 +439,8 @@ var labels = { } }, goHome: { - label: 'Go Home', - click: goHomeClicked, + label: 'Visit Sandbox', + click: visitSandboxClicked, enabled: false }, quit: { @@ -768,33 +782,27 @@ function onContentLoaded() { // maybeShowSplash(); if (buildInfo.releaseType == 'PRODUCTION' && !argv.noUpdater) { - var currentVersion = null; - try { - currentVersion = parseInt(buildInfo.buildIdentifier); - } catch (e) { - } - if (currentVersion !== null) { - const CHECK_FOR_UPDATES_INTERVAL_SECONDS = 60 * 30; - var hasShownUpdateNotification = false; - const updateChecker = new updater.UpdateChecker(currentVersion, CHECK_FOR_UPDATES_INTERVAL_SECONDS); - updateChecker.on('update-available', function(latestVersion, url) { - if (!hasShownUpdateNotification) { - notifier.notify({ - icon: notificationIcon, - title: 'An update is available!', - message: 'High Fidelity version ' + latestVersion + ' is available', - wait: true, - url: url - }); - hasShownUpdateNotification = true; - } - }); - notifier.on('click', function(notifierObject, options) { - log.debug("Got click", options.url); - shell.openExternal(options.url); - }); - } + const CHECK_FOR_UPDATES_INTERVAL_SECONDS = 60 * 30; + var hasShownUpdateNotification = false; + const updateChecker = new updater.UpdateChecker(buildInfo, CHECK_FOR_UPDATES_INTERVAL_SECONDS); + updateChecker.on('update-available', function(latestVersion, url) { + if (!hasShownUpdateNotification) { + notifier.notify({ + icon: notificationIcon, + title: 'An update is available!', + message: 'High Fidelity version ' + latestVersion + ' is available', + wait: true, + appID: buildInfo.appUserModelId, + url: url + }); + hasShownUpdateNotification = true; + } + }); + notifier.on('click', function(notifierObject, options) { + log.debug("Got click", options.url); + shell.openExternal(options.url); + }); } deleteOldFiles(logPath, DELETE_LOG_FILES_OLDER_THAN_X_SECONDS, LOG_FILE_REGEX); diff --git a/server-console/src/modules/hf-updater.js b/server-console/src/modules/hf-updater.js index 489364f655..8362174c5d 100644 --- a/server-console/src/modules/hf-updater.js +++ b/server-console/src/modules/hf-updater.js @@ -8,10 +8,48 @@ const os = require('os'); const platform = os.type() == 'Windows_NT' ? 'windows' : 'mac'; const BUILDS_URL = 'https://highfidelity.com/builds.xml'; +const DEV_BUILDS_URL = 'https://highfidelity.com/dev-builds.xml'; -function UpdateChecker(currentVersion, checkForUpdatesEveryXSeconds) { - this.currentVersion = currentVersion; - log.debug('cur', currentVersion); +// returns 1 if A is greater, 0 if equal, -1 if A is lesser +function semanticVersionCompare(versionA, versionB) { + var versionAParts = versionA.split('.'); + var versionBParts = versionB.split('.'); + + // make sure each version has 3 parts + var partsLength = versionAParts.length; + while (partsLength < 3) { + partsLength = versionAParts.push(0); + } + + partsLength = versionBParts.length; + while (partsLength < 3) { + partsLength = versionBParts.push(0); + } + + // map all of the parts to numbers + versionAParts = versionAParts.map(Number); + versionBParts = versionBParts.map(Number); + + for (var i = 0; i < 3; ++i) { + if (versionAParts[i] == versionBParts[i]) { + continue; + } else if (versionAParts[i] > versionBParts[i]) { + return 1; + } else { + return -1; + } + } + + return 0; +} + +function UpdateChecker(buildInfo, checkForUpdatesEveryXSeconds) { + this.stableBuild = (buildInfo.stableBuild == "1"); + + this.buildsURL = this.stableBuild ? BUILDS_URL : DEV_BUILDS_URL; + this.currentVersion = this.stableBuild ? buildInfo.buildIdentifier : parseInt(buildInfo.buildNumber); + + log.debug('Current version is', this.currentVersion); setInterval(this.checkForUpdates.bind(this), checkForUpdatesEveryXSeconds * 1000); this.checkForUpdates(); @@ -20,7 +58,7 @@ util.inherits(UpdateChecker, events.EventEmitter); UpdateChecker.prototype = extend(UpdateChecker.prototype, { checkForUpdates: function() { log.debug("Checking for updates"); - request(BUILDS_URL, (error, response, body) => { + request(this.buildsURL, (error, response, body) => { if (error) { log.debug("Error", error); return; @@ -29,12 +67,32 @@ UpdateChecker.prototype = extend(UpdateChecker.prototype, { try { var $ = cheerio.load(body, { xmlMode: true }); const latestBuild = $('project[name="interface"] platform[name="' + platform + '"]').children().first(); - const latestVersion = parseInt(latestBuild.find('version').text()); - log.debug("Latest version is:", latestVersion, this.currentVersion); - if (latestVersion > this.currentVersion) { + + var latestVersion = 0; + + if (this.stableBuild) { + latestVersion = latestBuild.find('stable_version').text(); + } else { + latestVersion = parseInt(latestBuild.find('version').text()); + } + + log.debug("Latest available update version is:", latestVersion); + + updateAvailable = false; + + if (this.stableBuild) { + // compare the semantic versions to see if the update is newer + updateAvailable = (semanticVersionCompare(latestVersion, this.currentVersion) == 1); + } else { + // for master builds we just compare the versions as integers + updateAvailable = latestVersion > this.currentVersion; + } + + if (updateAvailable) { const url = latestBuild.find('url').text(); this.emit('update-available', latestVersion, url); } + } catch (e) { log.warn("Error when checking for updates", e); } diff --git a/tests-manual/render-perf/CMakeLists.txt b/tests-manual/render-perf/CMakeLists.txt index d688474379..93ff325a98 100644 --- a/tests-manual/render-perf/CMakeLists.txt +++ b/tests-manual/render-perf/CMakeLists.txt @@ -13,7 +13,7 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") # link in the shared libraries link_hifi_libraries( - shared task networking animation + shared task workload networking animation ktx image octree gl gpu ${PLATFORM_GL_BACKEND} render render-utils graphics fbx model-networking graphics-scripting diff --git a/tests-manual/shaders/src/main.cpp b/tests-manual/shaders/src/main.cpp index 67cb58182c..6e117b33cb 100644 --- a/tests-manual/shaders/src/main.cpp +++ b/tests-manual/shaders/src/main.cpp @@ -57,9 +57,6 @@ #include #include -#include -#include - #include #include @@ -195,8 +192,6 @@ void QTestWindow::draw() { testShaderBuild(ambient_occlusion_vert::getSource(), occlusion_blend_frag::getSource()); */ - testShaderBuild(overlay3D_vert::getSource(), overlay3D_frag::getSource()); - testShaderBuild(paintStroke_vert::getSource(),paintStroke_frag::getSource()); testShaderBuild(polyvox_vert::getSource(), polyvox_frag::getSource()); diff --git a/tests/physics/src/CollisionRenderMeshCacheTests.cpp b/tests/physics/src/CollisionRenderMeshCacheTests.cpp deleted file mode 100644 index da28598dda..0000000000 --- a/tests/physics/src/CollisionRenderMeshCacheTests.cpp +++ /dev/null @@ -1,277 +0,0 @@ -// -// CollisionRenderMeshCacheTests.cpp -// tests/physics/src -// -// Created by Andrew Meadows on 2014.10.30 -// Copyright 2014 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 "CollisionRenderMeshCacheTests.h" - -#include -#include - -#include -#include - -#include -#include // for MAX_HULL_POINTS - -#include "MeshUtil.h" - - -QTEST_MAIN(CollisionRenderMeshCacheTests) - -const float INV_SQRT_THREE = 0.577350269f; - -const uint32_t numSphereDirections = 6 + 8; -btVector3 sphereDirections[] = { - btVector3(1.0f, 0.0f, 0.0f), - btVector3(-1.0f, 0.0f, 0.0f), - btVector3(0.0f, 1.0f, 0.0f), - btVector3(0.0f, -1.0f, 0.0f), - btVector3(0.0f, 0.0f, 1.0f), - btVector3(0.0f, 0.0f, -1.0f), - btVector3(INV_SQRT_THREE, INV_SQRT_THREE, INV_SQRT_THREE), - btVector3(INV_SQRT_THREE, INV_SQRT_THREE, -INV_SQRT_THREE), - btVector3(INV_SQRT_THREE, -INV_SQRT_THREE, INV_SQRT_THREE), - btVector3(INV_SQRT_THREE, -INV_SQRT_THREE, -INV_SQRT_THREE), - btVector3(-INV_SQRT_THREE, INV_SQRT_THREE, INV_SQRT_THREE), - btVector3(-INV_SQRT_THREE, INV_SQRT_THREE, -INV_SQRT_THREE), - btVector3(-INV_SQRT_THREE, -INV_SQRT_THREE, INV_SQRT_THREE), - btVector3(-INV_SQRT_THREE, -INV_SQRT_THREE, -INV_SQRT_THREE) -}; - -float randomFloat() { - return 2.0f * ((float)rand() / (float)RAND_MAX) - 1.0f; -} - -btBoxShape* createBoxShape(const btVector3& extent) { - btBoxShape* shape = new btBoxShape(0.5f * extent); - return shape; -} - -btConvexHullShape* createConvexHull(float radius) { - btConvexHullShape* hull = new btConvexHullShape(); - for (uint32_t i = 0; i < numSphereDirections; ++i) { - btVector3 point = radius * sphereDirections[i]; - hull->addPoint(point, false); - } - hull->recalcLocalAabb(); - return hull; -} - -void CollisionRenderMeshCacheTests::testShapeHullManifold() { - // make a box shape - btVector3 extent(1.0f, 2.0f, 3.0f); - btBoxShape* box = createBoxShape(extent); - - // wrap it with a ShapeHull - btShapeHull hull(box); - const float MARGIN = 0.0f; - hull.buildHull(MARGIN); - - // verify the vertex count is capped - uint32_t numVertices = (uint32_t)hull.numVertices(); - QVERIFY(numVertices <= MAX_HULL_POINTS); - - // verify the mesh is inside the radius - btVector3 halfExtents = box->getHalfExtentsWithMargin(); - float ACCEPTABLE_EXTENTS_ERROR = 0.01f; - float maxRadius = halfExtents.length() + ACCEPTABLE_EXTENTS_ERROR; - const btVector3* meshVertices = hull.getVertexPointer(); - for (uint32_t i = 0; i < numVertices; ++i) { - btVector3 vertex = meshVertices[i]; - QVERIFY(vertex.length() <= maxRadius); - } - - // verify the index count is capped - uint32_t numIndices = (uint32_t)hull.numIndices(); - QVERIFY(numIndices < 6 * MAX_HULL_POINTS); - - // verify the index count is a multiple of 3 - QVERIFY(numIndices % 3 == 0); - - // verify the mesh is closed - const uint32_t* meshIndices = hull.getIndexPointer(); - bool isClosed = MeshUtil::isClosedManifold(meshIndices, numIndices); - QVERIFY(isClosed); - - // verify the triangle normals are outward using right-hand-rule - const uint32_t INDICES_PER_TRIANGLE = 3; - for (uint32_t i = 0; i < numIndices; i += INDICES_PER_TRIANGLE) { - btVector3 A = meshVertices[meshIndices[i]]; - btVector3 B = meshVertices[meshIndices[i+1]]; - btVector3 C = meshVertices[meshIndices[i+2]]; - - btVector3 face = (B - A).cross(C - B); - btVector3 center = (A + B + C) / 3.0f; - QVERIFY(face.dot(center) > 0.0f); - } - - // delete unmanaged memory - delete box; -} - -void CollisionRenderMeshCacheTests::testCompoundShape() { - uint32_t numSubShapes = 3; - - btVector3 centers[] = { - btVector3(1.0f, 0.0f, 0.0f), - btVector3(0.0f, -2.0f, 0.0f), - btVector3(0.0f, 0.0f, 3.0f), - }; - - float radii[] = { 3.0f, 2.0f, 1.0f }; - - btCompoundShape* compoundShape = new btCompoundShape(); - for (uint32_t i = 0; i < numSubShapes; ++i) { - btTransform transform; - transform.setOrigin(centers[i]); - btConvexHullShape* hull = createConvexHull(radii[i]); - compoundShape->addChildShape(transform, hull); - } - - // create the cache - CollisionRenderMeshCache cache; - QVERIFY(cache.getNumMeshes() == 0); - - // get the mesh once - graphics::MeshPointer mesh = cache.getMesh(compoundShape); - QVERIFY((bool)mesh); - QVERIFY(cache.getNumMeshes() == 1); - - // get the mesh again - graphics::MeshPointer mesh2 = cache.getMesh(compoundShape); - QVERIFY(mesh2 == mesh); - QVERIFY(cache.getNumMeshes() == 1); - - // forget the mesh once - cache.releaseMesh(compoundShape); - mesh.reset(); - QVERIFY(cache.getNumMeshes() == 1); - - // collect garbage (should still cache mesh) - cache.collectGarbage(); - QVERIFY(cache.getNumMeshes() == 1); - - // forget the mesh a second time (should still cache mesh) - cache.releaseMesh(compoundShape); - mesh2.reset(); - QVERIFY(cache.getNumMeshes() == 1); - - // collect garbage (should no longer cache mesh) - cache.collectGarbage(); - QVERIFY(cache.getNumMeshes() == 0); - - // delete unmanaged memory - for (int i = 0; i < compoundShape->getNumChildShapes(); ++i) { - delete compoundShape->getChildShape(i); - } - delete compoundShape; -} - -void CollisionRenderMeshCacheTests::testMultipleShapes() { - // shapeA is compound of hulls - uint32_t numSubShapes = 3; - btVector3 centers[] = { - btVector3(1.0f, 0.0f, 0.0f), - btVector3(0.0f, -2.0f, 0.0f), - btVector3(0.0f, 0.0f, 3.0f), - }; - float radii[] = { 3.0f, 2.0f, 1.0f }; - btCompoundShape* shapeA = new btCompoundShape(); - for (uint32_t i = 0; i < numSubShapes; ++i) { - btTransform transform; - transform.setOrigin(centers[i]); - btConvexHullShape* hull = createConvexHull(radii[i]); - shapeA->addChildShape(transform, hull); - } - - // shapeB is compound of boxes - btVector3 extents[] = { - btVector3(1.0f, 2.0f, 3.0f), - btVector3(2.0f, 3.0f, 1.0f), - btVector3(3.0f, 1.0f, 2.0f), - }; - btCompoundShape* shapeB = new btCompoundShape(); - for (uint32_t i = 0; i < numSubShapes; ++i) { - btTransform transform; - transform.setOrigin(centers[i]); - btBoxShape* box = createBoxShape(extents[i]); - shapeB->addChildShape(transform, box); - } - - // shapeC is just a box - btVector3 extentC(7.0f, 3.0f, 5.0f); - btBoxShape* shapeC = createBoxShape(extentC); - - // create the cache - CollisionRenderMeshCache cache; - QVERIFY(cache.getNumMeshes() == 0); - - // get the meshes - graphics::MeshPointer meshA = cache.getMesh(shapeA); - graphics::MeshPointer meshB = cache.getMesh(shapeB); - graphics::MeshPointer meshC = cache.getMesh(shapeC); - QVERIFY((bool)meshA); - QVERIFY((bool)meshB); - QVERIFY((bool)meshC); - QVERIFY(cache.getNumMeshes() == 3); - - // get the meshes again - graphics::MeshPointer meshA2 = cache.getMesh(shapeA); - graphics::MeshPointer meshB2 = cache.getMesh(shapeB); - graphics::MeshPointer meshC2 = cache.getMesh(shapeC); - QVERIFY(meshA == meshA2); - QVERIFY(meshB == meshB2); - QVERIFY(meshC == meshC2); - QVERIFY(cache.getNumMeshes() == 3); - - // forget the meshes once - cache.releaseMesh(shapeA); - cache.releaseMesh(shapeB); - cache.releaseMesh(shapeC); - meshA2.reset(); - meshB2.reset(); - meshC2.reset(); - QVERIFY(cache.getNumMeshes() == 3); - - // collect garbage (should still cache mesh) - cache.collectGarbage(); - QVERIFY(cache.getNumMeshes() == 3); - - // forget again, one mesh at a time... - // shapeA... - cache.releaseMesh(shapeA); - meshA.reset(); - QVERIFY(cache.getNumMeshes() == 3); - cache.collectGarbage(); - QVERIFY(cache.getNumMeshes() == 2); - // shapeB... - cache.releaseMesh(shapeB); - meshB.reset(); - QVERIFY(cache.getNumMeshes() == 2); - cache.collectGarbage(); - QVERIFY(cache.getNumMeshes() == 1); - // shapeC... - cache.releaseMesh(shapeC); - meshC.reset(); - QVERIFY(cache.getNumMeshes() == 1); - cache.collectGarbage(); - QVERIFY(cache.getNumMeshes() == 0); - - // delete unmanaged memory - for (int i = 0; i < shapeA->getNumChildShapes(); ++i) { - delete shapeA->getChildShape(i); - } - delete shapeA; - for (int i = 0; i < shapeB->getNumChildShapes(); ++i) { - delete shapeB->getChildShape(i); - } - delete shapeB; - delete shapeC; -} diff --git a/tests/physics/src/CollisionRenderMeshCacheTests.h b/tests/physics/src/CollisionRenderMeshCacheTests.h deleted file mode 100644 index 640314a2a0..0000000000 --- a/tests/physics/src/CollisionRenderMeshCacheTests.h +++ /dev/null @@ -1,26 +0,0 @@ -// -// CollisionRenderMeshCacheTests.h -// tests/physics/src -// -// Created by Andrew Meadows on 2014.10.30 -// Copyright 2014 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_CollisionRenderMeshCacheTests_h -#define hifi_CollisionRenderMeshCacheTests_h - -#include - -class CollisionRenderMeshCacheTests : public QObject { - Q_OBJECT - -private slots: - void testShapeHullManifold(); - void testCompoundShape(); - void testMultipleShapes(); -}; - -#endif // hifi_CollisionRenderMeshCacheTests_h diff --git a/tests/shared/src/AACubeTests.cpp b/tests/shared/src/AACubeTests.cpp index 469dcfa981..95a4d7f9f0 100644 --- a/tests/shared/src/AACubeTests.cpp +++ b/tests/shared/src/AACubeTests.cpp @@ -152,3 +152,47 @@ void AACubeTests::touchesSphere() { } } +void AACubeTests::rayVsParabolaPerformance() { + // Test performance of findRayIntersection vs. findParabolaIntersection + // 100000 cubes with scale 500 in the +x +y +z quadrant + const int NUM_CUBES = 100000; + const float MAX_POS = 1000.0f; + const float MAX_SCALE = 500.0f; + int numRayHits = 0; + int numParabolaHits = 0; + std::vector cubes; + cubes.reserve(NUM_CUBES); + for (int i = 0; i < NUM_CUBES; i++) { + cubes.emplace_back(glm::vec3(randFloatInRange(0.0f, MAX_POS), randFloatInRange(0.0f, MAX_POS), randFloatInRange(0.0f, MAX_POS)), MAX_SCALE); + } + + glm::vec3 origin(0.0f); + glm::vec3 direction = glm::normalize(glm::vec3(1.0f)); + float distance; + BoxFace face; + glm::vec3 normal; + auto start = std::chrono::high_resolution_clock::now(); + for (auto& cube : cubes) { + if (cube.findRayIntersection(origin, direction, distance, face, normal)) { + numRayHits++; + } + } + + auto rayTime = std::chrono::high_resolution_clock::now() - start; + start = std::chrono::high_resolution_clock::now(); + direction = 10.0f * direction; + glm::vec3 acceleration = glm::vec3(-0.0001f, -0.0001f, -0.0001f); + for (auto& cube : cubes) { + if (cube.findParabolaIntersection(origin, direction, acceleration, distance, face, normal)) { + numParabolaHits++; + } + } + auto parabolaTime = std::chrono::high_resolution_clock::now() - start; + + qDebug() << "Ray vs. Parabola perfomance: rayHit%:" << numRayHits / ((float)NUM_CUBES) * 100.0f << ", rayTime:" << rayTime.count() << + ", parabolaHit%:" << numParabolaHits / ((float)NUM_CUBES) * 100.0f << ", parabolaTime:" << parabolaTime.count() << ", parabolaTime/rayTime: " << (float)parabolaTime.count()/(float)rayTime.count(); +} + +void AACubeTests::cleanupTestCase() { + +} \ No newline at end of file diff --git a/tests/shared/src/AACubeTests.h b/tests/shared/src/AACubeTests.h index a2b2e08cc5..569c978929 100644 --- a/tests/shared/src/AACubeTests.h +++ b/tests/shared/src/AACubeTests.h @@ -23,6 +23,8 @@ private slots: void ctorsAndSetters(); void containsPoint(); void touchesSphere(); + void rayVsParabolaPerformance(); + void cleanupTestCase(); }; #endif // hifi_AACubeTests_h diff --git a/tests/workload/src/SpaceTests.cpp b/tests/workload/src/SpaceTests.cpp index 1c6b0491a7..23e12074ea 100644 --- a/tests/workload/src/SpaceTests.cpp +++ b/tests/workload/src/SpaceTests.cpp @@ -55,7 +55,9 @@ void SpaceTests::testOverlaps() { float newRadius = 1.0f; glm::vec3 newPosition = viewCenter + glm::vec3(0.0f, 0.0f, far + newRadius - DELTA); workload::Space::Sphere newSphere(newPosition, newRadius); - space.updateProxy(proxyId, newSphere); + std::vector updates; + updates.push_back(workload::Space::ProxyUpdate(proxyId, newSphere)); + space.updateProxies(updates); Changes changes; space.categorizeAndGetChanges(changes); QVERIFY(changes.size() == 1); @@ -68,7 +70,9 @@ void SpaceTests::testOverlaps() { float newRadius = 1.0f; glm::vec3 newPosition = viewCenter + glm::vec3(0.0f, 0.0f, mid + newRadius - DELTA); workload::Space::Sphere newSphere(newPosition, newRadius); - space.updateProxy(proxyId, newSphere); + std::vector updates; + updates.push_back(workload::Space::ProxyUpdate(proxyId, newSphere)); + space.updateProxies(updates); Changes changes; space.categorizeAndGetChanges(changes); QVERIFY(changes.size() == 1); @@ -81,7 +85,9 @@ void SpaceTests::testOverlaps() { float newRadius = 1.0f; glm::vec3 newPosition = viewCenter + glm::vec3(0.0f, 0.0f, near + newRadius - DELTA); workload::Space::Sphere newSphere(newPosition, newRadius); - space.updateProxy(proxyId, newSphere); + std::vector updates; + updates.push_back(workload::Space::ProxyUpdate(proxyId, newSphere)); + space.updateProxies(updates); Changes changes; space.categorizeAndGetChanges(changes); QVERIFY(changes.size() == 1); @@ -92,7 +98,9 @@ void SpaceTests::testOverlaps() { { // delete proxy // NOTE: atm deleting a proxy doesn't result in a "Change" - space.deleteProxy(proxyId); + std::vector deadProxies; + deadProxies.push_back(proxyId); + space.deleteProxies(deadProxies); Changes changes; space.categorizeAndGetChanges(changes); QVERIFY(changes.size() == 0); @@ -145,9 +153,9 @@ void SpaceTests::benchmark() { uint32_t numProxies[] = { 100, 1000, 10000, 100000 }; uint32_t numTests = 4; std::vector timeToAddAll; - std::vector timeToRemoveAll; std::vector timeToMoveView; std::vector timeToMoveProxies; + std::vector timeToRemoveAll; for (uint32_t i = 0; i < numTests; ++i) { workload::Space space; diff --git a/tools/auto-tester/src/ImageComparer.cpp b/tools/auto-tester/src/ImageComparer.cpp index 3afdeaec0a..fa73f97887 100644 --- a/tools/auto-tester/src/ImageComparer.cpp +++ b/tools/auto-tester/src/ImageComparer.cpp @@ -15,13 +15,8 @@ // Computes SSIM - see https://en.wikipedia.org/wiki/Structural_similarity // The value is computed for the luminance component and the average value is returned double ImageComparer::compareImages(QImage resultImage, QImage expectedImage) const { - // Make sure the image is 8 bits per colour - QImage::Format format = expectedImage.format(); - if (format != QImage::Format::Format_ARGB32) { - throw -1; - } - const int L = 255; // (2^number of bits per pixel) - 1 + const double K1 { 0.01 }; const double K2 { 0.03 }; const double c1 = pow((K1 * L), 2); diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 1a25cc5c0e..4f02544c12 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -25,6 +25,11 @@ extern AutoTester* autoTester; Test::Test() { mismatchWindow.setModal(true); + + if (autoTester) { + autoTester->setUserText("highfidelity"); + autoTester->setBranchText("master"); + } } bool Test::createTestResultsFolderPath(const QString& directory) { @@ -63,7 +68,6 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) // Loop over both lists and compare each pair of images // Quit loop if user has aborted due to a failed test. - const double THRESHOLD { 0.9995 }; bool success{ true }; bool keepOn{ true }; for (int i = 0; keepOn && i < expectedImagesFullFilenames.length(); ++i) { @@ -71,17 +75,14 @@ bool Test::compareImageLists(bool isInteractiveMode, QProgressBar* progressBar) QImage resultImage(resultImagesFullFilenames[i]); QImage expectedImage(expectedImagesFullFilenames[i]); + double similarityIndex; // in [-1.0 .. 1.0], where 1.0 means images are identical + + // similarityIndex is set to -100.0 to indicate images are not the same size if (isInteractiveMode && (resultImage.width() != expectedImage.width() || resultImage.height() != expectedImage.height())) { QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Images are not the same size"); - exit(-1); - } - - double similarityIndex; // in [-1.0 .. 1.0], where 1.0 means images are identical - try { + similarityIndex = -100.0; + } else { similarityIndex = imageComparer.compareImages(resultImage, expectedImage); - } catch (...) { - QMessageBox::critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Image not in expected format"); - exit(-1); } if (similarityIndex < THRESHOLD) { @@ -176,20 +177,25 @@ void Test::appendTestResultsToFile(const QString& testResultsFolderPath, TestFai comparisonImage.save(failureFolderPath + "/" + "Difference Image.png"); } -void Test::startTestsEvaluation(const QString& testFolder) { - // Get list of JPEG images in folder, sorted by name - QString previousSelection = snapshotDirectory; - QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); - if (!parent.isNull() && parent.right(1) != "/") { - parent += "/"; - } - snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", parent, - QFileDialog::ShowDirsOnly); +void Test::startTestsEvaluation(const QString& testFolder, const QString& branchFromCommandLine, const QString& userFromCommandLine) { + if (testFolder.isNull()) { + // Get list of JPEG images in folder, sorted by name + QString previousSelection = snapshotDirectory; + QString parent = previousSelection.left(previousSelection.lastIndexOf('/')); + if (!parent.isNull() && parent.right(1) != "/") { + parent += "/"; + } + snapshotDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", parent, + QFileDialog::ShowDirsOnly); - // If user cancelled then restore previous selection and return - if (snapshotDirectory == "") { - snapshotDirectory = previousSelection; - return; + // If user cancelled then restore previous selection and return + if (snapshotDirectory == "") { + snapshotDirectory = previousSelection; + return; + } + } else { + snapshotDirectory = testFolder; + exitWhenComplete = true; } // Quit if test results folder could not be created @@ -197,17 +203,6 @@ void Test::startTestsEvaluation(const QString& testFolder) { return; } - // Before any processing - all images are converted to PNGs, as this is the format stored on GitHub - QStringList sortedSnapshotFilenames = createListOfAll_imagesInDirectory("jpg", snapshotDirectory); - foreach(QString filename, sortedSnapshotFilenames) { - QString filenameWithoutExtension = filename.left(filename.length() - 4); - copyJPGtoPNG(snapshotDirectory + "/" + filenameWithoutExtension + ".jpg", - snapshotDirectory + "/" + filenameWithoutExtension + ".png" - ); - - QFile::remove(snapshotDirectory + "/" + filenameWithoutExtension + ".jpg"); - } - // Create two lists. The first is the test results, the second is the expected images // The expected images are represented as a URL to enable download from GitHub // Images that are in the wrong format are ignored. @@ -219,6 +214,9 @@ void Test::startTestsEvaluation(const QString& testFolder) { expectedImagesFilenames.clear(); expectedImagesFullFilenames.clear(); + QString branch = (branchFromCommandLine.isNull()) ? autoTester->getSelectedBranch() : branchFromCommandLine; + QString user = (userFromCommandLine.isNull()) ? autoTester->getSelectedUser() : userFromCommandLine; + foreach(QString currentFilename, sortedTestResultsFilenames) { QString fullCurrentFilename = snapshotDirectory + "/" + currentFilename; if (isInSnapshotFilenameFormat("png", currentFilename)) { @@ -231,7 +229,7 @@ void Test::startTestsEvaluation(const QString& testFolder) { QString expectedImageFilenameTail = currentFilename.left(currentFilename.length() - 4).right(NUM_DIGITS); QString expectedImageStoredFilename = EXPECTED_IMAGE_PREFIX + expectedImageFilenameTail + ".png"; - QString imageURLString("https://raw.githubusercontent.com/" + GIT_HUB_USER + "/hifi_tests/" + GIT_HUB_BRANCH + "/" + + QString imageURLString("https://raw.githubusercontent.com/" + user + "/" + GIT_HUB_REPOSITORY + "/" + branch + "/" + expectedImagePartialSourceDirectory + "/" + expectedImageStoredFilename); expectedImagesURLs << imageURLString; @@ -259,6 +257,10 @@ void Test::finishTestsEvaluation(bool isRunningFromCommandline, bool interactive } zipAndDeleteTestResultsFolder(); + + if (exitWhenComplete) { + exit(0); + } } bool Test::isAValidDirectory(const QString& pathname) { @@ -299,10 +301,9 @@ QString Test::extractPathFromTestsDown(const QString& fullPath) { void Test::includeTest(QTextStream& textStream, const QString& testPathname) { QString partialPath = extractPathFromTestsDown(testPathname); - textStream << "Script.include(\"" - << "https://github.com/" << GIT_HUB_USER << "/hifi_tests/blob/" << GIT_HUB_BRANCH - << partialPath + "?raw=true\");" - << endl; + QString partialPathWithoutTests = partialPath.right(partialPath.length() - 7); + + textStream << "Script.include(testsRootPath + \"" << partialPathWithoutTests + "\");" << endl; } // Creates a single script in a user-selected folder. @@ -397,12 +398,20 @@ void Test::createRecursiveScript(const QString& topLevelDirectory, bool interact const QString DATE_TIME_FORMAT("MMM d yyyy, h:mm"); textStream << "// This is an automatically generated file, created by auto-tester on " << QDateTime::currentDateTime().toString(DATE_TIME_FORMAT) << endl << endl; - textStream << "user = \"" + GIT_HUB_USER + "/\";" << endl; - textStream << "repository = \"" + GIT_HUB_REPOSITORY + "/\";" << endl; - textStream << "branch = \"" + GIT_HUB_BRANCH + "/\";" << endl << endl; + // Include 'autoTest.js' + QString branch = autoTester->getSelectedBranch(); + QString user = autoTester->getSelectedUser(); - textStream << "var autoTester = Script.require(\"https://github.com/" + GIT_HUB_USER + "/hifi_tests/blob/" - + GIT_HUB_BRANCH + "/tests/utils/autoTester.js?raw=true\");" << endl << endl; + textStream << "PATH_TO_THE_REPO_PATH_UTILS_FILE = \"https://raw.githubusercontent.com/" + user + "/hifi_tests/" + branch + "/tests/utils/branchUtils.js\";" << endl; + textStream << "Script.include(PATH_TO_THE_REPO_PATH_UTILS_FILE);" << endl; + textStream << "var autoTester = createAutoTester(Script.resolvePath(\".\"));" << endl << endl; + + textStream << "var testsRootPath = autoTester.getTestsRootPath();" << endl << endl; + + // Wait 10 seconds before starting + textStream << "if (typeof Test !== 'undefined') {" << endl; + textStream << " Test.wait(10000);" << endl; + textStream << "};" << endl << endl; textStream << "autoTester.enableRecursive();" << endl; textStream << "autoTester.enableAuto();" << endl << endl; @@ -498,13 +507,13 @@ void Test::createTests() { return; } - QStringList sortedImageFilenames = createListOfAll_imagesInDirectory("jpg", snapshotDirectory); + QStringList sortedImageFilenames = createListOfAll_imagesInDirectory("png", snapshotDirectory); int i = 1; const int maxImages = pow(10, NUM_DIGITS); foreach (QString currentFilename, sortedImageFilenames) { QString fullCurrentFilename = snapshotDirectory + "/" + currentFilename; - if (isInSnapshotFilenameFormat("jpg", currentFilename)) { + if (isInSnapshotFilenameFormat("png", currentFilename)) { if (i >= maxImages) { QMessageBox::critical(0, "Error", "More than " + QString::number(maxImages) + " images not supported"); exit(-1); @@ -528,9 +537,12 @@ void Test::createTests() { fullNewFileName += "/" + newFilename; try { - copyJPGtoPNG(fullCurrentFilename, fullNewFileName); + if (QFile::exists(fullNewFileName)) { + QFile::remove(fullNewFileName); + } + QFile::copy(fullCurrentFilename, fullNewFileName); } catch (...) { - QMessageBox::critical(0, "Error", "Could not delete existing file: " + currentFilename + "\nTest creation aborted"); + QMessageBox::critical(0, "Error", "Could not copy file: " + fullCurrentFilename + " to " + fullNewFileName + "\n"); exit(-1); } ++i; @@ -560,9 +572,7 @@ ExtractedText Test::getTestScriptLines(QString testFileName) { const QString ws("\\h*"); //white-space character const QString functionPerformName(ws + "autoTester" + ws + "\\." + ws + "perform"); const QString quotedString("\\\".+\\\""); - const QString ownPath("Script" + ws + "\\." + ws + "resolvePath" + ws + "\\(" + ws + "\\\"\\.\\\"" + ws + "\\)"); - const QString functionParameter("function" + ws + "\\(testType" + ws + "\\)"); - QString regexTestTitle(ws + functionPerformName + "\\(" + quotedString + "\\," + ws + ownPath + "\\," + ws + functionParameter + ws + "{" + ".*"); + QString regexTestTitle(ws + functionPerformName + "\\(" + quotedString); QRegularExpression lineContainingTitle = QRegularExpression(regexTestTitle); @@ -811,19 +821,6 @@ void Test::createTestsOutline() { QMessageBox::information(0, "Success", "Test outline file " + testsOutlineFilename + " has been created"); } -void Test::copyJPGtoPNG(const QString& sourceJPGFullFilename, const QString& destinationPNGFullFilename) { - QFile::remove(destinationPNGFullFilename); - - QImageReader reader; - reader.setFileName(sourceJPGFullFilename); - - QImage image = reader.read(); - - QImageWriter writer; - writer.setFileName(destinationPNGFullFilename); - writer.write(image); -} - QStringList Test::createListOfAll_imagesInDirectory(const QString& imageFormat, const QString& pathToImageDirectory) { imageDirectory = QDir(pathToImageDirectory); QStringList nameFilters; diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h index 32564035e7..5c6d3e5686 100644 --- a/tools/auto-tester/src/Test.h +++ b/tools/auto-tester/src/Test.h @@ -37,7 +37,7 @@ class Test { public: Test(); - void startTestsEvaluation(const QString& testFolder = QString()); + void startTestsEvaluation(const QString& testFolder = QString(), const QString& branchFromCommandLine = QString(), const QString& userFromCommandLine = QString()); void finishTestsEvaluation(bool isRunningFromCommandline, bool interactiveMode, QProgressBar* progressBar); void createRecursiveScript(); @@ -69,13 +69,13 @@ public: QString getExpectedImageDestinationDirectory(const QString& filename); QString getExpectedImagePartialSourceDirectory(const QString& filename); - void copyJPGtoPNG(const QString& sourceJPGFullFilename, const QString& destinationPNGFullFilename); - private: const QString TEST_FILENAME { "test.js" }; const QString TEST_RESULTS_FOLDER { "TestResults" }; const QString TEST_RESULTS_FILENAME { "TestResults.txt" }; + const double THRESHOLD{ 0.96 }; + QDir imageDirectory; MismatchWindow mismatchWindow; @@ -102,9 +102,7 @@ private: QStringList resultImagesFullFilenames; // Used for accessing GitHub - const QString GIT_HUB_USER{ "highfidelity" }; const QString GIT_HUB_REPOSITORY{ "hifi_tests" }; - const QString GIT_HUB_BRANCH{ "master" }; const QString DATETIME_FORMAT{ "yyyy-MM-dd_hh-mm-ss" }; @@ -115,6 +113,8 @@ private: // var pathSeparator = "."; const QString ADVANCE_KEY{ "n" }; const QString PATH_SEPARATOR{ "." }; + + bool exitWhenComplete{ false }; }; #endif // hifi_test_h \ No newline at end of file diff --git a/tools/auto-tester/src/main.cpp b/tools/auto-tester/src/main.cpp index ffa7a0b237..03b8cf51ff 100644 --- a/tools/auto-tester/src/main.cpp +++ b/tools/auto-tester/src/main.cpp @@ -10,23 +10,63 @@ #include #include "ui/AutoTester.h" +#include + AutoTester* autoTester; int main(int argc, char *argv[]) { - // Only parameter is "--testFolder" + // If no parameters then run in interactive mode + // Parameter --testFolder + // Parameter --branch + // default is "master" + // Parameter --user + // default is "highfidelity" + // Parameter --repository + // default is "highfidelity" + QString testFolder; - if (argc == 3) { - if (QString(argv[1]) == "--testFolder") { - testFolder = QString(argv[2]); + + QString branch{ "master" }; + QString user{ "highfidelity" }; + + for (int i = 1; i < argc - 1; ++i) { + if (QString(argv[i]) == "--testFolder") { + ++i; + if (i < argc) { + testFolder = QString(argv[i]); + } else { + std::cout << "Missing parameter after --testFolder" << std::endl; + exit(-1); + } + } else if (QString(argv[i]) == "--branch") { + ++i; + if (i < argc) { + branch = QString(argv[i]); + } else { + std::cout << "Missing parameter after --branch" << std::endl; + exit(-1); + } + } else if (QString(argv[i]) == "--user") { + ++i; + if (i < argc) { + user = QString(argv[i]); + } else { + std::cout << "Missing parameter after --user" << std::endl; + exit(-1); + } + } else { + std::cout << "Unknown parameter" << std::endl; + exit(-1); } } QApplication application(argc, argv); autoTester = new AutoTester(); + autoTester->setup(); if (!testFolder.isNull()) { - autoTester->runFromCommandLine(testFolder); + autoTester->runFromCommandLine(testFolder, branch, user); } else { autoTester->show(); } diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp index 14329e36c2..079fa63a9d 100644 --- a/tools/auto-tester/src/ui/AutoTester.cpp +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -29,13 +29,15 @@ AutoTester::AutoTester(QWidget *parent) : QMainWindow(parent) { ui.hideTaskbarButton->setVisible(false); ui.showTaskbarButton->setVisible(false); #endif +} +void AutoTester::setup() { test = new Test(); } -void AutoTester::runFromCommandLine(const QString& testFolder) { +void AutoTester::runFromCommandLine(const QString& testFolder, const QString& branch, const QString& user) { isRunningFromCommandline = true; - test->startTestsEvaluation(testFolder); + test->startTestsEvaluation(testFolder, branch, user); } void AutoTester::on_evaluateTestsButton_clicked() { @@ -116,6 +118,7 @@ void AutoTester::downloadImages(const QStringList& URLs, const QString& director ui.progressBar->setValue(0); ui.progressBar->setVisible(true); + downloaders.clear(); for (int i = 0; i < _numberOfImagesToDownload; ++i) { QUrl imageURL(URLs[i]); downloadImage(imageURL); @@ -125,14 +128,12 @@ void AutoTester::downloadImages(const QStringList& URLs, const QString& director } void AutoTester::saveImage(int index) { - QPixmap pixmap; - pixmap.loadFromData(downloaders[index]->downloadedData()); - - QImage image = pixmap.toImage(); - image = image.convertToFormat(QImage::Format_ARGB32); - - QString fullPathname = _directoryName + "/" + _filenames[index]; - if (!image.save(fullPathname, 0, 100)) { + try { + QFile file(_directoryName + "/" + _filenames[index]); + file.open(QIODevice::WriteOnly); + file.write(downloaders[index]->downloadedData()); + file.close(); + } catch (...) { QMessageBox::information(0, "Test Aborted", "Failed to save image: " + _filenames[index]); ui.progressBar->setVisible(false); return; @@ -141,6 +142,7 @@ void AutoTester::saveImage(int index) { ++_numberOfImagesDownloaded; if (_numberOfImagesDownloaded == _numberOfImagesToDownload) { + disconnect(signalMapper, SIGNAL (mapped(int)), this, SLOT (saveImage(int))); test->finishTestsEvaluation(isRunningFromCommandline, ui.checkBoxInteractiveMode->isChecked(), ui.progressBar); } else { ui.progressBar->setValue(_numberOfImagesDownloaded); @@ -150,3 +152,20 @@ void AutoTester::saveImage(int index) { void AutoTester::about() { QMessageBox::information(0, "About", QString("Built ") + __DATE__ + " : " + __TIME__); } + +void AutoTester::setUserText(const QString& user) { + ui.userTextEdit->setText(user); +} + +QString AutoTester::getSelectedUser() +{ + return ui.userTextEdit->toPlainText(); +} + +void AutoTester::setBranchText(const QString& branch) { + ui.branchTextEdit->setText(branch); +} + +QString AutoTester::getSelectedBranch() { + return ui.branchTextEdit->toPlainText(); +} diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h index 7b419a9af8..d47c4929c4 100644 --- a/tools/auto-tester/src/ui/AutoTester.h +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -12,6 +12,7 @@ #include #include +#include #include "ui_AutoTester.h" #include "../Downloader.h" @@ -23,11 +24,19 @@ class AutoTester : public QMainWindow { public: AutoTester(QWidget *parent = Q_NULLPTR); - void runFromCommandLine(const QString& testFolder); + void setup(); + + void runFromCommandLine(const QString& testFolder, const QString& branch, const QString& user); void downloadImage(const QUrl& url); void downloadImages(const QStringList& URLs, const QString& directoryName, const QStringList& filenames); + void setUserText(const QString& user); + QString getSelectedUser(); + + void setBranchText(const QString& branch); + QString getSelectedBranch(); + private slots: void on_evaluateTestsButton_clicked(); void on_createRecursiveScriptButton_clicked(); diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui index 236138acf4..e12fc70e3f 100644 --- a/tools/auto-tester/src/ui/AutoTester.ui +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -6,8 +6,8 @@ 0 0 - 607 - 514 + 612 + 537 @@ -17,9 +17,9 @@ - 360 - 400 - 220 + 380 + 430 + 101 40 @@ -43,9 +43,9 @@ - 20 - 285 - 220 + 430 + 270 + 101 40 @@ -56,8 +56,8 @@ - 360 - 35 + 330 + 110 220 40 @@ -69,8 +69,8 @@ - 23 - 250 + 320 + 280 131 20 @@ -85,8 +85,8 @@ - 20 - 340 + 320 + 330 255 23 @@ -98,8 +98,8 @@ - 360 - 100 + 330 + 170 220 40 @@ -112,7 +112,7 @@ 20 - 80 + 110 220 40 @@ -125,7 +125,7 @@ 20 - 130 + 160 220 40 @@ -138,7 +138,7 @@ 20 - 180 + 250 220 40 @@ -150,27 +150,83 @@ - 490 - 280 - 91 + 10 + 440 + 211 40 - Show Taskbar + Show Windows Taskbar - 490 - 230 - 91 + 10 + 390 + 211 40 - Hide Taskbar + Hide Windows Taskbar + + + + + + 330 + 55 + 81 + 16 + + + + + 10 + + + + GitHub Branch + + + + + + 330 + 15 + 81 + 16 + + + + + 10 + + + + GitHub User + + + + + + 420 + 12 + 140 + 24 + + + + + + + 420 + 50 + 140 + 24 + @@ -179,7 +235,7 @@ 0 0 - 607 + 612 21 diff --git a/tools/dissectors/hfudt.lua b/tools/dissectors/1-hfudt.lua similarity index 83% rename from tools/dissectors/hfudt.lua rename to tools/dissectors/1-hfudt.lua index 9d2df801b2..137bee659b 100644 --- a/tools/dissectors/hfudt.lua +++ b/tools/dissectors/1-hfudt.lua @@ -1,296 +1,315 @@ -print("Loading hfudt") - --- create the HFUDT protocol -p_hfudt = Proto("hfudt", "HFUDT Protocol") - --- create fields shared between packets in HFUDT -local f_data = ProtoField.string("hfudt.data", "Data") - --- create the fields for data packets in HFUDT -local f_length = ProtoField.uint16("hfudt.length", "Length", base.DEC) -local f_control_bit = ProtoField.uint8("hfudt.control", "Control Bit", base.DEC) -local f_reliable_bit = ProtoField.uint8("hfudt.reliable", "Reliability Bit", base.DEC) -local f_message_bit = ProtoField.uint8("hfudt.message", "Message Bit", base.DEC) -local f_obfuscation_level = ProtoField.uint8("hfudt.obfuscation_level", "Obfuscation Level", base.DEC) -local f_sequence_number = ProtoField.uint32("hfudt.sequence_number", "Sequence Number", base.DEC) -local f_message_position = ProtoField.uint8("hfudt.message_position", "Message Position", base.DEC) -local f_message_number = ProtoField.uint32("hfudt.message_number", "Message Number", base.DEC) -local f_message_part_number = ProtoField.uint32("hfudt.message_part_number", "Message Part Number", base.DEC) -local f_type = ProtoField.uint8("hfudt.type", "Type", base.DEC) -local f_version = ProtoField.uint8("hfudt.version", "Version", base.DEC) -local f_type_text = ProtoField.string("hfudt.type_text", "TypeText") -local f_sender_id = ProtoField.uint16("hfudt.sender_id", "Sender ID", base.DEC) -local f_hmac_hash = ProtoField.bytes("hfudt.hmac_hash", "HMAC Hash") - --- create the fields for control packets in HFUDT -local f_control_type = ProtoField.uint16("hfudt.control_type", "Control Type", base.DEC) -local f_control_type_text = ProtoField.string("hfudt.control_type_text", "Control Type Text", base.ASCII) -local f_ack_sequence_number = ProtoField.uint32("hfudt.ack_sequence_number", "ACKed Sequence Number", base.DEC) -local f_control_sub_sequence = ProtoField.uint32("hfudt.control_sub_sequence", "Control Sub-Sequence Number", base.DEC) -local f_nak_sequence_number = ProtoField.uint32("hfudt.nak_sequence_number", "NAKed Sequence Number", base.DEC) -local f_nak_range_end = ProtoField.uint32("hfudt.nak_range_end", "NAK Range End", base.DEC) - -local SEQUENCE_NUMBER_MASK = 0x07FFFFFF - -p_hfudt.fields = { - f_length, - f_control_bit, f_reliable_bit, f_message_bit, f_sequence_number, f_type, f_type_text, f_version, - f_sender_id, f_hmac_hash, - f_message_position, f_message_number, f_message_part_number, f_obfuscation_level, - f_control_type, f_control_type_text, f_control_sub_sequence, f_ack_sequence_number, f_nak_sequence_number, f_nak_range_end, - f_data -} - -local control_types = { - [0] = { "ACK", "Acknowledgement" }, - [1] = { "ACK2", "Acknowledgement of acknowledgement" }, - [2] = { "LightACK", "Light Acknowledgement" }, - [3] = { "NAK", "Loss report (NAK)" }, - [4] = { "TimeoutNAK", "Loss report re-transmission (TimeoutNAK)" }, - [5] = { "Handshake", "Handshake" }, - [6] = { "HandshakeACK", "Acknowledgement of Handshake" }, - [7] = { "ProbeTail", "Probe tail" }, - [8] = { "HandshakeRequest", "Request a Handshake" } -} - -local message_positions = { - [0] = "ONLY", - [1] = "LAST", - [2] = "FIRST", - [3] = "MIDDLE" -} - -local packet_types = { - [0] = "Unknown", - [1] = "StunResponse", - [2] = "DomainList", - [3] = "Ping", - [4] = "PingReply", - [5] = "KillAvatar", - [6] = "AvatarData", - [7] = "InjectAudio", - [8] = "MixedAudio", - [9] = "MicrophoneAudioNoEcho", - [10] = "MicrophoneAudioWithEcho", - [11] = "BulkAvatarData", - [12] = "SilentAudioFrame", - [13] = "DomainListRequest", - [14] = "RequestAssignment", - [15] = "CreateAssignment", - [16] = "DomainConnectionDenied", - [17] = "MuteEnvironment", - [18] = "AudioStreamStats", - [19] = "DomainServerPathQuery", - [20] = "DomainServerPathResponse", - [21] = "DomainServerAddedNode", - [22] = "ICEServerPeerInformation", - [23] = "ICEServerQuery", - [24] = "OctreeStats", - [25] = "Jurisdiction", - [26] = "JurisdictionRequest", - [27] = "AssignmentClientStatus", - [28] = "NoisyMute", - [29] = "AvatarIdentity", - [30] = "AvatarBillboard", - [31] = "DomainConnectRequest", - [32] = "DomainServerRequireDTLS", - [33] = "NodeJsonStats", - [34] = "OctreeDataNack", - [35] = "StopNode", - [36] = "AudioEnvironment", - [37] = "EntityEditNack", - [38] = "ICEServerHeartbeat", - [39] = "ICEPing", - [40] = "ICEPingReply", - [41] = "EntityData", - [42] = "EntityQuery", - [43] = "EntityAdd", - [44] = "EntityErase", - [45] = "EntityEdit", - [46] = "DomainServerConnectionToken", - [47] = "DomainSettingsRequest", - [48] = "DomainSettings", - [49] = "AssetGet", - [50] = "AssetGetReply", - [51] = "AssetUpload", - [52] = "AssetUploadReply", - [53] = "AssetGetInfo", - [54] = "AssetGetInfoReply" -} - -function p_hfudt.dissector(buf, pinfo, tree) - - -- make sure this isn't a STUN packet - those don't follow HFUDT format - if pinfo.dst == Address.ip("stun.highfidelity.io") then return end - - -- validate that the packet length is at least the minimum control packet size - if buf:len() < 4 then return end - - -- create a subtree for HFUDT - subtree = tree:add(p_hfudt, buf(0)) - - -- set the packet length - subtree:add(f_length, buf:len()) - - -- pull out the entire first word - local first_word = buf(0, 4):le_uint() - - -- pull out the control bit and add it to the subtree - local control_bit = bit32.rshift(first_word, 31) - subtree:add(f_control_bit, control_bit) - - local data_length = 0 - - if control_bit == 1 then - -- dissect the control packet - pinfo.cols.protocol = p_hfudt.name .. " Control" - - -- remove the control bit and shift to the right to get the type value - local shifted_type = bit32.rshift(bit32.lshift(first_word, 1), 17) - local type = subtree:add(f_control_type, shifted_type) - - if control_types[shifted_type] ~= nil then - -- if we know this type then add the name - type:append_text(" (".. control_types[shifted_type][1] .. ")") - - subtree:add(f_control_type_text, control_types[shifted_type][1]) - end - - if shifted_type == 0 or shifted_type == 1 then - - -- this has a sub-sequence number - local second_word = buf(4, 4):le_uint() - subtree:add(f_control_sub_sequence, bit32.band(second_word, SEQUENCE_NUMBER_MASK)) - - local data_index = 8 - - if shifted_type == 0 then - -- if this is an ACK let's read out the sequence number - local sequence_number = buf(8, 4):le_uint() - subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) - - data_index = data_index + 4 - end - - data_length = buf:len() - data_index - - -- set the data from whatever is left in the packet - subtree:add(f_data, buf(data_index, data_length)) - - elseif shifted_type == 2 then - -- this is a Light ACK let's read out the sequence number - local sequence_number = buf(4, 4):le_uint() - subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) - - data_length = buf:len() - 4 - - -- set the data from whatever is left in the packet - subtree:add(f_data, buf(4, data_length)) - elseif shifted_type == 3 or shifted_type == 4 then - if buf:len() <= 12 then - -- this is a NAK pull the sequence number or range - local sequence_number = buf(4, 4):le_uint() - subtree:add(f_nak_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) - - data_length = buf:len() - 4 - - if buf:len() > 8 then - local range_end = buf(8, 4):le_uint() - subtree:add(f_nak_range_end, bit32.band(range_end, SEQUENCE_NUMBER_MASK)) - - data_length = data_length - 4 - end - end - else - data_length = buf:len() - 4 - - -- no sub-sequence number, just read the data - subtree:add(f_data, buf(4, data_length)) - end - else - -- dissect the data packet - pinfo.cols.protocol = p_hfudt.name - - -- set the reliability bit - subtree:add(f_reliable_bit, bit32.rshift(first_word, 30)) - - local message_bit = bit32.band(0x01, bit32.rshift(first_word, 29)) - - -- set the message bit - subtree:add(f_message_bit, message_bit) - - -- read the obfuscation level - local obfuscation_bits = bit32.band(0x03, bit32.rshift(first_word, 27)) - subtree:add(f_obfuscation_level, obfuscation_bits) - - -- read the sequence number - subtree:add(f_sequence_number, bit32.band(first_word, SEQUENCE_NUMBER_MASK)) - - local payload_offset = 4 - - -- if the message bit is set, handle the second word - if message_bit == 1 then - payload_offset = 12 - - local second_word = buf(4, 4):le_uint() - - -- read message position from upper 2 bits - local message_position = bit32.rshift(second_word, 30) - local position = subtree:add(f_message_position, message_position) - - if message_positions[message_position] ~= nil then - -- if we know this position then add the name - position:append_text(" (".. message_positions[message_position] .. ")") - end - - -- read message number from lower 30 bits - subtree:add(f_message_number, bit32.band(second_word, 0x3FFFFFFF)) - - -- read the message part number - subtree:add(f_message_part_number, buf(8, 4):le_uint()) - end - - -- read the type - local packet_type = buf(payload_offset, 1):le_uint() - local ptype = subtree:add_le(f_type, buf(payload_offset, 1)) - if packet_types[packet_type] ~= nil then - subtree:add(f_type_text, packet_types[packet_type]) - -- if we know this packet type then add the name - ptype:append_text(" (".. packet_types[packet_type] .. ")") - end - - -- read the version - subtree:add_le(f_version, buf(payload_offset + 1, 1)) - - -- read node local ID - local sender_id = buf(payload_offset + 2, 2) - subtree:add_le(f_sender_id, sender_id) - - local i = payload_offset + 4 - - -- read HMAC MD5 hash - subtree:add(f_hmac_hash, buf(i, 16)) - i = i + 16 - - -- AvatarData or BulkAvatarDataPacket - if packet_types[packet_type] == "AvatarData" or packet_types[packet_type] == "BulkAvatarDataPacket" then - Dissector.get("hf-avatar"):call(buf(i):tvb(), pinfo, tree) - end - - if packet_types[packet_type] == "EntityEdit" then - Dissector.get("hf-entity"):call(buf(i):tvb(), pinfo, tree) - end - end - - -- return the size of the header - return buf:len() - -end - -function p_hfudt.init() - local udp_dissector_table = DissectorTable.get("udp.port") - - for port=1000, 65000 do - udp_dissector_table:add(port, p_hfudt) - end -end +print("Loading hfudt") + +-- create the HFUDT protocol +p_hfudt = Proto("hfudt", "HFUDT Protocol") + +-- create fields shared between packets in HFUDT +local f_data = ProtoField.string("hfudt.data", "Data") + +-- create the fields for data packets in HFUDT +local f_length = ProtoField.uint16("hfudt.length", "Length", base.DEC) +local f_control_bit = ProtoField.uint8("hfudt.control", "Control Bit", base.DEC) +local f_reliable_bit = ProtoField.uint8("hfudt.reliable", "Reliability Bit", base.DEC) +local f_message_bit = ProtoField.uint8("hfudt.message", "Message Bit", base.DEC) +local f_obfuscation_level = ProtoField.uint8("hfudt.obfuscation_level", "Obfuscation Level", base.DEC) +local f_sequence_number = ProtoField.uint32("hfudt.sequence_number", "Sequence Number", base.DEC) +local f_message_position = ProtoField.uint8("hfudt.message_position", "Message Position", base.DEC) +local f_message_number = ProtoField.uint32("hfudt.message_number", "Message Number", base.DEC) +local f_message_part_number = ProtoField.uint32("hfudt.message_part_number", "Message Part Number", base.DEC) +local f_type = ProtoField.uint8("hfudt.type", "Type", base.DEC) +local f_version = ProtoField.uint8("hfudt.version", "Version", base.DEC) +local f_type_text = ProtoField.string("hfudt.type_text", "TypeText") +local f_sender_id = ProtoField.uint16("hfudt.sender_id", "Sender ID", base.DEC) +local f_hmac_hash = ProtoField.bytes("hfudt.hmac_hash", "HMAC Hash") + +-- create the fields for control packets in HFUDT +local f_control_type = ProtoField.uint16("hfudt.control_type", "Control Type", base.DEC) +local f_control_type_text = ProtoField.string("hfudt.control_type_text", "Control Type Text", base.ASCII) +local f_ack_sequence_number = ProtoField.uint32("hfudt.ack_sequence_number", "ACKed Sequence Number", base.DEC) +local f_control_sub_sequence = ProtoField.uint32("hfudt.control_sub_sequence", "Control Sub-Sequence Number", base.DEC) +local f_nak_sequence_number = ProtoField.uint32("hfudt.nak_sequence_number", "NAKed Sequence Number", base.DEC) +local f_nak_range_end = ProtoField.uint32("hfudt.nak_range_end", "NAK Range End", base.DEC) + +local SEQUENCE_NUMBER_MASK = 0x07FFFFFF + +p_hfudt.fields = { + f_length, + f_control_bit, f_reliable_bit, f_message_bit, f_sequence_number, f_type, f_type_text, f_version, + f_sender_id, f_hmac_hash, + f_message_position, f_message_number, f_message_part_number, f_obfuscation_level, + f_control_type, f_control_type_text, f_control_sub_sequence, f_ack_sequence_number, f_nak_sequence_number, f_nak_range_end, + f_data +} + +local control_types = { + [0] = { "ACK", "Acknowledgement" }, + [1] = { "ACK2", "Acknowledgement of acknowledgement" }, + [2] = { "LightACK", "Light Acknowledgement" }, + [3] = { "NAK", "Loss report (NAK)" }, + [4] = { "TimeoutNAK", "Loss report re-transmission (TimeoutNAK)" }, + [5] = { "Handshake", "Handshake" }, + [6] = { "HandshakeACK", "Acknowledgement of Handshake" }, + [7] = { "ProbeTail", "Probe tail" }, + [8] = { "HandshakeRequest", "Request a Handshake" } +} + +local message_positions = { + [0] = "ONLY", + [1] = "LAST", + [2] = "FIRST", + [3] = "MIDDLE" +} + +local packet_types = { + [0] = "Unknown", + [1] = "StunResponse", + [2] = "DomainList", + [3] = "Ping", + [4] = "PingReply", + [5] = "KillAvatar", + [6] = "AvatarData", + [7] = "InjectAudio", + [8] = "MixedAudio", + [9] = "MicrophoneAudioNoEcho", + [10] = "MicrophoneAudioWithEcho", + [11] = "BulkAvatarData", + [12] = "SilentAudioFrame", + [13] = "DomainListRequest", + [14] = "RequestAssignment", + [15] = "CreateAssignment", + [16] = "DomainConnectionDenied", + [17] = "MuteEnvironment", + [18] = "AudioStreamStats", + [19] = "DomainServerPathQuery", + [20] = "DomainServerPathResponse", + [21] = "DomainServerAddedNode", + [22] = "ICEServerPeerInformation", + [23] = "ICEServerQuery", + [24] = "OctreeStats", + [25] = "Jurisdiction", + [26] = "AvatarIdentityRequest", + [27] = "AssignmentClientStatus", + [28] = "NoisyMute", + [29] = "AvatarIdentity", + [30] = "AvatarBillboard", + [31] = "DomainConnectRequest", + [32] = "DomainServerRequireDTLS", + [33] = "NodeJsonStats", + [34] = "OctreeDataNack", + [35] = "StopNode", + [36] = "AudioEnvironment", + [37] = "EntityEditNack", + [38] = "ICEServerHeartbeat", + [39] = "ICEPing", + [40] = "ICEPingReply", + [41] = "EntityData", + [42] = "EntityQuery", + [43] = "EntityAdd", + [44] = "EntityErase", + [45] = "EntityEdit", + [46] = "DomainServerConnectionToken", + [47] = "DomainSettingsRequest", + [48] = "DomainSettings", + [49] = "AssetGet", + [50] = "AssetGetReply", + [51] = "AssetUpload", + [52] = "AssetUploadReply", + [53] = "AssetGetInfo", + [54] = "AssetGetInfoReply" +} + +local unsourced_packet_types = { + ["DomainList"] = true +} + +function p_hfudt.dissector(buf, pinfo, tree) + + -- make sure this isn't a STUN packet - those don't follow HFUDT format + if pinfo.dst == Address.ip("stun.highfidelity.io") then return end + + -- validate that the packet length is at least the minimum control packet size + if buf:len() < 4 then return end + + -- create a subtree for HFUDT + subtree = tree:add(p_hfudt, buf(0)) + + -- set the packet length + subtree:add(f_length, buf:len()) + + -- pull out the entire first word + local first_word = buf(0, 4):le_uint() + + -- pull out the control bit and add it to the subtree + local control_bit = bit32.rshift(first_word, 31) + subtree:add(f_control_bit, control_bit) + + local data_length = 0 + + if control_bit == 1 then + -- dissect the control packet + pinfo.cols.protocol = p_hfudt.name .. " Control" + + -- remove the control bit and shift to the right to get the type value + local shifted_type = bit32.rshift(bit32.lshift(first_word, 1), 17) + local type = subtree:add(f_control_type, shifted_type) + + if control_types[shifted_type] ~= nil then + -- if we know this type then add the name + type:append_text(" (".. control_types[shifted_type][1] .. ")") + + subtree:add(f_control_type_text, control_types[shifted_type][1]) + end + + if shifted_type == 0 or shifted_type == 1 then + + -- this has a sub-sequence number + local second_word = buf(4, 4):le_uint() + subtree:add(f_control_sub_sequence, bit32.band(second_word, SEQUENCE_NUMBER_MASK)) + + local data_index = 8 + + if shifted_type == 0 then + -- if this is an ACK let's read out the sequence number + local sequence_number = buf(8, 4):le_uint() + subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) + + data_index = data_index + 4 + end + + data_length = buf:len() - data_index + + -- set the data from whatever is left in the packet + subtree:add(f_data, buf(data_index, data_length)) + + elseif shifted_type == 2 then + -- this is a Light ACK let's read out the sequence number + local sequence_number = buf(4, 4):le_uint() + subtree:add(f_ack_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) + + data_length = buf:len() - 4 + + -- set the data from whatever is left in the packet + subtree:add(f_data, buf(4, data_length)) + elseif shifted_type == 3 or shifted_type == 4 then + if buf:len() <= 12 then + -- this is a NAK pull the sequence number or range + local sequence_number = buf(4, 4):le_uint() + subtree:add(f_nak_sequence_number, bit32.band(sequence_number, SEQUENCE_NUMBER_MASK)) + + data_length = buf:len() - 4 + + if buf:len() > 8 then + local range_end = buf(8, 4):le_uint() + subtree:add(f_nak_range_end, bit32.band(range_end, SEQUENCE_NUMBER_MASK)) + + data_length = data_length - 4 + end + end + else + data_length = buf:len() - 4 + + -- no sub-sequence number, just read the data + subtree:add(f_data, buf(4, data_length)) + end + else + -- dissect the data packet + pinfo.cols.protocol = p_hfudt.name + + -- set the reliability bit + subtree:add(f_reliable_bit, bit32.rshift(first_word, 30)) + + local message_bit = bit32.band(0x01, bit32.rshift(first_word, 29)) + + -- set the message bit + subtree:add(f_message_bit, message_bit) + + -- read the obfuscation level + local obfuscation_bits = bit32.band(0x03, bit32.rshift(first_word, 27)) + subtree:add(f_obfuscation_level, obfuscation_bits) + + -- read the sequence number + subtree:add(f_sequence_number, bit32.band(first_word, SEQUENCE_NUMBER_MASK)) + + local payload_offset = 4 + + -- if the message bit is set, handle the second word + if message_bit == 1 then + payload_offset = 12 + + local second_word = buf(4, 4):le_uint() + + -- read message position from upper 2 bits + local message_position = bit32.rshift(second_word, 30) + local position = subtree:add(f_message_position, message_position) + + if message_positions[message_position] ~= nil then + -- if we know this position then add the name + position:append_text(" (".. message_positions[message_position] .. ")") + end + + -- read message number from lower 30 bits + subtree:add(f_message_number, bit32.band(second_word, 0x3FFFFFFF)) + + -- read the message part number + subtree:add(f_message_part_number, buf(8, 4):le_uint()) + end + + -- read the type + local packet_type = buf(payload_offset, 1):le_uint() + local ptype = subtree:add_le(f_type, buf(payload_offset, 1)) + local packet_type_text = packet_types[packet_type] + if packet_type_text ~= nil then + subtree:add(f_type_text, packet_type_text) + -- if we know this packet type then add the name + ptype:append_text(" (".. packet_type_text .. ")") + end + + -- read the version + subtree:add_le(f_version, buf(payload_offset + 1, 1)) + + local i = payload_offset + 2 + + if unsourced_packet_types[packet_type_text] == nil then + -- read node local ID + local sender_id = buf(payload_offset + 2, 2) + subtree:add_le(f_sender_id, sender_id) + i = i + 2 + + -- read HMAC MD5 hash + subtree:add(f_hmac_hash, buf(i, 16)) + i = i + 16 + end + + -- Domain packets + if packet_type_text == "DomainList" then + Dissector.get("hf-domain"):call(buf(i):tvb(), pinfo, tree) + end + + -- AvatarData or BulkAvatarDataPacket + if packet_type_text == "AvatarData" or packet_type_text == "BulkAvatarData" then + Dissector.get("hf-avatar"):call(buf(i):tvb(), pinfo, tree) + end + + if packet_type_text == "EntityEdit" then + Dissector.get("hf-entity"):call(buf(i):tvb(), pinfo, tree) + end + + if packet_types[packet_type] == "MicrophoneAudioNoEcho" or + packet_types[packet_type] == "MicrophoneAudioWithEcho" or + packet_types[packet_type] == "SilentAudioFrame" then + Dissector.get("hf-audio"):call(buf(i):tvb(), pinfo, tree) + end + end + + -- return the size of the header + return buf:len() + +end + +function p_hfudt.init() + local udp_dissector_table = DissectorTable.get("udp.port") + + for port=1000, 65000 do + udp_dissector_table:add(port, p_hfudt) + end +end diff --git a/tools/dissectors/2-hf-audio.lua b/tools/dissectors/2-hf-audio.lua new file mode 100644 index 0000000000..fa4d50fab1 --- /dev/null +++ b/tools/dissectors/2-hf-audio.lua @@ -0,0 +1,46 @@ +print("Loading hf-audio") + +-- create the audio protocol +p_hf_audio = Proto("hf-audio", "HF Audio Protocol") + +-- audio packet fields +local f_audio_sequence_number = ProtoField.uint16("hf_audio.sequence_number", "Sequence Number") +local f_audio_codec_size = ProtoField.uint32("hf_audio.codec_size", "Codec Size") +local f_audio_codec = ProtoField.string("hf_audio.codec", "Codec") +local f_audio_is_stereo = ProtoField.bool("hf_audio.is_stereo", "Is Stereo") +local f_audio_num_silent_samples = ProtoField.uint16("hf_audio.num_silent_samples", "Num Silent Samples") + +p_hf_audio.fields = { + f_audio_sequence_number, f_audio_codec_size, f_audio_codec, + f_audio_is_stereo, f_audio_num_silent_samples +} + +local packet_type_extractor = Field.new('hfudt.type_text') + +function p_hf_audio.dissector(buf, pinfo, tree) + pinfo.cols.protocol = p_hf_audio.name + + audio_subtree = tree:add(p_hf_audio, buf()) + + local i = 0 + + audio_subtree:add_le(f_audio_sequence_number, buf(i, 2)) + i = i + 2 + + -- figure out the number of bytes the codec name takes + local codec_name_bytes = buf(i, 4):le_uint() + audio_subtree:add_le(f_audio_codec_size, buf(i, 4)) + i = i + 4 + + audio_subtree:add(f_audio_codec, buf(i, codec_name_bytes)) + i = i + codec_name_bytes + + local packet_type = packet_type_extractor().value + if packet_type == "SilentAudioFrame" then + audio_subtree:add_le(f_audio_num_silent_samples, buf(i, 2)) + i = i + 2 + else + audio_subtree:add_le(f_audio_is_stereo, buf(i, 1)) + i = i + 1 + end +end diff --git a/tools/dissectors/hf-avatar.lua b/tools/dissectors/3-hf-avatar.lua similarity index 99% rename from tools/dissectors/hf-avatar.lua rename to tools/dissectors/3-hf-avatar.lua index 231fd1b354..af648ed5b9 100644 --- a/tools/dissectors/hf-avatar.lua +++ b/tools/dissectors/3-hf-avatar.lua @@ -1,3 +1,5 @@ +print("Loading hf-avatar") + -- create the avatar protocol p_hf_avatar = Proto("hf-avatar", "HF Avatar Protocol") diff --git a/tools/dissectors/4-hf-entity.lua b/tools/dissectors/4-hf-entity.lua new file mode 100644 index 0000000000..568eb5baa3 --- /dev/null +++ b/tools/dissectors/4-hf-entity.lua @@ -0,0 +1,99 @@ +print("Loading hf-entity") + +-- create the entity protocol +p_hf_entity = Proto("hf-entity", "HF Entity Protocol") + +-- entity packet fields +local f_entity_sequence_number = ProtoField.uint16("hf_entity.sequence_number", "Sequence Number") +local f_entity_timestamp = ProtoField.uint64("hf_entity.timestamp", "Timestamp") +local f_octal_code_three_bit_sections = ProtoField.uint8("hf_entity.octal_code_three_bit_sections", "Octal Code Three Bit Sections") +local f_octal_code = ProtoField.bytes("hf_entity.octal_code", "Octal Code") +local f_entity_id = ProtoField.guid("hf_entity.entity_id", "Entity ID") +local f_last_edited = ProtoField.uint64("hf_entity.last_edited", "Last Edited") +local f_coded_property_type = ProtoField.bytes("hf_entity.coded_property_type", "Coded Property Type") +local f_property_type = ProtoField.uint32("hf_entity.property_type", "Property Type") +local f_coded_update_delta = ProtoField.bytes("hf_entity.f_coded_update_delta", "Coded Update Delta") +local f_update_delta = ProtoField.uint32("hf_entity.update_delta", "Update Delta") + +p_hf_entity.fields = { + f_entity_sequence_number, f_entity_timestamp, + f_octal_code_three_bit_sections, f_octal_code, + f_last_edited, f_entity_id, + f_coded_property_type, f_property_type, + f_coded_update_delta, f_update_delta +} + +function p_hf_entity.dissector(buf, pinfo, tree) + pinfo.cols.protocol = p_hf_entity.name + + entity_subtree = tree:add(p_hf_entity, buf()) + + local i = 0 + + entity_subtree:add_le(f_entity_sequence_number, buf(i, 2)) + i = i + 2 + + entity_subtree:add_le(f_entity_timestamp, buf(i, 8)) + i = i + 8 + + -- figure out the number of three bit sections in the octal code + local octal_code_three_bit_sections = buf(i, 1):le_uint() + entity_subtree:add_le(f_octal_code_three_bit_sections, buf(i, 1)) + i = i + 1 + + -- read the bytes for the octal code + local octal_code_bytes = math.ceil((octal_code_three_bit_sections * 3) / 8) + entity_subtree:add_le(f_octal_code, buf(i, octal_code_bytes)) + i = i + octal_code_bytes + + -- read the last edited timestamp + entity_subtree:add_le(f_last_edited, buf(i, 8)) + i = i + 8 + + -- read the entity ID + entity_subtree:add(f_entity_id, buf(i, 16)) + i = i + 16 + + -- figure out the property type and the size of the coded value + local property_type, coded_property_bytes = number_of_coded_bytes(buf(i)) + entity_subtree:add(f_coded_property_type, buf(i, coded_property_bytes)) + entity_subtree:add(f_property_type, property_type) + i = i + coded_property_bytes + + -- figure out the update delta and the size of the coded value + local update_delta, coded_update_delta_bytes = number_of_coded_bytes(buf(i)) + entity_subtree:add(f_coded_update_delta, buf(i, coded_update_delta_bytes)) + entity_subtree:add(f_update_delta, update_delta) + i = i + coded_update_delta_bytes +end + +function number_of_coded_bytes(buf) + local coded_buffer = buf(0, 4):le_uint() -- max 64 bit value means max 10 header bits + + -- first figure out the total number of bytes for the coded value based + -- on the bits in the header + local total_coded_bytes = 1 + + for bit = 0, 10, 1 do + local header_bit = bit32.extract(coded_buffer, bit) + + if header_bit == 1 then + total_coded_bytes = total_coded_bytes + 1 + else + break + end + end + + -- pull out the bits and write them to our decoded value + local decoded_value = 0 + local decoded_position = 0 + local total_bits = total_coded_bytes * 8 + + for bit = total_coded_bytes, total_bits - 1, 1 do + local value_bit = bit32.extract(coded_buffer, total_bits - bit - 1) + decoded_value = bit32.replace(decoded_value, value_bit, decoded_position) + decoded_position = decoded_position + 1 + end + + return decoded_value, total_coded_bytes +end diff --git a/tools/dissectors/5-hf-domain.lua b/tools/dissectors/5-hf-domain.lua new file mode 100644 index 0000000000..093026bc92 --- /dev/null +++ b/tools/dissectors/5-hf-domain.lua @@ -0,0 +1,23 @@ +-- create the domain protocol +p_hf_domain = Proto("hf-domain", "HF Domain Protocol") + +-- domain packet fields +local f_domain_id = ProtoField.guid("hf_domain.domain_id", "Domain ID") +local f_domain_local_id = ProtoField.uint16("hf_domain.domain_local_id", "Domain Local ID") + +p_hf_domain.fields = { + f_domain_id, f_domain_local_id +} + +function p_hf_domain.dissector(buf, pinfo, tree) + pinfo.cols.protocol = p_hf_domain.name + + domain_subtree = tree:add(p_hf_domain, buf()) + + local i = 0 + + domain_subtree:add(f_domain_id, buf(i, 16)) + i = i + 16 + + domain_subtree:add_le(f_domain_local_id, buf(i, 2)) +end diff --git a/tools/dissectors/hf-entity.lua b/tools/dissectors/hf-entity.lua deleted file mode 100644 index f4de5a995d..0000000000 --- a/tools/dissectors/hf-entity.lua +++ /dev/null @@ -1,36 +0,0 @@ --- create the entity protocol -p_hf_entity = Proto("hf-entity", "HF Entity Protocol") - --- entity packet fields -local f_entity_sequence_number = ProtoField.uint16("hf_entity.sequence_number", "Sequence Number") -local f_entity_timestamp = ProtoField.uint64("hf_entity.timestamp", "Timestamp") -local f_octal_code_bytes = ProtoField.uint8("hf_entity.octal_code_bytes", "Octal Code Bytes") -local f_entity_id = ProtoField.guid("hf_entity.entity_id", "Entity ID") - -p_hf_entity.fields = { - f_entity_sequence_number, f_entity_timestamp, f_octal_code_bytes, f_entity_id -} - -function p_hf_entity.dissector(buf, pinfo, tree) - pinfo.cols.protocol = p_hf_entity.name - - entity_subtree = tree:add(p_hf_entity, buf()) - - i = 0 - - entity_subtree:add_le(f_entity_sequence_number, buf(i, 2)) - i = i + 2 - - entity_subtree:add_le(f_entity_timestamp, buf(i, 4)) - i = i + 4 - - -- figure out the number of bytes the octal code takes - local octal_code_bytes = buf(i, 1):le_uint() - entity_subtree:add_le(f_octal_code_bytes, buf(i, 1)) - - -- skip over the octal code - i = i + 1 + octal_code_bytes - - -- read the entity ID - entity_subtree:add(f_entity_id, buf(i, 16)) -end diff --git a/tools/jsdoc/.gitignore b/tools/jsdoc/.gitignore index c585e19389..148363ca03 100644 --- a/tools/jsdoc/.gitignore +++ b/tools/jsdoc/.gitignore @@ -1 +1,2 @@ -out \ No newline at end of file +out + diff --git a/tools/jsdoc/CMakeLists.txt b/tools/jsdoc/CMakeLists.txt index 4a6c18f243..1c4333983f 100644 --- a/tools/jsdoc/CMakeLists.txt +++ b/tools/jsdoc/CMakeLists.txt @@ -8,7 +8,7 @@ set(JSDOC_WORKING_DIR ${CMAKE_SOURCE_DIR}/tools/jsdoc) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/node_modules/.bin/jsdoc JSDOC_PATH) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/config.json JSDOC_CONFIG_PATH) file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/out OUTPUT_DIR) -file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR} NATIVE_JSDOC_WORKING_DIR) +file(TO_NATIVE_PATH ${JSDOC_WORKING_DIR}/root.js NATIVE_JSDOC_WORKING_DIR) add_custom_command(TARGET ${TARGET_NAME} COMMAND ${NPM_EXECUTABLE} --no-progress install && ${JSDOC_PATH} ${NATIVE_JSDOC_WORKING_DIR} -c ${JSDOC_CONFIG_PATH} -d ${OUTPUT_DIR} diff --git a/tools/jsdoc/README.md b/tools/jsdoc/README.md index 5cce6bb2a6..f9864a21e4 100644 --- a/tools/jsdoc/README.md +++ b/tools/jsdoc/README.md @@ -2,12 +2,27 @@ ##Prerequisites -* Install node.js +* Install node.js. * Install jsdoc via npm. `npm install jsdoc -g` +If you would like the extra functionality for gravPrep: +* Run npm install + To generate html documentation for the High Fidelity JavaScript API: * `cd tools/jsdoc` -* `jsdoc . -c config.json` +* `jsdoc root.js -c config.json` The out folder should contain index.html. + +To generate the grav automation files, run node gravPrep.js after you have made a JSdoc output folder. + +This will create files that are needed for hifi-grav and hifi-grav-content repos + +The md files for hifi-grav-content are located in out/grav/06.api-reference. + +The template twig html files for hifi-grav are located out/grav/templates. + +if you would like to copy these to a local version of the docs on your system you can run with the follows args: + +* node grav true "path/to/grav/" "path/to/grav/content" \ No newline at end of file diff --git a/tools/jsdoc/gravPrep.js b/tools/jsdoc/gravPrep.js new file mode 100644 index 0000000000..849837bae0 --- /dev/null +++ b/tools/jsdoc/gravPrep.js @@ -0,0 +1,671 @@ +// Dependencies +const htmlclean = require('htmlclean'); +const fs = require('fs'); +const path = require('path'); +const pretty = require('pretty'); +const cheerio = require('cheerio'); +const rimraf = require('rimraf'); +const dedent = require('dedent-js'); + +// Arg Vars +const copyLocal = process.argv[2]; +console.log("copyLocal:", copyLocal); +let targetTemplateDirectory = ''; +let targetMDDirectory = ''; +if (copyLocal) { + targetTemplateDirectory = process.argv[3]; + targetMDDirectory = process.argv[4];; +} + +// Required directories +let dir_out = path.join(__dirname, 'out'); + +let dir_grav = path.join(dir_out, 'grav'); +let dir_css = path.join(dir_grav, 'css'); +let dir_js = path.join(dir_grav, 'js'); +let dir_template = path.join(dir_grav, 'templates'); + +let dir_md = path.join(dir_grav, '06.api-reference'); +let dir_md_objects = path.join(dir_md, '02.Objects'); +let dir_md_namespaces = path.join(dir_md, '01.Namespaces'); +let dir_md_globals = path.join(dir_md, '03.Globals'); + +// Array to itterate over and create if doesn't exist +let dirArray = [dir_grav, dir_css, dir_js, dir_template, dir_md, dir_md_objects, dir_md_namespaces, dir_md_globals]; + +// Base Grouping Directories for MD files +let baseMDDirectories = ["API-Reference", "Globals", "Namespaces", "Objects"]; + +// Maps for directory names +let map_dir_md = { + "API-Reference": dir_md, + "Globals": dir_md_globals, + "Objects": dir_md_objects, + "Namespaces": dir_md_namespaces, + "Class": dir_md_objects, + "Namespace": dir_md_namespaces, + "Global": dir_md_globals +} + +// Map for Links +let map_links = { + "Global": "globals", + "Namespace": "namespaces", + "Class": "objects" +} + +// Mapping for GroupNames and Members +let groupNameMemberMap = { + "Objects": [], + "Namespaces": [], + "Globals": [] +} + +// Html variables to be handle regex replacements +const html_reg_static = /\(static\)<\/span>/g +const html_reg_title = /\.+?\<\/h1\>/g; +const html_reg_htmlExt = /\.html/g; +const html_reg_objectHeader = /
[\s\S]+?<\/header>/; +const html_reg_objectSpanNew = /
<\/h5>/; +const html_reg_propertiesHeaderEdit = '

Properties:

'; +const html_reg_propertiesHeaderEdit_Replace = '

Properties

'; +const html_reg_typeEdit = /(
Returns[\s\S]*?Type)(<\/dt[\s\S]*?type">)(.*?)(<\/span><\/dd>[\s\S]*?<\/dl>)/g; +const html_reg_typeEdit_replace = '$1: $3' +const html_reg_methodSize = /()/g; +const html_reg_methodSize_replace = ''; +const html_reg_findByName = '
` +const html_reg_signalTitle = `

Signals

`; +const html_reg_typeDefinitonsTitle = /

Type Definitions<\/h3>/; +const html_reg_typeDefinitonsTitle_replace = `

Type Definitions

` +const html_reg_classDefinitonsTitle = /

Classes<\/h3>/; +const html_reg_classDefinitonsTitle_replace = `

Classes

` +const html_reg_firstDivClose = ``; +const html_reg_allNonHTTPLinks = /()/g; +const html_reg_allHTTPLinks = /()/g; +const html_reg_pretty = /(
)([\s\S]*?)(<\/pre>)/g;
+const html_reg_pretty_replace = "
$2<\/pre>";
+const html_reg_availableIn = /([\s\S]+?Available in:[\s\S]+?<\/table>)/g;
+const html_reg_findControllerCuratedList = /
Functions<\/h5>[\s\S]*?

Input Recordings[\s\S]*?<\/ul>/g +const html_reg_findEntityMethods = /

Entity Methods:[\s\S]+?<\/ul>/g; +const html_reg_EntityMethodsHeader = '
Entity Methods:
'; +const html_reg_EntityMethodsHeader_replace = '
Entity Methods
'; +const html_reg_dlClassDetails = /
<\/dl>/g +const html_reg_typeDefType = /(
)(Type:)(<\/h5>[\s\S]*?)([\s\S]*?<\/ul>)/g; +const html_reg_typeDefType_replace = `
$2 $4
`; +const html_reg_returnSize = /
Returns:<\/h5>/g; +const html_reg_returnSize_replace = '
Returns:<\/h6>'; +const html_reg_depreciated = /(
[\s\S]+?)(
)([\s\S]+?)([\s\S]+?)(<\/ul>[\s\S]+?)(<\/dd>)/g; +const html_reg_depreciated_replace = '$1
$4
' + +// Procedural functions + +//remove .html from non http links +function removeHTML(match, p1, p2, p3) { + p2 = p2.replace(".html", ""); + return [p1, p2, p3].join(""); +} + +// Turn links to lower case that aren't part of IDs +function allLinksToLowerCase(match, p1, p2, p3) { + // split on id # and make sure only the preceding is lower case + if (p2.indexOf("#") > -1) { + p2 = p2.split("#"); + p2 = [p2[0].toLowerCase(), "#", p2[1]].join(""); + } else { + p2 = p2.toLowerCase(); + } + return [p1, p2, p3].join(""); +} + +// Helper for fixing formatting of page links +function fixLinkGrouping(match, p1, p2, p3) { + // Handle if referencing ID + let count = (p2.match(/\./g) || []).length; + if (p2.indexOf("#") > -1) { + let split = p2.split("#"); + if (count >= 2) { + // console.log("MULTI DOTS!"); + split = p2.split("."); + // This is a case where we are in an object page and there are multiple levels referenced (only doing 2 levels at the moment) + // console.log("split", split) + return [p1, "/api-reference/", returnRightGroup(split[1].slice(0, -1)), "/", split[1], ".", split[2], p3].join(""); + } + if (split[0] === "global") { + return [p1, "/api-reference/", "globals", "#", split[1], p3].join(""); + } + return [p1, "/api-reference/", returnRightGroup(split[0]), "/", p2, p3].join(""); + } else { + // Handle if there are member references + // console.log("count", count) + let split; + if (count === 1) { + split = p2.split("."); + return [p1, "/api-reference/", returnRightGroup(split[1]), "/", split[1], p3].join(""); + } + return [p1, "/api-reference/", returnRightGroup(p2), "/", p2, p3].join(""); + } +} + +// Return the right group for where the method or type came from +function returnRightGroup(methodToCheck) { + for (var key in groupNameMemberMap) { + for (i = 0; i < groupNameMemberMap[key].length; i++) { + if (methodToCheck.toLowerCase() === groupNameMemberMap[key][i].toLowerCase()) { + return key.toLowerCase(); + } else { + // console.log("Couldn't find group: ", methodToCheck); + } + } + } +} + +// Create the actual MD file +function createMD(title, directory, needsDir, isGlobal) { + let mdSource = makeMdSource(title); + + if (needsDir) { + if (!fs.existsSync(directory)) { + fs.mkdirSync(directory); + } + } + + let destinationMDFile = path.join(directory, `API_${title}.md`); + fs.writeFileSync(destinationMDFile, mdSource); +} + +// Create the actual Template file +function createTemplate(title, content) { + let twigBasePartial = makeTwigFile(content); + let destinationFile = path.join(dir_template, `API_${title}.html.twig`); + fs.writeFileSync(destinationFile, twigBasePartial); +} + +// Copy file from source to target - used for recurssive call +function copyFileSync(source, target) { + let targetFile = target; + + // If target is a directory a new file with the same name will be created + if (fs.existsSync(target)) { + if (fs.lstatSync(target).isDirectory()) { + targetFile = path.join(target, path.basename(source)); + } + } + + fs.writeFileSync(targetFile, fs.readFileSync(source)); +} + +// Copy file from source to target +function copyFolderRecursiveSync(source, target) { + var files = []; + + // Check if folder needs to be created or integrated + var targetFolder = path.join(target, path.basename(source)); + if (!fs.existsSync(targetFolder)) { + fs.mkdirSync(targetFolder); + } + + // Copy + if (fs.lstatSync(source).isDirectory()) { + files = fs.readdirSync(source); + files.forEach(function(file) { + var curSource = path.join(source, file); + if (fs.lstatSync(curSource).isDirectory()) { + copyFolderRecursiveSync(curSource, targetFolder); + } else { + copyFileSync(curSource, targetFolder); + } + }); + } +} + +// Clean up the Html +function prepareHtml(source) { + let htmlBefore = fs.readFileSync(source, { encoding: 'utf8' }); + let htmlAfter = htmlclean(htmlBefore); + let htmlAfterPretty = pretty(htmlAfter); + return cheerio.load(htmlAfterPretty); +} + +// Base file for MD's +function makeMdSource(title) { + return dedent( + ` + --- + title: ${title} + taxonomy: + category: + - docs + visible: true + highlight: + enabled: false + --- + ` + ) +} + +// Base file for Templates +function makeTwigFile(contentHtml) { + return dedent( + ` + {% extends 'partials/base_noGit.html.twig' %} + {% set tags = page.taxonomy.tag %} + {% if tags %} + {% set progress = page.collection({'items':{'@taxonomy':{'category': 'docs', 'tag': tags}},'order': {'by': 'default', 'dir': 'asc'}}) %} + {% else %} + {% set progress = page.collection({'items':{'@taxonomy':{'category': 'docs'}},'order': {'by': 'default', 'dir': 'asc'}}) %} + {% endif %} + + {% block navigation %} + + {% endblock %} + + {% block content %} +
+
+

{{ page.title }}

+ ${contentHtml} +
+
+ {% endblock %} + ` + ) +} + +// Handle NameSpace Group +function handleNamespace(title, content) { + let destinationDirectory = path.join(map_dir_md["Namespace"], title); + createMD(title, destinationDirectory, true); + createTemplate(title, content); +} + +// Handle Class Group +function handleClass(title, content) { + let destinationDirectory = path.join(map_dir_md["Class"], title); + createMD(title, destinationDirectory, true) + + let formatedHtml = content + .replace(html_reg_objectSpanNew, "") + createTemplate(title, formatedHtml); +} + +// Handle Global Group +function handleGlobal(title, content) { + createMD("Globals", map_dir_md["Global"], false, true); + createTemplate("Globals", content); +} + +// Handle Group TOCs +function makeGroupTOC(group) { + let mappedGroup; + if (!Array.isArray(group)) { + mappedGroup = groupNameMemberMap[group]; + } else { + mappedGroup = group; + } + let htmlGroup = mappedGroup.map(item => { + return dedent( + ` +
+ ${item} +
+ ` + ) + }) + return htmlGroup.join("\n"); + } + +// Handle Class TOCS +function makeClassTOC(group){ + let linkArray = [] + group.forEach( item => { + linkArray.push(`
${item.type}
`) + item.array.forEach( link => { + if ( link.indexOf('.') > -1 ){ + linkArray.push(``); + } else { + linkArray.push(``); + + } + }) + linkArray.push("
"); + }) + return linkArray.join("\n"); +} + +// Extract IDS for TOC +function extractIDs(groupToExtract){ + let firstLine = ""; + let id = ""; + let extractedIDs = []; + groupToExtract.forEach((item)=>{ + firstLine = item.split("\n")[0]; + try { + id = firstLine.split('id="')[1].split(`"`)[0]; + } catch (e){ + id = ""; + } + if (id){ + extractedIDs.push(id) + } + }) + return extractedIDs; +} + +// Helper for splitting up html +// Takes: Content to split, SearchTerm to Split by, and term to End Splitting By +// Returns: [newContent after Split, Array of extracted ] +function splitBy(content, searchTerm, endSplitTerm, title){ + let foundArray = []; + let curIndex = -1; + let afterCurSearchIndex = -1 + let nextIndex = 0; + let findbyNameLength = searchTerm.length; + let curEndSplitTermIndex = -1; + let classHeader; + do { + // Find the index of where to stop searching + curEndSplitTermIndex = content.indexOf(endSplitTerm); + // console.log("curEndSplitTermIndex", curEndSplitTermIndex) + // Find the index of the the next Search term + curIndex = content.indexOf(searchTerm); + // console.log("curIndex", curIndex) + + // The index of where the next search will start + afterCurSearchIndex = curIndex+findbyNameLength; + // Find the content of the next Index + nextIndex = content.indexOf(searchTerm,afterCurSearchIndex); + // If the next index isn't found, then next index === index of the end term + if (nextIndex === -1){ + nextIndex = curEndSplitTermIndex; + } + if (curIndex > curEndSplitTermIndex){ + break; + } + // Push from the cur index to the next found || the end term + let contentSlice = content.slice(curIndex, nextIndex); + if (contentSlice.indexOf(`id="${title}"`) === -1){ + foundArray.push(contentSlice); + } else { + classHeader = contentSlice; + } + + // Remove that content + content = content.replace(contentSlice, ""); + + curEndSplitTermIndex = content.indexOf(endSplitTerm); + nextIndex = content.indexOf(searchTerm,afterCurSearchIndex); + // Handle if nextIndex goes beyond endSplitTerm + if (nextIndex > curEndSplitTermIndex) { + curIndex = content.indexOf(searchTerm); + contentSlice = content.slice(curIndex, curEndSplitTermIndex); + if (contentSlice.indexOf(`id="${title}"`) === -1){ + foundArray.push(contentSlice); + } + content = content.replace(contentSlice, ""); + break; + } + } while (curIndex > -1) + if (classHeader){ + content = append(content, html_reg_findByArticleClose, classHeader, true); + } + return [content, foundArray]; +} + +// Split the signals and methods [Might make this more generic] +function splitMethodsSignals(allItemToSplit){ + let methodArray = []; + let signalArray = []; + + allItemToSplit.forEach( (content, index) => { + firstLine = content.split("\n")[0]; + if (firstLine.indexOf("{Signal}") > -1){ + signalArray.push(content); + } else if (firstLine.indexOf("span") > -1) { + methodArray.push(content); + } else { + } + }) + return [methodArray, signalArray]; +} + +// Helper to append +// Takes content, the search term to appendTo, the content to append, +// and bool if the append is before the found area +function append(content, searchTermToAppendto, contentToAppend, appendBefore){ + let contentArray = content.split("\n"); + let foundIndex = findArrayTrim(contentArray, searchTermToAppendto) + foundIndex = appendBefore ? foundIndex : foundIndex +1 + + contentArray.splice(foundIndex,0,contentToAppend) + return contentArray.join("\n") +} + +// Helper function for append +function findArrayTrim(array, searchTerm){ + var index = -1; + for (var i = 0; i < array.length; i++){ + index = array[i].trim().indexOf(searchTerm.trim()); + if (index > -1){ + return i + } + } + return index; +} + +// Remove grav directory if exists to make sure old files aren't kept +if (fs.existsSync(dir_grav)){ + console.log("dir_grav exists"); + rimraf.sync(dir_grav); +} + +// Create Grav directories in JSDOC output +dirArray.forEach(function(dir){ + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); + } +}) + +// Create baseMD files +baseMDDirectories.forEach( md => { + createMD(md, map_dir_md[md]); +}) + +// Read jsdoc output folder and process html files +let files = fs.readdirSync(dir_out); + +// Create initial Group name member map to handle individual link +files.forEach(function (file){ + let curSource = path.join(dir_out, file); + if (path.extname(curSource) == ".html" && path.basename(curSource, '.html') !== "index") { + let loadedHtml = prepareHtml(curSource); + let splitTitle = loadedHtml("title").text().split(": "); + let groupName = splitTitle[1]; + let htmlTitle = splitTitle.pop(); + switch(groupName){ + case "Namespace": + groupNameMemberMap["Namespaces"].push(htmlTitle); + break; + case "Class": + groupNameMemberMap["Objects"].push(htmlTitle); + break; + default: + console.log(`Case not handled for ${groupName}`); + } + } +}) +files.forEach(function (file, index){ + // For testing individual files + // if (index !== 59) return; + let curSource = path.join(dir_out, file); + if (path.extname(curSource) == ".html" && path.basename(curSource, '.html') !== "index") { + // Clean up the html source + let loadedHtml = prepareHtml(curSource); + + // Extract the title, group name, and the main div + let splitTitle = loadedHtml("title").text().split(": "); + let groupName = splitTitle[1]; + let htmlTitle = splitTitle.pop(); + console.log("html title", htmlTitle) + let mainDiv = loadedHtml("#main") + + let methodIDs = []; + let signalIDs = []; + let typeDefIDs = []; + // Basic Regex HTML edits + let currentContent = mainDiv.html() + .replace(html_reg_findByMethod, "") //Remove Method title to be remade later + .replace(html_reg_static,"") // Remove static from the file names + .replace(html_reg_title,"") // Remove title + .replace(html_reg_objectHeader,"") // Remove extra Object Header + .replace(html_reg_dlClassDetails, "") // Remove unneccsary dlClassDetails Tag + .replace(html_reg_allNonHTTPLinks, removeHTML) // Remove the .html extension from all links + .replace(html_reg_allNonHTTPLinks, allLinksToLowerCase) // Turn all links into lowercase before ID tags + .replace(html_reg_allNonHTTPLinks, fixLinkGrouping) // Make sure links refer to correct grouping + .replace(html_reg_propertiesHeaderEdit, html_reg_propertiesHeaderEdit_Replace) // Remove : from Properties + .replace(html_reg_typeEdit, html_reg_typeEdit_replace) // Put type on the same line + .replace(html_reg_returnSize, html_reg_returnSize_replace) // Make return size h6 instead of h5 + .replace(html_reg_methodSize, html_reg_methodSize_replace) // Make method size into h5 + .replace(html_reg_pretty, html_reg_pretty_replace) // remove the references to pretty + .replace(html_reg_classDefinitonsTitle, html_reg_classDefinitonsTitle_replace) // Change the class def titles + .replace(html_reg_depreciated, html_reg_depreciated_replace); // format depreciated better + + // Further HTML Manipulation + // Make end term either Type Definitions or by the article + let endTerm; + let foundTypeDefinitions; + let foundSignalsAndMethods; + if (currentContent.indexOf("Type Definitions") > -1){ + // console.log("Found Type Definitions"); + endTerm = `

Type Definitions

`; + // Split HTML by Each named entry + let contentSplitArray = splitBy(currentContent, html_reg_findByName, endTerm, htmlTitle); + foundSignalsAndMethods = contentSplitArray[1]; + // console.log("foundSignalsAndMethods", foundSignalsAndMethods) + // Create a reference to the current content after split and the split functions + currentContent = contentSplitArray[0] + .replace(html_reg_typeDefType, html_reg_typeDefType_replace) // Edit how the typedef type looks + .replace(html_reg_typeDefinitonsTitle, ""); // Remove Type Definitions Title to be remade later; + endTerm = html_reg_findByArticleClose; + // Grab split Type Definitions + let contentSplitArrayForTypeDefs = splitBy(currentContent, html_reg_findByName, endTerm, htmlTitle); + currentContent = contentSplitArrayForTypeDefs[0]; + foundTypeDefinitions = contentSplitArrayForTypeDefs[1]; + // console.log("foundTypeDefinitions", foundTypeDefinitions) + + } else { + endTerm = html_reg_findByArticleClose; + let contentSplitArray = splitBy(currentContent, html_reg_findByName, endTerm, htmlTitle); + foundSignalsAndMethods = contentSplitArray[1]; + currentContent = contentSplitArray[0]; + } + + // Create references to the split methods and signals + let processedMethodsSignalsAndTypeDefs = splitMethodsSignals(foundSignalsAndMethods); + let splitMethods = processedMethodsSignalsAndTypeDefs[0]; + let splitSignals = processedMethodsSignalsAndTypeDefs[1]; + let splitTypeDefinitionIDS; + let splitMethodIDS = extractIDs(splitMethods); + let splitSignalIDS = extractIDs(splitSignals); + if (foundTypeDefinitions){ + splitTypeDefinitionIDS = extractIDs(foundTypeDefinitions); + } + let arrayToPassToClassToc = []; + + if (splitMethods.length > 0) { + arrayToPassToClassToc.push({type: "Methods", array: splitMethodIDS}); + // Add the Methods header to the Methods HTML + splitMethods.unshift(html_reg_findByMethod_replace) + currentContent = append(currentContent, html_reg_findByArticleClose, splitMethods.join('\n'), true); + } + if (splitSignals.length > 0) { + arrayToPassToClassToc.push({type: "Signals", array: splitSignalIDS}); + // Add the Signals header to the Signals HTML + splitSignals.unshift(html_reg_signalTitle) + currentContent = append(currentContent, html_reg_findByArticleClose, splitSignals.join('\n'),true); + } + if (foundTypeDefinitions && foundTypeDefinitions.length > 0) { + arrayToPassToClassToc.push({type: "Type Definitions", array: splitTypeDefinitionIDS}); + // Add the Type Defs header to the Type Defs HTML + foundTypeDefinitions.unshift(html_reg_typeDefinitonsTitle_replace) + currentContent = append(currentContent, html_reg_findByArticleClose, foundTypeDefinitions.join('\n'), true); + } + + let classTOC = makeClassTOC(arrayToPassToClassToc); + if (groupName === "Global"){ + currentContent = append(currentContent, html_reg_findByTitle, classTOC); + } else if (htmlTitle === "Controller") { + let curatedList = currentContent.match(html_reg_findControllerCuratedList); + currentContent = currentContent.replace(html_reg_findControllerCuratedList, ""); + let entityMethods = currentContent.match(html_reg_findEntityMethods); + currentContent = currentContent.replace(html_reg_findEntityMethods, ""); + currentContent = append(currentContent, html_reg_firstDivClose, [classTOC, curatedList, entityMethods].join("\n")); + currentContent = currentContent.replace(html_reg_EntityMethodsHeader, html_reg_EntityMethodsHeader_replace); + } else { + currentContent = append(currentContent, html_reg_firstDivClose, classTOC); + } + + // Final Pretty Content + currentContent = htmlclean(currentContent); + currentContent = pretty(currentContent); + + // Handle Unique Categories + switch(groupName){ + case "Namespace": + handleNamespace(htmlTitle, currentContent); + break; + case "Class": + handleClass(htmlTitle, currentContent); + break; + case "Global": + handleGlobal(htmlTitle, currentContent); + break; + default: + console.log(`Case not handled for ${groupName}`); + } + } +}) + +// Create the base Templates after processing individual files +createTemplate("API-Reference", makeGroupTOC(["Namespaces", "Objects", "Globals"])); +createTemplate("Namespaces", makeGroupTOC("Namespaces")); +createTemplate("Objects", makeGroupTOC("Objects")); + +// Copy the files to the target Directories if Local +if (copyLocal){ + // Copy files to the Twig Directory + let templateFiles = fs.readdirSync(path.resolve(targetTemplateDirectory)); + // Remove Existing API files + templateFiles.forEach(function(file){ + let curSource = path.join(targetTemplateDirectory, file); + + if(path.basename(file, '.html').indexOf("API") > -1){ + fs.unlink(curSource); + } + + }) + copyFolderRecursiveSync(dir_template, targetTemplateDirectory); + + // Copy files to the Md Directory + let baseMdRefDir = path.join(targetMDDirectory,"06.api-reference"); + // Remove existing MD directory + if (fs.existsSync(baseMdRefDir)){ + rimraf.sync(baseMdRefDir); + } + copyFolderRecursiveSync(dir_md, targetMDDirectory); +} \ No newline at end of file diff --git a/tools/jsdoc/package.json b/tools/jsdoc/package.json index 215ceec177..4bbb2ad4f2 100644 --- a/tools/jsdoc/package.json +++ b/tools/jsdoc/package.json @@ -1,7 +1,14 @@ { "name": "hifiJSDoc", "dependencies": { - "jsdoc": "^3.5.5" + "axios": "^0.18.0", + "cheerio": "^1.0.0-rc.2", + "dedent-js": "^1.0.1", + "htmlclean": "^3.0.8", + "jsdoc": "^3.5.5", + "pretty": "^2.0.0", + "request": "^2.85.0", + "rimraf": "^2.6.2" }, "private": true } diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index 049d32cceb..76f33e2c73 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -153,4 +153,4 @@ exports.defineTags = function (dictionary) { doclet.hifiServerEntity = true; } }); -}; +}; \ No newline at end of file diff --git a/tools/oven/src/BakerCLI.cpp b/tools/oven/src/BakerCLI.cpp index 0db70f6fe4..ff672d13bf 100644 --- a/tools/oven/src/BakerCLI.cpp +++ b/tools/oven/src/BakerCLI.cpp @@ -31,8 +31,8 @@ BakerCLI::BakerCLI(OvenCLIApplication* parent) : QObject(parent) { void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString& type) { // if the URL doesn't have a scheme, assume it is a local file - if (inputUrl.scheme() != "http" && inputUrl.scheme() != "https" && inputUrl.scheme() != "ftp") { - inputUrl.setScheme("file"); + if (inputUrl.scheme() != "http" && inputUrl.scheme() != "https" && inputUrl.scheme() != "ftp" && inputUrl.scheme() != "file") { + inputUrl = QUrl::fromLocalFile(inputUrl.toString()); } qDebug() << "Baking file type: " << type; diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index c3fec2d15e..52b6db1aa5 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -25,12 +25,6 @@ Oven* Oven::_staticInstance { nullptr }; Oven::Oven() { _staticInstance = this; - // enable compression in image library - image::setColorTexturesCompressionEnabled(true); - image::setGrayscaleTexturesCompressionEnabled(true); - image::setNormalTexturesCompressionEnabled(true); - image::setCubeTexturesCompressionEnabled(true); - // setup our worker threads setupWorkerThreads(QThread::idealThreadCount()); diff --git a/tools/oven/src/OvenCLIApplication.cpp b/tools/oven/src/OvenCLIApplication.cpp index 6f87359134..c405c5f4a0 100644 --- a/tools/oven/src/OvenCLIApplication.cpp +++ b/tools/oven/src/OvenCLIApplication.cpp @@ -15,6 +15,7 @@ #include #include +#include #include "BakerCLI.h" @@ -47,10 +48,7 @@ OvenCLIApplication::OvenCLIApplication(int argc, char* argv[]) : if (parser.isSet(CLI_DISABLE_TEXTURE_COMPRESSION_PARAMETER)) { qDebug() << "Disabling texture compression"; - image::setColorTexturesCompressionEnabled(false); - image::setGrayscaleTexturesCompressionEnabled(false); - image::setNormalTexturesCompressionEnabled(false); - image::setCubeTexturesCompressionEnabled(false); + TextureBaker::setCompressionEnabled(false); } QMetaObject::invokeMethod(cli, "bakeFile", Qt::QueuedConnection, Q_ARG(QUrl, inputUrl), diff --git a/unpublishedScripts/interaction/Interaction.js b/unpublishedScripts/interaction/Interaction.js deleted file mode 100644 index bb763c01e7..0000000000 --- a/unpublishedScripts/interaction/Interaction.js +++ /dev/null @@ -1,179 +0,0 @@ -// -// Interaction.js -// scripts/interaction -// -// Created by Trevor Berninger on 3/20/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 -// - -(function(){ - print("loading interaction script"); - - var Avatar = false; - var NPC = false; - var previousNPC = false; - var hasCenteredOnNPC = false; - var distance = 10; - var r = 8; - var player = false; - - var baselineX = 0; - var baselineY = 0; - var nodRange = 20; - var shakeRange = 20; - - var ticker = false; - var heartbeatTimer = false; - - function callOnNPC(message) { - if(NPC) - Messages.sendMessage("interactionComs", NPC + ":" + message); - else - Messages.sendMessage("interactionComs", previousNPC + ":" + message); - } - - LimitlessSpeechRecognition.onFinishedSpeaking.connect(function(speech) { - print("Got: " + speech); - callOnNPC("voiceData:" + speech); - }); - - LimitlessSpeechRecognition.onReceivedTranscription.connect(function(speech) { - callOnNPC("speaking"); - }); - - function setBaselineRotations(rot) { - baselineX = rot.x; - baselineY = rot.y; - } - - function findLookedAtNPC() { - var intersection = AvatarList.findRayIntersection({origin: MyAvatar.position, direction: Quat.getFront(Camera.getOrientation())}, true); - if (intersection.intersects && intersection.distance <= distance){ - var npcAvatar = AvatarList.getAvatar(intersection.avatarID); - if (npcAvatar.displayName.search("NPC") != -1) { - setBaselineRotations(Quat.safeEulerAngles(Camera.getOrientation())); - return intersection.avatarID; - } - } - return false; - } - - function isStillFocusedNPC() { - var avatar = AvatarList.getAvatar(NPC); - if (avatar) { - var avatarPosition = avatar.position; - return Vec3.distance(MyAvatar.position, avatarPosition) <= distance && Math.abs(Quat.dot(Camera.getOrientation(), Quat.lookAtSimple(MyAvatar.position, avatarPosition))) > 0.6; - } - return false; // NPC reference died. Maybe it crashed or we teleported to a new world? - } - - function onWeLostFocus() { - print("lost NPC: " + NPC); - callOnNPC("onLostFocused"); - var baselineX = 0; - var baselineY = 0; - } - - function onWeGainedFocus() { - print("found NPC: " + NPC); - callOnNPC("onFocused"); - var rotation = Quat.safeEulerAngles(Camera.getOrientation()); - baselineX = rotation.x; - baselineY = rotation.y; - LimitlessSpeechRecognition.setListeningToVoice(true); - } - - function checkFocus() { - var newNPC = findLookedAtNPC(); - - if (NPC && newNPC != NPC && !isStillFocusedNPC()) { - onWeLostFocus(); - previousNPC = NPC; - NPC = false; - } - if (!NPC && newNPC != false) { - NPC = newNPC; - onWeGainedFocus(); - } - } - - function checkGesture() { - var rotation = Quat.safeEulerAngles(Camera.getOrientation()); - - var deltaX = Math.abs(rotation.x - baselineX); - if (deltaX > 180) { - deltaX -= 180; - } - var deltaY = Math.abs(rotation.y - baselineY); - if (deltaY > 180) { - deltaY -= 180; - } - - if (deltaX >= nodRange && deltaY <= shakeRange) { - callOnNPC("onNodReceived"); - } else if (deltaY >= shakeRange && deltaX <= nodRange) { - callOnNPC("onShakeReceived"); - } - } - - function tick() { - checkFocus(); - if (NPC) { - checkGesture(); - } - } - - function heartbeat() { - callOnNPC("beat"); - } - - Messages.subscribe("interactionComs"); - - Messages.messageReceived.connect(function (channel, message, sender) { - if(channel === "interactionComs" && player) { - var codeIndex = message.search('clientexec'); - if(codeIndex != -1) { - var code = message.substr(codeIndex+11); - Script.evaluate(code, ''); - } - } - }); - - this.enterEntity = function(id) { - player = true; - print("Something entered me: " + id); - LimitlessSpeechRecognition.setAuthKey("testKey"); - if (!ticker) { - ticker = Script.setInterval(tick, 333); - } - if(!heartbeatTimer) { - heartbeatTimer = Script.setInterval(heartbeat, 1000); - } - }; - this.leaveEntity = function(id) { - LimitlessSpeechRecognition.setListeningToVoice(false); - player = false; - print("Something left me: " + id); - if (previousNPC) - Messages.sendMessage("interactionComs", previousNPC + ":leftArea"); - if (ticker) { - ticker.stop(); - ticker = false; - } - if (heartbeatTimer) { - heartbeatTimer.stop(); - heartbeatTimer = false; - } - }; - this.unload = function() { - print("Okay. I'm Unloading!"); - if (ticker) { - ticker.stop(); - ticker = false; - } - }; - print("finished loading interaction script"); -}); diff --git a/unpublishedScripts/interaction/NPCHelpers.js b/unpublishedScripts/interaction/NPCHelpers.js deleted file mode 100644 index 188178b281..0000000000 --- a/unpublishedScripts/interaction/NPCHelpers.js +++ /dev/null @@ -1,179 +0,0 @@ -// -// NPCHelpers.js -// scripts/interaction -// -// Created by Trevor Berninger on 3/20/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 -// - -var audioInjector = false; -var blocked = false; -var playingResponseAnim = false; -var storyURL = ""; -var _qid = "start"; - -print("TESTTEST"); - -function strContains(str, sub) { - return str.search(sub) != -1; -} - -function callbackOnCondition(conditionFunc, ms, callback, count) { - var thisCount = 0; - if (typeof count !== 'undefined') { - thisCount = count; - } - if (conditionFunc()) { - callback(); - } else if (thisCount < 10) { - Script.setTimeout(function() { - callbackOnCondition(conditionFunc, ms, callback, thisCount + 1); - }, ms); - } else { - print("callbackOnCondition timeout"); - } -} - -function playAnim(animURL, looping, onFinished) { - print("got anim: " + animURL); - print("looping: " + looping); - // Start caching the animation if not already cached. - AnimationCache.getAnimation(animURL); - - // Tell the avatar to animate so that we can tell if the animation is ready without crashing - Avatar.startAnimation(animURL, 30, 1, false, false, 0, 1); - - // Continually check if the animation is ready - callbackOnCondition(function(){ - var details = Avatar.getAnimationDetails(); - // if we are running the request animation and are past the first frame, the anim is loaded properly - print("running: " + details.running); - print("url and animURL: " + details.url.trim().replace(/ /g, "%20") + " | " + animURL.trim().replace(/ /g, "%20")); - print("currentFrame: " + details.currentFrame); - return details.running && details.url.trim().replace(/ /g, "%20") == animURL.trim().replace(/ /g, "%20") && details.currentFrame > 0; - }, 250, function(){ - var timeOfAnim = ((AnimationCache.getAnimation(animURL).frames.length / 30) * 1000) + 100; // frames to miliseconds plus a small buffer - print("animation loaded. length: " + timeOfAnim); - // Start the animation again but this time with frame information - Avatar.startAnimation(animURL, 30, 1, looping, true, 0, AnimationCache.getAnimation(animURL).frames.length); - if (typeof onFinished !== 'undefined') { - print("onFinished defined. setting the timeout with timeOfAnim"); - timers.push(Script.setTimeout(onFinished, timeOfAnim)); - } - }); -} - -function playSound(soundURL, onFinished) { - callbackOnCondition(function() { - return SoundCache.getSound(soundURL).downloaded; - }, 250, function() { - if (audioInjector) { - audioInjector.stop(); - } - audioInjector = Audio.playSound(SoundCache.getSound(soundURL), {position: Avatar.position, volume: 1.0}); - if (typeof onFinished !== 'undefined') { - audioInjector.finished.connect(onFinished); - } - }); -} - -function npcRespond(soundURL, animURL, onFinished) { - if (typeof soundURL !== 'undefined' && soundURL != '') { - print("npcRespond got soundURL!"); - playSound(soundURL, function(){ - print("sound finished"); - var animDetails = Avatar.getAnimationDetails(); - print("animDetails.lastFrame: " + animDetails.lastFrame); - print("animDetails.currentFrame: " + animDetails.currentFrame); - if (animDetails.lastFrame < animDetails.currentFrame + 1 || !playingResponseAnim) { - onFinished(); - } - audioInjector = false; - }); - } - if (typeof animURL !== 'undefined' && animURL != '') { - print("npcRespond got animURL!"); - playingResponseAnim = true; - playAnim(animURL, false, function() { - print("anim finished"); - playingResponseAnim = false; - print("injector: " + audioInjector); - if (!audioInjector || !audioInjector.isPlaying()) { - print("resetting Timer"); - print("about to call onFinished"); - onFinished(); - } - }); - } -} - -function npcRespondBlocking(soundURL, animURL, onFinished) { - print("blocking response requested"); - if (!blocked) { - print("not already blocked"); - blocked = true; - npcRespond(soundURL, animURL, function(){ - if (onFinished){ - onFinished(); - }blocked = false; - }); - } -} - -function npcContinueStory(soundURL, animURL, nextID, onFinished) { - if (!nextID) { - nextID = _qid; - } - npcRespondBlocking(soundURL, animURL, function(){ - if (onFinished){ - onFinished(); - }setQid(nextID); - }); -} - -function setQid(newQid) { - print("setting quid"); - print("_qid: " + _qid); - _qid = newQid; - print("_qid: " + _qid); - doActionFromServer("init"); -} - -function runOnClient(code) { - Messages.sendMessage("interactionComs", "clientexec:" + code); -} - -function doActionFromServer(action, data, useServerCache) { - if (action == "start") { - ignoreCount = 0; - _qid = "start"; - } - var xhr = new XMLHttpRequest(); - xhr.open("POST", "http://gserv_devel.studiolimitless.com/story", true); - xhr.onreadystatechange = function(){ - if (xhr.readyState == 4){ - if (xhr.status == 200) { - print("200!"); - print("evaluating: " + xhr.responseText); - Script.evaluate(xhr.responseText, ""); - } else if (xhr.status == 444) { - print("Limitless Serv 444: API error: " + xhr.responseText); - } else { - print("HTTP Code: " + xhr.status + ": " + xhr.responseText); - } - } - }; - xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); - var postData = "url=" + storyURL + "&action=" + action + "&qid=" + _qid; - if (typeof data !== 'undefined' && data != '') { - postData += "&data=" + data; - } - if (typeof useServerCache !== 'undefined' && !useServerCache) { - postData += "&nocache=true"; - } - print("Sending: " + postData); - xhr.send(postData); -} diff --git a/unpublishedScripts/interaction/NPC_AC.js b/unpublishedScripts/interaction/NPC_AC.js deleted file mode 100644 index eb2d9f4caf..0000000000 --- a/unpublishedScripts/interaction/NPC_AC.js +++ /dev/null @@ -1,102 +0,0 @@ -// -// NPC_AC.js -// scripts/interaction -// -// Created by Trevor Berninger on 3/20/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 -// - -var currentlyUsedIndices = []; -var timers = []; -var currentlyEngaged = false; -var questionNumber = 0; -var heartbeatTimeout = false; -function getRandomRiddle() { - var randIndex = null; - do { - randIndex = Math.floor(Math.random() * 15) + 1; - } while (randIndex in currentlyUsedIndices); - - currentlyUsedIndices.push(randIndex); - return randIndex.toString(); -} - -Script.include("https://raw.githubusercontent.com/Delamare2112/hifi/Interaction/unpublishedScripts/interaction/NPCHelpers.js", function(){ - print("NPCHelpers included.");main(); -}); - -var idleAnim = "https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/idle.fbx"; -var FST = "https://s3.amazonaws.com/hifi-public/tony/fixed-sphinx/sphinx.fst"; - -Agent.isAvatar = true; -Avatar.skeletonModelURL = FST; -Avatar.displayName = "NPC"; -Avatar.position = {x: 0.3, y: -23.4, z: 8.0}; -Avatar.orientation = {x: 0, y: 1, z: 0, w: 0}; -// Avatar.position = {x: 1340.3555, y: 4.078, z: -420.1562}; -// Avatar.orientation = {x: 0, y: -0.707, z: 0, w: 0.707}; -Avatar.scale = 2; - -Messages.subscribe("interactionComs"); - -function endInteraction() { - print("ending interaction"); - blocked = false; - currentlyEngaged = false; - if(audioInjector) - audioInjector.stop(); - for (var t in timers) { - Script.clearTimeout(timers[t]); - } - if(_qid != "Restarting") { - npcRespondBlocking( - 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/EarlyExit_0' + (Math.floor(Math.random() * 2) + 1).toString() + '.wav', - 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/reversedSphinx.fbx', - function(){ - Avatar.startAnimation('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Hifi_Sphinx_Anim_Entrance_Kneel_Combined_with_Intro.fbx', 0); - } - ); - } -} - -function main() { - storyURL = "https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Sphinx.json"; - Messages.messageReceived.connect(function (channel, message, sender) { - if(!strContains(message, 'beat')) - print(sender + " -> NPC @" + Agent.sessionUUID + ": " + message); - if (channel === "interactionComs" && strContains(message, Agent.sessionUUID)) { - if (strContains(message, 'beat')) { - if(heartbeatTimeout) { - Script.clearTimeout(heartbeatTimeout); - heartbeatTimeout = false; - } - heartbeatTimeout = Script.setTimeout(endInteraction, 1500); - } - else if (strContains(message, "onFocused") && !currentlyEngaged) { - blocked = false; - currentlyEngaged = true; - currentlyUsedIndices = []; - doActionFromServer("start"); - } else if (strContains(message, "leftArea")) { - - } else if (strContains(message, "speaking")) { - - } else { - var voiceDataIndex = message.search("voiceData"); - if (voiceDataIndex != -1) { - var words = message.substr(voiceDataIndex+10); - if (!isNaN(_qid) && (strContains(words, "repeat") || (strContains(words, "say") && strContains(words, "again")))) { - doActionFromServer("init"); - } else { - doActionFromServer("words", words); - } - } - } - } - }); - // Script.update.connect(updateGem); - Avatar.startAnimation("https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Hifi_Sphinx_Anim_Entrance_Kneel_Combined_with_Intro.fbx", 0); -} diff --git a/unpublishedScripts/interaction/Sphinx.json b/unpublishedScripts/interaction/Sphinx.json deleted file mode 100644 index 2a76417fd7..0000000000 --- a/unpublishedScripts/interaction/Sphinx.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "Name": "10 Questions", - "Defaults": - { - "Actions": - { - "positive": "var x=function(){if(questionNumber>=2){setQid('Finished');return;}var suffix=['A', 'B'][questionNumber++] + '_0' + (Math.floor(Math.random() * 2) + 2).toString() + '.wav';npcContinueStory('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/RightAnswer'+suffix, 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/RightAnswerB_02.fbx', getRandomRiddle());};x();", - "unknown": "var suffix=(Math.floor(Math.random() * 3) + 1).toString();npcContinueStory('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/WrongAnswer_0' + suffix + '.wav','https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/WrongAnswer_0' + suffix + '.fbx', getRandomRiddle());", - "hint": "var suffix=(Math.floor(Math.random() * 2) + 1).toString();npcContinueStory('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Hint_0' + suffix + '.wav','https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Hint_0' + suffix + '.fbx')" - }, - "Responses": - { - "positive": ["yes","yup","yeah","yahoo","sure","affirmative","okay","aye","right","exactly","course","naturally","unquestionably","positively","yep","definitely","certainly","fine","absolutely","positive","love","fantastic"], - "thinking": ["oh", "think about", "i know", "what was", "well", "not sure", "one before", "hold", "one moment", "one second", "1 second", "1 sec", "one sec"], - "hint": ["hint", "heads"] - } - }, - "Story": - [ - { - "QID": "start", - "init": "questionNumber=0;npcContinueStory('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/HiFi_Sphinx_Anim_Combined_Entrance_Audio.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Hifi_Sphinx_Anim_Entrance_Kneel_Combined_with_Intro.fbx', getRandomRiddle());" - }, - { - "QID": "1", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Blackboard.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Blackboard.fbx');", - "responses": - { - "positive": ["blackboard", "chalkboard", "chalk board", "slate"] - } - }, - { - "QID": "2", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Breath.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Breath.fbx');", - "responses": - { - "positive": ["breath", "death"] - } - }, - { - "QID": "3", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Clock.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Clock.fbx');", - "responses": - { - "positive": ["clock", "cock"] - } - }, - { - "QID": "4", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Coffin.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Coffin.fbx');", - "responses": - { - "positive": ["coffin", "casket", "possum"] - } - }, - { - "QID": "5", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Coin.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Coin.fbx');", - "responses": - { - "positive": ["coin", "boing", "coinage", "coin piece", "change", "join"] - } - }, - { - "QID": "6", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Corn.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Corn.fbx');", - "responses": - { - "positive": ["corn", "born", "maize", "maze", "means", "torn", "horn", "worn", "porn"] - } - }, - { - "QID": "7", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Darkness.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Darkness.fbx');", - "responses": - { - "positive": ["darkness", "dark", "blackness"] - } - }, - { - "QID": "8", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Gloves.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Gloves.fbx');", - "responses": - { - "positive": ["gloves", "love"] - } - }, - { - "QID": "9", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Gold.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Gold.fbx');", - "responses": - { - "positive": ["gold", "old", "bold", "cold", "told"] - } - }, - { - "QID": "10", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_River.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_River.fbx');", - "responses": - { - "positive": ["river", "bigger", "stream", "creek", "brook"] - } - }, - { - "QID": "11", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Secret.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Secret.fbx');", - "responses": - { - "positive": ["secret"] - } - }, - { - "QID": "12", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Shadow.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Shadow.fbx');", - "responses": - { - "positive": ["shadow"] - } - }, - { - "QID": "13", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Silence.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Silence.fbx');", - "responses": - { - "positive": ["silence", "lance", "quiet"] - } - }, - { - "QID": "14", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Stairs.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Stairs.fbx');", - "responses": - { - "positive": ["stairs", "steps", "stair", "stairwell", "there's", "stairway"] - } - }, - { - "QID": "15", - "init": "npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/Riddle_Umbrella.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Riddle_Umbrella.fbx');", - "responses": - { - "positive": ["umbrella"] - } - }, - { - "QID": "Finished", - "init": "Script.clearTimeout(heartbeatTimeout);heartbeatTimeout = false;npcRespondBlocking('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/ScratchDialogue/ConclusionRight_02.wav', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/ConclusionRight_02.fbx', function(){runOnClient('MyAvatar.goToLocation({x: 5, y: -29, z: -63}, true, true);');setQid('Restarting');});", - "positive": "", - "negative": "", - "unknown": "" - }, - { - "QID": "Restarting", - "init": "npcRespondBlocking('', 'https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/reversedSphinx.fbx', function(){Avatar.startAnimation('https://storage.googleapis.com/limitlessserv-144100.appspot.com/hifi%20assets/Animation/Hifi_Sphinx_Anim_Entrance_Kneel_Combined_with_Intro.fbx', 0);_qid='';});", - "positive": "", - "negative": "", - "unknown": "" - } - ] -} diff --git a/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml b/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml index 1b3698acd8..033039b87d 100644 --- a/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml +++ b/unpublishedScripts/marketplace/spectator-camera/SpectatorCamera.qml @@ -25,6 +25,8 @@ Rectangle { HifiStylesUit.HifiConstants { id: hifi; } id: root; + property bool uiReady: false; + property bool processingStillSnapshot: false; property bool processing360Snapshot: false; // Style color: "#404040"; @@ -58,7 +60,7 @@ Rectangle { // "Spectator" text HifiStylesUit.RalewaySemiBold { id: titleBarText; - text: "Spectator Camera"; + text: "Spectator Camera 2.3"; // Anchors anchors.left: parent.left; anchors.leftMargin: 30; @@ -91,13 +93,16 @@ Rectangle { } onClicked: { + if (!checked) { + flashCheckBox.checked = false; + } sendToScript({method: (checked ? 'spectatorCameraOn' : 'spectatorCameraOff')}); sendToScript({method: 'updateCameravFoV', vFoV: fieldOfViewSlider.value}); } background: Rectangle { color: parent.checked ? "#1FC6A6" : hifi.colors.white; - implicitWidth: masterSwitch.switchWidth; + implicitWidth: masterSwitch.width; implicitHeight: masterSwitch.height; radius: height/2; } @@ -127,7 +132,7 @@ Rectangle { z: 999; id: processingSnapshot; anchors.fill: parent; - visible: root.processing360Snapshot; + visible: root.processing360Snapshot || !root.uiReady; color: Qt.rgba(0.0, 0.0, 0.0, 0.85); // This object is always used in a popup. @@ -149,7 +154,7 @@ Rectangle { } HifiStylesUit.RalewaySemiBold { - text: "Processing..."; + text: root.uiReady ? "Processing..." : ""; // Anchors anchors.top: processingImage.bottom; anchors.topMargin: 4; @@ -202,10 +207,20 @@ Rectangle { verticalAlignment: Text.AlignVCenter; } + HifiStylesUit.FiraSansRegular { + text: ":)"; + size: 28; + color: hifi.colors.white; + visible: root.processing360Snapshot || root.processingStillSnapshot; + anchors.fill: parent; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + } + // Spectator Camera Preview Hifi.ResourceImageItem { id: spectatorCameraPreview; - visible: masterSwitch.checked; + visible: masterSwitch.checked && !root.processing360Snapshot && !root.processingStillSnapshot; url: showCameraView.checked || !HMD.active ? "resource://spectatorCameraFrame" : "resource://hmdPreviewFrame"; ready: masterSwitch.checked; mirrorVertically: true; @@ -311,7 +326,30 @@ Rectangle { } } } - + + HifiStylesUit.HiFiGlyphs { + id: flashGlyph; + visible: flashCheckBox.visible; + text: hifi.glyphs.lightning; + size: 26; + color: hifi.colors.white; + anchors.verticalCenter: flashCheckBox.verticalCenter; + anchors.right: flashCheckBox.left; + anchors.rightMargin: -2; + } + HifiControlsUit.CheckBox { + id: flashCheckBox; + visible: masterSwitch.checked; + color: hifi.colors.white; + colorScheme: hifi.colorSchemes.dark; + anchors.right: takeSnapshotButton.left; + anchors.rightMargin: -8; + anchors.verticalCenter: takeSnapshotButton.verticalCenter; + boxSize: 22; + onClicked: { + sendToScript({method: 'setFlashStatus', enabled: checked}); + } + } HifiControlsUit.Button { id: takeSnapshotButton; enabled: masterSwitch.checked; @@ -325,6 +363,7 @@ Rectangle { width: 135; height: 35; onClicked: { + root.processingStillSnapshot = true; sendToScript({method: 'takeSecondaryCameraSnapshot'}); } } @@ -582,8 +621,12 @@ Rectangle { // function fromScript(message) { switch (message.method) { - case 'updateSpectatorCameraCheckbox': - masterSwitch.checked = message.params; + case 'initializeUI': + masterSwitch.checked = message.masterSwitchOn; + flashCheckBox.checked = message.flashCheckboxChecked; + showCameraView.checked = message.monitorShowsCamView; + showHmdPreview.checked = !message.monitorShowsCamView; + root.uiReady = true; break; case 'updateMonitorShowsSwitch': showCameraView.checked = message.params; @@ -611,6 +654,12 @@ Rectangle { case 'finishedProcessing360Snapshot': root.processing360Snapshot = false; break; + case 'startedProcessingStillSnapshot': + root.processingStillSnapshot = true; + break; + case 'finishedProcessingStillSnapshot': + root.processingStillSnapshot = false; + break; default: console.log('Unrecognized message from spectatorCamera.js:', JSON.stringify(message)); } diff --git a/unpublishedScripts/marketplace/spectator-camera/flashOff.wav b/unpublishedScripts/marketplace/spectator-camera/flashOff.wav new file mode 100644 index 0000000000..fef7668de8 Binary files /dev/null and b/unpublishedScripts/marketplace/spectator-camera/flashOff.wav differ diff --git a/unpublishedScripts/marketplace/spectator-camera/flashOn.wav b/unpublishedScripts/marketplace/spectator-camera/flashOn.wav new file mode 100644 index 0000000000..f7e95c9607 Binary files /dev/null and b/unpublishedScripts/marketplace/spectator-camera/flashOn.wav differ diff --git a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.app.json b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.app.json index e71c657581..6370454f97 100644 --- a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.app.json +++ b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.app.json @@ -1,4 +1,4 @@ { - "scriptURL": "http://mpassets-staging.highfidelity.com/26156ea5-cdff-43c2-9581-d6b0fa5e00ef-v1/spectatorCamera.js", - "homeURL": "http://mpassets-staging.highfidelity.com/26156ea5-cdff-43c2-9581-d6b0fa5e00ef-v1/SpectatorCamera.qml" + "scriptURL": "http://mpassets.highfidelity.com/80d02930-f409-4f1a-824f-ae0109da32d6-v1/spectatorCamera.js", + "homeURL": "http://mpassets.highfidelity.com/80d02930-f409-4f1a-824f-ae0109da32d6-v1/SpectatorCamera.qml" } \ No newline at end of file diff --git a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js index df4cdfb385..4c39c5fb95 100644 --- a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js +++ b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js @@ -74,6 +74,7 @@ "collisionMask": 7, "dynamic": false, "modelURL": Script.resolvePath("spectator-camera.fbx"), + "name": "Spectator Camera", "registrationPoint": { "x": 0.56, "y": 0.545, @@ -97,11 +98,23 @@ if (button) { button.editProperties({ isActive: onSpectatorCameraScreen || camera }); } - Audio.playSound(CAMERA_ON_SOUND, { + Audio.playSound(SOUND_CAMERA_ON, { volume: 0.15, position: cameraPosition, localOnly: true }); + + // Remove the existing camera model from the domain if one exists. + // It's easy for this to happen if the user crashes while the Spectator Camera is on. + // We do this down here (after the new one is rezzed) so that we don't accidentally delete + // the newly-rezzed model. + var entityIDs = Entities.findEntitiesByName("Spectator Camera", MyAvatar.position, 100, false); + entityIDs.forEach(function (currentEntityID) { + var currentEntityOwner = Entities.getEntityProperties(currentEntityID, ['owningAvatarID']).owningAvatarID; + if (currentEntityOwner === MyAvatar.sessionUUID && currentEntityID !== camera) { + Entities.deleteEntity(currentEntityID); + } + }); } // Function Name: spectatorCameraOff() @@ -113,8 +126,14 @@ var WAIT_AFTER_DOMAIN_SWITCH_BEFORE_CAMERA_DELETE_MS = 1 * 1000; function spectatorCameraOff(isChangingDomains) { function deleteCamera() { - Entities.deleteEntity(camera); - camera = false; + if (flash) { + Entities.deleteEntity(flash); + flash = false; + } + if (camera) { + Entities.deleteEntity(camera); + camera = false; + } if (button) { // Change button to active when window is first openend OR if the camera is on, false otherwise. button.editProperties({ isActive: onSpectatorCameraScreen || camera }); @@ -391,21 +410,81 @@ } var takeSnapshotControllerMapping; var takeSnapshotControllerMappingName = 'Hifi-SpectatorCamera-Mapping-TakeSnapshot'; + + var flash = false; + function setFlashStatus(enabled) { + var cameraPosition = Entities.getEntityProperties(camera, ["positon"]).position; + if (enabled) { + if (camera) { + Audio.playSound(SOUND_FLASH_ON, { + position: cameraPosition, + localOnly: true, + volume: 0.8 + }); + flash = Entities.addEntity({ + "collidesWith": "", + "collisionMask": 0, + "color": { + "blue": 173, + "green": 252, + "red": 255 + }, + "cutoff": 90, + "dimensions": { + "x": 4, + "y": 4, + "z": 4 + }, + "dynamic": false, + "falloffRadius": 0.20000000298023224, + "intensity": 37, + "isSpotlight": true, + "localRotation": { w: 1, x: 0, y: 0, z: 0 }, + "localPosition": { x: 0, y: -0.005, z: -0.08 }, + "name": "Camera Flash", + "type": "Light", + "parentID": camera, + }, true); + } + } else { + if (flash) { + Audio.playSound(SOUND_FLASH_OFF, { + position: cameraPosition, + localOnly: true, + volume: 0.8 + }); + Entities.deleteEntity(flash); + flash = false; + } + } + } + function onStillSnapshotTaken() { Render.getConfig("SecondaryCameraJob.ToneMapping").curve = 1; + sendToQml({ + method: 'finishedProcessingStillSnapshot' + }); } function maybeTakeSnapshot() { if (camera) { + sendToQml({ + method: 'startedProcessingStillSnapshot' + }); + Render.getConfig("SecondaryCameraJob.ToneMapping").curve = 0; // Wait a moment before taking the snapshot for the tonemapping curve to update Script.setTimeout(function () { - Audio.playSound(SNAPSHOT_SOUND, { + Audio.playSound(SOUND_SNAPSHOT, { position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z }, localOnly: true, volume: 1.0 }); Window.takeSecondaryCameraSnapshot(); }, 250); + } else { + sendToQml({ + method: 'finishedProcessingStillSnapshot' + }); } } function on360SnapshotTaken() { @@ -418,7 +497,7 @@ } function maybeTake360Snapshot() { if (camera) { - Audio.playSound(SNAPSHOT_SOUND, { + Audio.playSound(SOUND_SNAPSHOT, { position: { x: MyAvatar.position.x, y: MyAvatar.position.y, z: MyAvatar.position.z }, localOnly: true, volume: 1.0 @@ -508,18 +587,8 @@ } function updateSpectatorCameraQML() { - sendToQml({ method: 'updateSpectatorCameraCheckbox', params: !!camera }); - sendToQml({ method: 'updateMonitorShowsSwitch', params: monitorShowsCameraView }); - if (!switchViewControllerMapping || !takeSnapshotControllerMapping) { - registerButtonMappings(); - } else { - sendToQml({ - method: 'updateControllerMappingCheckbox', - switchViewSetting: switchViewFromController, - takeSnapshotSetting: takeSnapshotFromController, - controller: controllerType - }); - } + sendToQml({ method: 'initializeUI', masterSwitchOn: !!camera, flashCheckboxChecked: !!flash, monitorShowsCamView: monitorShowsCameraView }); + registerButtonMappings(); Menu.setIsOptionChecked("Disable Preview", false); Menu.setIsOptionChecked("Mono Preview", true); } @@ -537,9 +606,13 @@ button.editProperties({ isActive: onSpectatorCameraScreen || camera }); } - if (onSpectatorCameraScreen) { - updateSpectatorCameraQML(); - } + // In the case of a remote QML app, it takes a bit of time + // for the event bridge to actually connect, so we have to wait... + Script.setTimeout(function () { + if (onSpectatorCameraScreen) { + updateSpectatorCameraQML(); + } + }, 700); } // Function Name: sendToQml() @@ -576,6 +649,9 @@ case 'updateCameravFoV': spectatorCameraConfig.vFoV = message.vFoV; break; + case 'setFlashStatus': + setFlashStatus(message.enabled); + break; case 'takeSecondaryCameraSnapshot': maybeTakeSnapshot(); break; @@ -600,9 +676,7 @@ // Description: // -Called from C++ when HMD mode is changed. The argument "isHMDMode" is true if HMD is on; false otherwise. function onHMDChanged(isHMDMode) { - if (!switchViewControllerMapping || !takeSnapshotControllerMapping) { - registerButtonMappings(); - } + registerButtonMappings(); if (!isHMDMode) { setMonitorShowsCameraView(false); } else { @@ -646,8 +720,10 @@ } // These functions will be called when the script is loaded. - var CAMERA_ON_SOUND = SoundCache.getSound(Script.resolvePath("cameraOn.wav")); - var SNAPSHOT_SOUND = SoundCache.getSound(Script.resourcesPath() + "sounds/snapshot/snap.wav"); + var SOUND_CAMERA_ON = SoundCache.getSound(Script.resolvePath("cameraOn.wav")); + var SOUND_SNAPSHOT = SoundCache.getSound(Script.resolvePath("snap.wav")); + var SOUND_FLASH_ON = SoundCache.getSound(Script.resolvePath("flashOn.wav")); + var SOUND_FLASH_OFF = SoundCache.getSound(Script.resolvePath("flashOff.wav")); startup(); Script.scriptEnding.connect(shutdown);