diff --git a/.gitignore b/.gitignore index c1eef3817f..665238e7da 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,9 @@ Makefile # Android Studio *.iml local.properties -android/libraries +android/gradle* +android/.gradle +android/app/src/main/jniLibs # VSCode # List taken from Github Global Ignores master@435c4d92 diff --git a/BUILD_ANDROID.md b/BUILD_ANDROID.md index cc51e58b1d..5d2e6b9293 100644 --- a/BUILD_ANDROID.md +++ b/BUILD_ANDROID.md @@ -1,25 +1,23 @@ -Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Android specific instructions are found in this file. +Please read the [general build guide](BUILD.md) for information on building other platform. Only Android specific instructions are found in this file. -# Android Dependencies +# Dependencies + +*Currently Android building is only supported on 64 bit Linux host environments* You will need the following tools to build our Android targets. -* [Qt](http://www.qt.io/download-open-source/#) ~> 5.9.1 +* [Gradle](https://gradle.org/install/) * [Android Studio](https://developer.android.com/studio/index.html) -* [Google VR SDK](https://github.com/googlevr/gvr-android-sdk/releases) -* [Gradle](https://gradle.org/releases/) -### Qt +### Gradle -Download the Qt online installer. Run the installer and select the android_armv7 binaries. Installing to the default path is recommended +Install gradle version 4.1 or higher. Following the instructions to install via [SDKMAN!](http://sdkman.io/install.html) is recommended. ### Android Studio Download the Android Studio installer and run it. Once installed, at the welcome screen, click configure in the lower right corner and select SDK manager -From the SDK Platforms tab, select API level 26. - -* Install the ARM EABI v7a System Image if you want to run an emulator. +From the SDK Platforms tab, select API levels 24 and 26. From the SDK Tools tab select the following @@ -29,123 +27,41 @@ From the SDK Tools tab select the following * LLDB * Android SDK Platform-Tools * Android SDK Tools -* Android SDK Tools * NDK (even if you have the NDK installed separately) -### Google VR SDK +# Environment -Download the 1.8 Google VR SDK [release](https://github.com/googlevr/gvr-android-sdk/archive/v1.80.0.zip). Unzip the archive to a location on your drive. - -### Gradle - -Download [Gradle 4.1](https://services.gradle.org/distributions/gradle-4.1-all.zip) and unzip it on your local drive. You may wish to add the location of the bin directory within the archive to your path +Setting up the environment for android builds requires some additional steps #### Set up machine specific Gradle properties -Create a `gradle.properties` file in ~/.gradle. Edit the file to contain the following +Create a `gradle.properties` file in $HOME/.gradle. Edit the file to contain the following - QT5_ROOT=C\:\\Qt\\5.9.1\\android_armv7 - GVR_ROOT=C\:\\Android\\gvr-android-sdk + HIFI_ANDROID_PRECOMPILED=/Android/hifi_externals -Replace the paths with your local installations of Qt5 and the Google VR SDK +Note, do not use `$HOME` for the path. It must be a fully qualified path name. + +### Setup the repository + +Clone the repository + +`git clone https://github.com/highfidelity/hifi.git` + +Enter the repository `android` directory + +`cd hifi/android` + +Execute a gradle pre-build setup. This step should only need to be done once + +`gradle setupDepedencies` -# TODO fix the rest +# Building & Running -You will also need to cross-compile the dependencies required for all platforms for Android, and help CMake find these compiled libraries on your machine. +* Open Android Studio +* Choose _Open Existing Android Studio Project_ +* Navigate to the `hifi` repository and choose the `android` folder and select _OK_ +* If Android Studio asks you if you want to use the Gradle wrapper, select cancel and tell it where your local gradle installation is. If you used SDKMAN to install gradle it will be located in `$HOME/.sdkman/candidates/gradle/current/` +* From the _Build_ menu select _Make Project_ +* Once the build completes, from the _Run_ menu select _Run App_ -#### Scribe - -High Fidelity has a shader pre-processing tool called `scribe` that various libraries will call on during the build process. You must compile scribe using your native toolchain (following the build instructions for your platform) and then pass a CMake variable or set an ENV variable `SCRIBE_PATH` that is a path to the scribe executable. - -CMake will fatally error if it does not find the scribe executable while using the android toolchain. - -#### Optional Components - -* [Oculus Mobile SDK](https://developer.oculus.com/downloads/#sdk=mobile) ~> 0.4.2 - -#### ANDROID_LIB_DIR - -Since you won't be installing Android dependencies to system paths on your development machine, CMake will need a little help tracking down your Android dependencies. - -This is most easily accomplished by installing all Android dependencies in the same folder. You can place this folder wherever you like on your machine. In this build guide and across our CMakeLists files this folder is referred to as `ANDROID_LIB_DIR`. You can set `ANDROID_LIB_DIR` in your environment or by passing when you run CMake. - -#### Qt - -Install Qt 5.5.1 for Android for your host environment from the [Qt downloads page](http://www.qt.io/download/). Install Qt to ``$ANDROID_LIB_DIR/Qt``. This is required so that our root CMakeLists file can help CMake find your Android Qt installation. - -The component required for the Android build is the `Android armv7` component. - -If you would like to install Qt to a different location, or attempt to build with a different Qt version, you can pass `ANDROID_QT_CMAKE_PREFIX_PATH` to CMake. Point to the `cmake` folder inside `$VERSION_NUMBER/android_armv7/lib`. Otherwise, our root CMakeLists will set it to `$ANDROID_LIB_DIR/Qt/5.5/android_armv7/lib/cmake`. - -#### OpenSSL - -Cross-compilation of OpenSSL has been tested from an OS X machine running 10.10 compiling OpenSSL 1.0.2. It is likely that the steps below will work for other OpenSSL versions than 1.0.2. - -The original instructions to compile OpenSSL for Android from your host environment can be found [here](http://wiki.openssl.org/index.php/Android). We required some tweaks to get OpenSSL to successfully compile, those tweaks are explained below. - -Download the [OpenSSL source](https://www.openssl.org/source/) and extract the tarball inside your `ANDROID_LIB_DIR`. Rename the extracted folder to `openssl`. - -You will need the [setenv-android.sh script](http://wiki.openssl.org/index.php/File:Setenv-android.sh) from the OpenSSL wiki. - -You must change three values at the top of the `setenv-android.sh` script - `_ANDROID_NDK`, `_ANDROID_EABI` and `_ANDROID_API`. -`_ANDROID_NDK` should be `android-ndk-r10`, `_ANDROID_EABI` should be `arm-linux-androidebi-4.9` and `_ANDROID_API` should be `19`. - -First, make sure `ANDROID_NDK_ROOT` is set in your env. This should be the path to the root of your Android NDK install. `setenv-android.sh` needs `ANDROID_NDK_ROOT` to set the environment variables required for building OpenSSL. - -Source the `setenv-android.sh` script so it can set environment variables that OpenSSL will use while compiling. If you use zsh as your shell you may need to modify the `setenv-android.sh` for it to set the correct variables in your env. - -``` -export ANDROID_NDK_ROOT=YOUR_NDK_ROOT -source setenv-android.sh -``` - -Then, from the OpenSSL directory, run the following commands. - -``` -perl -pi -e 's/install: all install_docs install_sw/install: install_docs install_sw/g' Makefile.org -./config shared -no-ssl2 -no-ssl3 -no-comp -no-hw -no-engine --openssldir=/usr/local/ssl/$ANDROID_API -make depend -make all -``` - -This should generate libcrypto and libssl in the root of the OpenSSL directory. YOU MUST remove the `libssl.so` and `libcrypto.so` files that are generated. They are symlinks to `libssl.so.VER` and `libcrypto.so.VER` which Android does not know how to handle. By removing `libssl.so` and `libcrypto.so` the FindOpenSSL module will find the static libs and use those instead. - -If you have been building other components it is possible that the OpenSSL compile will fail based on the values other cross-compilations (tbb, bullet) have set. Ensure that you are in a new terminal window to avoid compilation errors from previously set environment variables. - -#### Oculus Mobile SDK - -The Oculus Mobile SDK is optional, for Gear VR support. It is not required to compile gvr-interface. - -Download the [Oculus Mobile SDK](https://developer.oculus.com/downloads/#sdk=mobile) and extract the archive inside your `ANDROID_LIB_DIR` folder. Rename the extracted folder to `libovr`. - -From the VRLib directory, use ndk-build to build VrLib. - -``` -cd VRLib -ndk-build -``` - -This will create the liboculus.a archive that our FindLibOVR module will look for when cmake is run. - -##### Hybrid testing - -Currently the 'vr_dual' mode that would allow us to run a hybrid app has limited support in the Oculus Mobile SDK. The best way to have an application we can launch without having to connect to the GearVR is to put the Gear VR Service into developer mode. This stops Oculus Home from taking over the device when it is plugged into the Gear VR headset, and allows the application to be launched from the Applications page. - -To put the Gear VR Service into developer mode you need an application with an Oculus Signature File on your device. Generate an Oculus Signature File for your device on the [Oculus osig tool page](https://developer.oculus.com/tools/osig/). Place this file in the gvr-interface/assets directory. Cmake will automatically copy it into your apk in the right place when you execute `make gvr-interface-apk`. - -Once the application is on your device, go to `Settings->Application Manager->Gear VR Service->Manage Storage`. Tap on `VR Service Version` six times. It will scan your device to verify that you have an osig file in an application on your device, and then it will let you enable Developer mode. - -### CMake - -We use CMake to generate the makefiles that compile and deploy the Android APKs to your device. In order to create Makefiles for the Android targets, CMake requires that some environment variables are set, and that other variables are passed to it when it is run. - -The following must be set in your environment: - -* ANDROID_NDK - the root of your Android NDK install -* ANDROID_HOME - the root of your Android SDK install -* ANDROID_LIB_DIR - the directory containing cross-compiled versions of dependencies - -The following must be passed to CMake when it is run: - -* USE_ANDROID_TOOLCHAIN - set to true to build for Android diff --git a/android/app/CMakeLists.txt b/android/app/CMakeLists.txt index 2d6df925e9..3d4516d0bf 100644 --- a/android/app/CMakeLists.txt +++ b/android/app/CMakeLists.txt @@ -1,8 +1,10 @@ set(TARGET_NAME native-lib) setup_hifi_library() -link_hifi_libraries(shared networking gl gpu gpu-gles render-utils) -autoscribe_shader_lib(gpu model render render-utils) -target_opengl() +link_hifi_libraries(shared networking gl gpu gpu-gles image fbx render-utils physics) + target_link_libraries(native-lib android log m) -target_include_directories(native-lib PRIVATE "${GVR_ROOT}/libraries/headers") -target_link_libraries(native-lib "C:/Users/bdavis/Git/hifi/android/libraries/jni/armeabi-v7a/libgvr.so") + +target_opengl() +target_googlevr() + + diff --git a/android/app/build.gradle b/android/app/build.gradle index bd1c596bf3..29b7e4a6cc 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,27 +1,32 @@ apply plugin: 'com.android.application' +ext.RELEASE_NUMBER = project.hasProperty('RELEASE_NUMBER') ? project.getProperty('RELEASE_NUMBER') : '0' +ext.RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV' +ext.BUILD_BRANCH = project.hasProperty('BUILD_BRANCH') ? project.getProperty('BUILD_BRANCH') : '' + android { compileSdkVersion 26 - buildToolsVersion "26.0.1" defaultConfig { applicationId "org.saintandreas.testapp" minSdkVersion 24 targetSdkVersion 26 versionCode 1 versionName "1.0" - ndk { abiFilters 'armeabi-v7a' } + ndk { abiFilters 'arm64-v8a' } externalNativeBuild { cmake { arguments '-DHIFI_ANDROID=1', '-DANDROID_PLATFORM=android-24', '-DANDROID_TOOLCHAIN=clang', - '-DANDROID_STL=gnustl_shared', - '-DGVR_ROOT=' + GVR_ROOT, - '-DNATIVE_SCRIBE=c:/bin/scribe.exe', - "-DHIFI_ANDROID_PRECOMPILED=${project.rootDir}/libraries/jni/armeabi-v7a" + '-DANDROID_STL=c++_shared', + '-DQT_CMAKE_PREFIX_PATH=' + HIFI_ANDROID_PRECOMPILED + '/qt/lib/cmake', + '-DNATIVE_SCRIBE=' + HIFI_ANDROID_PRECOMPILED + '/scribe', + '-DHIFI_ANDROID_PRECOMPILED=' + HIFI_ANDROID_PRECOMPILED, + '-DRELEASE_NUMBER=' + RELEASE_NUMBER, + '-DRELEASE_TYPE=' + RELEASE_TYPE, + '-DBUILD_BRANCH=' + BUILD_BRANCH } } - jackOptions { enabled true } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -29,17 +34,20 @@ android { } buildTypes { + applicationVariants.all { variant -> + variant.outputs.all { + if (RELEASE_NUMBER != '0') { + outputFileName = "app_" + RELEASE_NUMBER + "_" + RELEASE_TYPE + ".apk" + } + } + } + release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } - sourceSets { - main { - jniLibs.srcDirs += '../libraries/jni'; - } - } externalNativeBuild { cmake { path '../../CMakeLists.txt' @@ -53,5 +61,3 @@ dependencies { compile 'com.google.vr:sdk-audio:1.80.0' compile 'com.google.vr:sdk-base:1.80.0' } - -build.dependsOn(':extractQt5') diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 05547bd5ae..c96ac0ef90 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -7,12 +7,10 @@ - - - - - - - - diff --git a/android/app/src/main/cpp/native-lib.cpp b/android/app/src/main/cpp/native-lib.cpp index 156d43d849..fe21a250de 100644 --- a/android/app/src/main/cpp/native-lib.cpp +++ b/android/app/src/main/cpp/native-lib.cpp @@ -37,12 +37,7 @@ extern "C" { JNI_METHOD(jlong, nativeCreateRenderer) (JNIEnv *env, jclass clazz, jobject class_loader, jobject android_context, jlong native_gvr_api) { qInstallMessageHandler(messageHandler); -#if defined(GVR) - auto gvrContext = reinterpret_cast(native_gvr_api); - return toJni(new NativeRenderer(gvrContext)); -#else - return toJni(new NativeRenderer(nullptr)); -#endif + return toJni(new NativeRenderer()); } JNI_METHOD(void, nativeDestroyRenderer) diff --git a/android/app/src/main/cpp/renderer.cpp b/android/app/src/main/cpp/renderer.cpp index a877ebd777..3b23b7e187 100644 --- a/android/app/src/main/cpp/renderer.cpp +++ b/android/app/src/main/cpp/renderer.cpp @@ -1,138 +1,14 @@ #include "renderer.h" #include +#include #include + #include - -#include "GoogleVRHelpers.h" - #include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#if 0 -#include -#include -#include -#include -#include -#include -#include -#include -#endif - - -template -void withFrameBuffer(gvr::Frame& frame, int32_t index, F f) { - frame.BindBuffer(index); - f(); - frame.Unbind(); -} - - -static const uint64_t kPredictionTimeWithoutVsyncNanos = 50000000; - -// Each shader has two variants: a single-eye ES 2.0 variant, and a multiview -// ES 3.0 variant. The multiview vertex shaders use transforms defined by -// arrays of mat4 uniforms, using gl_ViewID_OVR to determine the array index. - -#define UNIFORM_LIGHT_POS 20 -#define UNIFORM_M 16 -#define UNIFORM_MV 8 -#define UNIFORM_MVP 0 - -#if 0 -uniform Transform { // API uses “Transform[2]” to refer to instance 2 - mat4 u_MVP[2]; - mat4 u_MVMatrix[2]; - mat4 u_Model; - vec3 u_LightPos[2]; -}; -static const char *kDiffuseLightingVertexShader = R"glsl( -#version 300 es -#extension GL_OVR_multiview2 : enable - -layout(num_views=2) in; - -layout(location = 0) uniform mat4 u_MVP[2]; -layout(location = 8) uniform mat4 u_MVMatrix[2]; -layout(location = 16) uniform mat4 u_Model; -layout(location = 20) uniform vec3 u_LightPos[2]; - -layout(location = 0) in vec4 a_Position; -layout(location = 1) in vec4 a_Color; -layout(location = 2) in vec3 a_Normal; - -out vec4 v_Color; -out vec3 v_Grid; - -void main() { - mat4 mvp = u_MVP[gl_ViewID_OVR]; - mat4 modelview = u_MVMatrix[gl_ViewID_OVR]; - vec3 lightpos = u_LightPos[gl_ViewID_OVR]; - v_Grid = vec3(u_Model * a_Position); - vec3 modelViewVertex = vec3(modelview * a_Position); - vec3 modelViewNormal = vec3(modelview * vec4(a_Normal, 0.0)); - float distance = length(lightpos - modelViewVertex); - vec3 lightVector = normalize(lightpos - modelViewVertex); - float diffuse = max(dot(modelViewNormal, lightVector), 0.5); - diffuse = diffuse * (1.0 / (1.0 + (0.00001 * distance * distance))); - v_Color = vec4(a_Color.rgb * diffuse, a_Color.a); - gl_Position = mvp * a_Position; -} -)glsl"; -#endif - - -static const char *kSimepleVertexShader = R"glsl( -#version 300 es +static const char *kSimepleVertexShader = R"glsl(#version 300 es #extension GL_OVR_multiview2 : enable layout(num_views=2) in; @@ -147,9 +23,7 @@ void main() { } )glsl"; - -static const char *kPassthroughFragmentShader = R"glsl( -#version 300 es +static const char *kPassthroughFragmentShader = R"glsl(#version 300 es precision mediump float; in vec4 v_Color; out vec4 FragColor; @@ -157,6 +31,17 @@ out vec4 FragColor; void main() { FragColor = v_Color; } )glsl"; + +int LoadGLShader(int type, const char *shadercode) { + GLuint result = 0; + std::string shaderError; + static const std::string SHADER_DEFINES; + if (!gl::compileShader(type, shadercode, SHADER_DEFINES, result, shaderError)) { + qWarning() << "QQQ" << __FUNCTION__ << "Shader compile failure" << shaderError.c_str(); + } + return result; +} + static void CheckGLError(const char* label) { int gl_error = glGetError(); if (gl_error != GL_NO_ERROR) { @@ -167,158 +52,6 @@ static void CheckGLError(const char* label) { } // Contains vertex, normal and other data. -namespace cube { - const std::array CUBE_COORDS{{ - // Front face - -1.0f, 1.0f, 1.0f, - -1.0f, -1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, - -1.0f, -1.0f, 1.0f, - 1.0f, -1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, - - // Right face - 1.0f, 1.0f, 1.0f, - 1.0f, -1.0f, 1.0f, - 1.0f, 1.0f, -1.0f, - 1.0f, -1.0f, 1.0f, - 1.0f, -1.0f, -1.0f, - 1.0f, 1.0f, -1.0f, - - // Back face - 1.0f, 1.0f, -1.0f, - 1.0f, -1.0f, -1.0f, - -1.0f, 1.0f, -1.0f, - 1.0f, -1.0f, -1.0f, - -1.0f, -1.0f, -1.0f, - -1.0f, 1.0f, -1.0f, - - // Left face - -1.0f, 1.0f, -1.0f, - -1.0f, -1.0f, -1.0f, - -1.0f, 1.0f, 1.0f, - -1.0f, -1.0f, -1.0f, - -1.0f, -1.0f, 1.0f, - -1.0f, 1.0f, 1.0f, - - // Top face - -1.0f, 1.0f, -1.0f, - -1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, -1.0f, - -1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, -1.0f, - - // Bottom face - 1.0f, -1.0f, -1.0f, - 1.0f, -1.0f, 1.0f, - -1.0f, -1.0f, -1.0f, - 1.0f, -1.0f, 1.0f, - -1.0f, -1.0f, 1.0f, - -1.0f, -1.0f, -1.0f - }}; - - const std::array CUBE_COLORS{{ - // front, green - 0.0f, 0.5273f, 0.2656f, - 0.0f, 0.5273f, 0.2656f, - 0.0f, 0.5273f, 0.2656f, - 0.0f, 0.5273f, 0.2656f, - 0.0f, 0.5273f, 0.2656f, - 0.0f, 0.5273f, 0.2656f, - - // right, blue - 0.0f, 0.3398f, 0.9023f, - 0.0f, 0.3398f, 0.9023f, - 0.0f, 0.3398f, 0.9023f, - 0.0f, 0.3398f, 0.9023f, - 0.0f, 0.3398f, 0.9023f, - 0.0f, 0.3398f, 0.9023f, - - // back, also green - 0.0f, 0.5273f, 0.2656f, - 0.0f, 0.5273f, 0.2656f, - 0.0f, 0.5273f, 0.2656f, - 0.0f, 0.5273f, 0.2656f, - 0.0f, 0.5273f, 0.2656f, - 0.0f, 0.5273f, 0.2656f, - - // left, also blue - 0.0f, 0.3398f, 0.9023f, - 0.0f, 0.3398f, 0.9023f, - 0.0f, 0.3398f, 0.9023f, - 0.0f, 0.3398f, 0.9023f, - 0.0f, 0.3398f, 0.9023f, - 0.0f, 0.3398f, 0.9023f, - - // top, red - 0.8359375f, 0.17578125f, 0.125f, - 0.8359375f, 0.17578125f, 0.125f, - 0.8359375f, 0.17578125f, 0.125f, - 0.8359375f, 0.17578125f, 0.125f, - 0.8359375f, 0.17578125f, 0.125f, - 0.8359375f, 0.17578125f, 0.125f, - - // bottom, also red - 0.8359375f, 0.17578125f, 0.125f, - 0.8359375f, 0.17578125f, 0.125f, - 0.8359375f, 0.17578125f, 0.125f, - 0.8359375f, 0.17578125f, 0.125f, - 0.8359375f, 0.17578125f, 0.125f, - 0.8359375f, 0.17578125f, 0.125f - }}; - - const std::array CUBE_NORMALS{{ - // Front face - 0.0f, 0.0f, 1.0f, - 0.0f, 0.0f, 1.0f, - 0.0f, 0.0f, 1.0f, - 0.0f, 0.0f, 1.0f, - 0.0f, 0.0f, 1.0f, - 0.0f, 0.0f, 1.0f, - - // Right face - 1.0f, 0.0f, 0.0f, - 1.0f, 0.0f, 0.0f, - 1.0f, 0.0f, 0.0f, - 1.0f, 0.0f, 0.0f, - 1.0f, 0.0f, 0.0f, - 1.0f, 0.0f, 0.0f, - - // Back face - 0.0f, 0.0f, -1.0f, - 0.0f, 0.0f, -1.0f, - 0.0f, 0.0f, -1.0f, - 0.0f, 0.0f, -1.0f, - 0.0f, 0.0f, -1.0f, - 0.0f, 0.0f, -1.0f, - - // Left face - -1.0f, 0.0f, 0.0f, - -1.0f, 0.0f, 0.0f, - -1.0f, 0.0f, 0.0f, - -1.0f, 0.0f, 0.0f, - -1.0f, 0.0f, 0.0f, - -1.0f, 0.0f, 0.0f, - - // Top face - 0.0f, 1.0f, 0.0f, - 0.0f, 1.0f, 0.0f, - 0.0f, 1.0f, 0.0f, - 0.0f, 1.0f, 0.0f, - 0.0f, 1.0f, 0.0f, - 0.0f, 1.0f, 0.0f, - - // Bottom face - 0.0f, -1.0f, 0.0f, - 0.0f, -1.0f, 0.0f, - 0.0f, -1.0f, 0.0f, - 0.0f, -1.0f, 0.0f, - 0.0f, -1.0f, 0.0f, - 0.0f, -1.0f, 0.0f - }}; -} - namespace triangle { static std::array TRIANGLE_VERTS {{ -0.5f, -0.5f, 0.0f, @@ -327,229 +60,36 @@ namespace triangle { }}; } -std::array buildViewports(const std::unique_ptr &gvrapi) { - return { {gvrapi->CreateBufferViewport(), gvrapi->CreateBufferViewport()} }; -}; - -const std::string VERTEX_SHADER_DEFINES{ R"GLSL( -#version 300 es -#extension GL_EXT_clip_cull_distance : enable -#define GPU_VERTEX_SHADER -#define GPU_SSBO_TRANSFORM_OBJECT 1 -#define GPU_TRANSFORM_IS_STEREO -#define GPU_TRANSFORM_STEREO_CAMERA -#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED -#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN -)GLSL" }; - -const std::string PIXEL_SHADER_DEFINES{ R"GLSL( -#version 300 es -precision mediump float; -#define GPU_PIXEL_SHADER -#define GPU_TRANSFORM_IS_STEREO -#define GPU_TRANSFORM_STEREO_CAMERA -#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED -#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN -)GLSL" }; - - -#if defined(GVR) -NativeRenderer::NativeRenderer(gvr_context *vrContext) : - _gvrapi(new gvr::GvrApi(vrContext, false)), - _viewports(buildViewports(_gvrapi)), - _gvr_viewer_type(_gvrapi->GetViewerType()) -#else -NativeRenderer::NativeRenderer(void *vrContext) -#endif -{ - start = std::chrono::system_clock::now(); - qDebug() << "QQQ" << __FUNCTION__; -} - - -/** - * Converts a raw text file, saved as a resource, into an OpenGL ES shader. - * - * @param type The type of shader we will be creating. - * @param resId The resource ID of the raw text file. - * @return The shader object handler. - */ -int LoadGLShader(int type, const char *shadercode) { - GLuint result = 0; - std::string shaderError; - static const std::string SHADER_DEFINES; - if (!gl::compileShader(type, shadercode, SHADER_DEFINES, result, shaderError)) { - qWarning() << "QQQ" << __FUNCTION__ << "Shader compile failure" << shaderError.c_str(); - } - return result; -} - -// Computes a texture size that has approximately half as many pixels. This is -// equivalent to scaling each dimension by approximately sqrt(2)/2. -static gvr::Sizei HalfPixelCount(const gvr::Sizei &in) { - // Scale each dimension by sqrt(2)/2 ~= 7/10ths. - gvr::Sizei out; - out.width = (7 * in.width) / 10; - out.height = (7 * in.height) / 10; - return out; -} - - -#if defined(GVR) -void NativeRenderer::InitializeVR() { - _gvrapi->InitializeGl(); - bool multiviewEnabled = _gvrapi->IsFeatureSupported(GVR_FEATURE_MULTIVIEW); - qWarning() << "QQQ" << __FUNCTION__ << "Multiview enabled " << multiviewEnabled; - // Because we are using 2X MSAA, we can render to half as many pixels and - // achieve similar quality. - _renderSize = HalfPixelCount(_gvrapi->GetMaximumEffectiveRenderTargetSize()); - - std::vector specs; - specs.push_back(_gvrapi->CreateBufferSpec()); - specs[0].SetColorFormat(GVR_COLOR_FORMAT_RGBA_8888); - specs[0].SetDepthStencilFormat(GVR_DEPTH_STENCIL_FORMAT_DEPTH_16); - specs[0].SetSamples(2); - gvr::Sizei half_size = {_renderSize.width / 2, _renderSize.height}; - specs[0].SetMultiviewLayers(2); - specs[0].SetSize(half_size); - - _swapchain.reset(new gvr::SwapChain(_gvrapi->CreateSwapChain(specs))); - _viewportlist.reset(new gvr::BufferViewportList(_gvrapi->CreateEmptyBufferViewportList())); -} -void NativeRenderer::PrepareFramebuffer() { - const gvr::Sizei recommended_size = HalfPixelCount( - _gvrapi->GetMaximumEffectiveRenderTargetSize()); - if (_renderSize.width != recommended_size.width || - _renderSize.height != recommended_size.height) { - // We need to resize the framebuffer. Note that multiview uses two texture - // layers, each with half the render width. - gvr::Sizei framebuffer_size = recommended_size; - framebuffer_size.width /= 2; - _swapchain->ResizeBuffer(0, framebuffer_size); - _renderSize = recommended_size; - } -} -#endif - -void testShaderBuild(const char* vs_src, const char * fs_src) { - std::string error; - GLuint vs, fs; - if (!gl::compileShader(GL_VERTEX_SHADER, vs_src, VERTEX_SHADER_DEFINES, vs, error) || - !gl::compileShader(GL_FRAGMENT_SHADER, fs_src, PIXEL_SHADER_DEFINES, fs, error)) { - throw std::runtime_error("Failed to compile shader"); - } - auto pr = gl::compileProgram({ vs, fs }, error); - if (!pr) { - throw std::runtime_error("Failed to link shader"); - } -} void NativeRenderer::InitializeGl() { - qDebug() << "QQQ" << __FUNCTION__; - //gl::initModuleGl(); -#if defined(GVR) - InitializeVR(); -#endif - glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); glDisable(GL_SCISSOR_TEST); glDisable(GL_BLEND); - - const uint32_t vertShader = LoadGLShader(GL_VERTEX_SHADER, kSimepleVertexShader); - //const uint32_t vertShader = LoadGLShader(GL_VERTEX_SHADER, kDiffuseLightingVertexShader); const uint32_t fragShader = LoadGLShader(GL_FRAGMENT_SHADER, kPassthroughFragmentShader); std::string error; - _cubeProgram = gl::compileProgram({ vertShader, fragShader }, error); + _program = gl::compileProgram({ vertShader, fragShader }, error); CheckGLError("build program"); - glGenBuffers(1, &_cubeBuffer); - glBindBuffer(GL_ARRAY_BUFFER, _cubeBuffer); + glGenBuffers(1, &_geometryBuffer); + glBindBuffer(GL_ARRAY_BUFFER, _geometryBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 9, triangle::TRIANGLE_VERTS.data(), GL_STATIC_DRAW); - /* - glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 108 * 3, NULL, GL_STATIC_DRAW); - glBufferSubData(GL_ARRAY_BUFFER, sizeof(float) * 108 * 0, sizeof(float) * 108, cube::CUBE_COORDS.data()); - glBufferSubData(GL_ARRAY_BUFFER, sizeof(float) * 108 * 1, sizeof(float) * 108, cube::CUBE_COLORS.data()); - glBufferSubData(GL_ARRAY_BUFFER, sizeof(float) * 108 * 2, sizeof(float) * 108, cube::CUBE_NORMALS.data()); - */ glBindBuffer(GL_ARRAY_BUFFER, 0); CheckGLError("upload vertices"); - glGenVertexArrays(1, &_cubeVao); - glBindBuffer(GL_ARRAY_BUFFER, _cubeBuffer); - glBindVertexArray(_cubeVao); + glGenVertexArrays(1, &_vao); + glBindBuffer(GL_ARRAY_BUFFER, _geometryBuffer); + glBindVertexArray(_vao); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); - /* - glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0); - glEnableVertexAttribArray(0); - glVertexAttribPointer(1, 3, GL_FLOAT, false, 0, (const void*)(sizeof(float) * 108 * 1) ); - glEnableVertexAttribArray(1); - glVertexAttribPointer(2, 3, GL_FLOAT, false, 0, (const void*)(sizeof(float) * 108 * 2)); - glEnableVertexAttribArray(2); - */ glBindVertexArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); CheckGLError("build vao "); - - static std::once_flag once; - std::call_once(once, [&]{ - testShaderBuild(sdf_text3D_vert, sdf_text3D_frag); - - testShaderBuild(DrawTransformUnitQuad_vert, DrawTexture_frag); - testShaderBuild(DrawTexcoordRectTransformUnitQuad_vert, DrawTexture_frag); - testShaderBuild(DrawViewportQuadTransformTexcoord_vert, DrawTexture_frag); - testShaderBuild(DrawTransformUnitQuad_vert, DrawTextureOpaque_frag); - testShaderBuild(DrawTransformUnitQuad_vert, DrawColoredTexture_frag); - - testShaderBuild(simple_vert, simple_frag); - testShaderBuild(simple_vert, simple_textured_frag); - testShaderBuild(simple_vert, simple_textured_unlit_frag); - testShaderBuild(deferred_light_vert, directional_ambient_light_frag); - testShaderBuild(deferred_light_vert, directional_skybox_light_frag); - testShaderBuild(standardTransformPNTC_vert, standardDrawTexture_frag); - testShaderBuild(standardTransformPNTC_vert, DrawTextureOpaque_frag); - - testShaderBuild(model_vert, model_frag); - testShaderBuild(model_normal_map_vert, model_normal_map_frag); - testShaderBuild(model_vert, model_specular_map_frag); - testShaderBuild(model_normal_map_vert, model_normal_specular_map_frag); - testShaderBuild(model_vert, model_translucent_frag); - testShaderBuild(model_normal_map_vert, model_translucent_frag); - testShaderBuild(model_lightmap_vert, model_lightmap_frag); - testShaderBuild(model_lightmap_normal_map_vert, model_lightmap_normal_map_frag); - testShaderBuild(model_lightmap_vert, model_lightmap_specular_map_frag); - testShaderBuild(model_lightmap_normal_map_vert, model_lightmap_normal_specular_map_frag); - - testShaderBuild(skin_model_vert, model_frag); - testShaderBuild(skin_model_normal_map_vert, model_normal_map_frag); - testShaderBuild(skin_model_vert, model_specular_map_frag); - testShaderBuild(skin_model_normal_map_vert, model_normal_specular_map_frag); - testShaderBuild(skin_model_vert, model_translucent_frag); - testShaderBuild(skin_model_normal_map_vert, model_translucent_frag); - - testShaderBuild(model_shadow_vert, model_shadow_frag); - - testShaderBuild(overlay3D_vert, overlay3D_frag); - -#if 0 - testShaderBuild(textured_particle_vert, textured_particle_frag); - testShaderBuild(skybox_vert, skybox_frag); - testShaderBuild(paintStroke_vert,paintStroke_frag); - testShaderBuild(polyvox_vert, polyvox_frag); -#endif - - }); - - qDebug() << "done"; } -static const float kZNear = 1.0f; -static const float kZFar = 100.0f; -static const gvr_rectf fullscreen = {0, 1, 0, 1}; void NativeRenderer::DrawFrame() { auto now = std::chrono::duration_cast( @@ -559,65 +99,12 @@ void NativeRenderer::DrawFrame() { v.g = 1.0f - v.r; v.b = 1.0f; - PrepareFramebuffer(); - - // A client app does its rendering here. - gvr::ClockTimePoint target_time = gvr::GvrApi::GetTimePointNow(); - target_time.monotonic_system_time_nanos += kPredictionTimeWithoutVsyncNanos; - - using namespace googlevr; - using namespace bilateral; - const auto gvrHeadPose = _gvrapi->GetHeadSpaceFromStartSpaceRotation(target_time); - _head_view = toGlm(gvrHeadPose); - _viewportlist->SetToRecommendedBufferViewports(); - - glm::mat4 eye_views[2]; - for_each_side([&](bilateral::Side side) { - int eye = index(side); - const gvr::Eye gvr_eye = eye == 0 ? GVR_LEFT_EYE : GVR_RIGHT_EYE; - const auto& eyeView = eye_views[eye] = toGlm(_gvrapi->GetEyeFromHeadMatrix(gvr_eye)) * _head_view; - auto& viewport = _viewports[eye]; - - _viewportlist->GetBufferViewport(eye, &viewport); - viewport.SetSourceUv(fullscreen); - viewport.SetSourceLayer(eye); - _viewportlist->SetBufferViewport(eye, viewport); - const auto &mvc = _modelview_cube[eye] = eyeView * _model_cube; - const auto &mvf = _modelview_floor[eye] = eyeView * _model_floor; - const gvr_rectf fov = viewport.GetSourceFov(); - const glm::mat4 perspective = perspectiveMatrixFromView(fov, kZNear, kZFar); - _modelview_projection_cube[eye] = perspective * mvc; - _modelview_projection_floor[eye] = perspective * mvf; - _light_pos_eye_space[eye] = glm::vec3(eyeView * _light_pos_world_space); - }); - - - gvr::Frame frame = _swapchain->AcquireFrame(); - withFrameBuffer(frame, 0, [&]{ - glClearColor(v.r, v.g, v.b, 1); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glViewport(0, 0, _renderSize.width / 2, _renderSize.height); - glUseProgram(_cubeProgram); - glBindVertexArray(_cubeVao); - glDrawArrays(GL_TRIANGLES, 0, 3); - /* - float* fp; - fp = (float*)&_light_pos_eye_space[0]; - glUniform3fv(UNIFORM_LIGHT_POS, 2, fp); - fp = (float*)&_modelview_cube[0]; - glUniformMatrix4fv(UNIFORM_MV, 2, GL_FALSE, fp); - fp = (float*)&_modelview_projection_cube[0]; - glUniformMatrix4fv(UNIFORM_MVP, 2, GL_FALSE, fp); - fp = (float*)&_model_cube; - glUniformMatrix4fv(UNIFORM_M, 1, GL_FALSE, fp); - glDrawArrays(GL_TRIANGLES, 0, 36); - */ - glBindVertexArray(0); - }); - - frame.Submit(*_viewportlist, gvrHeadPose); - CheckGLError("onDrawFrame"); - + glClearColor(v.r, v.g, v.b, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glUseProgram(_program); + glBindVertexArray(_vao); + glDrawArrays(GL_TRIANGLES, 0, 3); + glBindVertexArray(0); } void NativeRenderer::OnTriggerEvent() { @@ -626,11 +113,8 @@ void NativeRenderer::OnTriggerEvent() { void NativeRenderer::OnPause() { qDebug() << "QQQ" << __FUNCTION__; - _gvrapi->PauseTracking(); } void NativeRenderer::OnResume() { qDebug() << "QQQ" << __FUNCTION__; - _gvrapi->ResumeTracking(); - _gvrapi->RefreshViewerProfile(); } diff --git a/android/app/src/main/cpp/renderer.h b/android/app/src/main/cpp/renderer.h index df7c51cab4..522f672e3b 100644 --- a/android/app/src/main/cpp/renderer.h +++ b/android/app/src/main/cpp/renderer.h @@ -4,21 +4,8 @@ #include #include -#define GVR - -#if defined(GVR) -#include -#endif - class NativeRenderer { public: - -#if defined(GVR) - NativeRenderer(gvr_context* vrContext); -#else - NativeRenderer(void* vrContext); -#endif - void InitializeGl(); void DrawFrame(); void OnTriggerEvent(); @@ -26,35 +13,9 @@ public: void OnResume(); private: + std::chrono::time_point start { std::chrono::system_clock::now() }; - - std::chrono::time_point start; -#if defined(GVR) - void InitializeVR(); - void PrepareFramebuffer(); - - std::unique_ptr _gvrapi; - gvr::ViewerType _gvr_viewer_type; - std::unique_ptr _viewportlist; - std::unique_ptr _swapchain; - std::array _viewports; - gvr::Sizei _renderSize; -#endif - - uint32_t _cubeBuffer { 0 }; - uint32_t _cubeVao { 0 }; - uint32_t _cubeProgram { 0 }; - - glm::mat4 _head_view; - glm::mat4 _model_cube; - glm::mat4 _camera; - glm::mat4 _view; - glm::mat4 _model_floor; - - std::array _modelview_cube; - std::array _modelview_floor; - std::array _modelview_projection_cube; - std::array _modelview_projection_floor; - std::array _light_pos_eye_space; - const glm::vec4 _light_pos_world_space{ 0, 2, 0, 1}; + uint32_t _geometryBuffer { 0 }; + uint32_t _vao { 0 }; + uint32_t _program { 0 }; }; diff --git a/android/app/src/main/java/org/saintandreas/testapp/MainActivity.java b/android/app/src/main/java/org/saintandreas/testapp/MainActivity.java index 7eea14dce9..65e6c394e7 100644 --- a/android/app/src/main/java/org/saintandreas/testapp/MainActivity.java +++ b/android/app/src/main/java/org/saintandreas/testapp/MainActivity.java @@ -26,10 +26,9 @@ public class MainActivity extends Activity { } private long nativeRenderer; - private GvrLayout gvrLayout; private GLSurfaceView surfaceView; - private native long nativeCreateRenderer(ClassLoader appClassLoader, Context context, long nativeGvrContext); + private native long nativeCreateRenderer(ClassLoader appClassLoader, Context context); private native void nativeDestroyRenderer(long renderer); private native void nativeInitializeGl(long renderer); private native void nativeDrawFrame(long renderer); @@ -55,30 +54,21 @@ public class MainActivity extends Activity { if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { setImmersiveSticky(); } }); - gvrLayout = new GvrLayout(this); nativeRenderer = nativeCreateRenderer( getClass().getClassLoader(), - getApplicationContext(), - gvrLayout.getGvrApi().getNativeGvrContext()); + getApplicationContext()); surfaceView = new GLSurfaceView(this); surfaceView.setEGLContextClientVersion(3); surfaceView.setEGLConfigChooser(8, 8, 8, 0, 0, 0); surfaceView.setPreserveEGLContextOnPause(true); surfaceView.setRenderer(new NativeRenderer()); - - gvrLayout.setPresentationView(surfaceView); - setContentView(gvrLayout); - if (gvrLayout.setAsyncReprojectionEnabled(true)) { - AndroidCompat.setSustainedPerformanceMode(this, true); - } - AndroidCompat.setVrModeEnabled(this, true); + setContentView(surfaceView); } @Override protected void onDestroy() { super.onDestroy(); - gvrLayout.shutdown(); nativeDestroyRenderer(nativeRenderer); nativeRenderer = 0; } @@ -87,14 +77,12 @@ public class MainActivity extends Activity { protected void onPause() { surfaceView.queueEvent(()->nativeOnPause(nativeRenderer)); surfaceView.onPause(); - gvrLayout.onPause(); super.onPause(); } @Override protected void onResume() { super.onResume(); - gvrLayout.onResume(); surfaceView.onResume(); surfaceView.queueEvent(()->nativeOnResume(nativeRenderer)); } diff --git a/android/build.gradle b/android/build.gradle index 77c3dd498c..75b1b7ad4e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,91 +1,216 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { jcenter() + google() } dependencies { - classpath 'com.android.tools.build:gradle:2.3.3' - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + classpath 'com.android.tools.build:gradle:3.0.1' } } +plugins { + id 'de.undercouch.download' version '3.3.0' +} + allprojects { repositories { jcenter() + google() } } + +def baseFolder = new File(HIFI_ANDROID_PRECOMPILED) +def jniFolder = new File('app/src/main/jniLibs/arm64-v8a') + +import org.apache.tools.ant.taskdefs.condition.Os + +def baseUrl = 'https://hifi-public.s3.amazonaws.com/austin/android/' +def qtFile='qt-5.9.3_linux_armv8-libcpp.tgz' +def qtChecksum='547da3547d5690144e23d6504c6d6e91' +if (Os.isFamily(Os.FAMILY_MAC)) { + qtFile = 'qt-5.9.3_osx_armv8-libcpp.tgz' + qtChecksum='6fa3e068cfdee863fc909b294a3a0cc6' +} else if (Os.isFamily(Os.FAMILY_WINDOWS)) { + qtFile = 'qt-5.9.3_win_armv8-libcpp.tgz' + qtChecksum='3a757378a7e9dbbfc662177e0eb46408' +} + +def packages = [ + qt: [ + file: qtFile, + checksum: qtChecksum, + sharedLibFolder: '', + includeLibs: ['lib/*.so', 'plugins/*/*.so'] + ], + bullet: [ + file: 'bullet-2.83_armv8-libcpp.tgz', + checksum: '2c558d604fce337f5eba3eb7ec1252fd' + ], + draco: [ + file: 'draco_armv8-libcpp.tgz', + checksum: '617a80d213a5ec69fbfa21a1f2f738cd' + ], + gvr: [ + file: 'gvrsdk_v1.101.0.tgz', + checksum: '57fd02baa069176ba18597a29b6b4fc7' + ], + openssl: [ + file: 'openssl-1.1.0g_armv8.tgz', + checksum: 'cabb681fbccd79594f65fcc266e02f32' + ], + polyvox: [ + file: 'polyvox_armv8-libcpp.tgz', + checksum: '5c918288741ee754c16aeb12bb46b9e1', + sharedLibFolder: 'lib', + includeLibs: ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so'] + ], + tbb: [ + file: 'tbb-2018_U1_armv8_libcpp.tgz', + checksum: '20768f298f53b195e71b414b0ae240c4', + sharedLibFolder: 'lib/release', + includeLibs: ['libtbb.so', 'libtbbmalloc.so'] + ] +] + +task downloadDependencies { + doLast { + packages.each { entry -> + def filename = entry.value['file']; + def url = baseUrl + filename; + download { + src url + dest new File(baseFolder, filename) + onlyIfNewer true + } + } + } +} + +import de.undercouch.gradle.tasks.download.Verify + +task verifyQt(type: Verify) { def p = packages['qt']; src new File(baseFolder, p['file']); checksum p['checksum']; } +task verifyBullet(type: Verify) { def p = packages['bullet']; src new File(baseFolder, p['file']); checksum p['checksum'] } +task verifyDraco(type: Verify) { def p = packages['draco']; src new File(baseFolder, p['file']); checksum p['checksum'] } +task verifyGvr(type: Verify) { def p = packages['gvr']; src new File(baseFolder, p['file']); checksum p['checksum'] } +task verifyOpenSSL(type: Verify) { def p = packages['openssl']; src new File(baseFolder, p['file']); checksum p['checksum'] } +task verifyPolyvox(type: Verify) { def p = packages['polyvox']; src new File(baseFolder, p['file']); checksum p['checksum'] } +task verifyTBB(type: Verify) { def p = packages['tbb']; src new File(baseFolder, p['file']); checksum p['checksum'] } + +task verifyDependencyDownloads(dependsOn: downloadDependencies) { } +verifyDependencyDownloads.dependsOn verifyQt +verifyDependencyDownloads.dependsOn verifyBullet +verifyDependencyDownloads.dependsOn verifyDraco +verifyDependencyDownloads.dependsOn verifyGvr +verifyDependencyDownloads.dependsOn verifyOpenSSL +verifyDependencyDownloads.dependsOn verifyPolyvox +verifyDependencyDownloads.dependsOn verifyTBB + +task extractDependencies(dependsOn: verifyDependencyDownloads) { + doLast { + packages.each { entry -> + def folder = entry.key; + def filename = entry.value['file']; + def localFile = new File(HIFI_ANDROID_PRECOMPILED, filename) + def localFolder = new File(HIFI_ANDROID_PRECOMPILED, folder) + copy { + from tarTree(resources.gzip(localFile)) + into localFolder + } + } + } +} + +task copyDependencies(dependsOn: extractDependencies) { + doLast { + packages.each { entry -> + def packageName = entry.key + def currentPackage = entry.value; + if (currentPackage.containsKey('sharedLibFolder')) { + def localFolder = new File(baseFolder, packageName + '/' + currentPackage['sharedLibFolder']) + def tree = fileTree(localFolder); + if (currentPackage.containsKey('includeLibs')) { + currentPackage['includeLibs'].each { includeSpec -> tree.include includeSpec } + } + tree.visit { element -> + if (!element.file.isDirectory()) { + copy { from element.file; into jniFolder } + } + } + } + } + } +} + +def scribeFile='scribe_linux_x86_64' +def scribeLocalFile='scribe' +def scribeChecksum='c98678d9726bd8bbf1bab792acf3ff6c' +if (Os.isFamily(Os.FAMILY_MAC)) { + scribeFile = 'scribe_osx_x86_64' + scribeChecksum='a137ad62c1bf7cca739da219544a9a16' +} else if (Os.isFamily(Os.FAMILY_WINDOWS)) { + scribeFile = 'scribe_win32_x86_64.exe' + scribeLocalFile = 'scribe.exe' + scribeChecksum='75c2ce9ed45d17de375e3988bfaba816' + +} + +import de.undercouch.gradle.tasks.download.Download + +task downloadScribe(type: Download) { + src baseUrl + scribeFile + dest new File(baseFolder, scribeLocalFile) + onlyIfNewer true +} + +task verifyScribe (type: Verify, dependsOn: downloadScribe) { + src new File(baseFolder, scribeLocalFile); + checksum scribeChecksum +} + +task fixScribePermissions(type: Exec, dependsOn: verifyScribe) { + commandLine 'chmod', 'a+x', HIFI_ANDROID_PRECOMPILED + '/' + scribeLocalFile +} + +task setupScribe(dependsOn: verifyScribe) { } + +// On Windows, we don't need to set the executable bit, but on OSX and Unix we do +if (!Os.isFamily(Os.FAMILY_WINDOWS)) { + setupScribe.dependsOn fixScribePermissions +} + +task extractGvrBinaries(dependsOn: extractDependencies) { + doLast { + def gvrLibFolder = new File(HIFI_ANDROID_PRECOMPILED, 'gvr/gvr-android-sdk-1.101.0/libraries'); + zipTree(new File(HIFI_ANDROID_PRECOMPILED, 'gvr/gvr-android-sdk-1.101.0/libraries/sdk-audio-1.101.0.aar')).visit { element -> + def fileName = element.file.toString(); + if (fileName.endsWith('libgvr_audio.so') && fileName.contains('arm64-v8a')) { + copy { from element.file; into gvrLibFolder } + } + } + zipTree(new File(HIFI_ANDROID_PRECOMPILED, 'gvr/gvr-android-sdk-1.101.0/libraries/sdk-base-1.101.0.aar')).visit { element -> + def fileName = element.file.toString(); + if (fileName.endsWith('libgvr.so') && fileName.contains('arm64-v8a')) { + copy { from element.file; into gvrLibFolder } + } + } + fileTree(gvrLibFolder).visit { element -> + if (element.file.toString().endsWith('.so')) { + copy { from element.file; into jniFolder } + } + } + } + +} + +task setupDependencies(dependsOn: [setupScribe, copyDependencies, extractGvrBinaries]) { +} + +task cleanDependencies(type: Delete) { + delete HIFI_ANDROID_PRECOMPILED + delete 'app/src/main/jniLibs/arm64-v8a' +} + task clean(type: Delete) { delete rootProject.buildDir } - -task extractQt5jars(type: Copy) { - from fileTree(QT5_ROOT + "/jar") - into("${project.rootDir}/libraries/jar") - include("*.jar") -} - -task extractQt5so(type: Copy) { - from fileTree(QT5_ROOT + "/lib") - into("${project.rootDir}/libraries/jni/armeabi-v7a/") - include("libQt5AndroidExtras.so") - include("libQt5Concurrent.so") - include("libQt5Core.so") - include("libQt5Gamepad.so") - include("libQt5Gui.so") - include("libQt5Location.so") - include("libQt5Multimedia.so") - include("libQt5MultimediaQuick_p.so") - include("libQt5Network.so") - include("libQt5NetworkAuth.so") - include("libQt5OpenGL.so") - include("libQt5Positioning.so") - include("libQt5Qml.so") - include("libQt5Quick.so") - include("libQt5QuickControls2.so") - include("libQt5QuickParticles.so") - include("libQt5QuickTemplates2.so") - include("libQt5QuickWidgets.so") - include("libQt5Script.so") - include("libQt5ScriptTools.so") - include("libQt5Sensors.so") - include("libQt5Svg.so") - include("libQt5WebChannel.so") - include("libQt5WebSockets.so") - include("libQt5WebView.so") - include("libQt5Widgets.so") - include("libQt5Xml.so") - include("libQt5XmlPatterns.so") -} - -task extractAudioSo(type: Copy) { - from zipTree(GVR_ROOT + "/libraries/sdk-audio-1.80.0.aar") - into "${project.rootDir}/libraries/" - include "jni/armeabi-v7a/libgvr_audio.so" -} - -task extractGvrSo(type: Copy) { - from zipTree(GVR_ROOT + "/libraries/sdk-base-1.80.0.aar") - into "${project.rootDir}/libraries/" - include "jni/armeabi-v7a/libgvr.so" -} - -task extractNdk { } -extractNdk.dependsOn extractAudioSo -extractNdk.dependsOn extractGvrSo - -task extractQt5 { } -extractQt5.dependsOn extractQt5so -extractQt5.dependsOn extractQt5jars - -task extractBinaries { } -extractBinaries.dependsOn extractQt5 -extractBinaries.dependsOn extractNdk - -task deleteBinaries(type: Delete) { - delete "${project.rootDir}/libraries/jni" -} - -//clean.dependsOn(deleteBinaries) diff --git a/android/setupGVR.gradle b/android/setupGVR.gradle new file mode 100644 index 0000000000..c91674068f --- /dev/null +++ b/android/setupGVR.gradle @@ -0,0 +1,41 @@ +buildscript { + repositories { + jcenter() + google() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'de.undercouch:gradle-download-task:3.3.0' + } +} + + +def file='gvrsdk_v1.101.0.tgz' +def url='https://hifi-public.s3.amazonaws.com/austin/android/' + file +def destFile = new File(HIFI_ANDROID_PRECOMPILED, file) + +// FIXME find a way to only download if the file doesn't exist +task downloadGVR(type: de.undercouch.gradle.tasks.download.Download) { + src url + dest destFile +} + +task extractGVR(dependsOn: downloadGVR, type: Copy) { + from tarTree(resources.gzip(destFile)) + into new File(HIFI_ANDROID_PRECOMPILED, 'gvr') +} + +task copyGVRAudioLibs(dependsOn: extractGVR, type: Copy) { + from zipTree(new File(HIFI_ANDROID_PRECOMPILED, 'gvr/gvr-android-sdk-1.101.0/libraries/sdk-audio-1.101.0.aar')) + include 'jni/arm64-v8a/libgvr_audio.so' + into HIFI_ANDROID_PRECOMPILED +} + +task copyGVRLibs(dependsOn: extractGVR, type: Copy) { + from zipTree(new File(HIFI_ANDROID_PRECOMPILED, 'gvr/gvr-android-sdk-1.101.0/libraries/sdk-base-1.101.0.aar')) + include 'jni/arm64-v8a/libgvr.so' + into HIFI_ANDROID_PRECOMPILED +} + +task setupGVR(dependsOn: [copyGVRLibs, copyGVRAudioLibs]) { +} diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 0421195612..e657587a7a 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -13,9 +13,25 @@ setup_memory_debugger() link_hifi_libraries( audio avatars octree gpu model fbx entities networking animation recording shared script-engine embedded-webserver - controllers physics plugins midi baking image + controllers physics plugins midi image ) +add_dependencies(${TARGET_NAME} oven) + +if (WIN32) + add_custom_command( + TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + $ + $) +else() + add_custom_command( + TARGET ${TARGET_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E create_symlink + $ + $/oven) +endif() + if (WIN32) package_libraries_for_deployment() endif() diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index ca0f222e0c..1b533f10f3 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -29,11 +29,10 @@ #include #include -#include -#include #include #include #include +#include #include "AssetServerLogging.h" #include "BakeAssetTask.h" @@ -250,7 +249,7 @@ AssetServer::AssetServer(ReceivedMessage& message) : image::setNormalTexturesCompressionEnabled(true); image::setCubeTexturesCompressionEnabled(true); - BAKEABLE_TEXTURE_EXTENSIONS = TextureBaker::getSupportedFormats(); + BAKEABLE_TEXTURE_EXTENSIONS = image::getSupportedFormats(); qDebug() << "Supported baking texture formats:" << BAKEABLE_MODEL_EXTENSIONS; // Most of the work will be I/O bound, reading from disk and constructing packet objects, @@ -416,6 +415,9 @@ void AssetServer::completeSetup() { if (assetsFilesizeLimit != 0 && assetsFilesizeLimit < MAX_UPLOAD_SIZE) { _filesizeLimit = assetsFilesizeLimit * BITS_PER_MEGABITS; } + + PathUtils::removeTemporaryApplicationDirs(); + PathUtils::removeTemporaryApplicationDirs("Oven"); } void AssetServer::cleanupUnmappedFiles() { diff --git a/assignment-client/src/assets/BakeAssetTask.cpp b/assignment-client/src/assets/BakeAssetTask.cpp index 6c78d2baf3..49322ca4cb 100644 --- a/assignment-client/src/assets/BakeAssetTask.cpp +++ b/assignment-client/src/assets/BakeAssetTask.cpp @@ -11,11 +11,18 @@ #include "BakeAssetTask.h" -#include +#include + +#include +#include -#include #include -#include + +static const int OVEN_STATUS_CODE_SUCCESS { 0 }; +static const int OVEN_STATUS_CODE_FAIL { 1 }; +static const int OVEN_STATUS_CODE_ABORT { 2 }; + +std::once_flag registerMetaTypesFlag; BakeAssetTask::BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) : _assetHash(assetHash), @@ -23,6 +30,10 @@ BakeAssetTask::BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetP _filePath(filePath) { + std::call_once(registerMetaTypesFlag, []() { + qRegisterMetaType("QProcess::ProcessError"); + qRegisterMetaType("QProcess::ExitStatus"); + }); } void cleanupTempFiles(QString tempOutputDir, std::vector files) { @@ -41,67 +52,76 @@ void cleanupTempFiles(QString tempOutputDir, std::vector files) { }; void BakeAssetTask::run() { - _isBaking.store(true); - - qRegisterMetaType >("QVector"); - TextureBakerThreadGetter fn = []() -> QThread* { return QThread::currentThread(); }; - - QString tempOutputDir; - - if (_assetPath.endsWith(".fbx")) { - tempOutputDir = PathUtils::generateTemporaryDir(); - _baker = std::unique_ptr { - new FBXBaker(QUrl("file:///" + _filePath), fn, tempOutputDir) - }; - } else if (_assetPath.endsWith(".js", Qt::CaseInsensitive)) { - _baker = std::unique_ptr{ - new JSBaker(QUrl("file:///" + _filePath), PathUtils::generateTemporaryDir()) - }; - } else { - tempOutputDir = PathUtils::generateTemporaryDir(); - _baker = std::unique_ptr { - new TextureBaker(QUrl("file:///" + _filePath), image::TextureUsage::CUBE_TEXTURE, - tempOutputDir) - }; + if (_isBaking.exchange(true)) { + qWarning() << "Tried to start bake asset task while already baking"; + return; } - QEventLoop loop; - connect(_baker.get(), &Baker::finished, &loop, &QEventLoop::quit); - connect(_baker.get(), &Baker::aborted, &loop, &QEventLoop::quit); - QMetaObject::invokeMethod(_baker.get(), "bake", Qt::QueuedConnection); - loop.exec(); + QString tempOutputDir = PathUtils::generateTemporaryDir(); + auto base = QFileInfo(QCoreApplication::applicationFilePath()).absoluteDir(); + QString path = base.absolutePath() + "/oven"; + QString extension = _assetPath.mid(_assetPath.lastIndexOf('.') + 1); + QStringList args { + "-i", _filePath, + "-o", tempOutputDir, + "-t", extension, + }; - if (_baker->wasAborted()) { - qDebug() << "Aborted baking: " << _assetHash << _assetPath; + _ovenProcess.reset(new QProcess()); - _wasAborted.store(true); + connect(_ovenProcess.get(), static_cast(&QProcess::finished), + this, [this, tempOutputDir](int exitCode, QProcess::ExitStatus exitStatus) { + qDebug() << "Baking process finished: " << exitCode << exitStatus; - cleanupTempFiles(tempOutputDir, _baker->getOutputFiles()); + if (exitStatus == QProcess::CrashExit) { + if (_wasAborted) { + emit bakeAborted(_assetHash, _assetPath); + } else { + QString errors = "Fatal error occurred while baking"; + emit bakeFailed(_assetHash, _assetPath, errors); + } + } else if (exitCode == OVEN_STATUS_CODE_SUCCESS) { + QDir outputDir = tempOutputDir; + auto files = outputDir.entryInfoList(QDir::Files); + QVector outputFiles; + for (auto& file : files) { + outputFiles.push_back(file.absoluteFilePath()); + } - emit bakeAborted(_assetHash, _assetPath); - } else if (_baker->hasErrors()) { - qDebug() << "Failed to bake: " << _assetHash << _assetPath << _baker->getErrors(); + emit bakeComplete(_assetHash, _assetPath, tempOutputDir, outputFiles); + } else if (exitStatus == QProcess::NormalExit && exitCode == OVEN_STATUS_CODE_ABORT) { + _wasAborted.store(true); + emit bakeAborted(_assetHash, _assetPath); + } else { + QString errors; + if (exitCode == OVEN_STATUS_CODE_FAIL) { + QDir outputDir = tempOutputDir; + auto errorFilePath = outputDir.absoluteFilePath("errors.txt"); + QFile errorFile { errorFilePath }; + if (errorFile.open(QIODevice::ReadOnly)) { + errors = errorFile.readAll(); + errorFile.close(); + } else { + errors = "Unknown error occurred while baking"; + } + } + emit bakeFailed(_assetHash, _assetPath, errors); + } - auto errors = _baker->getErrors().join('\n'); // Join error list into a single string for convenience - - _didFinish.store(true); - - cleanupTempFiles(tempOutputDir, _baker->getOutputFiles()); + }); + qDebug() << "Starting oven for " << _assetPath; + _ovenProcess->start(path, args, QIODevice::ReadOnly); + if (!_ovenProcess->waitForStarted(-1)) { + QString errors = "Oven process failed to start"; emit bakeFailed(_assetHash, _assetPath, errors); - } else { - auto vectorOutputFiles = QVector::fromStdVector(_baker->getOutputFiles()); - - qDebug() << "Finished baking: " << _assetHash << _assetPath << vectorOutputFiles; - - _didFinish.store(true); - - emit bakeComplete(_assetHash, _assetPath, tempOutputDir, vectorOutputFiles); + return; } + _ovenProcess->waitForFinished(); } void BakeAssetTask::abort() { - if (_baker) { - _baker->abort(); + if (!_wasAborted.exchange(true)) { + _ovenProcess->terminate(); } } diff --git a/assignment-client/src/assets/BakeAssetTask.h b/assignment-client/src/assets/BakeAssetTask.h index 90458ac223..c73a8bff65 100644 --- a/assignment-client/src/assets/BakeAssetTask.h +++ b/assignment-client/src/assets/BakeAssetTask.h @@ -17,9 +17,10 @@ #include #include #include +#include +#include #include -#include class BakeAssetTask : public QObject, public QRunnable { Q_OBJECT @@ -32,7 +33,6 @@ public: void abort(); bool wasAborted() const { return _wasAborted.load(); } - bool didFinish() const { return _didFinish.load(); } signals: void bakeComplete(QString assetHash, QString assetPath, QString tempOutputDir, QVector outputFiles); @@ -44,9 +44,8 @@ private: AssetHash _assetHash; AssetPath _assetPath; QString _filePath; - std::unique_ptr _baker; + std::unique_ptr _ovenProcess { nullptr }; std::atomic _wasAborted { false }; - std::atomic _didFinish { false }; }; #endif // hifi_BakeAssetTask_h diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index c67e998dd4..3ca924b007 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -870,8 +870,8 @@ AvatarMixerClientData* AvatarMixer::getOrCreateClientData(SharedNodePointer node node->setLinkedData(std::unique_ptr { new AvatarMixerClientData(node->getUUID()) }); clientData = dynamic_cast(node->getLinkedData()); auto& avatar = clientData->getAvatar(); - avatar.setDomainMinimumScale(_domainMinimumScale); - avatar.setDomainMaximumScale(_domainMaximumScale); + avatar.setDomainMinimumHeight(_domainMinimumHeight); + avatar.setDomainMaximumHeight(_domainMaximumHeight); } return clientData; @@ -939,21 +939,21 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { const QString AVATARS_SETTINGS_KEY = "avatars"; - static const QString MIN_SCALE_OPTION = "min_avatar_scale"; - float settingMinScale = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MIN_SCALE_OPTION].toDouble(MIN_AVATAR_SCALE); - _domainMinimumScale = glm::clamp(settingMinScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); + static const QString MIN_HEIGHT_OPTION = "min_avatar_height"; + float settingMinHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT); + _domainMinimumHeight = glm::clamp(settingMinHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); - static const QString MAX_SCALE_OPTION = "max_avatar_scale"; - float settingMaxScale = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MAX_SCALE_OPTION].toDouble(MAX_AVATAR_SCALE); - _domainMaximumScale = glm::clamp(settingMaxScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); + static const QString MAX_HEIGHT_OPTION = "max_avatar_height"; + float settingMaxHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT); + _domainMaximumHeight = glm::clamp(settingMaxHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); // make sure that the domain owner didn't flip min and max - if (_domainMinimumScale > _domainMaximumScale) { - std::swap(_domainMinimumScale, _domainMaximumScale); + if (_domainMinimumHeight > _domainMaximumHeight) { + std::swap(_domainMinimumHeight, _domainMaximumHeight); } - qCDebug(avatars) << "This domain requires a minimum avatar scale of" << _domainMinimumScale - << "and a maximum avatar scale of" << _domainMaximumScale; + qCDebug(avatars) << "This domain requires a minimum avatar height of" << _domainMinimumHeight + << "and a maximum avatar height of" << _domainMaximumHeight; const QString AVATAR_WHITELIST_DEFAULT{ "" }; static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist"; diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 610da8ad57..cb5f536faa 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -90,8 +90,8 @@ private: float _maxKbpsPerNode = 0.0f; - float _domainMinimumScale { MIN_AVATAR_SCALE }; - float _domainMaximumScale { MAX_AVATAR_SCALE }; + float _domainMinimumHeight { MIN_AVATAR_HEIGHT }; + float _domainMaximumHeight { MAX_AVATAR_HEIGHT }; RateCounter<> _broadcastRate; p_high_resolution_clock::time_point _lastDebugMessage; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index a4bf8fa253..288652715a 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -25,6 +25,23 @@ AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID) : _avatar->setID(nodeID); } +uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(QUuid otherAvatar) const { + std::unordered_map::const_iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar); + if (itr != _lastOtherAvatarEncodeTime.end()) { + return itr->second; + } + return 0; +} + +void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time) { + std::unordered_map::iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar); + if (itr != _lastOtherAvatarEncodeTime.end()) { + itr->second = time; + } else { + _lastOtherAvatarEncodeTime.emplace(std::pair(otherAvatar, time)); + } +} + void AvatarMixerClientData::queuePacket(QSharedPointer message, SharedNodePointer node) { if (!_packetQueue.node) { _packetQueue.node = node; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index d5c7784da7..acd9be0702 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -110,16 +110,10 @@ public: bool getRequestsDomainListData() { return _requestsDomainListData; } void setRequestsDomainListData(bool requesting) { _requestsDomainListData = requesting; } - ViewFrustum getViewFrustom() const { return _currentViewFrustum; } + ViewFrustum getViewFrustum() const { return _currentViewFrustum; } - quint64 getLastOtherAvatarEncodeTime(QUuid otherAvatar) { - quint64 result = 0; - if (_lastOtherAvatarEncodeTime.find(otherAvatar) != _lastOtherAvatarEncodeTime.end()) { - result = _lastOtherAvatarEncodeTime[otherAvatar]; - } - _lastOtherAvatarEncodeTime[otherAvatar] = usecTimestampNow(); - return result; - } + uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const; + void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time); QVector& getLastOtherAvatarSentJoints(QUuid otherAvatar) { _lastOtherAvatarSentJoints[otherAvatar].resize(_avatar->getJointCount()); @@ -143,7 +137,7 @@ private: // this is a map of the last time we encoded an "other" avatar for // sending to "this" node - std::unordered_map _lastOtherAvatarEncodeTime; + std::unordered_map _lastOtherAvatarEncodeTime; std::unordered_map> _lastOtherAvatarSentJoints; uint64_t _identityChangeTimestamp; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index dd045c24ea..47a81ba1fe 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -32,7 +33,6 @@ #include "AvatarMixerClientData.h" #include "AvatarMixerSlave.h" - void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) { _begin = begin; _end = end; @@ -184,10 +184,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // setup list of AvatarData as well as maps to map betweeen the AvatarData and the original nodes - // for calling the AvatarData::sortAvatars() function and getting our sorted list of client nodes - QList avatarList; + std::vector avatarsToSort; std::unordered_map avatarDataToNodes; - + std::unordered_map avatarEncodeTimes; std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) { // make sure this is an agent that we have avatar data for before considering it for inclusion if (otherNode->getType() == NodeType::Agent @@ -195,36 +194,56 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); AvatarSharedPointer otherAvatar = otherNodeData->getAvatarSharedPointer(); - avatarList << otherAvatar; + avatarsToSort.push_back(otherAvatar); avatarDataToNodes[otherAvatar] = otherNode; + QUuid id = otherAvatar->getSessionUUID(); + avatarEncodeTimes[id] = nodeData->getLastOtherAvatarEncodeTime(id); } }); - AvatarSharedPointer thisAvatar = nodeData->getAvatarSharedPointer(); - ViewFrustum cameraView = nodeData->getViewFrustom(); - std::priority_queue sortedAvatars; - AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars, - [&](AvatarSharedPointer avatar)->uint64_t { - auto avatarNode = avatarDataToNodes[avatar]; - assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map - return nodeData->getLastBroadcastTime(avatarNode->getUUID()); - }, [&](AvatarSharedPointer avatar)->float{ - glm::vec3 nodeBoxHalfScale = (avatar->getWorldPosition() - avatar->getGlobalBoundingBoxCorner() * avatar->getSensorToWorldScale()); - return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z)); - }, [&](AvatarSharedPointer avatar)->bool { + class SortableAvatar: public PrioritySortUtil::Sortable { + public: + SortableAvatar() = delete; + SortableAvatar(const AvatarSharedPointer& avatar, uint64_t lastEncodeTime) + : _avatar(avatar), _lastEncodeTime(lastEncodeTime) {} + glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); } + float getRadius() const override { + glm::vec3 nodeBoxHalfScale = (_avatar->getWorldPosition() - _avatar->getGlobalBoundingBoxCorner() * _avatar->getSensorToWorldScale()); + return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z)); + } + uint64_t getTimestamp() const override { + return _lastEncodeTime; + } + const AvatarSharedPointer& getAvatar() const { return _avatar; } + + private: + AvatarSharedPointer _avatar; + uint64_t _lastEncodeTime; + }; + + // prepare to sort + ViewFrustum cameraView = nodeData->getViewFrustum(); + PrioritySortUtil::PriorityQueue sortedAvatars(cameraView, + AvatarData::_avatarSortCoefficientSize, + AvatarData::_avatarSortCoefficientCenter, + AvatarData::_avatarSortCoefficientAge); + + // ignore or sort + const AvatarSharedPointer& thisAvatar = nodeData->getAvatarSharedPointer(); + for (const auto& avatar : avatarsToSort) { if (avatar == thisAvatar) { - return true; // ignore ourselves... + // don't echo updates to self + continue; } bool shouldIgnore = false; - - // We will also ignore other nodes for a couple of different reasons: + // We ignore other nodes for a couple of reasons: // 1) ignore bubbles and ignore specific node // 2) the node hasn't really updated it's frame data recently, this can // happen if for example the avatar is connected on a desktop and sending // updates at ~30hz. So every 3 frames we skip a frame. - auto avatarNode = avatarDataToNodes[avatar]; + auto avatarNode = avatarDataToNodes[avatar]; assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map const AvatarMixerClientData* avatarNodeData = reinterpret_cast(avatarNode->getLinkedData()); @@ -240,7 +259,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) || (avatarNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) { shouldIgnore = true; } else { - // Check to see if the space bubble is enabled // Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored if (node->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { @@ -267,8 +285,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) nodeData->removeFromRadiusIgnoringSet(node, avatarNode->getUUID()); } } - quint64 endIgnoreCalculation = usecTimestampNow(); - _stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation); if (!shouldIgnore) { AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getUUID()); @@ -292,20 +308,26 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) ++numAvatarsWithSkippedFrames; } } - return shouldIgnore; - }); + quint64 endIgnoreCalculation = usecTimestampNow(); + _stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation); + + if (!shouldIgnore) { + // sort this one for later + uint64_t lastEncodeTime = 0; + std::unordered_map::const_iterator itr = avatarEncodeTimes.find(avatar->getSessionUUID()); + if (itr != avatarEncodeTimes.end()) { + lastEncodeTime = itr->second; + } + sortedAvatars.push(SortableAvatar(avatar, lastEncodeTime)); + } + } // loop through our sorted avatars and allocate our bandwidth to them accordingly - int avatarRank = 0; - // this is overly conservative, because it includes some avatars we might not consider int remainingAvatars = (int)sortedAvatars.size(); - while (!sortedAvatars.empty()) { - AvatarPriority sortData = sortedAvatars.top(); + const auto& avatarData = sortedAvatars.top().getAvatar(); sortedAvatars.pop(); - const auto& avatarData = sortData.avatar; - avatarRank++; remainingAvatars--; auto otherNode = avatarDataToNodes[avatarData]; @@ -332,10 +354,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) nodeData->setLastBroadcastTime(otherNode->getUUID(), usecTimestampNow()); } + // determine if avatar is in view which determines how much data to send glm::vec3 otherPosition = otherAvatar->getClientGlobalPosition(); - - - // determine if avatar is in view, to determine how much data to include... glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f * otherAvatar->getSensorToWorldScale(); AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); bool isInView = nodeData->otherAvatarInView(otherNodeBox); @@ -405,14 +425,18 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // set the last sent sequence number for this sender on the receiver nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), otherNodeData->getLastReceivedSequenceNumber()); + nodeData->setLastOtherAvatarEncodeTime(otherNode->getUUID(), usecTimestampNow()); } + } else { + // TODO? this avatar is not included now, and will probably not be included next frame. + // It would be nice if we could tweak its future sort priority to put it at the back of the list. } avatarPacketList->endSegment(); quint64 endAvatarDataPacking = usecTimestampNow(); _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); - }; + } quint64 startPacketSending = usecTimestampNow(); diff --git a/cmake/macros/GenerateInstallers.cmake b/cmake/macros/GenerateInstallers.cmake index 6c131168d5..702636dd01 100644 --- a/cmake/macros/GenerateInstallers.cmake +++ b/cmake/macros/GenerateInstallers.cmake @@ -29,10 +29,6 @@ macro(GENERATE_INSTALLERS) if (WIN32) - # Do not install the Visual Studio C runtime libraries. The installer will do this automatically - set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE) - - include(InstallRequiredSystemLibraries) set(CPACK_NSIS_MUI_ICON "${HF_CMAKE_DIR}/installer/installer.ico") # install and reference the Add/Remove icon @@ -49,6 +45,10 @@ macro(GENERATE_INSTALLERS) set(_UNINSTALLER_HEADER_BAD_PATH "${HF_CMAKE_DIR}/installer/uninstaller-header.bmp") set(UNINSTALLER_HEADER_IMAGE "") fix_path_for_nsis(${_UNINSTALLER_HEADER_BAD_PATH} UNINSTALLER_HEADER_IMAGE) + + # grab the latest VC redist (2017) and add it to the installer, our NSIS template + # will call it during the install + install(CODE "file(DOWNLOAD https://go.microsoft.com/fwlink/?LinkId=746572 \"\${CMAKE_INSTALL_PREFIX}/vcredist_x64.exe\")") elseif (APPLE) # produce a drag and drop DMG on OS X set(CPACK_GENERATOR "DragNDrop") @@ -84,4 +84,3 @@ macro(GENERATE_INSTALLERS) include(CPack) endmacro() - diff --git a/cmake/macros/SetFromEnv.cmake b/cmake/macros/SetFromEnv.cmake new file mode 100644 index 0000000000..0832c5a536 --- /dev/null +++ b/cmake/macros/SetFromEnv.cmake @@ -0,0 +1,17 @@ +# +# Created by Bradley Austin Davis on 2017/11/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 +# + +function(set_from_env _RESULT_NAME _ENV_VAR_NAME _DEFAULT_VALUE) + if (NOT DEFINED ${_RESULT_NAME}) + if ("$ENV{${_ENV_VAR_NAME}}" STREQUAL "") + set (${_RESULT_NAME} ${_DEFAULT_VALUE} PARENT_SCOPE) + else() + set (${_RESULT_NAME} $ENV{${_ENV_VAR_NAME}} PARENT_SCOPE) + endif() + endif() +endfunction() diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index 8faa4e6d96..e26f81edd9 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -15,13 +15,14 @@ macro(SET_PACKAGING_PARAMETERS) set(PR_BUILD 0) set(PRODUCTION_BUILD 0) set(DEV_BUILD 0) - - set(RELEASE_TYPE $ENV{RELEASE_TYPE}) - set(RELEASE_NUMBER $ENV{RELEASE_NUMBER}) - string(TOLOWER "$ENV{BRANCH}" BUILD_BRANCH) set(BUILD_GLOBAL_SERVICES "DEVELOPMENT") set(USE_STABLE_GLOBAL_SERVICES 0) + set_from_env(RELEASE_TYPE RELEASE_TYPE "DEV") + set_from_env(RELEASE_NUMBER RELEASE_NUMBER "") + set_from_env(BUILD_BRANCH BRANCH "") + string(TOLOWER "${BUILD_BRANCH}" BUILD_BRANCH) + message(STATUS "The BUILD_BRANCH variable is: ${BUILD_BRANCH}") message(STATUS "The BRANCH environment variable is: $ENV{BRANCH}") message(STATUS "The RELEASE_TYPE variable is: ${RELEASE_TYPE}") diff --git a/cmake/macros/SetupQt.cmake b/cmake/macros/SetupQt.cmake index ac67e12044..00a398761b 100644 --- a/cmake/macros/SetupQt.cmake +++ b/cmake/macros/SetupQt.cmake @@ -1,21 +1,11 @@ # -# Copyright 2015 High Fidelity, Inc. -# Created by Bradley Austin Davis on 2015/10/10 +# Created by Bradley Austin Davis on 2017/09/02 +# 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 # -function(set_from_env _RESULT_NAME _ENV_VAR_NAME _DEFAULT_VALUE) - if (NOT DEFINED ${_RESULT_NAME}) - if ("$ENV{${_ENV_VAR_NAME}}" STREQUAL "") - set (${_RESULT_NAME} ${_DEFAULT_VALUE} PARENT_SCOPE) - else() - set (${_RESULT_NAME} $ENV{${_ENV_VAR_NAME}} PARENT_SCOPE) - endif() - endif() -endfunction() - # Construct a default QT location from a root path, a version and an architecture function(calculate_default_qt_dir _RESULT_NAME) if (ANDROID) diff --git a/cmake/macros/TargetBullet.cmake b/cmake/macros/TargetBullet.cmake index 207595d23f..48fe0e0c05 100644 --- a/cmake/macros/TargetBullet.cmake +++ b/cmake/macros/TargetBullet.cmake @@ -6,8 +6,19 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # macro(TARGET_BULLET) - add_dependency_external_projects(bullet) - find_package(Bullet REQUIRED) + if (ANDROID) + set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/bullet) + set(BULLET_INCLUDE_DIRS "${INSTALL_DIR}/include/bullet" CACHE TYPE INTERNAL) + + set(LIB_DIR ${INSTALL_DIR}/lib) + list(APPEND BULLET_LIBRARIES ${LIB_DIR}/libBulletDynamics.a) + list(APPEND BULLET_LIBRARIES ${LIB_DIR}/libBulletCollision.a) + list(APPEND BULLET_LIBRARIES ${LIB_DIR}/libLinearMath.a) + list(APPEND BULLET_LIBRARIES ${LIB_DIR}/libBulletSoftBody.a) + else() + add_dependency_external_projects(bullet) + find_package(Bullet REQUIRED) + endif() # perform the system include hack for OS X to ignore warnings if (APPLE) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${BULLET_INCLUDE_DIRS}") @@ -16,3 +27,5 @@ macro(TARGET_BULLET) endif() target_link_libraries(${TARGET_NAME} ${BULLET_LIBRARIES}) endmacro() + + diff --git a/cmake/macros/TargetDraco.cmake b/cmake/macros/TargetDraco.cmake new file mode 100755 index 0000000000..c198ac35b0 --- /dev/null +++ b/cmake/macros/TargetDraco.cmake @@ -0,0 +1,18 @@ +macro(TARGET_DRACO) + if (ANDROID) + set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/draco) + set(DRACO_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE TYPE INTERNAL) + + set(LIB_DIR ${INSTALL_DIR}/lib) + list(APPEND DRACO_LIBRARIES ${LIB_DIR}/libdraco.a) + list(APPEND DRACO_LIBRARIES ${LIB_DIR}/libdracodec.a) + list(APPEND DRACO_LIBRARIES ${LIB_DIR}/libdracoenc.a) + else() + add_dependency_external_projects(draco) + find_package(Draco REQUIRED) + list(APPEND DRACO_LIBRARIES ${DRACO_LIBRARY}) + list(APPEND DRACO_LIBRARIES ${DRACO_ENCODER_LIBRARY}) + endif() + target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${DRACO_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${DRACO_LIBRARIES}) +endmacro() diff --git a/cmake/macros/TargetGoogleVR.cmake b/cmake/macros/TargetGoogleVR.cmake new file mode 100644 index 0000000000..852037770d --- /dev/null +++ b/cmake/macros/TargetGoogleVR.cmake @@ -0,0 +1,14 @@ +# +# Created by Bradley Austin Davis on 2017/11/28 +# 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 +# +macro(TARGET_GOOGLEVR) + if (ANDROID) + set(GVR_ROOT "${HIFI_ANDROID_PRECOMPILED}/gvr/gvr-android-sdk-1.101.0/") + target_include_directories(native-lib PRIVATE "${GVR_ROOT}/libraries/headers") + target_link_libraries(native-lib "${GVR_ROOT}/libraries/libgvr.so") + endif() +endmacro() diff --git a/cmake/macros/TargetOpenSSL.cmake b/cmake/macros/TargetOpenSSL.cmake index 7ee0283a48..82601bf6aa 100644 --- a/cmake/macros/TargetOpenSSL.cmake +++ b/cmake/macros/TargetOpenSSL.cmake @@ -6,14 +6,10 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # macro(TARGET_OPENSSL) - if (ANDROID) - - # FIXME use a distributable binary - set(OPENSSL_INSTALL_DIR C:/Android/openssl) + set(OPENSSL_INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/openssl) set(OPENSSL_INCLUDE_DIR "${OPENSSL_INSTALL_DIR}/include" CACHE TYPE INTERNAL) set(OPENSSL_LIBRARIES "${OPENSSL_INSTALL_DIR}/lib/libcrypto.a;${OPENSSL_INSTALL_DIR}/lib/libssl.a" CACHE TYPE INTERNAL) - else() find_package(OpenSSL REQUIRED) @@ -28,5 +24,4 @@ macro(TARGET_OPENSSL) include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES}) - endmacro() diff --git a/cmake/macros/TargetPolyvox.cmake b/cmake/macros/TargetPolyvox.cmake new file mode 100644 index 0000000000..9db6b522c7 --- /dev/null +++ b/cmake/macros/TargetPolyvox.cmake @@ -0,0 +1,24 @@ +# +# Created by Bradley Austin Davis on 2017/11/28 +# 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 +# +macro(TARGET_POLYVOX) + if (ANDROID) + set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/polyvox) + set(POLYVOX_INCLUDE_DIRS "${INSTALL_DIR}/include" CACHE TYPE INTERNAL) + set(LIB_DIR ${INSTALL_DIR}/lib) + list(APPEND POLYVOX_LIBRARIES ${LIB_DIR}/libPolyVoxUtil.so) + list(APPEND POLYVOX_LIBRARIES ${LIB_DIR}/Release/libPolyVoxCore.so) + else() + add_dependency_external_projects(polyvox) + find_package(PolyVox REQUIRED) + endif() + target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${POLYVOX_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${POLYVOX_LIBRARIES}) +endmacro() + + + diff --git a/cmake/macros/TargetTBB.cmake b/cmake/macros/TargetTBB.cmake index e9c4639c3d..1e2e69eeaa 100644 --- a/cmake/macros/TargetTBB.cmake +++ b/cmake/macros/TargetTBB.cmake @@ -8,10 +8,10 @@ macro(TARGET_TBB) if (ANDROID) - set(TBB_INSTALL_DIR C:/tbb-2018/built) - set(TBB_LIBRARY ${HIFI_ANDROID_PRECOMPILED}/libtbb.so CACHE FILEPATH "TBB library location") - set(TBB_MALLOC_LIBRARY ${HIFI_ANDROID_PRECOMPILED}/libtbbmalloc.so CACHE FILEPATH "TBB malloc library location") - set(TBB_INCLUDE_DIRS ${TBB_INSTALL_DIR}/include CACHE TYPE "List of tbb include directories" CACHE FILEPATH "TBB includes location") + set(TBB_INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/tbb) + set(TBB_INCLUDE_DIRS ${TBB_INSTALL_DIR}/include CACHE FILEPATH "TBB includes location") + set(TBB_LIBRARY ${TBB_INSTALL_DIR}/lib/release/libtbb.so CACHE FILEPATH "TBB library location") + set(TBB_MALLOC_LIBRARY ${TBB_INSTALL_DIR}/lib/release/libtbbmalloc.so CACHE FILEPATH "TBB malloc library location") set(TBB_LIBRARIES ${TBB_LIBRARY} ${TBB_MALLOC_LIBRARY}) else() add_dependency_external_projects(tbb) diff --git a/cmake/templates/FixupBundlePostBuild.cmake.in b/cmake/templates/FixupBundlePostBuild.cmake.in index d4726884c2..57379bb48b 100644 --- a/cmake/templates/FixupBundlePostBuild.cmake.in +++ b/cmake/templates/FixupBundlePostBuild.cmake.in @@ -45,5 +45,4 @@ else() endif() file(GLOB EXTRA_PLUGINS "${BUNDLE_PLUGIN_DIR}/*.${PLUGIN_EXTENSION}") -fixup_bundle("${BUNDLE_EXECUTABLE}" "${EXTRA_PLUGINS}" "@FIXUP_LIBS@") - +fixup_bundle("${BUNDLE_EXECUTABLE}" "${EXTRA_PLUGINS}" "@FIXUP_LIBS@" IGNORE_ITEM "vcredist_x86.exe;vcredist_x64.exe") diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 31f224667c..c6d118a87b 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1,5 +1,5 @@ { - "version": 2.0, + "version": 2.1, "settings": [ { "name": "label", @@ -1015,20 +1015,20 @@ "assignment-types": [ 1, 2 ], "settings": [ { - "name": "min_avatar_scale", + "name": "min_avatar_height", "type": "double", - "label": "Minimum Avatar Scale", - "help": "Limits the scale of avatars in your domain. Must be at least 0.005.", - "placeholder": 0.25, - "default": 0.25 + "label": "Minimum Avatar Height (meters)", + "help": "Limits the height of avatars in your domain. Must be at least 0.009.", + "placeholder": 0.4, + "default": 0.4 }, { - "name": "max_avatar_scale", + "name": "max_avatar_height", "type": "double", - "label": "Maximum Avatar Scale", - "help": "Limits the scale of avatars in your domain. Cannot be greater than 1000.", - "placeholder": 3.0, - "default": 3.0 + "label": "Maximum Avatar Height (meters)", + "help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.", + "placeholder": 5.2, + "default": 5.2 }, { "name": "avatar_whitelist", diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index a4d3e675aa..05227d35b7 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -304,6 +304,26 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList *wizardCompletedOnce = QVariant(true); } + if (oldVersion < 2.1) { + // convert old avatar scale settings into avatar height. + + const QString AVATAR_MIN_SCALE_KEYPATH = "avatars.min_avatar_scale"; + const QString AVATAR_MAX_SCALE_KEYPATH = "avatars.max_avatar_scale"; + const QString AVATAR_MIN_HEIGHT_KEYPATH = "avatars.min_avatar_height"; + const QString AVATAR_MAX_HEIGHT_KEYPATH = "avatars.max_avatar_height"; + + QVariant* avatarMinScale = _configMap.valueForKeyPath(AVATAR_MIN_SCALE_KEYPATH); + if (avatarMinScale) { + float scale = avatarMinScale->toFloat(); + _configMap.valueForKeyPath(AVATAR_MIN_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT); + } + + QVariant* avatarMaxScale = _configMap.valueForKeyPath(AVATAR_MAX_SCALE_KEYPATH); + if (avatarMaxScale) { + float scale = avatarMaxScale->toFloat(); + _configMap.valueForKeyPath(AVATAR_MAX_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT); + } + } // write the current description version to our settings *versionVariant = _descriptionVersion; diff --git a/interface/resources/controllers/oculus_touch.json b/interface/resources/controllers/oculus_touch.json index 03fc1cbefb..b818d371e3 100644 --- a/interface/resources/controllers/oculus_touch.json +++ b/interface/resources/controllers/oculus_touch.json @@ -13,11 +13,11 @@ { "from": "OculusTouch.LY", "to": "Standard.LY", "filters": [ - { "type": "deadZone", "min": 0.3 }, + { "type": "deadZone", "min": 0.7 }, "invert" ] }, - { "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.3 }, "to": "Standard.LX" }, + { "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.LX" }, { "from": "OculusTouch.LT", "to": "Standard.LTClick", "peek": true, "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] @@ -29,11 +29,11 @@ { "from": "OculusTouch.RY", "to": "Standard.RY", "filters": [ - { "type": "deadZone", "min": 0.3 }, + { "type": "deadZone", "min": 0.7 }, "invert" ] }, - { "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.3 }, "to": "Standard.RX" }, + { "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.RX" }, { "from": "OculusTouch.RT", "to": "Standard.RTClick", "peek": true, "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] diff --git a/interface/resources/html/createGlobalEventBridge.js b/interface/resources/html/createGlobalEventBridge.js index b85aa33e33..a180fbc6cc 100644 --- a/interface/resources/html/createGlobalEventBridge.js +++ b/interface/resources/html/createGlobalEventBridge.js @@ -34,7 +34,7 @@ var EventBridge; var tempEventBridge = EventBridge; EventBridge = channel.objects.eventBridge; EventBridge.audioOutputDeviceChanged.connect(function(deviceName) { - navigator.mediaDevices.getUserMedia({ audio: true, video: true }).then(function(mediaStream) { + navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(function(mediaStream) { navigator.mediaDevices.enumerateDevices().then(function(devices) { devices.forEach(function(device) { if (device.kind == "audiooutput") { diff --git a/interface/resources/html/help.html b/interface/resources/html/help.html deleted file mode 100644 index 1c70300297..0000000000 --- a/interface/resources/html/help.html +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - Welcome to Interface - - - - - - -
- - - - -
- - - diff --git a/interface/resources/html/img/controls-help-gamepad.png b/interface/resources/html/img/controls-help-gamepad.png deleted file mode 100644 index c9d2aa14ec..0000000000 Binary files a/interface/resources/html/img/controls-help-gamepad.png and /dev/null differ diff --git a/interface/resources/html/img/controls-help-keyboard.png b/interface/resources/html/img/controls-help-keyboard.png deleted file mode 100644 index 16564f5c22..0000000000 Binary files a/interface/resources/html/img/controls-help-keyboard.png and /dev/null differ diff --git a/interface/resources/html/img/controls-help-oculus.png b/interface/resources/html/img/controls-help-oculus.png deleted file mode 100644 index 8887bc9ab1..0000000000 Binary files a/interface/resources/html/img/controls-help-oculus.png and /dev/null differ diff --git a/interface/resources/html/img/controls-help-vive.png b/interface/resources/html/img/controls-help-vive.png deleted file mode 100644 index 98b339b7e6..0000000000 Binary files a/interface/resources/html/img/controls-help-vive.png and /dev/null differ diff --git a/interface/resources/html/img/tablet-help-gamepad.jpg b/interface/resources/html/img/tablet-help-gamepad.jpg index 87d79c2d67..2594cbd86c 100644 Binary files a/interface/resources/html/img/tablet-help-gamepad.jpg and b/interface/resources/html/img/tablet-help-gamepad.jpg differ diff --git a/interface/resources/html/img/tablet-help-keyboard.jpg b/interface/resources/html/img/tablet-help-keyboard.jpg index 51edf3c1d3..a62fbe9450 100644 Binary files a/interface/resources/html/img/tablet-help-keyboard.jpg and b/interface/resources/html/img/tablet-help-keyboard.jpg differ diff --git a/interface/resources/html/img/tablet-help-oculus.jpg b/interface/resources/html/img/tablet-help-oculus.jpg index 92f92ae813..bbff760e96 100644 Binary files a/interface/resources/html/img/tablet-help-oculus.jpg and b/interface/resources/html/img/tablet-help-oculus.jpg differ diff --git a/interface/resources/html/img/tablet-help-vive.jpg b/interface/resources/html/img/tablet-help-vive.jpg index abfbd8554a..849e3a5588 100644 Binary files a/interface/resources/html/img/tablet-help-vive.jpg and b/interface/resources/html/img/tablet-help-vive.jpg differ diff --git a/interface/resources/html/raiseAndLowerKeyboard.js b/interface/resources/html/raiseAndLowerKeyboard.js index 23f3a7e9a8..a0aa1eb7fe 100644 --- a/interface/resources/html/raiseAndLowerKeyboard.js +++ b/interface/resources/html/raiseAndLowerKeyboard.js @@ -12,9 +12,9 @@ var MAX_WARNINGS = 3; var numWarnings = 0; var isWindowFocused = true; - var isKeyboardRaised = false; - var isNumericKeyboard = false; - var isPasswordField = false; + window.isKeyboardRaised = false; + window.isNumericKeyboard = false; + window.isPasswordField = false; function shouldSetPasswordField() { var nodeType = document.activeElement.type; @@ -62,7 +62,7 @@ var passwordField = shouldSetPasswordField(); if (isWindowFocused && - (keyboardRaised !== isKeyboardRaised || numericKeyboard !== isNumericKeyboard || passwordField !== isPasswordField)) { + (keyboardRaised !== window.isKeyboardRaised || numericKeyboard !== window.isNumericKeyboard || passwordField !== window.isPasswordField)) { if (typeof EventBridge !== "undefined" && EventBridge !== null) { EventBridge.emitWebEvent( @@ -75,20 +75,20 @@ } } - if (!isKeyboardRaised) { + if (!window.isKeyboardRaised) { scheduleBringToView(250); // Allow time for keyboard to be raised in QML. // 2DO: should it be rather done from 'client area height changed' event? } - isKeyboardRaised = keyboardRaised; - isNumericKeyboard = numericKeyboard; - isPasswordField = passwordField; + window.isKeyboardRaised = keyboardRaised; + window.isNumericKeyboard = numericKeyboard; + window.isPasswordField = passwordField; } }, POLL_FREQUENCY); window.addEventListener("click", function () { var keyboardRaised = shouldRaiseKeyboard(); - if(keyboardRaised && isKeyboardRaised) { + if (keyboardRaised && window.isKeyboardRaised) { scheduleBringToView(150); } }); @@ -99,7 +99,7 @@ window.addEventListener("blur", function () { isWindowFocused = false; - isKeyboardRaised = false; - isNumericKeyboard = false; + window.isKeyboardRaised = false; + window.isNumericKeyboard = false; }); })(); diff --git a/interface/resources/qml/AudioScope.qml b/interface/resources/qml/AudioScope.qml new file mode 100644 index 0000000000..aea1473c3d --- /dev/null +++ b/interface/resources/qml/AudioScope.qml @@ -0,0 +1,634 @@ +// +// AudioScope.qml +// +// Created by Luis Cuenca on 11/22/2017 +// Copyright 2017 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.5 +import QtQuick.Controls 1.4 +import "styles-uit" +import "controls-uit" as HifiControlsUit + +Item { + id: root + width: parent.width + height: parent.height + + property var _scopeInputData + property var _scopeOutputLeftData + property var _scopeOutputRightData + + property var _triggerInputData + property var _triggerOutputLeftData + property var _triggerOutputRightData + + property var _triggerValues: QtObject{ + property int x: parent.width/2 + property int y: parent.height/3 + } + + property var _triggered: false + property var _steps + property var _refreshMs: 32 + property var _framesPerSecond: AudioScope.getFramesPerSecond() + property var _isFrameUnits: true + + property var _holdStart: QtObject{ + property int x: 0 + property int y: 0 + } + + property var _holdEnd: QtObject{ + property int x: 0 + property int y: 0 + } + + property var _timeBeforeHold: 300 + property var _pressedTime: 0 + property var _isPressed: false + + property var _recOpacity : 0.0 + property var _recSign : 0.05 + + property var _outputLeftState: false + property var _outputRightState: false + + property var _wavFilePath: "" + + function isHolding() { + return (_pressedTime > _timeBeforeHold); + } + + function updateMeasureUnits() { + timeButton.text = _isFrameUnits ? "Display Frames" : "Milliseconds"; + fiveLabel.text = _isFrameUnits ? "5" : "" + (Math.round(1000 * 5.0/_framesPerSecond)); + twentyLabel.text = _isFrameUnits ? "20" : "" + (Math.round(1000 * 20.0/_framesPerSecond)); + fiftyLabel.text = _isFrameUnits ? "50" : "" + (Math.round(1000 * 50.0/_framesPerSecond)); + } + + function collectScopeData() { + if (inputCh.checked) { + _scopeInputData = AudioScope.scopeInput; + } + if (outputLeftCh.checked) { + _scopeOutputLeftData = AudioScope.scopeOutputLeft; + } + if (outputRightCh.checked) { + _scopeOutputRightData = AudioScope.scopeOutputRight; + } + } + + function collectTriggerData() { + if (inputCh.checked) { + _triggerInputData = AudioScope.triggerInput; + } + if (outputLeftCh.checked) { + _triggerOutputLeftData = AudioScope.triggerOutputLeft; + } + if (outputRightCh.checked) { + _triggerOutputRightData = AudioScope.triggerOutputRight; + } + } + + function setRecordingLabelOpacity(opacity) { + _recOpacity = opacity; + recCircle.opacity = _recOpacity; + recText.opacity = _recOpacity; + } + + function updateRecordingLabel() { + _recOpacity += _recSign; + if (_recOpacity > 1.0 || _recOpacity < 0.0) { + _recOpacity = _recOpacity > 1.0 ? 1.0 : 0.0; + _recSign *= -1; + } + setRecordingLabelOpacity(_recOpacity); + } + + function pullFreshValues() { + if (Audio.getRecording()) { + updateRecordingLabel(); + } + + if (!AudioScope.getPause()) { + if (!_triggered) { + collectScopeData(); + } + } + if (inputCh.checked || outputLeftCh.checked || outputRightCh.checked) { + mycanvas.requestPaint(); + } + } + + function startRecording() { + _wavFilePath = (new Date()).toISOString(); // yyyy-mm-ddThh:mm:ss.sssZ + _wavFilePath = _wavFilePath.replace(/[\-:]|\.\d*Z$/g, "").replace("T", "-") + ".wav"; + // Using controller recording default directory + _wavFilePath = Recording.getDefaultRecordingSaveDirectory() + _wavFilePath; + if (!Audio.startRecording(_wavFilePath)) { + Messages.sendMessage("Hifi-Notifications", JSON.stringify({message:"Error creating: "+_wavFilePath})); + updateRecordingUI(false); + } + } + + function stopRecording() { + Audio.stopRecording(); + setRecordingLabelOpacity(0.0); + Messages.sendMessage("Hifi-Notifications", JSON.stringify({message:"Saved: "+_wavFilePath})); + } + + function updateRecordingUI(isRecording) { + if (!isRecording) { + recordButton.text = "Record"; + recordButton.color = hifi.buttons.black; + outputLeftCh.checked = _outputLeftState; + outputRightCh.checked = _outputRightState; + } else { + recordButton.text = "Stop"; + recordButton.color = hifi.buttons.red; + _outputLeftState = outputLeftCh.checked; + _outputRightState = outputRightCh.checked; + outputLeftCh.checked = true; + outputRightCh.checked = true; + } + } + + function toggleRecording() { + if (Audio.getRecording()) { + updateRecordingUI(false); + stopRecording(); + } else { + updateRecordingUI(true); + startRecording(); + } + } + + Timer { + interval: _refreshMs; running: true; repeat: true + onTriggered: pullFreshValues() + } + + Canvas { + id: mycanvas + anchors.fill:parent + + onPaint: { + + function displayMeasureArea(ctx) { + + ctx.fillStyle = Qt.rgba(0.1, 0.1, 0.1, 1); + ctx.fillRect(_holdStart.x, 0, _holdEnd.x - _holdStart.x, height); + + ctx.lineWidth = "2"; + ctx.strokeStyle = "#555555"; + + ctx.beginPath(); + ctx.moveTo(_holdStart.x, 0); + ctx.lineTo(_holdStart.x, height); + ctx.moveTo(_holdEnd.x, 0); + ctx.lineTo(_holdEnd.x, height); + + ctx.moveTo(_holdStart.x, _holdStart.y); + ctx.lineTo(_holdEnd.x, _holdStart.y); + ctx.moveTo(_holdEnd.x, _holdEnd.y); + ctx.lineTo(_holdStart.x, _holdEnd.y); + + ctx.stroke(); + } + + function displayTrigger(ctx, lineWidth, color) { + var crossSize = 3; + var holeSize = 2; + + ctx.lineWidth = lineWidth; + ctx.strokeStyle = color; + + ctx.beginPath(); + ctx.moveTo(_triggerValues.x - (crossSize + holeSize), _triggerValues.y); + ctx.lineTo(_triggerValues.x - holeSize, _triggerValues.y); + ctx.moveTo(_triggerValues.x + holeSize, _triggerValues.y); + ctx.lineTo(_triggerValues.x + (crossSize + holeSize), _triggerValues.y); + + ctx.moveTo(_triggerValues.x, _triggerValues.y - (crossSize + holeSize)); + ctx.lineTo(_triggerValues.x, _triggerValues.y - holeSize); + ctx.moveTo(_triggerValues.x, _triggerValues.y + holeSize); + ctx.lineTo(_triggerValues.x, _triggerValues.y + (crossSize + holeSize)); + + ctx.stroke(); + } + + function displayBackground(ctx, datawidth, steps, lineWidth, color) { + var verticalPadding = 100; + + ctx.strokeStyle = color; + ctx.lineWidth = lineWidth; + + ctx.moveTo(0, height/2); + ctx.lineTo(datawidth, height/2); + + var gap = datawidth/steps; + for (var i = 0; i < steps; i++) { + ctx.moveTo(i*gap + 1, verticalPadding); + ctx.lineTo(i*gap + 1, height-verticalPadding); + } + ctx.moveTo(datawidth-1, verticalPadding); + ctx.lineTo(datawidth-1, height-verticalPadding); + + ctx.stroke(); + } + + function drawScope(ctx, data, width, color) { + ctx.beginPath(); + ctx.strokeStyle = color; + ctx.lineWidth = width; + var x = 0; + for (var i = 0; i < data.length-1; i++) { + ctx.moveTo(x, data[i] + height/2); + ctx.lineTo(++x, data[i+1] + height/2); + } + ctx.stroke(); + } + + function getMeasurementText(dist) { + var datasize = _scopeInputData.length; + var value = 0; + if (fiveFrames.checked) { + value = (_isFrameUnits) ? 5.0*dist/datasize : (Math.round(1000 * 5.0/_framesPerSecond))*dist/datasize; + } else if (twentyFrames.checked) { + value = (_isFrameUnits) ? 20.0*dist/datasize : (Math.round(1000 * 20.0/_framesPerSecond))*dist/datasize; + } else if (fiftyFrames.checked) { + value = (_isFrameUnits) ? 50.0*dist/datasize : (Math.round(1000 * 50.0/_framesPerSecond))*dist/datasize; + } + value = Math.abs(Math.round(value*100)/100); + var measureText = "" + value + (_isFrameUnits ? " frames" : " milliseconds"); + return measureText; + } + + function drawMeasurements(ctx, color) { + ctx.fillStyle = color; + ctx.font = "normal 16px sans-serif"; + var fontwidth = 8; + var measureText = getMeasurementText(_holdEnd.x - _holdStart.x); + if (_holdStart.x < _holdEnd.x) { + ctx.fillText("" + height/2 - _holdStart.y, _holdStart.x-40, _holdStart.y); + ctx.fillText("" + height/2 - _holdEnd.y, _holdStart.x-40, _holdEnd.y); + ctx.fillText(measureText, _holdEnd.x+10, _holdEnd.y); + } else { + ctx.fillText("" + height/2 - _holdStart.y, _holdStart.x+10, _holdStart.y); + ctx.fillText("" + height/2 - _holdEnd.y, _holdStart.x+10, _holdEnd.y); + ctx.fillText(measureText, _holdEnd.x-fontwidth*measureText.length, _holdEnd.y); + } + } + + var ctx = getContext("2d"); + + ctx.fillStyle = Qt.rgba(0, 0, 0, 1); + ctx.fillRect(0, 0, width, height); + + if (isHolding()) { + displayMeasureArea(ctx); + } + + var guideLinesColor = "#555555" + var guideLinesWidth = "1" + + displayBackground(ctx, _scopeInputData.length, _steps, guideLinesWidth, guideLinesColor); + + var triggerWidth = "3" + var triggerColor = "#EFB400" + + if (AudioScope.getAutoTrigger()) { + displayTrigger(ctx, triggerWidth, triggerColor); + } + + var scopeWidth = "2" + var scopeInputColor = "#00B4EF" + var scopeOutputLeftColor = "#BB0000" + var scopeOutputRightColor = "#00BB00" + + if (!_triggered) { + if (inputCh.checked) { + drawScope(ctx, _scopeInputData, scopeWidth, scopeInputColor); + } + if (outputLeftCh.checked) { + drawScope(ctx, _scopeOutputLeftData, scopeWidth, scopeOutputLeftColor); + } + if (outputRightCh.checked) { + drawScope(ctx, _scopeOutputRightData, scopeWidth, scopeOutputRightColor); + } + } else { + if (inputCh.checked) { + drawScope(ctx, _triggerInputData, scopeWidth, scopeInputColor); + } + if (outputLeftCh.checked) { + drawScope(ctx, _triggerOutputLeftData, scopeWidth, scopeOutputLeftColor); + } + if (outputRightCh.checked) { + drawScope(ctx, _triggerOutputRightData, scopeWidth, scopeOutputRightColor); + } + } + + if (isHolding()) { + drawMeasurements(ctx, "#eeeeee"); + } + + if (_isPressed) { + _pressedTime += _refreshMs; + } + } + } + + MouseArea { + id: hitbox + anchors.fill: mycanvas + hoverEnabled: true + onPressed: { + _isPressed = true; + _pressedTime = 0; + _holdStart.x = mouseX; + _holdStart.y = mouseY; + } + onPositionChanged: { + _holdEnd.x = mouseX; + _holdEnd.y = mouseY; + } + onReleased: { + if (!isHolding() && AudioScope.getAutoTrigger()) { + _triggerValues.x = mouseX + _triggerValues.y = mouseY + AudioScope.setTriggerValues(mouseX, mouseY-height/2); + } + _isPressed = false; + _pressedTime = 0; + } + } + + HifiControlsUit.CheckBox { + id: activated + boxSize: 20 + anchors.top: parent.top; + anchors.left: parent.left; + anchors.topMargin: 8; + anchors.leftMargin: 20; + checked: AudioScope.getVisible(); + onCheckedChanged: { + AudioScope.setVisible(checked); + activelabel.text = AudioScope.getVisible() ? "On" : "Off" + } + } + + HifiControlsUit.Label { + id: activelabel + text: AudioScope.getVisible() ? "On" : "Off" + anchors.top: activated.top; + anchors.left: activated.right; + } + + HifiControlsUit.CheckBox { + id: outputLeftCh + boxSize: 20 + text: "Output L" + anchors.horizontalCenter: parent.horizontalCenter; + anchors.top: parent.top; + anchors.topMargin: 8; + onCheckedChanged: { + AudioScope.setServerEcho(outputLeftCh.checked || outputRightCh.checked); + } + } + + HifiControlsUit.Label { + text: "Channels"; + anchors.horizontalCenter: outputLeftCh.horizontalCenter; + anchors.bottom: outputLeftCh.top; + anchors.bottomMargin: 8; + } + + HifiControlsUit.CheckBox { + id: inputCh + boxSize: 20 + text: "Input Mono" + anchors.bottom: outputLeftCh.bottom; + anchors.right: outputLeftCh.left; + anchors.rightMargin: 40; + onCheckedChanged: { + AudioScope.setLocalEcho(checked); + } + } + + HifiControlsUit.CheckBox { + id: outputRightCh + boxSize: 20 + text: "Output R" + anchors.bottom: outputLeftCh.bottom; + anchors.left: outputLeftCh.right; + anchors.leftMargin: 40; + onCheckedChanged: { + AudioScope.setServerEcho(outputLeftCh.checked || outputRightCh.checked); + } + } + + HifiControlsUit.Button { + id: recordButton; + text: "Record"; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + anchors.rightMargin: 30; + anchors.bottomMargin: 8; + width: 95; + height: 55; + onClicked: { + toggleRecording(); + } + } + + HifiControlsUit.Button { + id: pauseButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.right: recordButton.left; + anchors.bottom: parent.bottom; + anchors.rightMargin: 30; + anchors.bottomMargin: 8; + height: 55; + width: 95; + text: " Pause "; + onClicked: { + AudioScope.togglePause(); + } + } + + HifiControlsUit.CheckBox { + id: twentyFrames + boxSize: 20 + anchors.left: parent.horizontalCenter; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + onCheckedChanged: { + if (checked){ + fiftyFrames.checked = false; + fiveFrames.checked = false; + AudioScope.selectAudioScopeTwentyFrames(); + _steps = 20; + AudioScope.setPause(false); + } + } + } + + HifiControlsUit.Label { + id:twentyLabel + anchors.left: twentyFrames.right; + anchors.verticalCenter: twentyFrames.verticalCenter; + } + + HifiControlsUit.Button { + id: timeButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + text: "Display Frames"; + anchors.horizontalCenter: twentyFrames.horizontalCenter; + anchors.bottom: twentyFrames.top; + anchors.bottomMargin: 8; + height: 26; + onClicked: { + _isFrameUnits = !_isFrameUnits; + updateMeasureUnits(); + } + } + + HifiControlsUit.CheckBox { + id: fiveFrames + boxSize: 20 + anchors.horizontalCenter: parent.horizontalCenter; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + anchors.horizontalCenterOffset: -50; + checked: true; + onCheckedChanged: { + if (checked) { + fiftyFrames.checked = false; + twentyFrames.checked = false; + AudioScope.selectAudioScopeFiveFrames(); + _steps = 5; + AudioScope.setPause(false); + } + } + } + + HifiControlsUit.Label { + id:fiveLabel + anchors.left: fiveFrames.right; + anchors.verticalCenter: fiveFrames.verticalCenter; + } + + HifiControlsUit.CheckBox { + id: fiftyFrames + boxSize: 20 + anchors.horizontalCenter: parent.horizontalCenter; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + anchors.horizontalCenterOffset: 70; + onCheckedChanged: { + if (checked) { + twentyFrames.checked = false; + fiveFrames.checked = false; + AudioScope.selectAudioScopeFiftyFrames(); + _steps = 50; + AudioScope.setPause(false); + } + } + } + + HifiControlsUit.Label { + id:fiftyLabel + anchors.left: fiftyFrames.right; + anchors.verticalCenter: fiftyFrames.verticalCenter; + } + + HifiControlsUit.Switch { + id: triggerSwitch; + height: 26; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.leftMargin: 75; + anchors.bottomMargin: 8; + labelTextOff: "Off"; + labelTextOn: "On"; + onCheckedChanged: { + if (!checked) AudioScope.setPause(false); + AudioScope.setPause(false); + AudioScope.setAutoTrigger(checked); + AudioScope.setTriggerValues(_triggerValues.x, _triggerValues.y-root.height/2); + } + } + + HifiControlsUit.Label { + text: "Trigger"; + anchors.left: triggerSwitch.left; + anchors.leftMargin: -15; + anchors.bottom: triggerSwitch.top; + } + + Rectangle { + id: recordIcon; + width:110; + height:40; + anchors.right: parent.right; + anchors.top: parent.top; + anchors.topMargin: 8; + color: "transparent" + + Text { + id: recText + text: "REC" + color: "red" + font.pixelSize: 30; + anchors.left: recCircle.right; + anchors.leftMargin: 10; + opacity: _recOpacity; + y: -8; + } + + Rectangle { + id: recCircle; + width: 25; + height: 25; + radius: width*0.5 + opacity: _recOpacity; + color: "red"; + } + } + + Component.onCompleted: { + _steps = AudioScope.getFramesPerScope(); + AudioScope.setTriggerValues(_triggerValues.x, _triggerValues.y-root.height/2); + activated.checked = true; + inputCh.checked = true; + updateMeasureUnits(); + } + + Connections { + target: AudioScope + onPauseChanged: { + if (!AudioScope.getPause()) { + pauseButton.text = "Pause"; + pauseButton.color = hifi.buttons.black; + AudioScope.setTriggered(false); + _triggered = false; + } else { + pauseButton.text = "Continue"; + pauseButton.color = hifi.buttons.blue; + } + } + onTriggered: { + _triggered = true; + collectTriggerData(); + AudioScope.setPause(true); + } + } +} diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 300bcd46f0..0e2f2a5282 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -52,7 +52,11 @@ Item { targetHeight += hifi.dimensions.contentSpacing.y + additionalInformation.height } - parent.width = root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth)); + var newWidth = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth)); + if(!isNaN(newWidth)) { + parent.width = root.width = newWidth; + } + parent.height = root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight)) + (keyboardEnabled && keyboardRaised ? (200 + 2 * hifi.dimensions.contentSpacing.y) : hifi.dimensions.contentSpacing.y); } diff --git a/interface/resources/qml/controls-uit/Table.qml b/interface/resources/qml/controls-uit/Table.qml index 11d1920f95..1443cd5d6e 100644 --- a/interface/resources/qml/controls-uit/Table.qml +++ b/interface/resources/qml/controls-uit/Table.qml @@ -11,6 +11,7 @@ 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" @@ -24,6 +25,45 @@ TableView { 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 @@ -98,74 +138,13 @@ TableView { backgroundVisible: true horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - verticalScrollBarPolicy: Qt.ScrollBarAsNeeded + 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 - - handle: Item { - id: scrollbarHandle - implicitWidth: hifi.dimensions.scrollbarHandleWidth - Rectangle { - anchors { - fill: parent - topMargin: 3 - bottomMargin: 3 // "" - leftMargin: 1 // Move it right - rightMargin: -1 // "" - } - radius: hifi.dimensions.scrollbarHandleWidth/2 - color: isLightColorScheme ? hifi.colors.tableScrollHandleLight : hifi.colors.tableScrollHandleDark - } - } - - scrollBarBackground: Item { - implicitWidth: hifi.dimensions.scrollbarBackgroundWidth - Rectangle { - anchors { - fill: parent - margins: -1 // Expand - topMargin: -1 - } - color: isLightColorScheme ? hifi.colors.tableScrollBackgroundLight : hifi.colors.tableScrollBackgroundDark - - // Extend header color above scrollbar background - Rectangle { - anchors { - top: parent.top - topMargin: -hifi.dimensions.tableHeaderHeight - left: parent.left - right: parent.right - } - height: hifi.dimensions.tableHeaderHeight - color: tableView.isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark - visible: headerVisible - } - Rectangle { - // Extend header bottom border - anchors { - top: parent.top - left: parent.left - right: parent.right - } - height: 1 - color: isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight - visible: headerVisible - } - } - } - - incrementControl: Item { - visible: false - } - - decrementControl: Item { - visible: false - } } rowDelegate: Rectangle { diff --git a/interface/resources/qml/controls-uit/Tree.qml b/interface/resources/qml/controls-uit/Tree.qml index 79d3b958ea..6bd11295b1 100644 --- a/interface/resources/qml/controls-uit/Tree.qml +++ b/interface/resources/qml/controls-uit/Tree.qml @@ -9,9 +9,11 @@ // import QtQml.Models 2.2 -import QtQuick 2.5 +import QtQuick 2.7 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 +import QtQuick.Controls 2.2 as QQC2 + import "../styles-uit" @@ -35,6 +37,45 @@ TreeView { headerVisible: false + Component.onCompleted: { + if (flickableItem !== null && flickableItem !== undefined) { + treeView.flickableItem.QQC2.ScrollBar.vertical = scrollbar + } + } + + QQC2.ScrollBar { + id: scrollbar + parent: treeView.flickableItem + policy: QQC2.ScrollBar.AsNeeded + orientation: Qt.Vertical + visible: size < 1.0 + topPadding: treeView.headerVisible ? hifi.dimensions.tableHeaderHeight + 1 : 1 + anchors.top: treeView.top + anchors.left: treeView.right + anchors.bottom: treeView.bottom + + background: Item { + implicitWidth: hifi.dimensions.scrollbarBackgroundWidth + Rectangle { + anchors { + fill: parent; + topMargin: treeView.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 + } + } + } + // Use rectangle to draw border with rounded corners. frameVisible: false Rectangle { @@ -50,7 +91,7 @@ TreeView { backgroundVisible: true horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff - verticalScrollBarPolicy: Qt.ScrollBarAsNeeded + verticalScrollBarPolicy: Qt.ScrollBarAlwaysOff style: TreeViewStyle { // Needed in order for rows to keep displaying rows after end of table entries. @@ -126,66 +167,6 @@ TreeView { leftMargin: hifi.dimensions.tablePadding / 2 } } - - handle: Item { - id: scrollbarHandle - implicitWidth: hifi.dimensions.scrollbarHandleWidth - Rectangle { - anchors { - fill: parent - topMargin: treeView.headerVisible ? hifi.dimensions.tableHeaderHeight + 3 : 3 - bottomMargin: 3 // "" - leftMargin: 1 // Move it right - rightMargin: -1 // "" - } - radius: hifi.dimensions.scrollbarHandleWidth / 2 - color: treeView.isLightColorScheme ? hifi.colors.tableScrollHandleLight : hifi.colors.tableScrollHandleDark - } - } - - scrollBarBackground: Item { - implicitWidth: hifi.dimensions.scrollbarBackgroundWidth - Rectangle { - anchors { - fill: parent - topMargin: treeView.headerVisible ? hifi.dimensions.tableHeaderHeight - 1 : -1 - margins: -1 // Expand - } - color: treeView.isLightColorScheme ? hifi.colors.tableScrollBackgroundLight : hifi.colors.tableScrollBackgroundDark - - // Extend header color above scrollbar background - Rectangle { - anchors { - top: parent.top - topMargin: -hifi.dimensions.tableHeaderHeight - left: parent.left - right: parent.right - } - height: hifi.dimensions.tableHeaderHeight - color: treeView.isLightColorScheme ? hifi.colors.tableBackgroundLight : hifi.colors.tableBackgroundDark - visible: treeView.headerVisible - } - Rectangle { - // Extend header bottom border - anchors { - top: parent.top - left: parent.left - right: parent.right - } - height: 1 - color: treeView.isLightColorScheme ? hifi.colors.lightGrayText : hifi.colors.baseGrayHighlight - visible: treeView.headerVisible - } - } - } - - incrementControl: Item { - visible: false - } - - decrementControl: Item { - visible: false - } } rowDelegate: Rectangle { @@ -193,8 +174,8 @@ TreeView { color: styleData.selected ? hifi.colors.primaryHighlight : treeView.isLightColorScheme - ? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd) - : (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd) + ? (styleData.alternate ? hifi.colors.tableRowLightEven : hifi.colors.tableRowLightOdd) + : (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd) } itemDelegate: FiraSansSemiBold { @@ -209,9 +190,9 @@ TreeView { text: styleData.value size: hifi.fontSizes.tableText color: colorScheme == hifi.colorSchemes.light - ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) - : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) - + ? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) + : (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) + elide: Text.ElideRight } diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index 1e0a936bf0..6ab4fd9758 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -21,6 +21,8 @@ Item { signal newViewRequestedCallback(var request) signal loadingChangedCallback(var loadRequest) + width: parent.width + property bool interactive: false StylesUIt.HifiConstants { @@ -58,7 +60,8 @@ Item { WebEngineView { id: webViewCore - anchors.fill: parent + width: parent.width + height: parent.height profile: HFWebEngineProfile; settings.pluginsEnabled: true @@ -91,20 +94,19 @@ Item { userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] - property string newUrl: "" - Component.onCompleted: { webChannel.registerObject("eventBridge", eventBridge); webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - // Ensure the JS from the web-engine makes it to our logging - webViewCore.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { - console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); - }); + if (webViewCoreUserAgent !== undefined) { webViewCore.profile.httpUserAgent = webViewCoreUserAgent } else { webViewCore.profile.httpUserAgent += " (HighFidelityInterface)"; } + // Ensure the JS from the web-engine makes it to our logging + webViewCore.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { + console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); + }); } onFeaturePermissionRequested: { diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index efcf6ccfcf..64f61f0d69 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -26,6 +26,7 @@ Rectangle { // Style color: "#E3E3E3"; // Properties + property bool debug: false; property int myCardWidth: width - upperRightInfoContainer.width; property int myCardHeight: 80; property int rowHeight: 60; @@ -1120,7 +1121,9 @@ Rectangle { break; case 'connections': var data = message.params; - console.log('Got connection data: ', JSON.stringify(data)); + if (pal.debug) { + console.log('Got connection data: ', JSON.stringify(data)); + } connectionsUserModelData = data; sortConnectionsModel(); connectionsLoading.visible = false; diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 6c4e020694..a058f32994 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -79,10 +79,12 @@ Rectangle { if (result.status !== 'success') { failureErrorText.text = result.message; root.activeView = "checkoutFailure"; + UserActivityLogger.commercePurchaseFailure(root.itemId, root.itemPrice, !root.alreadyOwned, result.message); } else { root.itemHref = result.data.download_url; root.isWearable = result.data.categories.indexOf("Wearables") > -1; root.activeView = "checkoutSuccess"; + UserActivityLogger.commercePurchaseSuccess(root.itemId, root.itemPrice, !root.alreadyOwned); } } @@ -599,6 +601,7 @@ Rectangle { sendToScript({method: 'checkout_rezClicked', itemHref: root.itemHref, isWearable: root.isWearable}); rezzedNotifContainer.visible = true; rezzedNotifContainerTimer.start(); + UserActivityLogger.commerceEntityRezzed(root.itemId, "checkout", root.isWearable ? "rez" : "wear"); } } RalewaySemiBold { @@ -902,7 +905,7 @@ Rectangle { } buyTextContainer.color = "#FFC3CD"; buyTextContainer.border.color = "#F3808F"; - buyGlyph.text = hifi.glyphs.error; + buyGlyph.text = hifi.glyphs.alert; buyGlyph.size = 54; } else { if (root.alreadyOwned) { diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 15ebada0c4..f7913e5b1e 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -349,6 +349,7 @@ Item { sendToPurchases({method: 'purchases_rezClicked', itemHref: root.itemHref, isWearable: root.isWearable}); rezzedNotifContainer.visible = true; rezzedNotifContainerTimer.start(); + UserActivityLogger.commerceEntityRezzed(root.itemId, "purchases", root.isWearable ? "rez" : "wear"); } style: ButtonStyle { diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 4c10bdc097..ff9ce16e93 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -343,6 +343,9 @@ Rectangle { ListModel { id: previousPurchasesModel; } + HifiCommerceCommon.SortableListModel { + id: tempPurchasesModel; + } HifiCommerceCommon.SortableListModel { id: filteredPurchasesModel; } @@ -635,20 +638,40 @@ Rectangle { } function buildFilteredPurchasesModel() { - filteredPurchasesModel.clear(); + var sameItemCount = 0; + + tempPurchasesModel.clear(); for (var i = 0; i < purchasesModel.count; i++) { if (purchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) { if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems) { - filteredPurchasesModel.insert(0, purchasesModel.get(i)); + tempPurchasesModel.insert(0, purchasesModel.get(i)); } else if ((root.isShowingMyItems && purchasesModel.get(i).edition_number === "0") || (!root.isShowingMyItems && purchasesModel.get(i).edition_number !== "0")) { - filteredPurchasesModel.append(purchasesModel.get(i)); + tempPurchasesModel.append(purchasesModel.get(i)); } } } + + for (var i = 0; i < tempPurchasesModel.count; i++) { + if (!filteredPurchasesModel.get(i)) { + sameItemCount = -1; + break; + } else if (tempPurchasesModel.get(i).itemId === filteredPurchasesModel.get(i).itemId && + tempPurchasesModel.get(i).edition_number === filteredPurchasesModel.get(i).edition_number && + tempPurchasesModel.get(i).status === filteredPurchasesModel.get(i).status) { + sameItemCount++; + } + } - populateDisplayedItemCounts(); - sortByDate(); + if (sameItemCount !== tempPurchasesModel.count) { + filteredPurchasesModel.clear(); + for (var i = 0; i < tempPurchasesModel.count; i++) { + filteredPurchasesModel.append(tempPurchasesModel.get(i)); + } + + populateDisplayedItemCounts(); + sortByDate(); + } } function checkIfAnyItemStatusChanged() { diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml index d967a36b68..582052c4c3 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseModal.qml @@ -206,16 +206,6 @@ Item { root.isPasswordField = (focus && passphraseField.echoMode === TextInput.Password); } - MouseArea { - anchors.fill: parent; - - onClicked: { - root.keyboardRaised = true; - root.isPasswordField = (passphraseField.echoMode === TextInput.Password); - mouse.accepted = false; - } - } - onAccepted: { submitPassphraseInputButton.enabled = false; commerce.setPassphrase(passphraseField.text); @@ -362,25 +352,6 @@ Item { right: parent.right; } - Image { - id: lowerKeyboardButton; - z: 999; - source: "images/lowerKeyboard.png"; - anchors.right: keyboard.right; - anchors.top: keyboard.showMirrorText ? keyboard.top : undefined; - anchors.bottom: keyboard.showMirrorText ? undefined : keyboard.bottom; - height: 50; - width: 60; - - MouseArea { - anchors.fill: parent; - - onClicked: { - root.keyboardRaised = false; - } - } - } - HifiControlsUit.Keyboard { id: keyboard; raised: HMD.mounted && root.keyboardRaised; diff --git a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml index ffeedde8f0..9ba066a9fd 100644 --- a/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml +++ b/interface/resources/qml/hifi/commerce/wallet/PassphraseSelection.qml @@ -82,17 +82,6 @@ Item { if (focus) { var hidePassword = (currentPassphraseField.echoMode === TextInput.Password); sendSignalToWallet({method: 'walletSetup_raiseKeyboard', isPasswordField: hidePassword}); - } else if (!passphraseFieldAgain.focus) { - sendSignalToWallet({method: 'walletSetup_lowerKeyboard', isPasswordField: false}); - } - } - - MouseArea { - anchors.fill: parent; - onPressed: { - var hidePassword = (currentPassphraseField.echoMode === TextInput.Password); - sendSignalToWallet({method: 'walletSetup_raiseKeyboard', isPasswordField: hidePassword}); - mouse.accepted = false; } } @@ -115,21 +104,10 @@ Item { activeFocusOnPress: true; activeFocusOnTab: true; - MouseArea { - anchors.fill: parent; - onPressed: { - var hidePassword = (passphraseField.echoMode === TextInput.Password); - sendSignalToWallet({method: 'walletSetup_raiseKeyboard', isPasswordField: hidePassword}); - mouse.accepted = false; - } - } - onFocusChanged: { if (focus) { var hidePassword = (passphraseField.echoMode === TextInput.Password); sendMessageToLightbox({method: 'walletSetup_raiseKeyboard', isPasswordField: hidePassword}); - } else if (!passphraseFieldAgain.focus) { - sendMessageToLightbox({method: 'walletSetup_lowerKeyboard', isPasswordField: false}); } } @@ -151,21 +129,10 @@ Item { activeFocusOnPress: true; activeFocusOnTab: true; - MouseArea { - anchors.fill: parent; - onPressed: { - var hidePassword = (passphraseFieldAgain.echoMode === TextInput.Password); - sendSignalToWallet({method: 'walletSetup_raiseKeyboard', isPasswordField: hidePassword}); - mouse.accepted = false; - } - } - onFocusChanged: { if (focus) { var hidePassword = (passphraseFieldAgain.echoMode === TextInput.Password); sendMessageToLightbox({method: 'walletSetup_raiseKeyboard', isPasswordField: hidePassword}); - } else if (!passphraseField.focus) { - sendMessageToLightbox({method: 'walletSetup_lowerKeyboard', isPasswordField: false}); } } diff --git a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml index 759d7a37eb..ac05bf7c84 100644 --- a/interface/resources/qml/hifi/commerce/wallet/Wallet.qml +++ b/interface/resources/qml/hifi/commerce/wallet/Wallet.qml @@ -47,6 +47,12 @@ Rectangle { } else if (walletStatus === 1) { if (root.activeView !== "walletSetup") { root.activeView = "walletSetup"; + commerce.resetLocalWalletOnly(); + var timestamp = new Date(); + walletSetup.startingTimestamp = timestamp; + walletSetup.setupAttemptID = generateUUID(); + UserActivityLogger.commerceWalletSetupStarted(timestamp, setupAttemptID, walletSetup.setupFlowVersion, walletSetup.referrer ? walletSetup.referrer : "wallet app", + (AddressManager.placename || AddressManager.hostname || '') + (AddressManager.pathname ? AddressManager.pathname.match(/\/[^\/]+/)[0] : '')); } } else if (walletStatus === 2) { if (root.activeView !== "passphraseModal") { @@ -172,7 +178,7 @@ Rectangle { Connections { onSendSignalToWallet: { if (msg.method === 'walletSetup_finished') { - if (msg.referrer === '') { + if (msg.referrer === '' || msg.referrer === 'marketplace cta') { root.activeView = "initialize"; commerce.getWalletStatus(); } else if (msg.referrer === 'purchases') { @@ -666,25 +672,6 @@ Rectangle { right: parent.right; } - Image { - id: lowerKeyboardButton; - z: 999; - source: "images/lowerKeyboard.png"; - anchors.right: keyboard.right; - anchors.top: keyboard.showMirrorText ? keyboard.top : undefined; - anchors.bottom: keyboard.showMirrorText ? undefined : keyboard.bottom; - height: 50; - width: 60; - - MouseArea { - anchors.fill: parent; - - onClicked: { - root.keyboardRaised = false; - } - } - } - HifiControlsUit.Keyboard { id: keyboard; raised: HMD.mounted && root.keyboardRaised; @@ -719,12 +706,28 @@ Rectangle { case 'updateWalletReferrer': walletSetup.referrer = message.referrer; break; + case 'inspectionCertificate_resetCert': + // NOP + break; default: console.log('Unrecognized message from wallet.js:', JSON.stringify(message)); } } signal sendToScript(var message); + // generateUUID() taken from: + // https://stackoverflow.com/a/8809472 + function generateUUID() { // Public Domain/MIT + var d = new Date().getTime(); + if (typeof performance !== 'undefined' && typeof performance.now === 'function'){ + d += performance.now(); //use high-precision timer if available + } + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = (d + Math.random() * 16) % 16 | 0; + d = Math.floor(d / 16); + return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); + }); + } // // FUNCTION DEFINITIONS END // diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 4d8dde0331..d23079d3f3 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -38,10 +38,28 @@ Item { onHistoryResult : { historyReceived = true; if (result.status === 'success') { - transactionHistoryModel.clear(); - transactionHistoryModel.append(result.data.history); + var sameItemCount = 0; + tempTransactionHistoryModel.clear(); + + tempTransactionHistoryModel.append(result.data.history); + + for (var i = 0; i < tempTransactionHistoryModel.count; i++) { + if (!transactionHistoryModel.get(i)) { + sameItemCount = -1; + break; + } else if (tempTransactionHistoryModel.get(i).transaction_type === transactionHistoryModel.get(i).transaction_type && + tempTransactionHistoryModel.get(i).text === transactionHistoryModel.get(i).text) { + sameItemCount++; + } + } - calculatePendingAndInvalidated(); + if (sameItemCount !== tempTransactionHistoryModel.count) { + transactionHistoryModel.clear(); + for (var i = 0; i < tempTransactionHistoryModel.count; i++) { + transactionHistoryModel.append(tempTransactionHistoryModel.get(i)); + } + calculatePendingAndInvalidated(); + } } refreshTimer.start(); } @@ -50,6 +68,7 @@ Item { Connections { target: GlobalServices onMyUsernameChanged: { + transactionHistoryModel.clear(); usernameText.text = Account.username; } } @@ -143,10 +162,9 @@ Item { Timer { id: refreshTimer; - interval: 4000; // Remove this after demo? + interval: 4000; onTriggered: { console.log("Refreshing Wallet Home..."); - historyReceived = false; commerce.balance(); commerce.history(); } @@ -187,6 +205,9 @@ Item { // Style color: hifi.colors.baseGrayHighlight; } + ListModel { + id: tempTransactionHistoryModel; + } ListModel { id: transactionHistoryModel; } diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml index 1a62fe6f0d..d7859d2800 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletSetup.qml @@ -31,6 +31,10 @@ Item { property bool hasShownSecurityImageTip: false; property string referrer; property string keyFilePath; + property date startingTimestamp; + property string setupAttemptID; + readonly property int setupFlowVersion: 1; + readonly property var setupStepNames: [ "Setup Prompt", "Security Image Selection", "Passphrase Selection", "Private Keys Ready" ]; Image { anchors.fill: parent; @@ -67,6 +71,13 @@ Item { anchors.fill: parent; } + onActiveViewChanged: { + var timestamp = new Date(); + var currentStepNumber = root.activeView.substring(5); + UserActivityLogger.commerceWalletSetupProgress(timestamp, root.setupAttemptID, + Math.round((timestamp - root.startingTimestamp)/1000), currentStepNumber, root.setupStepNames[currentStepNumber - 1]); + } + // // TITLE BAR START // @@ -371,7 +382,7 @@ Item { Item { id: securityImageTip; - visible: false; + visible: !root.hasShownSecurityImageTip && root.activeView === "step_3"; z: 999; anchors.fill: root; @@ -421,7 +432,6 @@ Item { text: "Got It"; onClicked: { root.hasShownSecurityImageTip = true; - securityImageTip.visible = false; passphraseSelection.focusFirstTextField(); } } @@ -439,9 +449,6 @@ Item { onVisibleChanged: { if (visible) { commerce.getWalletAuthenticatedStatus(); - if (!root.hasShownSecurityImageTip) { - securityImageTip.visible = true; - } } } @@ -732,7 +739,11 @@ Item { text: "Finish"; onClicked: { root.visible = false; + root.hasShownSecurityImageTip = false; sendSignalToWallet({method: 'walletSetup_finished', referrer: root.referrer ? root.referrer : ""}); + + var timestamp = new Date(); + UserActivityLogger.commerceWalletSetupFinished(timestamp, setupAttemptID, Math.round((timestamp - root.startingTimestamp)/1000)); } } } diff --git a/interface/resources/qml/hifi/dialogs/content/AttachmentsContent.qml b/interface/resources/qml/hifi/dialogs/content/AttachmentsContent.qml index 5c9d6822c8..0e0786975e 100644 --- a/interface/resources/qml/hifi/dialogs/content/AttachmentsContent.qml +++ b/interface/resources/qml/hifi/dialogs/content/AttachmentsContent.qml @@ -26,6 +26,7 @@ Item { } Connections { + id: onAttachmentsChangedConnection target: MyAvatar onAttachmentsChanged: reload() } @@ -34,6 +35,12 @@ Item { reload() } + function setAttachmentsVariant(attachments) { + onAttachmentsChangedConnection.enabled = false; + MyAvatar.setAttachmentsVariant(attachments); + onAttachmentsChangedConnection.enabled = true; + } + Column { width: pane.width @@ -92,11 +99,15 @@ Item { attachments.splice(index, 1); listView.model.remove(index, 1); } - onUpdateAttachment: MyAvatar.setAttachmentsVariant(attachments); + onUpdateAttachment: { + setAttachmentsVariant(attachments); + } } } - onCountChanged: MyAvatar.setAttachmentsVariant(attachments); + onCountChanged: { + setAttachmentsVariant(attachments); + } /* // DEBUG @@ -220,7 +231,7 @@ Item { }; attachments.push(template); listView.model.append({}); - MyAvatar.setAttachmentsVariant(attachments); + setAttachmentsVariant(attachments); } } @@ -250,7 +261,7 @@ Item { id: cancelAction text: "Cancel" onTriggered: { - MyAvatar.setAttachmentsVariant(originalAttachments); + setAttachmentsVariant(originalAttachments); closeDialog(); } } @@ -263,7 +274,7 @@ Item { console.log("Attachment " + i + ": " + attachments[i]); } - MyAvatar.setAttachmentsVariant(attachments); + setAttachmentsVariant(attachments); closeDialog(); } } diff --git a/interface/resources/qml/hifi/overlays/ImageOverlay.qml b/interface/resources/qml/hifi/overlays/ImageOverlay.qml index b509f0ce3a..6899c38e67 100644 --- a/interface/resources/qml/hifi/overlays/ImageOverlay.qml +++ b/interface/resources/qml/hifi/overlays/ImageOverlay.qml @@ -9,33 +9,30 @@ Overlay { Image { id: image - property bool scaleFix: true; - property real xOffset: 0 - property real yOffset: 0 + property bool scaleFix: true + property real xStart: 0 + property real yStart: 0 + property real xSize: 0 + property real ySize: 0 property real imageScale: 1.0 property var resizer: Timer { interval: 50 repeat: false running: false onTriggered: { - var targetAspect = root.width / root.height; - var sourceAspect = image.sourceSize.width / image.sourceSize.height; - if (sourceAspect <= targetAspect) { - if (root.width === image.sourceSize.width) { - return; - } - image.imageScale = root.width / image.sourceSize.width; - } else if (sourceAspect > targetAspect){ - if (root.height === image.sourceSize.height) { - return; - } - image.imageScale = root.height / image.sourceSize.height; + if (image.xSize === 0) { + image.xSize = image.sourceSize.width - image.xStart; } - image.sourceSize = Qt.size(image.sourceSize.width * image.imageScale, image.sourceSize.height * image.imageScale); + if (image.ySize === 0) { + image.ySize = image.sourceSize.height - image.yStart; + } + + image.anchors.leftMargin = -image.xStart * root.width / image.xSize; + image.anchors.topMargin = -image.yStart * root.height / image.ySize; + image.anchors.rightMargin = (image.xStart + image.xSize - image.sourceSize.width) * root.width / image.xSize; + image.anchors.bottomMargin = (image.yStart + image.ySize - image.sourceSize.height) * root.height / image.ySize; } } - x: -1 * xOffset * imageScale - y: -1 * yOffset * imageScale onSourceSizeChanged: { if (sourceSize.width !== 0 && sourceSize.height !== 0 && progress === 1.0 && scaleFix) { @@ -43,6 +40,8 @@ Overlay { resizer.start(); } } + + anchors.fill: parent } ColorOverlay { @@ -57,8 +56,10 @@ Overlay { var key = keys[i]; var value = subImage[key]; switch (key) { - case "x": image.xOffset = value; break; - case "y": image.yOffset = value; break; + case "x": image.xStart = value; break; + case "y": image.yStart = value; break; + case "width": image.xSize = value; break; + case "height": image.ySize = value; break; } } } @@ -78,6 +79,7 @@ Overlay { case "imageURL": image.source = value; break; case "subImage": updateSubImage(value); break; case "color": color.color = Qt.rgba(value.red / 255, value.green / 255, value.blue / 255, root.opacity); break; + case "bounds": break; // The bounds property is handled in C++. default: console.log("OVERLAY Unhandled image property " + key); } } diff --git a/interface/resources/qml/hifi/overlays/RectangleOverlay.qml b/interface/resources/qml/hifi/overlays/RectangleOverlay.qml index cba3b560d2..514b646c36 100644 --- a/interface/resources/qml/hifi/overlays/RectangleOverlay.qml +++ b/interface/resources/qml/hifi/overlays/RectangleOverlay.qml @@ -29,6 +29,7 @@ Overlay { case "borderColor": rectangle.border.color = Qt.rgba(value.red / 255, value.green / 255, value.blue / 255, rectangle.border.color.a); break; case "borderWidth": rectangle.border.width = value; break; case "radius": rectangle.radius = value; break; + case "bounds": break; // The bounds property is handled in C++. default: console.warn("OVERLAY Unhandled rectangle property " + key); } } diff --git a/interface/resources/qml/hifi/overlays/TextOverlay.qml b/interface/resources/qml/hifi/overlays/TextOverlay.qml index 20336fdde6..301a2aa0bf 100644 --- a/interface/resources/qml/hifi/overlays/TextOverlay.qml +++ b/interface/resources/qml/hifi/overlays/TextOverlay.qml @@ -46,6 +46,7 @@ Overlay { case "backgroundColor": background.color = Qt.rgba(value.red / 255, value.green / 255, value.blue / 255, background.color.a); break; case "font": textField.font.pixelSize = value.size; break; case "lineHeight": textField.lineHeight = value; break; + case "bounds": break; // The bounds property is handled in C++. default: console.warn("OVERLAY text unhandled property " + key); } } diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 6aa3b8e7fe..160151144b 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -20,6 +20,8 @@ import "../" import "../toolbars" import "../../styles-uit" as HifiStyles import "../../controls-uit" as HifiControls +import QtQuick.Controls 2.2 as QQC2 +import QtQuick.Templates 2.2 as T // references HMD, AddressManager, AddressBarDialog from root context @@ -223,7 +225,7 @@ StackView { visible: addressLine.text.length === 0 } - TextField { + QQC2.TextField { id: addressLine width: addressLineContainer.width - addressLineContainer.anchors.leftMargin - addressLineContainer.anchors.rightMargin; anchors { @@ -238,16 +240,36 @@ StackView { addressBarDialog.keyboardEnabled = false; toggleOrGo(); } - placeholderText: "Type domain address here" + + // unfortunately TextField from Quick Controls 2 disallow customization of placeHolderText color without creation of new style + property string placeholderText2: "Type domain address here" verticalAlignment: TextInput.AlignBottom - style: TextFieldStyle { - textColor: hifi.colors.text - placeholderTextColor: "gray" - font { - family: hifi.fonts.fontFamily - pixelSize: hifi.fonts.pixelSize * 0.75 + + font { + family: hifi.fonts.fontFamily + pixelSize: hifi.fonts.pixelSize * 0.75 + } + + 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 } - background: Item {} + + font: parent.font + + x: control.leftPadding + y: control.topPadding + + text: parent.placeholderText2 + verticalAlignment: "AlignVCenter" + color: 'gray' + visible: parent.text === '' } } diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index a0af92dec0..cf800cb62e 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -162,7 +162,7 @@ Item { readonly property real controlLineHeight: 28 // Height of spinbox control on 1920 x 1080 monitor readonly property real controlInterlineHeight: 21 // 75% of controlLineHeight readonly property vector2d menuPadding: Qt.vector2d(14, 102) - readonly property real scrollbarBackgroundWidth: 18 + readonly property real scrollbarBackgroundWidth: 20 readonly property real scrollbarHandleWidth: scrollbarBackgroundWidth - 2 readonly property real tabletMenuHeader: 90 } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d7fcbf6467..0176acf108 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2812,10 +2812,10 @@ static int getEventQueueSize(QThread* thread) { static void dumpEventQueue(QThread* thread) { auto threadData = QThreadData::get2(thread); QMutexLocker locker(&threadData->postEventList.mutex); - qDebug() << "AJT: event list, size =" << threadData->postEventList.size(); + qDebug() << "Event list, size =" << threadData->postEventList.size(); for (auto& postEvent : threadData->postEventList) { QEvent::Type type = (postEvent.event ? postEvent.event->type() : QEvent::None); - qDebug() << "AJT: " << type; + qDebug() << " " << type; } } #endif // DEBUG_EVENT_QUEUE @@ -5974,7 +5974,7 @@ bool Application::acceptURL(const QString& urlString, bool defaultUpload) { } } - if (defaultUpload) { + if (defaultUpload && !url.fileName().isEmpty() && url.isLocalFile()) { showAssetServerWidget(urlString); } return defaultUpload; @@ -7075,11 +7075,11 @@ QRect Application::getRecommendedHUDRect() const { return result; } -QSize Application::getDeviceSize() const { +glm::vec2 Application::getDeviceSize() const { static const int MIN_SIZE = 1; - QSize result(MIN_SIZE, MIN_SIZE); + glm::vec2 result(MIN_SIZE); if (_displayPlugin) { - result = fromGlm(getActiveDisplayPlugin()->getRecommendedRenderSize()); + result = getActiveDisplayPlugin()->getRecommendedRenderSize(); } return result; } @@ -7098,10 +7098,6 @@ bool Application::hasFocus() const { return (QApplication::activeWindow() != nullptr); } -glm::vec2 Application::getViewportDimensions() const { - return toGlm(getDeviceSize()); -} - void Application::setMaxOctreePacketsPerSecond(int maxOctreePPS) { if (maxOctreePPS != _maxOctreePPS) { _maxOctreePPS = maxOctreePPS; diff --git a/interface/src/Application.h b/interface/src/Application.h index 5d9028f835..9542c5ccb6 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -158,7 +158,7 @@ public: glm::uvec2 getUiSize() const; QRect getRecommendedHUDRect() const; - QSize getDeviceSize() const; + glm::vec2 getDeviceSize() const; bool hasFocus() const; void showCursor(const Cursor::Icon& cursor); @@ -228,8 +228,6 @@ public: FileLogger* getLogger() const { return _logger; } - glm::vec2 getViewportDimensions() const; - NodeToJurisdictionMap& getEntityServerJurisdictions() { return _entityServerJurisdictions; } float getRenderResolutionScale() const; diff --git a/interface/src/Application_render.cpp b/interface/src/Application_render.cpp index 44d9dfee03..1231e5834b 100644 --- a/interface/src/Application_render.cpp +++ b/interface/src/Application_render.cpp @@ -104,8 +104,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 - QSize size = getDeviceSize(); - renderArgs._viewport = glm::ivec4(0, 0, size.width(), size.height()); + renderArgs._viewport = glm::ivec4(0, 0, getDeviceSize()); _applicationOverlay.renderOverlay(&renderArgs); } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 9ec5cc6034..9bbb72357b 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -679,36 +679,16 @@ Menu::Menu() { }); auto audioIO = DependencyManager::get(); - addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoServerAudio, 0, false, - audioIO.data(), SLOT(toggleServerEcho())); - addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoLocalAudio, 0, false, - audioIO.data(), SLOT(toggleLocalEcho())); addActionToQMenuAndActionHash(audioDebugMenu, MenuOption::MuteEnvironment, 0, audioIO.data(), SLOT(sendMuteEnvironmentPacket())); - auto scope = DependencyManager::get(); - MenuWrapper* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope"); - addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_F2, false, - scope.data(), SLOT(toggle())); - addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopePause, Qt::CTRL | Qt::SHIFT | Qt::Key_F2, false, - scope.data(), SLOT(togglePause())); - - addDisabledActionAndSeparator(audioScopeMenu, "Display Frames"); - { - QAction* fiveFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeFiveFrames, - 0, true, scope.data(), SLOT(selectAudioScopeFiveFrames())); - - QAction* twentyFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeTwentyFrames, - 0, false, scope.data(), SLOT(selectAudioScopeTwentyFrames())); - - QAction* fiftyFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeFiftyFrames, - 0, false, scope.data(), SLOT(selectAudioScopeFiftyFrames())); - - QActionGroup* audioScopeFramesGroup = new QActionGroup(audioScopeMenu); - audioScopeFramesGroup->addAction(fiveFrames); - audioScopeFramesGroup->addAction(twentyFrames); - audioScopeFramesGroup->addAction(fiftyFrames); - } + action = addActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScope); + connect(action, &QAction::triggered, [] { + auto scriptEngines = DependencyManager::get(); + QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); + defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/audio/audioScope.js"); + scriptEngines->loadScript(defaultScriptsLoc.toString()); + }); // Developer > Physics >>> MenuWrapper* physicsOptionsMenu = developerMenu->addMenu("Physics"); diff --git a/interface/src/audio/AudioScope.cpp b/interface/src/audio/AudioScope.cpp index cf9984e32b..1a2e867d51 100644 --- a/interface/src/audio/AudioScope.cpp +++ b/interface/src/audio/AudioScope.cpp @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include @@ -21,13 +22,14 @@ #include "AudioScope.h" static const unsigned int DEFAULT_FRAMES_PER_SCOPE = 5; -static const unsigned int SCOPE_WIDTH = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * DEFAULT_FRAMES_PER_SCOPE; static const unsigned int MULTIPLIER_SCOPE_HEIGHT = 20; static const unsigned int SCOPE_HEIGHT = 2 * 15 * MULTIPLIER_SCOPE_HEIGHT; AudioScope::AudioScope() : _isEnabled(false), _isPaused(false), + _isTriggered(false), + _autoTrigger(false), _scopeInputOffset(0), _scopeOutputOffset(0), _framesPerScope(DEFAULT_FRAMES_PER_SCOPE), @@ -43,6 +45,7 @@ AudioScope::AudioScope() : _outputRightD(DependencyManager::get()->allocateID()) { auto audioIO = DependencyManager::get(); + connect(&audioIO->getReceivedAudioStream(), &MixedProcessedAudioStream::addedSilence, this, &AudioScope::addStereoSilenceToScope); connect(&audioIO->getReceivedAudioStream(), &MixedProcessedAudioStream::addedLastFrameRepeatedWithFade, @@ -75,6 +78,18 @@ void AudioScope::selectAudioScopeFiftyFrames() { reallocateScope(50); } +void AudioScope::setLocalEcho(bool localEcho) { + DependencyManager::get()->setLocalEcho(localEcho); +} + +void AudioScope::setServerEcho(bool serverEcho) { + DependencyManager::get()->setServerEcho(serverEcho); +} + +float AudioScope::getFramesPerSecond(){ + return AudioConstants::NETWORK_FRAMES_PER_SEC; +} + void AudioScope::allocateScope() { _scopeInputOffset = 0; _scopeOutputOffset = 0; @@ -108,63 +123,14 @@ void AudioScope::freeScope() { } } -void AudioScope::render(RenderArgs* renderArgs, int width, int height) { - - if (!_isEnabled) { - return; - } - - static const glm::vec4 backgroundColor = { 0.4f, 0.4f, 0.4f, 0.6f }; - static const glm::vec4 gridColor = { 0.7f, 0.7f, 0.7f, 1.0f }; - static const glm::vec4 inputColor = { 0.3f, 1.0f, 0.3f, 1.0f }; - static const glm::vec4 outputLeftColor = { 1.0f, 0.3f, 0.3f, 1.0f }; - static const glm::vec4 outputRightColor = { 0.3f, 0.3f, 1.0f, 1.0f }; - static const int gridCols = 2; - int gridRows = _framesPerScope; - - int x = (width - (int)SCOPE_WIDTH) / 2; - int y = (height - (int)SCOPE_HEIGHT) / 2; - int w = (int)SCOPE_WIDTH; - int h = (int)SCOPE_HEIGHT; - - gpu::Batch& batch = *renderArgs->_batch; - auto geometryCache = DependencyManager::get(); - - // Grid uses its own pipeline, so draw it before setting another - const float GRID_EDGE = 0.005f; - geometryCache->renderGrid(batch, glm::vec2(x, y), glm::vec2(x + w, y + h), - gridRows, gridCols, GRID_EDGE, gridColor, true, _audioScopeGrid); - - geometryCache->useSimpleDrawPipeline(batch); - auto textureCache = DependencyManager::get(); - batch.setResourceTexture(0, textureCache->getWhiteTexture()); - - // FIXME - do we really need to reset this here? we know that we're called inside of ApplicationOverlay::renderOverlays - // which already set up our batch for us to have these settings - mat4 legacyProjection = glm::ortho(0, width, height, 0, -1000, 1000); - batch.setProjectionTransform(legacyProjection); - batch.setModelTransform(Transform()); - batch.resetViewTransform(); - - geometryCache->renderQuad(batch, x, y, w, h, backgroundColor, _audioScopeBackground); - renderLineStrip(batch, _inputID, inputColor, x, y, _samplesPerScope, _scopeInputOffset, _scopeInput); - renderLineStrip(batch, _outputLeftID, outputLeftColor, x, y, _samplesPerScope, _scopeOutputOffset, _scopeOutputLeft); - renderLineStrip(batch, _outputRightD, outputRightColor, x, y, _samplesPerScope, _scopeOutputOffset, _scopeOutputRight); -} - -void AudioScope::renderLineStrip(gpu::Batch& batch, int id, const glm::vec4& color, int x, int y, int n, int offset, const QByteArray* byteArray) { - +QVector AudioScope::getScopeVector(const QByteArray* byteArray, int offset) { int16_t sample; - int16_t* samples = ((int16_t*) byteArray->data()) + offset; + QVector points; + if (!_isEnabled || byteArray == NULL) return points; + int16_t* samples = ((int16_t*)byteArray->data()) + offset; int numSamplesToAverage = _framesPerScope / DEFAULT_FRAMES_PER_SCOPE; - int count = (n - offset) / numSamplesToAverage; - int remainder = (n - offset) % numSamplesToAverage; - y += SCOPE_HEIGHT / 2; - - auto geometryCache = DependencyManager::get(); - - QVector points; - + int count = (_samplesPerScope - offset) / numSamplesToAverage; + int remainder = (_samplesPerScope - offset) % numSamplesToAverage; // Compute and draw the sample averages from the offset position for (int i = count; --i >= 0; ) { @@ -173,7 +139,7 @@ void AudioScope::renderLineStrip(gpu::Batch& batch, int id, const glm::vec4& col sample += *samples++; } sample /= numSamplesToAverage; - points << glm::vec2(x++, y - sample); + points << -sample; } // Compute and draw the sample average across the wrap boundary @@ -182,16 +148,17 @@ void AudioScope::renderLineStrip(gpu::Batch& batch, int id, const glm::vec4& col for (int j = remainder; --j >= 0; ) { sample += *samples++; } - - samples = (int16_t*) byteArray->data(); + + samples = (int16_t*)byteArray->data(); for (int j = numSamplesToAverage - remainder; --j >= 0; ) { sample += *samples++; } sample /= numSamplesToAverage; - points << glm::vec2(x++, y - sample); - } else { - samples = (int16_t*) byteArray->data(); + points << -sample; + } + else { + samples = (int16_t*)byteArray->data(); } // Compute and draw the sample average from the beginning to the offset @@ -202,12 +169,51 @@ void AudioScope::renderLineStrip(gpu::Batch& batch, int id, const glm::vec4& col sample += *samples++; } sample /= numSamplesToAverage; - points << glm::vec2(x++, y - sample); + + points << -sample; + } + return points; +} + +bool AudioScope::shouldTrigger(const QVector& scope) { + int threshold = 4; + if (_autoTrigger && _triggerValues.x < scope.size()) { + for (int i = -4*threshold; i < +4*threshold; i++) { + int idx = _triggerValues.x + i; + idx = (idx < 0) ? 0 : (idx < scope.size() ? idx : scope.size() - 1); + int dif = abs(_triggerValues.y - scope[idx]); + if (dif < threshold) { + return true; + } + } + } + return false; +} + +void AudioScope::storeTriggerValues() { + _triggerInputData = _scopeInputData; + _triggerOutputLeftData = _scopeOutputLeftData; + _triggerOutputRightData = _scopeOutputRightData; + _isTriggered = true; + emit triggered(); +} + +void AudioScope::computeInputData() { + _scopeInputData = getScopeVector(_scopeInput, _scopeInputOffset); + if (shouldTrigger(_scopeInputData)) { + storeTriggerValues(); + } +} + +void AudioScope::computeOutputData() { + _scopeOutputLeftData = getScopeVector(_scopeOutputLeft, _scopeOutputOffset); + if (shouldTrigger(_scopeOutputLeftData)) { + storeTriggerValues(); + } + _scopeOutputRightData = getScopeVector(_scopeOutputRight, _scopeOutputOffset); + if (shouldTrigger(_scopeOutputRightData)) { + storeTriggerValues(); } - - - geometryCache->updateVertices(id, points, color); - geometryCache->renderVertices(batch, gpu::LINE_STRIP, id); } int AudioScope::addBufferToScope(QByteArray* byteArray, int frameOffset, const int16_t* source, int sourceSamplesPerChannel, @@ -231,7 +237,7 @@ int AudioScope::addBufferToScope(QByteArray* byteArray, int frameOffset, const i } int AudioScope::addSilenceToScope(QByteArray* byteArray, int frameOffset, int silentSamples) { - + // Short int pointer to mapped samples in byte array int16_t* destination = (int16_t*)byteArray->data(); @@ -271,6 +277,7 @@ void AudioScope::addStereoSamplesToScope(const QByteArray& samples) { _scopeOutputOffset = addBufferToScope(_scopeOutputRight, _scopeOutputOffset, samplesData, samplesPerChannel, 1, AudioConstants::STEREO); _scopeLastFrame = samples.right(AudioConstants::NETWORK_FRAME_BYTES_STEREO); + computeOutputData(); } void AudioScope::addLastFrameRepeatedWithFadeToScope(int samplesPerChannel) { @@ -302,4 +309,5 @@ void AudioScope::addInputToScope(const QByteArray& inputSamples) { _scopeInputOffset = addBufferToScope(_scopeInput, _scopeInputOffset, reinterpret_cast(inputSamples.data()), inputSamples.size() / sizeof(int16_t), INPUT_AUDIO_CHANNEL, NUM_INPUT_CHANNELS); + computeInputData(); } diff --git a/interface/src/audio/AudioScope.h b/interface/src/audio/AudioScope.h index e0c8840bb2..e99b8378e3 100644 --- a/interface/src/audio/AudioScope.h +++ b/interface/src/audio/AudioScope.h @@ -24,27 +24,60 @@ class AudioScope : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY + + Q_PROPERTY(QVector scopeInput READ getScopeInput) + Q_PROPERTY(QVector scopeOutputLeft READ getScopeOutputLeft) + Q_PROPERTY(QVector scopeOutputRight READ getScopeOutputRight) + + Q_PROPERTY(QVector triggerInput READ getTriggerInput) + Q_PROPERTY(QVector triggerOutputLeft READ getTriggerOutputLeft) + Q_PROPERTY(QVector triggerOutputRight READ getTriggerOutputRight) + public: // Audio scope methods for allocation/deallocation void allocateScope(); void freeScope(); void reallocateScope(int frames); - void render(RenderArgs* renderArgs, int width, int height); - public slots: void toggle() { setVisible(!_isEnabled); } void setVisible(bool visible); bool getVisible() const { return _isEnabled; } - void togglePause() { _isPaused = !_isPaused; } - void setPause(bool paused) { _isPaused = paused; } + void togglePause() { setPause(!_isPaused); } + void setPause(bool paused) { _isPaused = paused; emit pauseChanged(); } bool getPause() { return _isPaused; } + void toggleTrigger() { _autoTrigger = !_autoTrigger; } + bool getAutoTrigger() { return _autoTrigger; } + void setAutoTrigger(bool autoTrigger) { _isTriggered = false; _autoTrigger = autoTrigger; } + + void setTriggerValues(int x, int y) { _triggerValues.x = x; _triggerValues.y = y; } + void setTriggered(bool triggered) { _isTriggered = triggered; } + bool getTriggered() { return _isTriggered; } + + float getFramesPerSecond(); + int getFramesPerScope() { return _framesPerScope; } + void selectAudioScopeFiveFrames(); void selectAudioScopeTwentyFrames(); void selectAudioScopeFiftyFrames(); - + + QVector getScopeInput() { return _scopeInputData; }; + QVector getScopeOutputLeft() { return _scopeOutputLeftData; }; + QVector getScopeOutputRight() { return _scopeOutputRightData; }; + + QVector getTriggerInput() { return _triggerInputData; }; + QVector getTriggerOutputLeft() { return _triggerOutputLeftData; }; + QVector getTriggerOutputRight() { return _triggerOutputRightData; }; + + void setLocalEcho(bool serverEcho); + void setServerEcho(bool serverEcho); + +signals: + void pauseChanged(); + void triggered(); + protected: AudioScope(); @@ -55,24 +88,44 @@ private slots: void addInputToScope(const QByteArray& inputSamples); private: - // Audio scope methods for rendering - void renderLineStrip(gpu::Batch& batch, int id, const glm::vec4& color, int x, int y, int n, int offset, const QByteArray* byteArray); // Audio scope methods for data acquisition int addBufferToScope(QByteArray* byteArray, int frameOffset, const int16_t* source, int sourceSamples, unsigned int sourceChannel, unsigned int sourceNumberOfChannels, float fade = 1.0f); int addSilenceToScope(QByteArray* byteArray, int frameOffset, int silentSamples); + QVector getScopeVector(const QByteArray* scope, int offset); + + bool shouldTrigger(const QVector& scope); + void computeInputData(); + void computeOutputData(); + + void storeTriggerValues(); + bool _isEnabled; bool _isPaused; + bool _isTriggered; + bool _autoTrigger; int _scopeInputOffset; int _scopeOutputOffset; int _framesPerScope; int _samplesPerScope; + QByteArray* _scopeInput; QByteArray* _scopeOutputLeft; QByteArray* _scopeOutputRight; QByteArray _scopeLastFrame; + + QVector _scopeInputData; + QVector _scopeOutputLeftData; + QVector _scopeOutputRightData; + + QVector _triggerInputData; + QVector _triggerOutputLeftData; + QVector _triggerOutputRightData; + + + glm::ivec2 _triggerValues; int _audioScopeBackground; int _audioScopeGrid; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 9ffe74d470..8a294182bd 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -142,32 +143,39 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { PerformanceTimer perfTimer("otherAvatars"); - auto avatarMap = getHashCopy(); - QList avatarList = avatarMap.values(); + class SortableAvatar: public PrioritySortUtil::Sortable { + public: + SortableAvatar() = delete; + SortableAvatar(const AvatarSharedPointer& avatar) : _avatar(avatar) {} + glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); } + float getRadius() const override { return std::static_pointer_cast(_avatar)->getBoundingRadius(); } + uint64_t getTimestamp() const override { return std::static_pointer_cast(_avatar)->getLastRenderUpdateTime(); } + const AvatarSharedPointer& getAvatar() const { return _avatar; } + private: + AvatarSharedPointer _avatar; + }; + ViewFrustum cameraView; qApp->copyDisplayViewFrustum(cameraView); + PrioritySortUtil::PriorityQueue sortedAvatars(cameraView, + AvatarData::_avatarSortCoefficientSize, + AvatarData::_avatarSortCoefficientCenter, + AvatarData::_avatarSortCoefficientAge); - std::priority_queue sortedAvatars; - AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars, - - [](AvatarSharedPointer avatar)->uint64_t{ - return std::static_pointer_cast(avatar)->getLastRenderUpdateTime(); - }, - - [](AvatarSharedPointer avatar)->float{ - return std::static_pointer_cast(avatar)->getBoundingRadius(); - }, - - [this](AvatarSharedPointer avatar)->bool{ - const auto& castedAvatar = std::static_pointer_cast(avatar); - if (castedAvatar == _myAvatar || !castedAvatar->isInitialized()) { - // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. - // DO NOT update or fade out uninitialized Avatars - return true; // ignore it - } - return false; - }); + // sort + auto avatarMap = getHashCopy(); + AvatarHash::iterator itr = avatarMap.begin(); + while (itr != avatarMap.end()) { + const auto& avatar = std::static_pointer_cast(*itr); + // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. + // DO NOT update or fade out uninitialized Avatars + if (avatar != _myAvatar && avatar->isInitialized()) { + sortedAvatars.push(SortableAvatar(avatar)); + } + ++itr; + } + // process in sorted order uint64_t startTime = usecTimestampNow(); const uint64_t UPDATE_BUDGET = 2000; // usec uint64_t updateExpiry = startTime + UPDATE_BUDGET; @@ -176,8 +184,8 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { render::Transaction transaction; while (!sortedAvatars.empty()) { - const AvatarPriority& sortData = sortedAvatars.top(); - const auto& avatar = std::static_pointer_cast(sortData.avatar); + const SortableAvatar& sortData = sortedAvatars.top(); + const auto& avatar = std::static_pointer_cast(sortData.getAvatar()); bool ignoring = DependencyManager::get()->isPersonalMutingNode(avatar->getID()); if (ignoring) { @@ -207,7 +215,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { uint64_t now = usecTimestampNow(); if (now < updateExpiry) { // we're within budget - bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD; + bool inView = sortData.getPriority() > OUT_OF_VIEW_THRESHOLD; if (inView && avatar->hasNewJointData()) { numAvatarsUpdated++; } @@ -221,7 +229,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { // --> some avatar velocity measurements may be a little off // no time simulate, but we take the time to count how many were tragically missed - bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD; + bool inView = sortData.getPriority() > OUT_OF_VIEW_THRESHOLD; if (!inView) { break; } @@ -230,9 +238,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { } sortedAvatars.pop(); while (inView && !sortedAvatars.empty()) { - const AvatarPriority& newSortData = sortedAvatars.top(); - const auto& newAvatar = std::static_pointer_cast(newSortData.avatar); - inView = newSortData.priority > OUT_OF_VIEW_THRESHOLD; + const SortableAvatar& newSortData = sortedAvatars.top(); + const auto& newAvatar = std::static_pointer_cast(newSortData.getAvatar()); + inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD; if (inView && newAvatar->hasNewJointData()) { numAVatarsNotUpdated++; } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index df2089223b..c874205611 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -114,7 +114,8 @@ MyAvatar::MyAvatar(QThread* thread) : _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); using namespace recording; _skeletonModel->flagAsCauterized(); @@ -1516,9 +1517,19 @@ void MyAvatar::updateMotors() { _characterController.clearMotors(); glm::quat motorRotation; if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { + + const float FLYING_MOTOR_TIMESCALE = 0.05f; + const float WALKING_MOTOR_TIMESCALE = 0.2f; + const float INVALID_MOTOR_TIMESCALE = 1.0e6f; + + float horizontalMotorTimescale; + float verticalMotorTimescale; + if (_characterController.getState() == CharacterController::State::Hover || _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { motorRotation = getMyHead()->getHeadOrientation(); + horizontalMotorTimescale = FLYING_MOTOR_TIMESCALE; + verticalMotorTimescale = FLYING_MOTOR_TIMESCALE; } else { // non-hovering = walking: follow camera twist about vertical but not lift // we decompose camera's rotation and store the twist part in motorRotation @@ -1529,11 +1540,12 @@ void MyAvatar::updateMotors() { glm::quat liftRotation; swingTwistDecomposition(headOrientation, Vectors::UNIT_Y, liftRotation, motorRotation); motorRotation = orientation * motorRotation; + horizontalMotorTimescale = WALKING_MOTOR_TIMESCALE; + verticalMotorTimescale = INVALID_MOTOR_TIMESCALE; } - const float DEFAULT_MOTOR_TIMESCALE = 0.2f; - const float INVALID_MOTOR_TIMESCALE = 1.0e6f; + if (_isPushing || _isBraking || !_isBeingPushed) { - _characterController.addMotor(_actionMotorVelocity, motorRotation, DEFAULT_MOTOR_TIMESCALE, INVALID_MOTOR_TIMESCALE); + _characterController.addMotor(_actionMotorVelocity, motorRotation, horizontalMotorTimescale, verticalMotorTimescale); } else { // _isBeingPushed must be true --> disable action motor by giving it a long timescale, // otherwise it's attempt to "stand in in place" could defeat scripted motor/thrusts @@ -1799,6 +1811,7 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { _skeletonModel->setCauterizeBoneSet(_headBoneSet); _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); initAnimGraph(); + _isAnimatingScale = true; } if (_enableDebugDrawDefaultPose || _enableDebugDrawAnimPose) { @@ -1956,27 +1969,33 @@ void MyAvatar::updateOrientation(float deltaTime) { // Use head/HMD roll to turn while flying, but not when standing still. if (qApp->isHMDMode() && getCharacterController()->getState() == CharacterController::State::Hover && _hmdRollControlEnabled && hasDriveInput()) { + // Turn with head roll. - const float MIN_CONTROL_SPEED = 0.01f; - float speed = glm::length(getWorldVelocity()); - if (speed >= MIN_CONTROL_SPEED) { - // Feather turn when stopping moving. - float speedFactor; - if (getDriveKey(TRANSLATE_Z) != 0.0f || _lastDrivenSpeed == 0.0f) { - _lastDrivenSpeed = speed; - speedFactor = 1.0f; - } else { - speedFactor = glm::min(speed / _lastDrivenSpeed, 1.0f); - } + const float MIN_CONTROL_SPEED = 2.0f * getSensorToWorldScale(); // meters / sec + const glm::vec3 characterForward = getWorldOrientation() * Vectors::UNIT_NEG_Z; + float forwardSpeed = glm::dot(characterForward, getWorldVelocity()); - float direction = glm::dot(getWorldVelocity(), getWorldOrientation() * Vectors::UNIT_NEG_Z) > 0.0f ? 1.0f : -1.0f; + // only enable roll-turns if we are moving forward or backward at greater then MIN_CONTROL_SPEED + if (fabsf(forwardSpeed) >= MIN_CONTROL_SPEED) { + float direction = forwardSpeed > 0.0f ? 1.0f : -1.0f; float rollAngle = glm::degrees(asinf(glm::dot(IDENTITY_UP, _hmdSensorOrientation * IDENTITY_RIGHT))); float rollSign = rollAngle < 0.0f ? -1.0f : 1.0f; rollAngle = fabsf(rollAngle); - rollAngle = rollAngle > _hmdRollControlDeadZone ? rollSign * (rollAngle - _hmdRollControlDeadZone) : 0.0f; - totalBodyYaw += speedFactor * direction * rollAngle * deltaTime * _hmdRollControlRate; + const float MIN_ROLL_ANGLE = _hmdRollControlDeadZone; + const float MAX_ROLL_ANGLE = 90.0f; // degrees + + if (rollAngle > MIN_ROLL_ANGLE) { + // rate of turning is linearly proportional to rollAngle + rollAngle = glm::clamp(rollAngle, MIN_ROLL_ANGLE, MAX_ROLL_ANGLE); + + // scale rollAngle into a value from zero to one. + float rollFactor = (rollAngle - MIN_ROLL_ANGLE) / (MAX_ROLL_ANGLE - MIN_ROLL_ANGLE); + + float angularSpeed = rollSign * rollFactor * _hmdRollControlRate; + totalBodyYaw += direction * angularSpeed * deltaTime; + } } } @@ -2022,12 +2041,13 @@ void MyAvatar::updateActionMotor(float deltaTime) { _isBraking = _wasPushing || (_isBraking && speed > MIN_ACTION_BRAKE_SPEED); } + CharacterController::State state = _characterController.getState(); + // compute action input glm::vec3 forward = (getDriveKey(TRANSLATE_Z)) * IDENTITY_FORWARD; glm::vec3 right = (getDriveKey(TRANSLATE_X)) * IDENTITY_RIGHT; glm::vec3 direction = forward + right; - CharacterController::State state = _characterController.getState(); if (state == CharacterController::State::Hover || _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { // we can fly --> support vertical motion @@ -2161,41 +2181,6 @@ bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float // target scale to match the new scale they have chosen. When they leave the domain they will not return to the scale they were // before they entered the limiting domain. -void MyAvatar::clampTargetScaleToDomainLimits() { - // when we're about to change the target scale because the user has asked to increase or decrease their scale, - // we first make sure that we're starting from a target scale that is allowed by the current domain - - auto clampedTargetScale = glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); - - if (clampedTargetScale != _targetScale) { - qCDebug(interfaceapp, "Clamped scale to %f since original target scale %f was not allowed by domain", - (double)clampedTargetScale, (double)_targetScale); - - setTargetScale(clampedTargetScale); - } -} - -void MyAvatar::clampScaleChangeToDomainLimits(float desiredScale) { - auto clampedTargetScale = glm::clamp(desiredScale, _domainMinimumScale, _domainMaximumScale); - - if (clampedTargetScale != desiredScale) { - qCDebug(interfaceapp, "Forcing scale to %f since %f is not allowed by domain", - clampedTargetScale, desiredScale); - } - - setTargetScale(clampedTargetScale); - qCDebug(interfaceapp, "Changed scale to %f", (double)_targetScale); - emit(scaleChanged()); -} - -float MyAvatar::getDomainMinScale() { - return _domainMinimumScale; -} - -float MyAvatar::getDomainMaxScale() { - return _domainMaximumScale; -} - void MyAvatar::setGravity(float gravity) { _characterController.setGravity(gravity); } @@ -2205,70 +2190,58 @@ float MyAvatar::getGravity() { } void MyAvatar::increaseSize() { - // make sure we're starting from an allowable scale - clampTargetScaleToDomainLimits(); + float minScale = getDomainMinScale(); + float maxScale = getDomainMaxScale(); - // calculate what our new scale should be - float updatedTargetScale = _targetScale * (1.0f + SCALING_RATIO); + float clampedTargetScale = glm::clamp(_targetScale, minScale, maxScale); + float newTargetScale = glm::clamp(clampedTargetScale * (1.0f + SCALING_RATIO), minScale, maxScale); - // attempt to change to desired scale (clamped to the domain limits) - clampScaleChangeToDomainLimits(updatedTargetScale); + setTargetScale(newTargetScale); } void MyAvatar::decreaseSize() { - // make sure we're starting from an allowable scale - clampTargetScaleToDomainLimits(); + float minScale = getDomainMinScale(); + float maxScale = getDomainMaxScale(); - // calculate what our new scale should be - float updatedTargetScale = _targetScale * (1.0f - SCALING_RATIO); + float clampedTargetScale = glm::clamp(_targetScale, minScale, maxScale); + float newTargetScale = glm::clamp(clampedTargetScale * (1.0f - SCALING_RATIO), minScale, maxScale); - // attempt to change to desired scale (clamped to the domain limits) - clampScaleChangeToDomainLimits(updatedTargetScale); + setTargetScale(newTargetScale); } void MyAvatar::resetSize() { // attempt to reset avatar size to the default (clamped to domain limits) const float DEFAULT_AVATAR_SCALE = 1.0f; - - clampScaleChangeToDomainLimits(DEFAULT_AVATAR_SCALE); + setTargetScale(DEFAULT_AVATAR_SCALE); } void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject) { - // pull out the minimum and maximum scale and set them to restrict our scale + // pull out the minimum and maximum height and set them to restrict our scale static const QString AVATAR_SETTINGS_KEY = "avatars"; auto avatarsObject = domainSettingsObject[AVATAR_SETTINGS_KEY].toObject(); - static const QString MIN_SCALE_OPTION = "min_avatar_scale"; - float settingMinScale = avatarsObject[MIN_SCALE_OPTION].toDouble(MIN_AVATAR_SCALE); - setDomainMinimumScale(settingMinScale); + static const QString MIN_HEIGHT_OPTION = "min_avatar_height"; + float settingMinHeight = avatarsObject[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT); + setDomainMinimumHeight(settingMinHeight); - static const QString MAX_SCALE_OPTION = "max_avatar_scale"; - float settingMaxScale = avatarsObject[MAX_SCALE_OPTION].toDouble(MAX_AVATAR_SCALE); - setDomainMaximumScale(settingMaxScale); + static const QString MAX_HEIGHT_OPTION = "max_avatar_height"; + float settingMaxHeight = avatarsObject[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT); + setDomainMaximumHeight(settingMaxHeight); // make sure that the domain owner didn't flip min and max - if (_domainMinimumScale > _domainMaximumScale) { - std::swap(_domainMinimumScale, _domainMaximumScale); + if (_domainMinimumHeight > _domainMaximumHeight) { + std::swap(_domainMinimumHeight, _domainMaximumHeight); } // Set avatar current scale Settings settings; settings.beginGroup("Avatar"); _targetScale = loadSetting(settings, "scale", 1.0f); - qCDebug(interfaceapp) << "This domain requires a minimum avatar scale of " << _domainMinimumScale - << " and a maximum avatar scale of " << _domainMaximumScale - << ". Current avatar scale is " << _targetScale; + qCDebug(interfaceapp) << "This domain requires a minimum avatar scale of " << _domainMinimumHeight + << " and a maximum avatar scale of " << _domainMaximumHeight; - // debug to log if this avatar's scale in this domain will be clamped - float clampedScale = glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); - - if (_targetScale != clampedScale) { - qCDebug(interfaceapp) << "Current avatar scale is clamped to " << clampedScale - << " because " << _targetScale << " is not allowed by current domain"; - // The current scale of avatar should not be more than domain's max_avatar_scale and not less than domain's min_avatar_scale . - _targetScale = clampedScale; - } + _isAnimatingScale = true; setModelScale(_targetScale); rebuildCollisionShape(); @@ -2288,8 +2261,8 @@ void MyAvatar::saveAvatarScale() { } void MyAvatar::clearScaleRestriction() { - _domainMinimumScale = MIN_AVATAR_SCALE; - _domainMaximumScale = MAX_AVATAR_SCALE; + _domainMinimumHeight = MIN_AVATAR_HEIGHT; + _domainMaximumHeight = MAX_AVATAR_HEIGHT; } void MyAvatar::goToLocation(const QVariant& propertiesVar) { @@ -3248,6 +3221,7 @@ void MyAvatar::setModelScale(float scale) { if (changed) { float sensorToWorldScale = getEyeHeight() / getUserEyeHeight(); emit sensorToWorldScaleChanged(sensorToWorldScale); + emit scaleChanged(); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 563fb7fccd..7c9513cb3e 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -110,6 +110,10 @@ class MyAvatar : public Avatar { * @property userEyeHeight {number} Estimated height of the users eyes in sensor space. (meters) * @property SELF_ID {string} READ-ONLY. UUID representing "my avatar". Only use for local-only entities and overlays in situations where MyAvatar.sessionUUID is not available (e.g., if not connected to a domain). * Note: Likely to be deprecated. + * @property hmdRollControlEnabled {bool} When enabled the roll angle of your HMD will turn your avatar while flying. + * @property hmdRollControlDeadZone {number} If hmdRollControlEnabled is true, this value can be used to tune what roll angle is required to begin turning. + * This angle is specified in degrees. + * @property hmdRollControlRate {number} If hmdRollControlEnabled is true, this value determines the maximum turn rate of your avatar when rolling your HMD in degrees per second. */ // FIXME: `glm::vec3 position` is not accessible from QML, so this exposes position in a QML-native type @@ -158,7 +162,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(float userEyeHeight READ getUserEyeHeight) Q_PROPERTY(QUuid SELF_ID READ getSelfID CONSTANT) - + const QString DOMINANT_LEFT_HAND = "left"; const QString DOMINANT_RIGHT_HAND = "right"; @@ -558,8 +562,6 @@ public slots: void increaseSize(); void decreaseSize(); void resetSize(); - float getDomainMinScale(); - float getDomainMaxScale(); void setGravity(float gravity); float getGravity(); @@ -737,12 +739,12 @@ private: bool _clearOverlayWhenMoving { true }; QString _dominantHand { DOMINANT_RIGHT_HAND }; - const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // deg - const float ROLL_CONTROL_RATE_DEFAULT = 2.5f; // deg/sec/deg + const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // degrees + const float ROLL_CONTROL_RATE_DEFAULT = 114.0f; // degrees / sec + bool _hmdRollControlEnabled { true }; float _hmdRollControlDeadZone { ROLL_CONTROL_DEAD_ZONE_DEFAULT }; float _hmdRollControlRate { ROLL_CONTROL_RATE_DEFAULT }; - float _lastDrivenSpeed { 0.0f }; // working copy -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access glm::mat4 _sensorToWorldMatrix { glm::mat4() }; diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index f29e46d843..69089df9c2 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -83,19 +83,28 @@ void QmlCommerce::buy(const QString& assetId, int cost, const bool controlledFai void QmlCommerce::balance() { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); - ledger->balance(wallet->listPublicKeys()); + QStringList cachedPublicKeys = wallet->listPublicKeys(); + if (!cachedPublicKeys.isEmpty()) { + ledger->balance(cachedPublicKeys); + } } void QmlCommerce::inventory() { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); - ledger->inventory(wallet->listPublicKeys()); + QStringList cachedPublicKeys = wallet->listPublicKeys(); + if (!cachedPublicKeys.isEmpty()) { + ledger->inventory(cachedPublicKeys); + } } void QmlCommerce::history() { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); - ledger->history(wallet->listPublicKeys()); + QStringList cachedPublicKeys = wallet->listPublicKeys(); + if (!cachedPublicKeys.isEmpty()) { + ledger->history(cachedPublicKeys); + } } void QmlCommerce::changePassphrase(const QString& oldPassphrase, const QString& newPassphrase) { @@ -128,6 +137,11 @@ void QmlCommerce::reset() { wallet->reset(); } +void QmlCommerce::resetLocalWalletOnly() { + auto wallet = DependencyManager::get(); + wallet->reset(); +} + void QmlCommerce::account() { auto ledger = DependencyManager::get(); ledger->account(); diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index d4f4aa35d2..fd8dd0d395 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -65,6 +65,7 @@ protected: Q_INVOKABLE void history(); Q_INVOKABLE void generateKeyPair(); Q_INVOKABLE void reset(); + Q_INVOKABLE void resetLocalWalletOnly(); Q_INVOKABLE void account(); Q_INVOKABLE void certificateInfo(const QString& certificateId); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index d4611d3e9a..69914e97a4 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -321,6 +321,16 @@ Wallet::Wallet() { auto accountManager = DependencyManager::get(); connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { getWalletStatus(); + _publicKeys.clear(); + + if (_securityImage) { + delete _securityImage; + } + _securityImage = nullptr; + + // tell the provider we got nothing + updateImageProvider(); + _passphrase->clear(); }); } diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index 2f6e69bc7e..501e8d1e42 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -84,18 +84,7 @@ glm::vec2 RayPick::projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3 glm::vec2 RayPick::projectOntoOverlayXYPlane(const QUuid& overlayID, const glm::vec3& worldPos, bool unNormalized) { glm::vec3 position = vec3FromVariant(qApp->getOverlays().getProperty(overlayID, "position").value); glm::quat rotation = quatFromVariant(qApp->getOverlays().getProperty(overlayID, "rotation").value); - glm::vec3 dimensions; - - float dpi = qApp->getOverlays().getProperty(overlayID, "dpi").value.toFloat(); - if (dpi > 0) { - // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property is used as a scale. - glm::vec3 resolution = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "resolution").value), 1); - glm::vec3 scale = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "dimensions").value), 0.01f); - const float INCHES_TO_METERS = 1.0f / 39.3701f; - dimensions = (resolution * INCHES_TO_METERS / dpi) * scale; - } else { - dimensions = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "dimensions").value), 0.01); - } + glm::vec3 dimensions = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "dimensions").value), 0.01f); return projectOntoXYPlane(worldPos, position, rotation, dimensions, ENTITY_ITEM_DEFAULT_REGISTRATION_POINT, unNormalized); } diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index f9c1a95fb5..be9b4280f7 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -58,6 +58,21 @@ Audio::Audio() : _devices(_contextIsHMD) { enableNoiseReduction(enableNoiseReductionSetting.get()); } +bool Audio::startRecording(const QString& filepath) { + auto client = DependencyManager::get().data(); + return client->startRecording(filepath); +} + +bool Audio::getRecording() { + auto client = DependencyManager::get().data(); + return client->getRecording(); +} + +void Audio::stopRecording() { + auto client = DependencyManager::get().data(); + client->stopRecording(); +} + void Audio::setMuted(bool isMuted) { if (_isMuted != isMuted) { auto client = DependencyManager::get().data(); diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index abd2312cf0..0f0043510c 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -16,6 +16,7 @@ #include "AudioDevices.h" #include "AudioEffectOptions.h" #include "SettingHandle.h" +#include "AudioFileWav.h" namespace scripting { @@ -55,6 +56,10 @@ public: Q_INVOKABLE void setReverb(bool enable); Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options); + Q_INVOKABLE bool startRecording(const QString& filename); + Q_INVOKABLE void stopRecording(); + Q_INVOKABLE bool getRecording(); + signals: void nop(); void mutedChanged(bool isMuted); @@ -83,7 +88,6 @@ private: bool _isMuted { false }; bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled. bool _contextIsHMD { false }; - AudioDevices* getDevices() { return &_devices; } AudioDevices _devices; }; diff --git a/interface/src/scripting/SelectionScriptingInterface.cpp b/interface/src/scripting/SelectionScriptingInterface.cpp index 1adf5650dd..233e61c8ae 100644 --- a/interface/src/scripting/SelectionScriptingInterface.cpp +++ b/interface/src/scripting/SelectionScriptingInterface.cpp @@ -18,7 +18,9 @@ GameplayObjects::GameplayObjects() { bool GameplayObjects::addToGameplayObjects(const QUuid& avatarID) { containsData = true; - _avatarIDs.push_back(avatarID); + if (std::find(_avatarIDs.begin(), _avatarIDs.end(), avatarID) == _avatarIDs.end()) { + _avatarIDs.push_back(avatarID); + } return true; } bool GameplayObjects::removeFromGameplayObjects(const QUuid& avatarID) { @@ -28,7 +30,9 @@ bool GameplayObjects::removeFromGameplayObjects(const QUuid& avatarID) { bool GameplayObjects::addToGameplayObjects(const EntityItemID& entityID) { containsData = true; - _entityIDs.push_back(entityID); + if (std::find(_entityIDs.begin(), _entityIDs.end(), entityID) == _entityIDs.end()) { + _entityIDs.push_back(entityID); + } return true; } bool GameplayObjects::removeFromGameplayObjects(const EntityItemID& entityID) { @@ -38,7 +42,9 @@ bool GameplayObjects::removeFromGameplayObjects(const EntityItemID& entityID) { bool GameplayObjects::addToGameplayObjects(const OverlayID& overlayID) { containsData = true; - _overlayIDs.push_back(overlayID); + if (std::find(_overlayIDs.begin(), _overlayIDs.end(), overlayID) == _overlayIDs.end()) { + _overlayIDs.push_back(overlayID); + } return true; } bool GameplayObjects::removeFromGameplayObjects(const OverlayID& overlayID) { @@ -72,28 +78,125 @@ bool SelectionScriptingInterface::removeFromSelectedItemsList(const QString& lis } bool SelectionScriptingInterface::clearSelectedItemsList(const QString& listName) { - _selectedItemsListMap.insert(listName, GameplayObjects()); - emit selectedItemsListChanged(listName); + { + QWriteLocker lock(&_selectionListsLock); + _selectedItemsListMap.insert(listName, GameplayObjects()); + } + onSelectedItemsListChanged(listName); return true; } +QStringList SelectionScriptingInterface::getListNames() const { + QStringList list; + QReadLocker lock(&_selectionListsLock); + list = _selectedItemsListMap.keys(); + return list; +} + +QStringList SelectionScriptingInterface::getHighlightedListNames() const { + QStringList list; + QReadLocker lock(&_highlightStylesLock); + list = _highlightStyleMap.keys(); + return list; +} + +bool SelectionScriptingInterface::enableListHighlight(const QString& listName, const QVariantMap& highlightStyleValues) { + QWriteLocker lock(&_highlightStylesLock); + + auto highlightStyle = _highlightStyleMap.find(listName); + if (highlightStyle == _highlightStyleMap.end()) { + highlightStyle = _highlightStyleMap.insert(listName, SelectionHighlightStyle()); + + } + + if (!(*highlightStyle).isBoundToList()) { + setupHandler(listName); + (*highlightStyle).setBoundToList(true); + } + + (*highlightStyle).fromVariantMap(highlightStyleValues); + + auto mainScene = qApp->getMain3DScene(); + if (mainScene) { + render::Transaction transaction; + transaction.resetSelectionHighlight(listName.toStdString(), (*highlightStyle).getStyle()); + mainScene->enqueueTransaction(transaction); + } + else { + qWarning() << "SelectionToSceneHandler::highlightStyleChanged(), Unexpected null scene, possibly during application shutdown"; + } + + return true; +} + +bool SelectionScriptingInterface::disableListHighlight(const QString& listName) { + QWriteLocker lock(&_highlightStylesLock); + auto highlightStyle = _highlightStyleMap.find(listName); + if (highlightStyle != _highlightStyleMap.end()) { + if ((*highlightStyle).isBoundToList()) { + } + + _highlightStyleMap.erase(highlightStyle); + + auto mainScene = qApp->getMain3DScene(); + if (mainScene) { + render::Transaction transaction; + transaction.removeHighlightFromSelection(listName.toStdString()); + mainScene->enqueueTransaction(transaction); + } + else { + qWarning() << "SelectionToSceneHandler::highlightStyleChanged(), Unexpected null scene, possibly during application shutdown"; + } + } + + return true; +} + +QVariantMap SelectionScriptingInterface::getListHighlightStyle(const QString& listName) const { + QReadLocker lock(&_highlightStylesLock); + auto highlightStyle = _highlightStyleMap.find(listName); + if (highlightStyle == _highlightStyleMap.end()) { + return QVariantMap(); + } else { + return (*highlightStyle).toVariantMap(); + } +} + +render::HighlightStyle SelectionScriptingInterface::getHighlightStyle(const QString& listName) const { + QReadLocker lock(&_highlightStylesLock); + auto highlightStyle = _highlightStyleMap.find(listName); + if (highlightStyle == _highlightStyleMap.end()) { + return render::HighlightStyle(); + } else { + return (*highlightStyle).getStyle(); + } +} + template bool SelectionScriptingInterface::addToGameplayObjects(const QString& listName, T idToAdd) { - GameplayObjects currentList = _selectedItemsListMap.value(listName); - currentList.addToGameplayObjects(idToAdd); - _selectedItemsListMap.insert(listName, currentList); - - emit selectedItemsListChanged(listName); + { + QWriteLocker lock(&_selectionListsLock); + GameplayObjects currentList = _selectedItemsListMap.value(listName); + currentList.addToGameplayObjects(idToAdd); + _selectedItemsListMap.insert(listName, currentList); + } + onSelectedItemsListChanged(listName); return true; } template bool SelectionScriptingInterface::removeFromGameplayObjects(const QString& listName, T idToRemove) { - GameplayObjects currentList = _selectedItemsListMap.value(listName); - if (currentList.getContainsData()) { - currentList.removeFromGameplayObjects(idToRemove); - _selectedItemsListMap.insert(listName, currentList); - - emit selectedItemsListChanged(listName); + bool listExist = false; + { + QWriteLocker lock(&_selectionListsLock); + auto currentList = _selectedItemsListMap.find(listName); + if (currentList != _selectedItemsListMap.end()) { + listExist = true; + (*currentList).removeFromGameplayObjects(idToRemove); + } + } + if (listExist) { + onSelectedItemsListChanged(listName); return true; - } else { + } + else { return false; } } @@ -102,50 +205,123 @@ template bool SelectionScriptingInterface::removeFromGameplayObjects(c // GameplayObjects SelectionScriptingInterface::getList(const QString& listName) { + QReadLocker lock(&_selectionListsLock); return _selectedItemsListMap.value(listName); } void SelectionScriptingInterface::printList(const QString& listName) { - GameplayObjects currentList = _selectedItemsListMap.value(listName); - if (currentList.getContainsData()) { + QReadLocker lock(&_selectionListsLock); + auto currentList = _selectedItemsListMap.find(listName); + if (currentList != _selectedItemsListMap.end()) { + if ((*currentList).getContainsData()) { - qDebug() << "Avatar IDs:"; - for (auto i : currentList.getAvatarIDs()) { - qDebug() << i << ';'; - } - qDebug() << ""; + qDebug() << "List named " << listName << ":"; + qDebug() << "Avatar IDs:"; + for (auto i : (*currentList).getAvatarIDs()) { + qDebug() << i << ';'; + } + qDebug() << ""; - qDebug() << "Entity IDs:"; - for (auto j : currentList.getEntityIDs()) { - qDebug() << j << ';'; - } - qDebug() << ""; + qDebug() << "Entity IDs:"; + for (auto j : (*currentList).getEntityIDs()) { + qDebug() << j << ';'; + } + qDebug() << ""; - qDebug() << "Overlay IDs:"; - for (auto k : currentList.getOverlayIDs()) { - qDebug() << k << ';'; + qDebug() << "Overlay IDs:"; + for (auto k : (*currentList).getOverlayIDs()) { + qDebug() << k << ';'; + } + qDebug() << ""; + } + else { + qDebug() << "List named " << listName << " empty"; } - qDebug() << ""; } else { - qDebug() << "List named" << listName << "doesn't exist."; + qDebug() << "List named " << listName << " doesn't exist."; + } +} + +QVariantMap SelectionScriptingInterface::getSelectedItemsList(const QString& listName) const { + QReadLocker lock(&_selectionListsLock); + QVariantMap list; + auto currentList = _selectedItemsListMap.find(listName); + if (currentList != _selectedItemsListMap.end()) { + QList avatarIDs; + QList entityIDs; + QList overlayIDs; + + if ((*currentList).getContainsData()) { + if (!(*currentList).getAvatarIDs().empty()) { + for (auto j : (*currentList).getAvatarIDs()) { + avatarIDs.push_back((QUuid)j); + } + } + if (!(*currentList).getEntityIDs().empty()) { + for (auto j : (*currentList).getEntityIDs()) { + entityIDs.push_back((QUuid)j ); + } + } + if (!(*currentList).getOverlayIDs().empty()) { + for (auto j : (*currentList).getOverlayIDs()) { + overlayIDs.push_back((QUuid)j); + } + } + } + list["avatars"] = (avatarIDs); + list["entities"] = (entityIDs); + list["overlays"] = (overlayIDs); + + return list; + } + else { + return list; } } bool SelectionScriptingInterface::removeListFromMap(const QString& listName) { - if (_selectedItemsListMap.remove(listName)) { - emit selectedItemsListChanged(listName); + bool removed = false; + { + QWriteLocker lock(&_selectionListsLock); + removed = _selectedItemsListMap.remove(listName); + } + if (removed) { + onSelectedItemsListChanged(listName); return true; } else { return false; } } +void SelectionScriptingInterface::setupHandler(const QString& selectionName) { + QWriteLocker lock(&_selectionHandlersLock); + auto handler = _handlerMap.find(selectionName); + if (handler == _handlerMap.end()) { + handler = _handlerMap.insert(selectionName, new SelectionToSceneHandler()); + } + + (*handler)->initialize(selectionName); +} + +void SelectionScriptingInterface::onSelectedItemsListChanged(const QString& listName) { + { + QWriteLocker lock(&_selectionHandlersLock); + auto handler = _handlerMap.find(listName); + if (handler != _handlerMap.end()) { + (*handler)->updateSceneFromSelectedList(); + } + } + + emit selectedItemsListChanged(listName); +} + SelectionToSceneHandler::SelectionToSceneHandler() { } void SelectionToSceneHandler::initialize(const QString& listName) { _listName = listName; + updateSceneFromSelectedList(); } void SelectionToSceneHandler::selectedItemsListChanged(const QString& listName) { @@ -199,3 +375,85 @@ void SelectionToSceneHandler::updateSceneFromSelectedList() { qWarning() << "SelectionToSceneHandler::updateRendererSelectedList(), Unexpected null scene, possibly during application shutdown"; } } + +bool SelectionHighlightStyle::fromVariantMap(const QVariantMap& properties) { + auto colorVariant = properties["outlineUnoccludedColor"]; + if (colorVariant.isValid()) { + bool isValid; + auto color = xColorFromVariant(colorVariant, isValid); + if (isValid) { + _style._outlineUnoccluded.color = toGlm(color); + } + } + colorVariant = properties["outlineOccludedColor"]; + if (colorVariant.isValid()) { + bool isValid; + auto color = xColorFromVariant(colorVariant, isValid); + if (isValid) { + _style._outlineOccluded.color = toGlm(color); + } + } + colorVariant = properties["fillUnoccludedColor"]; + if (colorVariant.isValid()) { + bool isValid; + auto color = xColorFromVariant(colorVariant, isValid); + if (isValid) { + _style._fillUnoccluded.color = toGlm(color); + } + } + colorVariant = properties["fillOccludedColor"]; + if (colorVariant.isValid()) { + bool isValid; + auto color = xColorFromVariant(colorVariant, isValid); + if (isValid) { + _style._fillOccluded.color = toGlm(color); + } + } + + auto intensityVariant = properties["outlineUnoccludedAlpha"]; + if (intensityVariant.isValid()) { + _style._outlineUnoccluded.alpha = intensityVariant.toFloat(); + } + intensityVariant = properties["outlineOccludedAlpha"]; + if (intensityVariant.isValid()) { + _style._outlineOccluded.alpha = intensityVariant.toFloat(); + } + intensityVariant = properties["fillUnoccludedAlpha"]; + if (intensityVariant.isValid()) { + _style._fillUnoccluded.alpha = intensityVariant.toFloat(); + } + intensityVariant = properties["fillOccludedAlpha"]; + if (intensityVariant.isValid()) { + _style._fillOccluded.alpha = intensityVariant.toFloat(); + } + + auto outlineWidth = properties["outlineWidth"]; + if (outlineWidth.isValid()) { + _style._outlineWidth = outlineWidth.toFloat(); + } + auto isOutlineSmooth = properties["isOutlineSmooth"]; + if (isOutlineSmooth.isValid()) { + _style._isOutlineSmooth = isOutlineSmooth.toBool(); + } + + return true; +} + +QVariantMap SelectionHighlightStyle::toVariantMap() const { + QVariantMap properties; + + properties["outlineUnoccludedColor"] = xColorToVariant(xColorFromGlm(_style._outlineUnoccluded.color)); + properties["outlineOccludedColor"] = xColorToVariant(xColorFromGlm(_style._outlineOccluded.color)); + properties["fillUnoccludedColor"] = xColorToVariant(xColorFromGlm(_style._fillUnoccluded.color)); + properties["fillOccludedColor"] = xColorToVariant(xColorFromGlm(_style._fillOccluded.color)); + + properties["outlineUnoccludedAlpha"] = _style._outlineUnoccluded.alpha; + properties["outlineOccludedAlpha"] = _style._outlineOccluded.alpha; + properties["fillUnoccludedAlpha"] = _style._fillUnoccluded.alpha; + properties["fillOccludedAlpha"] = _style._fillOccluded.alpha; + + properties["outlineWidth"] = _style._outlineWidth; + properties["isOutlineSmooth"] = _style._isOutlineSmooth; + + return properties; +} \ No newline at end of file diff --git a/interface/src/scripting/SelectionScriptingInterface.h b/interface/src/scripting/SelectionScriptingInterface.h index d9003c2c32..8295375870 100644 --- a/interface/src/scripting/SelectionScriptingInterface.h +++ b/interface/src/scripting/SelectionScriptingInterface.h @@ -21,22 +21,23 @@ #include "RenderableEntityItem.h" #include "ui/overlays/Overlay.h" #include +#include class GameplayObjects { public: GameplayObjects(); - bool getContainsData() { return containsData; } + bool getContainsData() const { return containsData; } - std::vector getAvatarIDs() { return _avatarIDs; } + std::vector getAvatarIDs() const { return _avatarIDs; } bool addToGameplayObjects(const QUuid& avatarID); bool removeFromGameplayObjects(const QUuid& avatarID); - std::vector getEntityIDs() { return _entityIDs; } + std::vector getEntityIDs() const { return _entityIDs; } bool addToGameplayObjects(const EntityItemID& entityID); bool removeFromGameplayObjects(const EntityItemID& entityID); - std::vector getOverlayIDs() { return _overlayIDs; } + std::vector getOverlayIDs() const { return _overlayIDs; } bool addToGameplayObjects(const OverlayID& overlayID); bool removeFromGameplayObjects(const OverlayID& overlayID); @@ -48,20 +49,52 @@ private: }; +class SelectionToSceneHandler : public QObject { + Q_OBJECT +public: + SelectionToSceneHandler(); + void initialize(const QString& listName); + + void updateSceneFromSelectedList(); + +public slots: + void selectedItemsListChanged(const QString& listName); + +private: + QString _listName{ "" }; +}; +using SelectionToSceneHandlerPointer = QSharedPointer; + +class SelectionHighlightStyle { +public: + SelectionHighlightStyle() {} + + void setBoundToList(bool bound) { _isBoundToList = bound; } + bool isBoundToList() const { return _isBoundToList; } + + bool fromVariantMap(const QVariantMap& properties); + QVariantMap toVariantMap() const; + + render::HighlightStyle getStyle() const { return _style; } + +protected: + bool _isBoundToList{ false }; + render::HighlightStyle _style; +}; + class SelectionScriptingInterface : public QObject, public Dependency { Q_OBJECT public: SelectionScriptingInterface(); - GameplayObjects getList(const QString& listName); - /**jsdoc - * Prints out the list of avatars, entities and overlays stored in a particular selection. - * @function Selection.printList - * @param listName {string} name of the selection + * Query the names of all the selection lists + * @function Selection.getListNames + * @return An array of names of all the selection lists */ - Q_INVOKABLE void printList(const QString& listName); + Q_INVOKABLE QStringList getListNames() const; + /**jsdoc * Removes a named selection from the list of selections. * @function Selection.removeListFromMap @@ -96,30 +129,103 @@ public: */ Q_INVOKABLE bool clearSelectedItemsList(const QString& listName); + /**jsdoc + * Prints out the list of avatars, entities and overlays stored in a particular selection. + * @function Selection.printList + * @param listName {string} name of the selection + */ + Q_INVOKABLE void printList(const QString& listName); + + /**jsdoc + * Query the list of avatars, entities and overlays stored in a particular selection. + * @function Selection.getList + * @param listName {string} name of the selection + * @return a js object describing the content of a selection list with the following properties: + * - "entities": [ and array of the entityID of the entities in the selection] + * - "avatars": [ and array of the avatarID of the avatars in the selection] + * - "overlays": [ and array of the overlayID of the overlays in the selection] + * If the list name doesn't exist, the function returns an empty js object with no properties. + */ + Q_INVOKABLE QVariantMap getSelectedItemsList(const QString& listName) const; + + /**jsdoc + * Query the names of the highlighted selection lists + * @function Selection.getHighlightedListNames + * @return An array of names of the selection list currently highlight enabled + */ + Q_INVOKABLE QStringList getHighlightedListNames() const; + + /**jsdoc + * Enable highlighting for the named selection. + * If the Selection doesn't exist, it will be created. + * All objects in the list will be displayed with the highlight effect as specified from the highlightStyle. + * The function can be called several times with different values in the style to modify it. + * + * @function Selection.enableListHighlight + * @param listName {string} name of the selection + * @param highlightStyle {jsObject} highlight style fields (see Selection.getListHighlightStyle for a detailed description of the highlightStyle). + * @returns {bool} true if the selection was successfully enabled for highlight. + */ + Q_INVOKABLE bool enableListHighlight(const QString& listName, const QVariantMap& highlightStyle); + /**jsdoc + * Disable highlighting for the named selection. + * If the Selection doesn't exist or wasn't enabled for highliting then nothing happens simply returning false. + * + * @function Selection.disableListHighlight + * @param listName {string} name of the selection + * @returns {bool} true if the selection was successfully disabled for highlight, false otherwise. + */ + Q_INVOKABLE bool disableListHighlight(const QString& listName); + /**jsdoc + * Query the highlight style values for the named selection. + * If the Selection doesn't exist or hasn't been highlight enabled yet, it will return an empty object. + * Otherwise, the jsObject describes the highlight style properties: + * - outlineUnoccludedColor: {xColor} Color of the specified highlight region + * - outlineOccludedColor: {xColor} " + * - fillUnoccludedColor: {xColor} " + * - fillOccludedColor: {xColor} " + * + * - outlineUnoccludedAlpha: {float} Alpha value ranging from 0.0 (not visible) to 1.0 (fully opaque) for the specified highlight region + * - outlineOccludedAlpha: {float} " + * - fillUnoccludedAlpha: {float} " + * - fillOccludedAlpha: {float} " + * + * - outlineWidth: {float} width of the outline expressed in pixels + * - isOutlineSmooth: {bool} true to enable oultine smooth falloff + * + * @function Selection.getListHighlightStyle + * @param listName {string} name of the selection + * @returns {jsObject} highlight style as described above + */ + Q_INVOKABLE QVariantMap getListHighlightStyle(const QString& listName) const; + + + GameplayObjects getList(const QString& listName); + + render::HighlightStyle getHighlightStyle(const QString& listName) const; + + void onSelectedItemsListChanged(const QString& listName); + signals: void selectedItemsListChanged(const QString& listName); private: + mutable QReadWriteLock _selectionListsLock; QMap _selectedItemsListMap; + mutable QReadWriteLock _selectionHandlersLock; + QMap _handlerMap; + + mutable QReadWriteLock _highlightStylesLock; + QMap _highlightStyleMap; + template bool addToGameplayObjects(const QString& listName, T idToAdd); template bool removeFromGameplayObjects(const QString& listName, T idToRemove); -}; + + void setupHandler(const QString& selectionName); -class SelectionToSceneHandler : public QObject { - Q_OBJECT -public: - SelectionToSceneHandler(); - void initialize(const QString& listName); - - void updateSceneFromSelectedList(); - -public slots: - void selectedItemsListChanged(const QString& listName); - -private: - QString _listName { "" }; + }; #endif // hifi_SelectionScriptingInterface_h diff --git a/interface/src/scripting/WalletScriptingInterface.cpp b/interface/src/scripting/WalletScriptingInterface.cpp index 99fdd5fbde..8b4279af02 100644 --- a/interface/src/scripting/WalletScriptingInterface.cpp +++ b/interface/src/scripting/WalletScriptingInterface.cpp @@ -22,31 +22,3 @@ void WalletScriptingInterface::refreshWalletStatus() { auto wallet = DependencyManager::get(); wallet->getWalletStatus(); } - -static const QString CHECKOUT_QML_PATH = qApp->applicationDirPath() + "../../../qml/hifi/commerce/checkout/Checkout.qml"; -void WalletScriptingInterface::buy(const QString& name, const QString& id, const int& price, const QString& href) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "buy", Q_ARG(const QString&, name), Q_ARG(const QString&, id), Q_ARG(const int&, price), Q_ARG(const QString&, href)); - return; - } - - auto tabletScriptingInterface = DependencyManager::get(); - auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); - - tablet->loadQMLSource(CHECKOUT_QML_PATH); - DependencyManager::get()->openTablet(); - - QQuickItem* root = nullptr; - if (tablet->getToolbarMode() || (!tablet->getTabletRoot() && !qApp->isHMDMode())) { - root = DependencyManager::get()->getRootItem(); - } else { - root = tablet->getTabletRoot(); - } - CheckoutProxy* checkout = new CheckoutProxy(root->findChild("checkout")); - - // Example: Wallet.buy("Test Flaregun", "0d90d21c-ce7a-4990-ad18-e9d2cf991027", 17, "http://mpassets.highfidelity.com/0d90d21c-ce7a-4990-ad18-e9d2cf991027-v1/flaregun.json"); - checkout->writeProperty("itemName", name); - checkout->writeProperty("itemId", id); - checkout->writeProperty("itemPrice", price); - checkout->writeProperty("itemHref", href); -} \ No newline at end of file diff --git a/interface/src/scripting/WalletScriptingInterface.h b/interface/src/scripting/WalletScriptingInterface.h index 038c580197..d7f9d9242e 100644 --- a/interface/src/scripting/WalletScriptingInterface.h +++ b/interface/src/scripting/WalletScriptingInterface.h @@ -41,8 +41,6 @@ public: Q_INVOKABLE uint getWalletStatus() { return _walletStatus; } void setWalletStatus(const uint& status) { _walletStatus = status; } - Q_INVOKABLE void buy(const QString& name, const QString& id, const int& price, const QString& href); - signals: void walletStatusChanged(); void walletNotSetup(); diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index c99e190d12..4b355653b6 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -176,6 +176,10 @@ bool WindowScriptingInterface::isPointOnDesktopWindow(QVariant point) { return offscreenUi->isPointOnDesktopWindow(point); } +glm::vec2 WindowScriptingInterface::getDeviceSize() const { + return qApp->getDeviceSize(); +} + /// Makes sure that the reticle is visible, use this in blocking forms that require a reticle and /// might be in same thread as a script that sets the reticle to invisible void WindowScriptingInterface::ensureReticleVisible() const { diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 61aaec7bea..d223f95af4 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -12,6 +12,8 @@ #ifndef hifi_WindowScriptingInterface_h #define hifi_WindowScriptingInterface_h +#include + #include #include #include @@ -73,6 +75,7 @@ public slots: bool isPhysicsEnabled(); bool setDisplayTexture(const QString& name); bool isPointOnDesktopWindow(QVariant point); + glm::vec2 getDeviceSize() const; int openMessageBox(QString title, QString text, int buttons, int defaultButton); void updateMessageBox(int id, QString title, QString text, int buttons, int defaultButton); diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index a99fe002ee..52b53a3298 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -82,7 +82,6 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { // Now render the overlay components together into a single texture renderDomainConnectionStatusBorder(renderArgs); // renders the connected domain line - renderAudioScope(renderArgs); // audio scope in the very back - NOTE: this is the debug audio scope, not the VU meter renderOverlays(renderArgs); // renders Scripts Overlay and AudioScope renderQmlUi(renderArgs); // renders a unit quad with the QML UI texture, and the text overlays from scripts }); @@ -118,25 +117,6 @@ void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) { geometryCache->renderUnitQuad(batch, glm::vec4(1), _qmlGeometryId); } -void ApplicationOverlay::renderAudioScope(RenderArgs* renderArgs) { - PROFILE_RANGE(app, __FUNCTION__); - - gpu::Batch& batch = *renderArgs->_batch; - auto geometryCache = DependencyManager::get(); - geometryCache->useSimpleDrawPipeline(batch); - auto textureCache = DependencyManager::get(); - batch.setResourceTexture(0, textureCache->getWhiteTexture()); - int width = renderArgs->_viewport.z; - int height = renderArgs->_viewport.w; - mat4 legacyProjection = glm::ortho(0, width, height, 0, ORTHO_NEAR_CLIP, ORTHO_FAR_CLIP); - batch.setProjectionTransform(legacyProjection); - batch.setModelTransform(Transform()); - batch.resetViewTransform(); - - // Render the audio scope - DependencyManager::get()->render(renderArgs, width, height); -} - void ApplicationOverlay::renderOverlays(RenderArgs* renderArgs) { PROFILE_RANGE(app, __FUNCTION__); diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index af4d8779d4..0d30123c61 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -32,7 +32,6 @@ private: void renderStatsAndLogs(RenderArgs* renderArgs); void renderDomainConnectionStatusBorder(RenderArgs* renderArgs); void renderQmlUi(RenderArgs* renderArgs); - void renderAudioScope(RenderArgs* renderArgs); void renderOverlays(RenderArgs* renderArgs); void buildFramebufferObject(); diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 556399c741..6323ff9dc8 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -35,6 +35,8 @@ public: // getters virtual bool is3D() const override { return true; } + virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return (uint32_t) subItems.size(); } + // TODO: consider implementing registration points in this class glm::vec3 getCenter() const { return getWorldPosition(); } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 3681f42381..de644f165b 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -72,14 +72,7 @@ ContextOverlayInterface::ContextOverlayInterface() { connect(&qApp->getOverlays(), &Overlays::hoverLeaveOverlay, this, &ContextOverlayInterface::contextOverlays_hoverLeaveOverlay); { - render::Transaction transaction; - initializeSelectionToSceneHandler(_selectionToSceneHandlers[0], "contextOverlayHighlightList", transaction); - for (auto i = 1; i < MAX_SELECTION_COUNT; i++) { - auto selectionName = QString("highlightList") + QString::number(i); - initializeSelectionToSceneHandler(_selectionToSceneHandlers[i], selectionName, transaction); - } - const render::ScenePointer& scene = qApp->getMain3DScene(); - scene->enqueueTransaction(transaction); + _selectionScriptingInterface->enableListHighlight("contextOverlayHighlightList", QVariantMap()); } auto nodeList = DependencyManager::get(); @@ -88,12 +81,6 @@ ContextOverlayInterface::ContextOverlayInterface() { _challengeOwnershipTimeoutTimer.setSingleShot(true); } -void ContextOverlayInterface::initializeSelectionToSceneHandler(SelectionToSceneHandler& handler, const QString& selectionName, render::Transaction& transaction) { - handler.initialize(selectionName); - connect(_selectionScriptingInterface.data(), &SelectionScriptingInterface::selectedItemsListChanged, &handler, &SelectionToSceneHandler::selectedItemsListChanged); - transaction.resetSelectionHighlight(selectionName.toStdString()); -} - static const xColor CONTEXT_OVERLAY_COLOR = { 255, 255, 255 }; static const float CONTEXT_OVERLAY_INSIDE_DISTANCE = 1.0f; // in meters static const float CONTEXT_OVERLAY_SIZE = 0.09f; // in meters, same x and y dims diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index 81e398e15d..990a7fe599 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -96,9 +96,6 @@ private: void disableEntityHighlight(const EntityItemID& entityItemID); void deletingEntity(const EntityItemID& entityItemID); - void initializeSelectionToSceneHandler(SelectionToSceneHandler& handler, const QString& selectionName, render::Transaction& transaction); - - SelectionToSceneHandler _selectionToSceneHandlers[MAX_SELECTION_COUNT]; Q_INVOKABLE void startChallengeOwnershipTimer(); QTimer _challengeOwnershipTimeoutTimer; diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index 66a6772aa5..7dfec8e448 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -166,26 +166,36 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) { bool newEndSet { false }; auto start = properties["start"]; - // if "start" property was not there, check to see if they included aliases: startPoint + // If "start" property was not there, check to see if they included aliases: startPoint, p1 if (!start.isValid()) { start = properties["startPoint"]; } + if (!start.isValid()) { + start = properties["p1"]; + } if (start.isValid()) { newStart = vec3FromVariant(start); newStartSet = true; } properties.remove("start"); // so that Base3DOverlay doesn't respond to it + properties.remove("startPoint"); + properties.remove("p1"); auto end = properties["end"]; - // if "end" property was not there, check to see if they included aliases: endPoint + // If "end" property was not there, check to see if they included aliases: endPoint, p2 if (!end.isValid()) { end = properties["endPoint"]; } + if (!end.isValid()) { + end = properties["p2"]; + } if (end.isValid()) { newEnd = vec3FromVariant(end); newEndSet = true; } properties.remove("end"); // so that Base3DOverlay doesn't respond to it + properties.remove("endPoint"); + properties.remove("p2"); auto length = properties["length"]; if (length.isValid()) { @@ -252,14 +262,23 @@ QVariant Line3DOverlay::getProperty(const QString& property) { if (property == "end" || property == "endPoint" || property == "p2") { return vec3toVariant(getEnd()); } + if (property == "length") { + return QVariant(getLength()); + } + if (property == "endParentID") { + return _endParentID; + } + if (property == "endParentJointIndex") { + return _endParentJointIndex; + } if (property == "localStart") { return vec3toVariant(getLocalStart()); } if (property == "localEnd") { return vec3toVariant(getLocalEnd()); } - if (property == "length") { - return QVariant(getLength()); + if (property == "glow") { + return getGlow(); } if (property == "lineWidth") { return _lineWidth; diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 17dbe9850e..0846599728 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -35,7 +35,8 @@ ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) : _modelTextures(QVariantMap()), _url(modelOverlay->_url), _updateModel(false), - _loadPriority(modelOverlay->getLoadPriority()) + _scaleToFit(modelOverlay->_scaleToFit), + _loadPriority(modelOverlay->_loadPriority) { _model->init(); _model->setLoadingPriority(_loadPriority); @@ -78,6 +79,12 @@ void ModelOverlay::update(float deltatime) { if (_model->needsFixupInScene()) { _model->removeFromScene(scene, transaction); _model->addToScene(scene, transaction); + + auto newRenderItemIDs{ _model->fetchRenderItemIDs() }; + transaction.updateItem(getRenderItemID(), [newRenderItemIDs](Overlay& data) { + auto modelOverlay = static_cast(&data); + modelOverlay->setSubRenderItemIDs(newRenderItemIDs); + }); } if (_visibleDirty) { _visibleDirty = false; @@ -103,6 +110,10 @@ bool ModelOverlay::addToScene(Overlay::Pointer overlay, const render::ScenePoint void ModelOverlay::removeFromScene(Overlay::Pointer overlay, const render::ScenePointer& scene, render::Transaction& transaction) { Volume3DOverlay::removeFromScene(overlay, scene, transaction); _model->removeFromScene(scene, transaction); + transaction.updateItem(getRenderItemID(), [](Overlay& data) { + auto modelOverlay = static_cast(&data); + modelOverlay->clearSubRenderItemIDs(); + }); } void ModelOverlay::setVisible(bool visible) { @@ -134,6 +145,9 @@ void ModelOverlay::setProperties(const QVariantMap& properties) { } auto dimensions = properties["dimensions"]; + if (!dimensions.isValid()) { + dimensions = properties["size"]; + } if (dimensions.isValid()) { _scaleToFit = true; setDimensions(vec3FromVariant(dimensions)); @@ -525,3 +539,19 @@ void ModelOverlay::copyAnimationJointDataToModel(QVector jointsData) _updateModel = true; } +void ModelOverlay::clearSubRenderItemIDs() { + _subRenderItemIDs.clear(); +} + +void ModelOverlay::setSubRenderItemIDs(const render::ItemIDs& ids) { + _subRenderItemIDs = ids; +} + +uint32_t ModelOverlay::fetchMetaSubItems(render::ItemIDs& subItems) const { + if (_model) { + auto metaSubItems = _subRenderItemIDs; + subItems.insert(subItems.end(), metaSubItems.begin(), metaSubItems.end()); + return (uint32_t)metaSubItems.size(); + } + return 0; +} diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index ea0eff170c..4f7f1e0cae 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -30,6 +30,12 @@ public: virtual void update(float deltatime) override; virtual void render(RenderArgs* args) override {}; + + virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override; + + void clearSubRenderItemIDs(); + void setSubRenderItemIDs(const render::ItemIDs& ids); + 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, @@ -74,9 +80,11 @@ private: ModelPointer _model; QVariantMap _modelTextures; + render::ItemIDs _subRenderItemIDs; + QUrl _url; - bool _updateModel = { false }; - bool _scaleToFit = { false }; + bool _updateModel { false }; + bool _scaleToFit { false }; float _loadPriority { 0.0f }; AnimationPointer _animation; @@ -87,7 +95,7 @@ private: bool _animationRunning { false }; bool _animationLoop { false }; float _animationFirstFrame { 0.0f }; - float _animationLastFrame = { 0.0f }; + float _animationLastFrame { 0.0f }; bool _animationHold { false }; bool _animationAllowTranslation { false }; uint64_t _lastAnimated { 0 }; diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index 39208f01a0..806fc1aa14 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -53,6 +53,8 @@ public: virtual const render::ShapeKey getShapeKey() { return render::ShapeKey::Builder::ownPipeline(); } + virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const { return 0; } + // getters virtual QString getType() const = 0; virtual bool is3D() const = 0; @@ -130,6 +132,7 @@ namespace render { template <> int payloadGetLayer(const Overlay::Pointer& overlay); template <> void payloadRender(const Overlay::Pointer& overlay, RenderArgs* args); template <> const ShapeKey shapeGetShapeKey(const Overlay::Pointer& overlay); + template <> uint32_t metaFetchMetaSubItems(const Overlay::Pointer& overlay, ItemIDs& subItems); } Q_DECLARE_METATYPE(OverlayID); diff --git a/interface/src/ui/overlays/Overlay2D.h b/interface/src/ui/overlays/Overlay2D.h index a1efe8a6de..3175df92f1 100644 --- a/interface/src/ui/overlays/Overlay2D.h +++ b/interface/src/ui/overlays/Overlay2D.h @@ -26,6 +26,8 @@ public: virtual bool is3D() const override { return false; } + virtual uint32_t fetchMetaSubItems(render::ItemIDs& subItems) const override { subItems.push_back(getRenderItemID()); return 1; } + // getters int getX() const { return _bounds.x(); } int getY() const { return _bounds.y(); } diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index a8f504bbc5..e1996e6bfc 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -305,13 +305,6 @@ public slots: OverlayID getKeyboardFocusOverlay(); void setKeyboardFocusOverlay(const OverlayID& id); - void mousePressPointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void mouseMovePointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void mouseReleasePointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void hoverEnterPointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void hoverOverPointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void hoverLeavePointerEvent(const OverlayID& overlayID, const PointerEvent& event); - signals: /**jsdoc * Emitted when an overlay is deleted @@ -358,6 +351,14 @@ private: OverlayID _currentHoverOverOverlayID { UNKNOWN_OVERLAY_ID }; RayToOverlayIntersectionResult findRayIntersectionForMouseEvent(PickRay ray); + +private slots: + void mousePressPointerEvent(const OverlayID& overlayID, const PointerEvent& event); + void mouseMovePointerEvent(const OverlayID& overlayID, const PointerEvent& event); + void mouseReleasePointerEvent(const OverlayID& overlayID, const PointerEvent& event); + void hoverEnterPointerEvent(const OverlayID& overlayID, const PointerEvent& event); + void hoverOverPointerEvent(const OverlayID& overlayID, const PointerEvent& event); + void hoverLeavePointerEvent(const OverlayID& overlayID, const PointerEvent& event); }; #endif // hifi_Overlays_h diff --git a/interface/src/ui/overlays/OverlaysPayload.cpp b/interface/src/ui/overlays/OverlaysPayload.cpp index 8d3e514a0f..fceb261503 100644 --- a/interface/src/ui/overlays/OverlaysPayload.cpp +++ b/interface/src/ui/overlays/OverlaysPayload.cpp @@ -87,4 +87,10 @@ namespace render { template <> const ShapeKey shapeGetShapeKey(const Overlay::Pointer& overlay) { return overlay->getShapeKey(); } + + + template <> uint32_t metaFetchMetaSubItems(const Overlay::Pointer& overlay, ItemIDs& subItems) { + return overlay->fetchMetaSubItems(subItems); + } } + diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index ca6446d215..07949d5349 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -18,8 +18,9 @@ QString const Shape3DOverlay::TYPE = "shape"; -Shape3DOverlay::Shape3DOverlay(const Shape3DOverlay* Shape3DOverlay) : - Volume3DOverlay(Shape3DOverlay) +Shape3DOverlay::Shape3DOverlay(const Shape3DOverlay* shape3DOverlay) : + Volume3DOverlay(shape3DOverlay), + _shape(shape3DOverlay->_shape) { } diff --git a/interface/src/ui/overlays/Shape3DOverlay.h b/interface/src/ui/overlays/Shape3DOverlay.h index e9e26e3c94..7fc95ec981 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.h +++ b/interface/src/ui/overlays/Shape3DOverlay.h @@ -23,7 +23,7 @@ public: virtual QString getType() const override { return TYPE; } Shape3DOverlay() {} - Shape3DOverlay(const Shape3DOverlay* Shape3DOverlay); + Shape3DOverlay(const Shape3DOverlay* shape3DOverlay); virtual void render(RenderArgs* args) override; virtual const render::ShapeKey getShapeKey() override; diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index b580545288..49c76e6108 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -14,7 +14,8 @@ #include Volume3DOverlay::Volume3DOverlay(const Volume3DOverlay* volume3DOverlay) : - Base3DOverlay(volume3DOverlay) + Base3DOverlay(volume3DOverlay), + _localBoundingBox(volume3DOverlay->_localBoundingBox) { } diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 34b8c6c865..7d5fefcb31 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -55,17 +55,15 @@ #include #include "ui/Snapshot.h" #include "SoundCache.h" - #include "raypick/PointerScriptingInterface.h" -static const float DPI = 30.47f; -static const float INCHES_TO_METERS = 1.0f / 39.3701f; +static int MAX_WINDOW_SIZE = 4096; static const float METERS_TO_INCHES = 39.3701f; static const float OPAQUE_ALPHA_THRESHOLD = 0.99f; const QString Web3DOverlay::TYPE = "web3d"; const QString Web3DOverlay::QML = "Web3DOverlay.qml"; -Web3DOverlay::Web3DOverlay() : _dpi(DPI) { +Web3DOverlay::Web3DOverlay() { _touchDevice.setCapabilities(QTouchDevice::Position); _touchDevice.setType(QTouchDevice::TouchScreen); _touchDevice.setName("Web3DOverlayTouchDevice"); @@ -82,7 +80,6 @@ Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) : _url(Web3DOverlay->_url), _scriptURL(Web3DOverlay->_scriptURL), _dpi(Web3DOverlay->_dpi), - _resolution(Web3DOverlay->_resolution), _showKeyboardFocusHighlight(Web3DOverlay->_showKeyboardFocusHighlight) { _geometryId = DependencyManager::get()->allocateID(); @@ -154,7 +151,7 @@ void Web3DOverlay::buildWebSurface() { setupQmlSurface(); } _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getWorldPosition())); - _webSurface->resize(QSize(_resolution.x, _resolution.y)); + onResizeWebSurface(); _webSurface->resume(); }); @@ -244,8 +241,16 @@ void Web3DOverlay::setMaxFPS(uint8_t maxFPS) { } void Web3DOverlay::onResizeWebSurface() { - _mayNeedResize = false; - _webSurface->resize(QSize(_resolution.x, _resolution.y)); + glm::vec2 dims = glm::vec2(getDimensions()); + dims *= METERS_TO_INCHES * _dpi; + + // ensure no side is never larger then MAX_WINDOW_SIZE + float max = (dims.x > dims.y) ? dims.x : dims.y; + if (max > MAX_WINDOW_SIZE) { + dims *= MAX_WINDOW_SIZE / max; + } + + _webSurface->resize(QSize(dims.x, dims.y)); } unsigned int Web3DOverlay::deviceIdByTouchPoint(qreal x, qreal y) { @@ -266,14 +271,14 @@ void Web3DOverlay::render(RenderArgs* args) { return; } - if (_currentMaxFPS != _desiredMaxFPS) { - setMaxFPS(_desiredMaxFPS); - } - if (_mayNeedResize) { emit resizeWebSurface(); } + if (_currentMaxFPS != _desiredMaxFPS) { + setMaxFPS(_desiredMaxFPS); + } + vec4 color(toGlm(getColor()), getAlpha()); if (!_texture) { @@ -310,7 +315,7 @@ void Web3DOverlay::render(RenderArgs* args) { Transform Web3DOverlay::evalRenderTransform() { Transform transform = Parent::evalRenderTransform(); transform.setScale(1.0f); - transform.postScale(glm::vec3(getSize(), 1.0f)); + transform.postScale(glm::vec3(getDimensions(), 1.0f)); return transform; } @@ -434,18 +439,10 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { } } - auto resolution = properties["resolution"]; - if (resolution.isValid()) { - bool valid; - auto res = vec2FromVariant(resolution, valid); - if (valid) { - _resolution = res; - } - } - auto dpi = properties["dpi"]; if (dpi.isValid()) { _dpi = dpi.toFloat(); + _mayNeedResize = true; } auto maxFPS = properties["maxFPS"]; @@ -467,8 +464,6 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { _inputMode = Touch; } } - - _mayNeedResize = true; } QVariant Web3DOverlay::getProperty(const QString& property) { @@ -478,9 +473,6 @@ QVariant Web3DOverlay::getProperty(const QString& property) { if (property == "scriptURL") { return _scriptURL; } - if (property == "resolution") { - return vec2toVariant(_resolution); - } if (property == "dpi") { return _dpi; } @@ -536,17 +528,18 @@ void Web3DOverlay::setScriptURL(const QString& scriptURL) { } } -glm::vec2 Web3DOverlay::getSize() const { - return _resolution / _dpi * INCHES_TO_METERS * getDimensions(); -}; - bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) { - // FIXME - face and surfaceNormal not being returned + glm::vec2 dimensions = getDimensions(); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition(); - // 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. - return findRayRectangleIntersection(origin, direction, getWorldOrientation(), getWorldPosition(), getSize(), distance); + 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 { @@ -555,4 +548,4 @@ Web3DOverlay* Web3DOverlay::createClone() const { void Web3DOverlay::emitScriptEvent(const QVariant& message) { QMetaObject::invokeMethod(this, "scriptEventReceived", Q_ARG(QVariant, message)); -} +} \ No newline at end of file diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index 563c1c0c4e..4098e98488 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -52,8 +52,6 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; - glm::vec2 getSize() const override; - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) override; @@ -93,10 +91,9 @@ private: gpu::TexturePointer _texture; QString _url; QString _scriptURL; - float _dpi; - vec2 _resolution{ 640, 480 }; + float _dpi { 30.0f }; int _geometryId { 0 }; - bool _showKeyboardFocusHighlight{ true }; + bool _showKeyboardFocusHighlight { true }; QTouchDevice _touchDevice; diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index e9cc444bd4..e738ad1c19 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -231,6 +231,9 @@ public: const glm::mat4& getGeometryToRigTransform() const { return _geometryToRigTransform; } + const AnimPose& getModelOffsetPose() const { return _modelOffset; } + const AnimPose& getGeometryOffsetPose() const { return _geometryOffset; } + void setEnableDebugDrawIKTargets(bool enableDebugDrawIKTargets) { _enableDebugDrawIKTargets = enableDebugDrawIKTargets; } void setEnableDebugDrawIKConstraints(bool enableDebugDrawIKConstraints) { _enableDebugDrawIKConstraints = enableDebugDrawIKConstraints; } void setEnableDebugDrawIKChains(bool enableDebugDrawIKChains) { _enableDebugDrawIKChains = enableDebugDrawIKChains; } diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 78475f5b68..af86499101 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -79,6 +79,7 @@ Setting::Handle staticJitterBufferFrames("staticJitterBufferFrames", using Mutex = std::mutex; using Lock = std::unique_lock; Mutex _deviceMutex; +Mutex _recordMutex; // thread-safe QList getAvailableDevices(QAudio::Mode mode) { @@ -222,8 +223,7 @@ AudioClient::AudioClient() : // initialize wasapi; if getAvailableDevices is called from the CheckDevicesThread before this, it will crash getAvailableDevices(QAudio::AudioInput); getAvailableDevices(QAudio::AudioOutput); - - + // start a thread to detect any device changes _checkDevicesTimer = new QTimer(this); connect(_checkDevicesTimer, &QTimer::timeout, [this] { @@ -1845,11 +1845,9 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { qCDebug(audiostream, "Read %d samples from buffer (%d available, %d requested)", networkSamplesPopped, _receivedAudioStream.getSamplesAvailable(), samplesRequested); AudioRingBuffer::ConstIterator lastPopOutput = _receivedAudioStream.getLastPopOutput(); lastPopOutput.readSamples(scratchBuffer, networkSamplesPopped); - for (int i = 0; i < networkSamplesPopped; i++) { mixBuffer[i] = convertToFloat(scratchBuffer[i]); } - samplesRequested = networkSamplesPopped; } @@ -1911,6 +1909,13 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { bytesWritten = maxSize; } + // send output buffer for recording + if (_audio->_isRecording) { + Lock lock(_recordMutex); + _audio->_audioFileWav.addRawAudioChunk(reinterpret_cast(scratchBuffer), bytesWritten); + } + + int bytesAudioOutputUnplayed = _audio->_audioOutput->bufferSize() - _audio->_audioOutput->bytesFree(); float msecsAudioOutputUnplayed = bytesAudioOutputUnplayed / (float)_audio->_outputFormat.bytesForDuration(USECS_PER_MSEC); _audio->_stats.updateOutputMsUnplayed(msecsAudioOutputUnplayed); @@ -1922,6 +1927,22 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { return bytesWritten; } +bool AudioClient::startRecording(const QString& filepath) { + if (!_audioFileWav.create(_outputFormat, filepath)) { + qDebug() << "Error creating audio file: " + filepath; + return false; + } + _isRecording = true; + return true; +} + +void AudioClient::stopRecording() { + if (_isRecording) { + _isRecording = false; + _audioFileWav.close(); + } +} + void AudioClient::loadSettings() { _receivedAudioStream.setDynamicJitterBufferEnabled(dynamicJitterBufferEnabled.get()); _receivedAudioStream.setStaticJitterBufferFrames(staticJitterBufferFrames.get()); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 01a487455c..0ceb9c4dc3 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -47,11 +47,13 @@ #include #include + #include #include #include "AudioIOStats.h" +#include "AudioFileWav.h" #ifdef _WIN32 #pragma warning( push ) @@ -67,7 +69,6 @@ class QAudioInput; class QAudioOutput; class QIODevice; - class Transform; class NLPacket; @@ -118,6 +119,8 @@ public: const MixedProcessedAudioStream& getReceivedAudioStream() const { return _receivedAudioStream; } MixedProcessedAudioStream& getReceivedAudioStream() { return _receivedAudioStream; } + const QAudioFormat& getOutputFormat() const { return _outputFormat; } + float getLastInputLoudness() const { return _lastInputLoudness; } // TODO: relative to noise floor? float getTimeSinceLastClip() const { return _timeSinceLastClip; } @@ -142,7 +145,7 @@ public: void setIsPlayingBackRecording(bool isPlayingBackRecording) { _isPlayingBackRecording = isPlayingBackRecording; } Q_INVOKABLE void setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 scale); - + bool outputLocalInjector(const AudioInjectorPointer& injector) override; QAudioDeviceInfo getActiveAudioDevice(QAudio::Mode mode) const; @@ -155,6 +158,13 @@ public: bool getNamedAudioDeviceForModeExists(QAudio::Mode mode, const QString& deviceName); + void setRecording(bool isRecording) { _isRecording = isRecording; }; + bool getRecording() { return _isRecording; }; + + bool startRecording(const QString& filename); + void stopRecording(); + + #ifdef Q_OS_WIN static QString getWinDeviceName(wchar_t* guid); #endif @@ -184,13 +194,17 @@ public slots: void toggleMute(); bool isMuted() { return _muted; } - virtual void setIsStereoInput(bool stereo) override; void setNoiseReduction(bool isNoiseGateEnabled); bool isNoiseReductionEnabled() const { return _isNoiseGateEnabled; } + bool getLocalEcho() { return _shouldEchoLocally; } + void setLocalEcho(bool localEcho) { _shouldEchoLocally = localEcho; } void toggleLocalEcho() { _shouldEchoLocally = !_shouldEchoLocally; } + + bool getServerEcho() { return _shouldEchoToServer; } + void setServerEcho(bool serverEcho) { _shouldEchoToServer = serverEcho; } void toggleServerEcho() { _shouldEchoToServer = !_shouldEchoToServer; } void processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer); @@ -239,6 +253,8 @@ signals: void muteEnvironmentRequested(glm::vec3 position, float radius); + void outputBufferReceived(const QByteArray _outputBuffer); + protected: AudioClient(); ~AudioClient(); @@ -354,9 +370,8 @@ private: int16_t _localScratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC]; float* _localOutputMixBuffer { NULL }; Mutex _localAudioMutex; - AudioLimiter _audioLimiter; - + // Adds Reverb void configureReverb(); void updateReverbOptions(); @@ -391,6 +406,8 @@ private: QList _inputDevices; QList _outputDevices; + AudioFileWav _audioFileWav; + bool _hasReceivedFirstPacket { false }; QVector _activeLocalAudioInjectors; @@ -412,6 +429,8 @@ private: QTimer* _checkDevicesTimer { nullptr }; QTimer* _checkPeakValuesTimer { nullptr }; + + bool _isRecording { false }; }; diff --git a/libraries/audio-client/src/AudioFileWav.cpp b/libraries/audio-client/src/AudioFileWav.cpp new file mode 100644 index 0000000000..613628883c --- /dev/null +++ b/libraries/audio-client/src/AudioFileWav.cpp @@ -0,0 +1,69 @@ +// +// AudioWavFile.h +// libraries/audio-client/src +// +// Created by Luis Cuenca on 12/1/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 "AudioFileWav.h" + +bool AudioFileWav::create(const QAudioFormat& audioFormat, const QString& filepath) { + if (_file.isOpen()) { + _file.close(); + } + _file.setFileName(filepath); + if (!_file.open(QIODevice::WriteOnly)) { + return false; + } + addHeader(audioFormat); + return true; +} + +bool AudioFileWav::addRawAudioChunk(char* chunk, int size) { + if (_file.isOpen()) { + QDataStream stream(&_file); + stream.writeRawData(chunk, size); + return true; + } + return false; +} + +void AudioFileWav::close() { + QDataStream stream(&_file); + stream.setByteOrder(QDataStream::LittleEndian); + + // fill RIFF and size data on header + _file.seek(4); + stream << quint32(_file.size() - 8); + _file.seek(40); + stream << quint32(_file.size() - 44); + _file.close(); +} + +void AudioFileWav::addHeader(const QAudioFormat& audioFormat) { + QDataStream stream(&_file); + + stream.setByteOrder(QDataStream::LittleEndian); + + // RIFF + stream.writeRawData("RIFF", 4); + stream << quint32(0); + stream.writeRawData("WAVE", 4); + + // Format description PCM = 16 + stream.writeRawData("fmt ", 4); + stream << quint32(16); + stream << quint16(1); + stream << quint16(audioFormat.channelCount()); + stream << quint32(audioFormat.sampleRate()); + stream << quint32(audioFormat.sampleRate() * audioFormat.channelCount() * audioFormat.sampleSize() / 8); // bytes per second + stream << quint16(audioFormat.channelCount() * audioFormat.sampleSize() / 8); // block align + stream << quint16(audioFormat.sampleSize()); // bits Per Sample + // Init data chunck + stream.writeRawData("data", 4); + stream << quint32(0); +} diff --git a/libraries/audio-client/src/AudioFileWav.h b/libraries/audio-client/src/AudioFileWav.h new file mode 100644 index 0000000000..7e9c83a23b --- /dev/null +++ b/libraries/audio-client/src/AudioFileWav.h @@ -0,0 +1,34 @@ +// +// AudioWavFile.h +// libraries/audio-client/src +// +// Created by Luis Cuenca on 12/1/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AudioFileWav_h +#define hifi_AudioFileWav_h + +#include +#include +#include +#include +#include + +class AudioFileWav : public QObject { + Q_OBJECT +public: + AudioFileWav() {} + bool create(const QAudioFormat& audioFormat, const QString& filepath); + bool addRawAudioChunk(char* chunk, int size); + void close(); + +private: + void addHeader(const QAudioFormat& audioFormat); + QFile _file; +}; + +#endif // hifi_AudioFileWav_h \ No newline at end of file diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 055a3cf24a..bb7f141cd9 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -162,6 +162,7 @@ AABox Avatar::getBounds() const { } void Avatar::animateScaleChanges(float deltaTime) { + if (_isAnimatingScale) { float currentScale = getModelScale(); float desiredScale = getDomainLimitedScale(); @@ -172,7 +173,7 @@ void Avatar::animateScaleChanges(float deltaTime) { float animatedScale = (1.0f - blendFactor) * currentScale + blendFactor * desiredScale; // snap to the end when we get close enough - const float MIN_RELATIVE_ERROR = 0.03f; + const float MIN_RELATIVE_ERROR = 0.001f; float relativeError = fabsf(desiredScale - currentScale) / desiredScale; if (relativeError < MIN_RELATIVE_ERROR) { animatedScale = desiredScale; @@ -698,6 +699,7 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) { _skeletonModel->removeFromScene(scene, transaction); _skeletonModel->addToScene(scene, transaction); canTryFade = true; + _isAnimatingScale = true; } for (auto attachmentModel : _attachmentModels) { if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) { @@ -1195,6 +1197,8 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { void Avatar::setModelURLFinished(bool success) { invalidateJointIndicesCache(); + _isAnimatingScale = true; + if (!success && _skeletonModelURL != AvatarData::defaultFullAvatarModelUrl()) { const int MAX_SKELETON_DOWNLOAD_ATTEMPTS = 4; // NOTE: we don't want to be as generous as ResourceCache is, we only want 4 attempts if (_skeletonModel->getResourceDownloadAttemptsRemaining() <= 0 || @@ -1213,6 +1217,15 @@ void Avatar::setModelURLFinished(bool success) { } } +// rig is ready +void Avatar::rigReady() { + buildUnscaledEyeHeightCache(); +} + +// rig has been reset. +void Avatar::rigReset() { + clearUnscaledEyeHeightCache(); +} // create new model, can return an instance of a SoftAttachmentModel rather then Model static std::shared_ptr allocateAttachmentModel(bool isSoft, const Rig& rigOverride, bool isCauterized) { @@ -1580,53 +1593,91 @@ void Avatar::ensureInScene(AvatarSharedPointer self, const render::ScenePointer& } } +// thread-safe float Avatar::getEyeHeight() const { + return getModelScale() * getUnscaledEyeHeight(); +} - if (QThread::currentThread() != thread()) { - float result = DEFAULT_AVATAR_EYE_HEIGHT; - BLOCKING_INVOKE_METHOD(const_cast(this), "getEyeHeight", Q_RETURN_ARG(float, result)); - return result; +// thread-safe +float Avatar::getUnscaledEyeHeight() const { + return _unscaledEyeHeightCache.get(); +} + +void Avatar::buildUnscaledEyeHeightCache() { + float skeletonHeight = getUnscaledEyeHeightFromSkeleton(); + + // Sanity check by looking at the model extents. + Extents meshExtents = _skeletonModel->getUnscaledMeshExtents(); + float meshHeight = meshExtents.size().y; + + // if we determine the mesh is much larger then the skeleton, then we use the mesh height instead. + // This helps prevent absurdly large avatars from exceeding the domain height limit. + const float MESH_SLOP_RATIO = 1.5f; + if (meshHeight > skeletonHeight * MESH_SLOP_RATIO) { + _unscaledEyeHeightCache.set(meshHeight); + } else { + _unscaledEyeHeightCache.set(skeletonHeight); } +} + +void Avatar::clearUnscaledEyeHeightCache() { + _unscaledEyeHeightCache.set(DEFAULT_AVATAR_EYE_HEIGHT); +} + +float Avatar::getUnscaledEyeHeightFromSkeleton() const { // TODO: if performance becomes a concern we can cache this value rather then computing it everytime. - // Makes assumption that the y = 0 plane in geometry is the ground plane. - // We also make that assumption in Rig::computeAvatarBoundingCapsule() - float avatarScale = getModelScale(); + if (_skeletonModel) { auto& rig = _skeletonModel->getRig(); + + // Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here. + AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), rig.getModelOffsetPose().rot(), rig.getModelOffsetPose().trans()); + AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * rig.getGeometryOffsetPose(); + + // This factor can be used to scale distances in the geometry frame into the unscaled rig frame. + // Typically it will be the unit conversion from cm to m. + float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor. + int headTopJoint = rig.indexOfJoint("HeadTop_End"); int headJoint = rig.indexOfJoint("Head"); int eyeJoint = rig.indexOfJoint("LeftEye") != -1 ? rig.indexOfJoint("LeftEye") : rig.indexOfJoint("RightEye"); int toeJoint = rig.indexOfJoint("LeftToeBase") != -1 ? rig.indexOfJoint("LeftToeBase") : rig.indexOfJoint("RightToeBase"); + + // Makes assumption that the y = 0 plane in geometry is the ground plane. + // We also make that assumption in Rig::computeAvatarBoundingCapsule() + const float GROUND_Y = 0.0f; + + // Values from the skeleton are in the geometry coordinate frame. + auto skeleton = rig.getAnimSkeleton(); if (eyeJoint >= 0 && toeJoint >= 0) { - // measure from eyes to toes. - float eyeHeight = rig.getAbsoluteDefaultPose(eyeJoint).trans().y - rig.getAbsoluteDefaultPose(toeJoint).trans().y; - return eyeHeight; + // Measure from eyes to toes. + float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; + return scaleFactor * eyeHeight; } else if (eyeJoint >= 0) { - // measure eyes to y = 0 plane. - float groundHeight = transformPoint(rig.getGeometryToRigTransform(), glm::vec3(0.0f)).y; - float eyeHeight = rig.getAbsoluteDefaultPose(eyeJoint).trans().y - groundHeight; - return eyeHeight; + // Measure Eye joint to y = 0 plane. + float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y; + return scaleFactor * eyeHeight; } else if (headTopJoint >= 0 && toeJoint >= 0) { - // measure toe to top of head. Note: default poses already include avatar scale factor + // Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance. const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; - float height = rig.getAbsoluteDefaultPose(headTopJoint).trans().y - rig.getAbsoluteDefaultPose(toeJoint).trans().y; - return height - height * ratio; + float height = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; + return scaleFactor * (height - height * ratio); } else if (headTopJoint >= 0) { + // Measure from HeadTop_End joint to the ground, then remove forehead distance. const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; - float groundHeight = transformPoint(rig.getGeometryToRigTransform(), glm::vec3(0.0f)).y; - float headHeight = rig.getAbsoluteDefaultPose(headTopJoint).trans().y - groundHeight; - return headHeight - headHeight * ratio; + float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y; + return scaleFactor * (headHeight - headHeight * ratio); } else if (headJoint >= 0) { - float groundHeight = transformPoint(rig.getGeometryToRigTransform(), glm::vec3(0.0f)).y; + // Measure Head joint to the ground, then add in distance from neck to eye. const float DEFAULT_AVATAR_NECK_TO_EYE = DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT; - float neckHeight = rig.getAbsoluteDefaultPose(headJoint).trans().y - groundHeight; - return neckHeight + neckHeight * ratio; + float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GROUND_Y; + return scaleFactor * (neckHeight + neckHeight * ratio); } else { - return avatarScale * DEFAULT_AVATAR_EYE_HEIGHT; + return DEFAULT_AVATAR_EYE_HEIGHT; } } else { - return avatarScale * DEFAULT_AVATAR_EYE_HEIGHT; + return DEFAULT_AVATAR_EYE_HEIGHT; } } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index a723a8e1b0..c75b54fdc4 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -255,12 +255,16 @@ public: bool isFading() const { return _isFading; } void updateFadingStatus(render::ScenePointer scene); - /**jsdoc - * Provides read only access to the current eye height of the avatar. - * @function Avatar.getEyeHeight - * @returns {number} eye height of avatar in meters - */ - Q_INVOKABLE float getEyeHeight() const; + Q_INVOKABLE virtual float getEyeHeight() const override; + + // returns eye height of avatar in meters, ignoring avatar scale. + // if _targetScale is 1 then this will be identical to getEyeHeight. + virtual float getUnscaledEyeHeight() const override; + + // returns true, if an acurate eye height estimage can be obtained by inspecting the avatar model skeleton and geometry, + // 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; } @@ -276,9 +280,17 @@ public slots: glm::vec3 getRightPalmPosition() const; glm::quat getRightPalmRotation() const; + // hooked up to Model::setURLFinished signal void setModelURLFinished(bool success); + // hooked up to Model::rigReady & rigReset signals + void rigReady(); + void rigReset(); + protected: + float getUnscaledEyeHeightFromSkeleton() const; + void buildUnscaledEyeHeightCache(); + void clearUnscaledEyeHeightCache(); virtual const QString& getSessionDisplayNameForTransport() const override { return _empty; } // Save a tiny bit of bandwidth. Mixer won't look at what we send. QString _empty{}; virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter! @@ -349,7 +361,7 @@ protected: RateCounter<> _skeletonModelSimulationRate; RateCounter<> _jointDataSimulationRate; -private: +protected: class AvatarEntityDataHash { public: AvatarEntityDataHash(uint32_t h) : hash(h) {}; @@ -379,6 +391,8 @@ private: float _displayNameTargetAlpha { 1.0f }; float _displayNameAlpha { 1.0f }; + + ThreadSafeValueCache _unscaledEyeHeightCache { DEFAULT_AVATAR_EYE_HEIGHT }; }; #endif // hifi_Avatar_h diff --git a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp index e870e2de12..4382216575 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp @@ -13,4 +13,6 @@ OtherAvatar::OtherAvatar(QThread* thread) : Avatar(thread) { _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/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index cb43aeee5d..f2053e29d7 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -117,6 +117,55 @@ void AvatarData::setTargetScale(float targetScale) { } } +float AvatarData::getDomainLimitedScale() const { + if (canMeasureEyeHeight()) { + const float minScale = getDomainMinScale(); + const float maxScale = getDomainMaxScale(); + return glm::clamp(_targetScale, minScale, maxScale); + } else { + // We can't make a good estimate. + return _targetScale; + } +} + +void AvatarData::setDomainMinimumHeight(float domainMinimumHeight) { + _domainMinimumHeight = glm::clamp(domainMinimumHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); +} + +void AvatarData::setDomainMaximumHeight(float domainMaximumHeight) { + _domainMaximumHeight = glm::clamp(domainMaximumHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); +} + +float AvatarData::getDomainMinScale() const { + float unscaledHeight = getUnscaledHeight(); + const float EPSILON = 1.0e-4f; + if (unscaledHeight <= EPSILON) { + unscaledHeight = DEFAULT_AVATAR_HEIGHT; + } + return _domainMinimumHeight / unscaledHeight; +} + +float AvatarData::getDomainMaxScale() const { + float unscaledHeight = getUnscaledHeight(); + const float EPSILON = 1.0e-4f; + if (unscaledHeight <= EPSILON) { + unscaledHeight = DEFAULT_AVATAR_HEIGHT; + } + return _domainMaximumHeight / unscaledHeight; +} + +float AvatarData::getUnscaledHeight() const { + const float eyeHeight = getUnscaledEyeHeight(); + const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT; + return eyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; +} + +float AvatarData::getHeight() const { + const float eyeHeight = getEyeHeight(); + const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT; + return eyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; +} + glm::vec3 AvatarData::getHandPosition() const { return getWorldOrientation() * _handPosition + getWorldPosition(); } @@ -2387,63 +2436,10 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra const float AvatarData::OUT_OF_VIEW_PENALTY = -10.0f; -float AvatarData::_avatarSortCoefficientSize { 0.5f }; +float AvatarData::_avatarSortCoefficientSize { 1.0f }; float AvatarData::_avatarSortCoefficientCenter { 0.25 }; float AvatarData::_avatarSortCoefficientAge { 1.0f }; -void AvatarData::sortAvatars( - QList avatarList, - const ViewFrustum& cameraView, - std::priority_queue& sortedAvatarsOut, - std::function getLastUpdated, - std::function getBoundingRadius, - std::function shouldIgnore) { - - PROFILE_RANGE(simulation, "sort"); - uint64_t now = usecTimestampNow(); - - glm::vec3 frustumCenter = cameraView.getPosition(); - const glm::vec3& forward = cameraView.getDirection(); - for (int32_t i = 0; i < avatarList.size(); ++i) { - const auto& avatar = avatarList.at(i); - - if (shouldIgnore(avatar)) { - continue; - } - - // priority = weighted linear combination of: - // (a) apparentSize - // (b) proximity to center of view - // (c) time since last update - glm::vec3 avatarPosition = avatar->getWorldPosition(); - glm::vec3 offset = avatarPosition - frustumCenter; - float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero - - // FIXME - AvatarData has something equivolent to this - float radius = getBoundingRadius(avatar); - - float apparentSize = 2.0f * radius / distance; - float cosineAngle = glm::dot(offset, forward) / distance; - float age = (float)(now - getLastUpdated(avatar)) / (float)(USECS_PER_SECOND); - - // NOTE: we are adding values of different units to get a single measure of "priority". - // Thus we multiply each component by a conversion "weight" that scales its units relative to the others. - // These weights are pure magic tuning and should be hard coded in the relation below, - // but are currently exposed for anyone who would like to explore fine tuning: - float priority = _avatarSortCoefficientSize * apparentSize - + _avatarSortCoefficientCenter * cosineAngle - + _avatarSortCoefficientAge * age; - - // decrement priority of avatars outside keyhole - if (distance > cameraView.getCenterRadius()) { - if (!cameraView.sphereIntersectsFrustum(avatarPosition, radius)) { - priority += OUT_OF_VIEW_PENALTY; - } - } - sortedAvatarsOut.push(AvatarPriority(avatar, priority)); - } -} - QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value) { QScriptValue obj = engine->newObject(); for (auto entityID : value.keys()) { diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 2f3154ad08..d7dd2837cb 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -35,6 +35,7 @@ #include #include +#include #include #include #include @@ -257,9 +258,6 @@ namespace AvatarDataPacket { size_t maxJointDataSize(size_t numJoints); } -static const float MAX_AVATAR_SCALE = 1000.0f; -static const float MIN_AVATAR_SCALE = .005f; - const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000; @@ -484,12 +482,52 @@ public: // Scale virtual void setTargetScale(float targetScale); - float getDomainLimitedScale() const { return glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); } + float getDomainLimitedScale() const; - void setDomainMinimumScale(float domainMinimumScale) - { _domainMinimumScale = glm::clamp(domainMinimumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _scaleChanged = usecTimestampNow(); } - void setDomainMaximumScale(float domainMaximumScale) - { _domainMaximumScale = glm::clamp(domainMaximumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _scaleChanged = usecTimestampNow(); } + /**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. + * @function AvatarData.getDomainMinScale + * @returns {number} minimum scale allowed for this avatar in the current domain. + */ + Q_INVOKABLE float getDomainMinScale() const; + + /**jsdoc + * returns the maximum scale allowed for this avatar in the current domain. + * This value can change as the user changes avatars or when changing domains. + * @function AvatarData.getDomainMaxScale + * @returns {number} maximum scale allowed for this avatar in the current domain. + */ + Q_INVOKABLE float getDomainMaxScale() const; + + // returns eye height of avatar in meters, ignoreing avatar scale. + // if _targetScale is 1 then this will be identical to getEyeHeight; + virtual float getUnscaledEyeHeight() const { return DEFAULT_AVATAR_EYE_HEIGHT; } + + // returns true, if an acurate eye height estimage can be obtained by inspecting the avatar model skeleton and geometry, + // not all subclasses of AvatarData have access to this data. + virtual bool canMeasureEyeHeight() const { return false; } + + /**jsdoc + * Provides read only access to the current eye height of the avatar. + * This height is only an estimate and might be incorrect for avatars that are missing standard joints. + * @function AvatarData.getEyeHeight + * @returns {number} eye height of avatar in meters + */ + Q_INVOKABLE virtual float getEyeHeight() const { return _targetScale * getUnscaledEyeHeight(); } + + /**jsdoc + * Provides read only access to the current height of the avatar. + * This height is only an estimate and might be incorrect for avatars that are missing standard joints. + * @function AvatarData.getHeight + * @returns {number} height of avatar in meters + */ + Q_INVOKABLE virtual float getHeight() const; + + float getUnscaledHeight() const; + + void setDomainMinimumHeight(float domainMinimumHeight); + void setDomainMaximumHeight(float domainMaximumHeight); // Hand State Q_INVOKABLE void setHandState(char s) { _handState = s; } @@ -629,14 +667,6 @@ public: static const float OUT_OF_VIEW_PENALTY; - static void sortAvatars( - QList avatarList, - const ViewFrustum& cameraView, - std::priority_queue& sortedAvatarsOut, - std::function getLastUpdated, - std::function getBoundingRadius, - std::function shouldIgnore); - // TODO: remove this HACK once we settle on optimal sort coefficients // These coefficients exposed for fine tuning the sort priority for transfering new _jointData to the render pipeline. static float _avatarSortCoefficientSize; @@ -706,8 +736,8 @@ protected: // Body scale float _targetScale; - float _domainMinimumScale { MIN_AVATAR_SCALE }; - float _domainMaximumScale { MAX_AVATAR_SCALE }; + float _domainMinimumHeight { MIN_AVATAR_HEIGHT }; + float _domainMaximumHeight { MAX_AVATAR_HEIGHT }; // Hand state (are we grabbing something or not) char _handState; diff --git a/libraries/baking/src/FBXBaker.cpp b/libraries/baking/src/FBXBaker.cpp index ee24d6a4c2..a765e66bbc 100644 --- a/libraries/baking/src/FBXBaker.cpp +++ b/libraries/baking/src/FBXBaker.cpp @@ -607,7 +607,7 @@ void FBXBaker::rewriteAndBakeSceneTextures() { return; } - if (!TextureBaker::getSupportedFormats().contains(textureFileInfo.suffix())) { + if (!image::getSupportedFormats().contains(textureFileInfo.suffix())) { // this is a texture format we don't bake, skip it handleWarning(fbxTextureFileName + " is not a bakeable texture format"); continue; diff --git a/libraries/baking/src/TextureBaker.cpp b/libraries/baking/src/TextureBaker.cpp index 1a320efabc..b6edd07965 100644 --- a/libraries/baking/src/TextureBaker.cpp +++ b/libraries/baking/src/TextureBaker.cpp @@ -61,14 +61,6 @@ void TextureBaker::abort() { _abortProcessing.store(true); } -const QStringList TextureBaker::getSupportedFormats() { - auto formats = QImageReader::supportedImageFormats(); - QStringList stringFormats; - std::transform(formats.begin(), formats.end(), std::back_inserter(stringFormats), - [](QByteArray& format) -> QString { return format; }); - return stringFormats; -} - void TextureBaker::loadTexture() { // check if the texture is local or first needs to be downloaded if (_textureURL.isLocalFile()) { @@ -121,8 +113,15 @@ void TextureBaker::handleTextureNetworkReply() { } void TextureBaker::processTexture() { - auto processedTexture = image::processImage(_originalTexture, _textureURL.toString().toStdString(), + // the baked textures need to have the source hash added for cache checks in Interface + // so we add that to the processed texture before handling it off to be serialized + auto hashData = QCryptographicHash::hash(_originalTexture, QCryptographicHash::Md5); + std::string hash = hashData.toHex().toStdString(); + + // 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()) { return; @@ -133,11 +132,6 @@ void TextureBaker::processTexture() { return; } - // the baked textures need to have the source hash added for cache checks in Interface - // so we add that to the processed texture before handling it off to be serialized - auto hashData = QCryptographicHash::hash(_originalTexture, QCryptographicHash::Md5); - std::string hash = hashData.toHex().toStdString(); - processedTexture->setSourceHash(hash); auto memKTX = gpu::Texture::serialize(*processedTexture); diff --git a/libraries/baking/src/TextureBaker.h b/libraries/baking/src/TextureBaker.h index b2e86b2b5b..90ecfe52f7 100644 --- a/libraries/baking/src/TextureBaker.h +++ b/libraries/baking/src/TextureBaker.h @@ -31,8 +31,6 @@ public: const QDir& outputDirectory, const QString& bakedFilename = QString(), const QByteArray& textureContent = QByteArray()); - static const QStringList getSupportedFormats(); - const QByteArray& getOriginalTexture() const { return _originalTexture; } QUrl getTextureURL() const { return _textureURL; } diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index 7b639e8308..f3f81c0b2e 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -437,9 +437,11 @@ glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const } else { d = glm::normalize(overlaySurfacePoint); } - reticlePosition = headPosition + (d * getReticleDepth()); + // Our sensor to world matrix always has uniform scale + float sensorSpaceReticleDepth = getReticleDepth() / extractScale(_sensorToWorldMatrix).x; + reticlePosition = headPosition + (d * sensorSpaceReticleDepth); quat reticleOrientation = cancelOutRoll(glm::quat_cast(_currentDisplayPlugin->getHeadPose())); - vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize * getReticleDepth()); + vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize * sensorSpaceReticleDepth); return glm::inverse(eyePose) * createMatFromScaleQuatAndPos(reticleScale, reticleOrientation, reticlePosition); } else { static const float CURSOR_PIXEL_SIZE = 32.0f; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 0993daaa8b..cb9d06dce1 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -685,11 +685,6 @@ void OpenGLDisplayPlugin::present() { } incrementPresentCount(); - { - PROFILE_RANGE_EX(render, "recycle", 0xff00ff00, frameId) - _gpuContext->recycle(); - } - if (_currentFrame) { { withPresentThreadLock([&] { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 06b81ff428..c992eb5dc4 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1149,7 +1149,7 @@ bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin if (model && model->isLoaded()) { if (!entity->_dimensionsInitialized || entity->_needsInitialSimulation) { return true; - } + } // Check to see if we need to update the model bounds if (entity->needsUpdateModelBounds()) { diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index 5359ebd31b..550c8f17c4 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -199,7 +199,7 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { // define the uncaughtException function QScriptEngine& engineRef = *engine; - filterData.uncaughtExceptions = [this, &engineRef, urlString]() { return hadUncaughtExceptions(engineRef, urlString); }; + filterData.uncaughtExceptions = [&engineRef, urlString]() { return hadUncaughtExceptions(engineRef, urlString); }; // now get the filter function auto global = engine->globalObject(); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index c8e7ce6c11..ddc5e9795f 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1605,6 +1605,52 @@ void EntityItem::setParentID(const QUuid& value) { if (tree && !oldParentID.isNull()) { tree->removeFromChildrenOfAvatars(getThisPointer()); } + + uint32_t oldParentNoBootstrapping = 0; + uint32_t newParentNoBootstrapping = 0; + if (!value.isNull() && tree) { + EntityItemPointer entity = tree->findEntityByEntityItemID(value); + if (entity) { + newParentNoBootstrapping = entity->getDirtyFlags() & Simulation::NO_BOOTSTRAPPING; + } + } + + if (!oldParentID.isNull() && tree) { + EntityItemPointer entity = tree->findEntityByEntityItemID(oldParentID); + if (entity) { + oldParentNoBootstrapping = entity->getDirtyFlags() & Simulation::NO_BOOTSTRAPPING; + } + } + + if (!value.isNull() && (value == Physics::getSessionUUID() || value == AVATAR_SELF_ID)) { + newParentNoBootstrapping |= Simulation::NO_BOOTSTRAPPING; + } + + if (!oldParentID.isNull() && (oldParentID == Physics::getSessionUUID() || oldParentID == AVATAR_SELF_ID)) { + oldParentNoBootstrapping |= Simulation::NO_BOOTSTRAPPING; + } + + if ((bool)(oldParentNoBootstrapping ^ newParentNoBootstrapping)) { + if ((bool)(newParentNoBootstrapping & Simulation::NO_BOOTSTRAPPING)) { + markDirtyFlags(Simulation::NO_BOOTSTRAPPING); + forEachDescendant([&](SpatiallyNestablePointer object) { + if (object->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast(object); + entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP | Simulation::NO_BOOTSTRAPPING); + } + }); + } else { + clearDirtyFlags(Simulation::NO_BOOTSTRAPPING); + forEachDescendant([&](SpatiallyNestablePointer object) { + if (object->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast(object); + entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); + entity->clearDirtyFlags(Simulation::NO_BOOTSTRAPPING); + } + }); + } + } + SpatiallyNestable::setParentID(value); // children are forced to be kinematic // may need to not collide with own avatar @@ -1834,39 +1880,8 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask } } - if (userMask & USER_COLLISION_GROUP_MY_AVATAR) { - bool iAmHoldingThis = false; - // if this entity is a descendant of MyAvatar, don't collide with MyAvatar. This avoids the - // "bootstrapping" problem where you can shoot yourself across the room by grabbing something - // and holding it against your own avatar. - if (isChildOfMyAvatar()) { - iAmHoldingThis = true; - } - // also, don't bootstrap our own avatar with a hold action - QList holdActions = getActionsOfType(DYNAMIC_TYPE_HOLD); - QList::const_iterator i = holdActions.begin(); - while (i != holdActions.end()) { - EntityDynamicPointer action = *i; - if (action->isMine()) { - iAmHoldingThis = true; - break; - } - i++; - } - QList farGrabActions = getActionsOfType(DYNAMIC_TYPE_FAR_GRAB); - i = farGrabActions.begin(); - while (i != farGrabActions.end()) { - EntityDynamicPointer action = *i; - if (action->isMine()) { - iAmHoldingThis = true; - break; - } - i++; - } - - if (iAmHoldingThis) { - userMask &= ~USER_COLLISION_GROUP_MY_AVATAR; - } + if ((bool)(_dirtyFlags & Simulation::NO_BOOTSTRAPPING)) { + userMask &= ~USER_COLLISION_GROUP_MY_AVATAR; } mask = Physics::getDefaultCollisionMask(group) & (int16_t)(userMask); } @@ -1961,7 +1976,20 @@ bool EntityItem::addActionInternal(EntitySimulationPointer simulation, EntityDyn if (success) { _allActionsDataCache = newDataCache; _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; - _dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar + + auto actionType = action->getType(); + if (actionType == DYNAMIC_TYPE_HOLD || actionType == DYNAMIC_TYPE_FAR_GRAB) { + if (!(bool)(_dirtyFlags & Simulation::NO_BOOTSTRAPPING)) { + _dirtyFlags |= Simulation::NO_BOOTSTRAPPING; + _dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar + forEachDescendant([&](SpatiallyNestablePointer child) { + if (child->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast(child); + entity->markDirtyFlags(Simulation::NO_BOOTSTRAPPING | Simulation::DIRTY_COLLISION_GROUP); + } + }); + } + } } else { qCDebug(entities) << "EntityItem::addActionInternal -- serializeActions failed"; } @@ -2002,6 +2030,29 @@ bool EntityItem::removeAction(EntitySimulationPointer simulation, const QUuid& a return success; } +bool EntityItem::stillHasGrabActions() const { + QList holdActions = getActionsOfType(DYNAMIC_TYPE_HOLD); + QList::const_iterator i = holdActions.begin(); + while (i != holdActions.end()) { + EntityDynamicPointer action = *i; + if (action->isMine()) { + return true; + } + i++; + } + QList farGrabActions = getActionsOfType(DYNAMIC_TYPE_FAR_GRAB); + i = farGrabActions.begin(); + while (i != farGrabActions.end()) { + EntityDynamicPointer action = *i; + if (action->isMine()) { + return true; + } + i++; + } + + return false; +} + bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPointer simulation) { _previouslyDeletedActions.insert(actionID, usecTimestampNow()); if (_objectActions.contains(actionID)) { @@ -2015,7 +2066,6 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi action->setOwnerEntity(nullptr); action->setIsMine(false); - _objectActions.remove(actionID); if (simulation) { action->removeFromSimulation(simulation); @@ -2024,7 +2074,23 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi bool success = true; serializeActions(success, _allActionsDataCache); _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; - _dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar + auto removedActionType = action->getType(); + if ((removedActionType == DYNAMIC_TYPE_HOLD || removedActionType == DYNAMIC_TYPE_FAR_GRAB) && !stillHasGrabActions()) { + _dirtyFlags &= ~Simulation::NO_BOOTSTRAPPING; + _dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar + forEachDescendant([&](SpatiallyNestablePointer child) { + if (child->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast(child); + entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); + entity->clearDirtyFlags(Simulation::NO_BOOTSTRAPPING); + } + }); + } else { + // NO-OP: we assume NO_BOOTSTRAPPING bits and collision group are correct + // because they should have been set correctly when the action was added + // and/or when children were linked + } + _objectActions.remove(actionID); setDynamicDataNeedsTransmit(true); return success; } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 882b8e6812..4c7f37bd6a 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -470,6 +470,7 @@ protected: void setSimulated(bool simulated) { _simulated = simulated; } const QByteArray getDynamicDataInternal() const; + bool stillHasGrabActions() const; void setDynamicDataInternal(QByteArray dynamicData); virtual void dimensionsChanged() override; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 5ab4bdee01..9f7ba1cc80 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -2486,7 +2486,7 @@ QByteArray EntityItemProperties::getStaticCertificateJSON() const { ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL); ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL); ADD_INT_PROPERTY(editionNumber, EditionNumber); - ADD_INT_PROPERTY(instanceNumber, EntityInstanceNumber); + ADD_INT_PROPERTY(entityInstanceNumber, EntityInstanceNumber); ADD_STRING_PROPERTY(itemArtist, ItemArtist); ADD_STRING_PROPERTY(itemCategories, ItemCategories); ADD_STRING_PROPERTY(itemDescription, ItemDescription); diff --git a/libraries/entities/src/SimulationFlags.h b/libraries/entities/src/SimulationFlags.h index e2b2224b4a..aaa92000e7 100644 --- a/libraries/entities/src/SimulationFlags.h +++ b/libraries/entities/src/SimulationFlags.h @@ -27,6 +27,7 @@ namespace Simulation { const uint32_t DIRTY_PHYSICS_ACTIVATION = 0x0800; // should activate object in physics engine const uint32_t DIRTY_SIMULATOR_ID = 0x1000; // the simulatorID has changed const uint32_t DIRTY_SIMULATION_OWNERSHIP_PRIORITY = 0x2000; // our own bid priority has changed + const uint32_t NO_BOOTSTRAPPING = 0x4000; const uint32_t DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION; const uint32_t DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY; diff --git a/libraries/fbx/CMakeLists.txt b/libraries/fbx/CMakeLists.txt index 7cead5aa4f..683ddb52f7 100644 --- a/libraries/fbx/CMakeLists.txt +++ b/libraries/fbx/CMakeLists.txt @@ -4,7 +4,4 @@ setup_hifi_library() link_hifi_libraries(shared model networking image) include_hifi_library_headers(gpu image) -add_dependency_external_projects(draco) -find_package(Draco REQUIRED) -target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${DRACO_INCLUDE_DIRS}) -target_link_libraries(${TARGET_NAME} ${DRACO_LIBRARY} ${DRACO_ENCODER_LIBRARY}) +target_draco() diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index f138244fa2..a36a6e0fb5 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -776,7 +776,7 @@ void GLBackend::recycle() const { GLVariableAllocationSupport::manageMemory(); GLVariableAllocationSupport::_frameTexturesCreated = 0; - + Texture::KtxStorage::releaseOpenKtxFiles(); } void GLBackend::setCameraCorrection(const Mat4& correction) { diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gles/src/gpu/gl/GLBackend.cpp index 1d66618703..cb00d00b3e 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLBackend.cpp @@ -37,7 +37,7 @@ static const char* GL_BACKEND_PROPERTY_NAME = "com.highfidelity.gl.backend"; BackendPointer GLBackend::createBackend() { // FIXME provide a mechanism to override the backend for testing // Where the gpuContext is initialized and where the TRUE Backend is created and assigned - auto version = QOpenGLContextWrapper::currentContextVersion(); + //auto version = QOpenGLContextWrapper::currentContextVersion(); std::shared_ptr result; qDebug() << "Using OpenGL ES backend"; diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackendQuery.cpp b/libraries/gpu-gles/src/gpu/gl/GLBackendQuery.cpp index 530e01d8ff..e5126bb3df 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLBackendQuery.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLBackendQuery.cpp @@ -17,10 +17,10 @@ using namespace gpu::gl; // Eventually, we want to test with TIME_ELAPSED instead of TIMESTAMP #ifdef Q_OS_MAC -const uint32_t MAX_RANGE_QUERY_DEPTH = 1; +//const uint32_t MAX_RANGE_QUERY_DEPTH = 1; static bool timeElapsed = true; #else -const uint32_t MAX_RANGE_QUERY_DEPTH = 10000; +//const uint32_t MAX_RANGE_QUERY_DEPTH = 10000; static bool timeElapsed = false; #endif diff --git a/libraries/gpu-gles/src/gpu/gl/GLBackendState.cpp b/libraries/gpu-gles/src/gpu/gl/GLBackendState.cpp index 27b8d23bf3..0a7db78b11 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLBackendState.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLBackendState.cpp @@ -56,11 +56,11 @@ void GLBackend::syncPipelineStateCache() { void GLBackend::do_setStateFillMode(int32 mode) { if (_pipeline._stateCache.fillMode != mode) { - static GLenum GL_FILL_MODES[] = { /*GL_POINT, GL_LINE, GL_FILL*/ }; + //static GLenum GL_FILL_MODES[] = { /*GL_POINT, GL_LINE, GL_FILL*/ }; + //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL_MODES[mode]); qDebug() << "TODO: GLBackendState.cpp:do_setStateFillMode GL_POINT"; qDebug() << "TODO: GLBackendState.cpp:do_setStateFillMode GL_LINE"; qDebug() << "TODO: GLBackendState.cpp:do_setStateFillMode GL_FILL"; - //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL_MODES[mode]); qDebug() << "TODO: GLBackendState.cpp:do_setStateFillMode glPolygonMode"; (void)CHECK_GL_ERROR(); diff --git a/libraries/gpu-gles/src/gpu/gl/GLTexelFormat.cpp b/libraries/gpu-gles/src/gpu/gl/GLTexelFormat.cpp index 6eec4b5292..9808d389f1 100644 --- a/libraries/gpu-gles/src/gpu/gl/GLTexelFormat.cpp +++ b/libraries/gpu-gles/src/gpu/gl/GLTexelFormat.cpp @@ -178,6 +178,10 @@ GLenum GLTexelFormat::evalGLTexelFormatInternal(const gpu::Element& dstFormat) { case gpu::NINT8: result = GL_RGBA8_SNORM; break; + case gpu::COMPRESSED: + case gpu::NUINT2: + case gpu::NINT16: + case gpu::NUINT16: case gpu::NUINT32: case gpu::NINT32: case gpu::NUM_TYPES: // quiet compiler @@ -446,6 +450,8 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_R8_SNORM; break; } + case gpu::COMPRESSED: + case gpu::NUINT2: case gpu::NUM_TYPES: { // quiet compiler Q_UNREACHABLE(); } @@ -489,6 +495,8 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E texel.internalFormat = GL_DEPTH_COMPONENT24; break; } + case gpu::COMPRESSED: + case gpu::NUINT2: case gpu::NUM_TYPES: { // quiet compiler Q_UNREACHABLE(); } @@ -614,6 +622,8 @@ GLTexelFormat GLTexelFormat::evalGLTexelFormat(const Element& dstFormat, const E break; case gpu::NUINT32: case gpu::NINT32: + case gpu::COMPRESSED: + case gpu::NUINT2: case gpu::NUM_TYPES: // quiet compiler Q_UNREACHABLE(); } diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackend.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackend.cpp index 8c843c1ce3..b277889771 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackend.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackend.cpp @@ -166,8 +166,7 @@ void GLESBackend::do_drawIndexedInstanced(const Batch& batch, size_t paramOffset void GLESBackend::do_multiDrawIndirect(const Batch& batch, size_t paramOffset) { #if (GPU_INPUT_PROFILE == GPU_CORE_43) uint commandCount = batch._params[paramOffset + 0]._uint; - GLenum mode = gl::PRIMITIVE_TO_GL[(Primitive)batch._params[paramOffset + 1]._uint]; - + //GLenum mode = gl::PRIMITIVE_TO_GL[(Primitive)batch._params[paramOffset + 1]._uint]; //glMultiDrawArraysIndirect(mode, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); qDebug() << "TODO: GLESBackend.cpp:do_multiDrawIndirect do_multiDrawIndirect"; _stats._DSNumDrawcalls += commandCount; @@ -183,9 +182,8 @@ void GLESBackend::do_multiDrawIndirect(const Batch& batch, size_t paramOffset) { void GLESBackend::do_multiDrawIndexedIndirect(const Batch& batch, size_t paramOffset) { //#if (GPU_INPUT_PROFILE == GPU_CORE_43) uint commandCount = batch._params[paramOffset + 0]._uint; - GLenum mode = gl::PRIMITIVE_TO_GL[(Primitive)batch._params[paramOffset + 1]._uint]; - GLenum indexType = gl::ELEMENT_TYPE_TO_GL[_input._indexBufferType]; - + //GLenum mode = gl::PRIMITIVE_TO_GL[(Primitive)batch._params[paramOffset + 1]._uint]; + //GLenum indexType = gl::ELEMENT_TYPE_TO_GL[_input._indexBufferType]; //glMultiDrawElementsIndirect(mode, indexType, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); qDebug() << "TODO: GLESBackend.cpp:do_multiDrawIndexedIndirect glMultiDrawElementsIndirect"; _stats._DSNumDrawcalls += commandCount; diff --git a/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp b/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp index 8bf9267fde..0b7c525fbb 100644 --- a/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp +++ b/libraries/gpu-gles/src/gpu/gles/GLESBackendOutput.cpp @@ -99,7 +99,8 @@ public: if (!_colorBuffers.empty()) { glDrawBuffers((GLsizei)_colorBuffers.size(), _colorBuffers.data()); } else { - glDrawBuffers(1, {GL_NONE}); + static const std::vector NO_BUFFERS{ GL_NONE }; + glDrawBuffers((GLsizei)NO_BUFFERS.size(), NO_BUFFERS.data()); } // Now check for completness diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 1877b494cf..06208179e0 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -321,13 +321,18 @@ public: void reset() override { } + // Don't keep files open forever. We close them at the beginning of each frame (GLBackend::recycle) + static void releaseOpenKtxFiles(); + protected: std::shared_ptr maybeOpenFile() const; - mutable std::mutex _cacheFileCreateMutex; - mutable std::mutex _cacheFileWriteMutex; + mutable std::shared_ptr _cacheFileMutex { std::make_shared() }; mutable std::weak_ptr _cacheFile; + static std::vector, std::shared_ptr>> _cachedKtxFiles; + static std::mutex _cachedKtxFilesMutex; + std::string _filename; cache::FilePointer _cacheEntry; std::atomic _minMipLevelAvailable; diff --git a/libraries/gpu/src/gpu/Texture_ktx.cpp b/libraries/gpu/src/gpu/Texture_ktx.cpp index 08fc4ec101..883d9abf15 100644 --- a/libraries/gpu/src/gpu/Texture_ktx.cpp +++ b/libraries/gpu/src/gpu/Texture_ktx.cpp @@ -23,6 +23,9 @@ using namespace gpu; using PixelsPointer = Texture::PixelsPointer; using KtxStorage = Texture::KtxStorage; +std::vector, std::shared_ptr>> KtxStorage::_cachedKtxFiles; +std::mutex KtxStorage::_cachedKtxFilesMutex; + struct GPUKTXPayload { using Version = uint8; @@ -187,36 +190,44 @@ KtxStorage::KtxStorage(const std::string& filename) : _filename(filename) { } } +// maybeOpenFile should be called with _cacheFileMutex already held to avoid modifying the file from multiple threads std::shared_ptr KtxStorage::maybeOpenFile() const { - // 1. Try to get the shared ptr - // 2. If it doesn't exist, grab the mutex around its creation - // 3. If it was created before we got the mutex, return it - // 4. Otherwise, create it - + // Try to get the shared_ptr std::shared_ptr file = _cacheFile.lock(); if (file) { return file; } + // If the file isn't open, create it and save a weak_ptr to it + file = std::make_shared(_filename.c_str()); + _cacheFile = file; + { - std::lock_guard lock{ _cacheFileCreateMutex }; - - file = _cacheFile.lock(); - if (file) { - return file; - } - - file = std::make_shared(_filename.c_str()); - _cacheFile = file; + // Add the shared_ptr to the global list of open KTX files, to be released at the beginning of the next present thread frame + std::lock_guard lock(_cachedKtxFilesMutex); + _cachedKtxFiles.emplace_back(file, _cacheFileMutex); } return file; } +void KtxStorage::releaseOpenKtxFiles() { + std::vector, std::shared_ptr>> localKtxFiles; + { + std::lock_guard lock(_cachedKtxFilesMutex); + localKtxFiles.swap(_cachedKtxFiles); + } + for (auto& cacheFileAndMutex : localKtxFiles) { + std::lock_guard lock(*(cacheFileAndMutex.second)); + cacheFileAndMutex.first.reset(); + } +} + PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const { auto faceOffset = _ktxDescriptor->getMipFaceTexelsOffset(level, face); auto faceSize = _ktxDescriptor->getMipFaceTexelsSize(level, face); if (faceSize != 0 && faceOffset != 0) { + std::lock_guard lock(*_cacheFileMutex); auto file = maybeOpenFile(); if (file) { auto storageView = file->createView(faceSize, faceOffset); @@ -262,6 +273,7 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor return; } + std::lock_guard lock(*_cacheFileMutex); auto file = maybeOpenFile(); if (!file) { qWarning() << "Failed to open file to assign mip data " << QString::fromStdString(_filename); @@ -279,8 +291,6 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor imageData += ktx::IMAGE_SIZE_WIDTH; { - std::lock_guard lock { _cacheFileWriteMutex }; - if (level != _minMipLevelAvailable - 1) { qWarning() << "Invalid level to be stored"; return; diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 5959714cd8..f78ed1a583 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -11,7 +11,6 @@ #include "Image.h" -#include #include #include @@ -20,6 +19,15 @@ #include #include + +#if defined(Q_OS_ANDROID) +#define CPU_MIPMAPS 0 +#else +#define CPU_MIPMAPS 1 +#include +#endif + + #include #include #include @@ -29,13 +37,6 @@ using namespace gpu; -#if defined(Q_OS_ANDROID) -#define CPU_MIPMAPS 0 -#else -#define CPU_MIPMAPS 1 -#include -#endif - static const glm::uvec2 SPARSE_PAGE_SIZE(128); static const glm::uvec2 MAX_TEXTURE_SIZE(4096); @@ -74,6 +75,14 @@ glm::uvec2 rectifyToSparseSize(const glm::uvec2& size) { namespace image { +const QStringList getSupportedFormats() { + auto formats = QImageReader::supportedImageFormats(); + QStringList stringFormats; + std::transform(formats.begin(), formats.end(), std::back_inserter(stringFormats), + [](QByteArray& format) -> QString { return format; }); + return stringFormats; +} + QImage::Format QIMAGE_HDR_FORMAT = QImage::Format_RGB30; TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, const QVariantMap& options) { @@ -109,64 +118,64 @@ TextureUsage::TextureLoader TextureUsage::getTextureLoaderForType(Type type, con } } -gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createStrict2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(srcImage, srcImageName, true, abortProcessing); + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, true, abortProcessing); } -gpu::TexturePointer TextureUsage::create2DTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::create2DTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createAlbedoTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createEmissiveTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createLightmapTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureColorFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createNormalTextureFromNormalImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureNormalMapFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createNormalTextureFromBumpImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureNormalMapFromImage(srcImage, srcImageName, true, abortProcessing); + return process2DTextureNormalMapFromImage(std::move(srcImage), srcImageName, true, abortProcessing); } -gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createRoughnessTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createRoughnessTextureFromGlossImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(srcImage, srcImageName, true, abortProcessing); + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, true, abortProcessing); } -gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createMetallicTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return process2DTextureGrayscaleFromImage(srcImage, srcImageName, false, abortProcessing); + return process2DTextureGrayscaleFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } -gpu::TexturePointer TextureUsage::createCubeTextureFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createCubeTextureFromImage(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return processCubeTextureColorFromImage(srcImage, srcImageName, true, abortProcessing); + return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, true, abortProcessing); } -gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::createCubeTextureFromImageWithoutIrradiance(QImage&& srcImage, const std::string& srcImageName, const std::atomic& abortProcessing) { - return processCubeTextureColorFromImage(srcImage, srcImageName, false, abortProcessing); + return processCubeTextureColorFromImage(std::move(srcImage), srcImageName, false, abortProcessing); } @@ -237,33 +246,43 @@ uint32 packR11G11B10F(const glm::vec3& color) { return glm::packF2x11_1x10(ucolor); } -gpu::TexturePointer processImage(const QByteArray& content, const std::string& filename, - int maxNumPixels, TextureUsage::Type textureType, - const std::atomic& abortProcessing) { +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); + // 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(content); + buffer.setData(localCopy); QImageReader imageReader(&buffer, filenameExtension.c_str()); - QImage image; if (imageReader.canRead()) { - image = imageReader.read(); + return imageReader.read(); } else { // Extension could be incorrect, try to detect the format from the content QImageReader newImageReader; newImageReader.setDecideFormatFromContent(true); - buffer.setData(content); + buffer.setData(localCopy); newImageReader.setDevice(&buffer); if (newImageReader.canRead()) { qCWarning(imagelogging) << "Image file" << filename.c_str() << "has extension" << filenameExtension.c_str() << "but is actually a" << qPrintable(newImageReader.format()) << "(recovering)"; - image = newImageReader.read(); + return newImageReader.read(); } } + return QImage(); +} + +gpu::TexturePointer processImage(QByteArray&& content, const std::string& filename, + int maxNumPixels, TextureUsage::Type textureType, + const std::atomic& abortProcessing) { + + QImage image = processRawImageData(std::move(content), filename); + int imageWidth = image.width(); int imageHeight = image.height(); @@ -281,22 +300,26 @@ gpu::TexturePointer processImage(const QByteArray& content, const std::string& f int originalHeight = imageHeight; imageWidth = (int)(scaleFactor * (float)imageWidth + 0.5f); imageHeight = (int)(scaleFactor * (float)imageHeight + 0.5f); - QImage newImage = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); - image.swap(newImage); + image = image.scaled(QSize(imageWidth, imageHeight), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); qCDebug(imagelogging).nospace() << "Downscaled " << filename.c_str() << " (" << QSize(originalWidth, originalHeight) << " to " << QSize(imageWidth, imageHeight) << ")"; } auto loader = TextureUsage::getTextureLoaderForType(textureType); - auto texture = loader(image, filename, abortProcessing); + auto texture = loader(std::move(image), filename, abortProcessing); return texture; } -QImage processSourceImage(const QImage& srcImage, bool cubemap) { +QImage processSourceImage(QImage&& srcImage, bool cubemap) { PROFILE_RANGE(resource_parse, "processSourceImage"); - const glm::uvec2 srcImageSize = toGlm(srcImage.size()); + + // 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 + QImage localCopy = std::move(srcImage); + + const glm::uvec2 srcImageSize = toGlm(localCopy.size()); glm::uvec2 targetSize = srcImageSize; while (glm::any(glm::greaterThan(targetSize, MAX_TEXTURE_SIZE))) { @@ -318,10 +341,10 @@ QImage processSourceImage(const QImage& srcImage, bool cubemap) { if (targetSize != srcImageSize) { PROFILE_RANGE(resource_parse, "processSourceImage Rectify"); qCDebug(imagelogging) << "Resizing texture from " << srcImageSize.x << "x" << srcImageSize.y << " to " << targetSize.x << "x" << targetSize.y; - return srcImage.scaled(fromGlm(targetSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + return localCopy.scaled(fromGlm(targetSize), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } - return srcImage; + return localCopy; } #if defined(NVTT_API) @@ -410,7 +433,6 @@ struct MyErrorHandler : public nvtt::ErrorHandler { qCWarning(imagelogging) << "Texture compression error:" << nvtt::errorString(e); } }; -#endif class SequentialTaskDispatcher : public nvtt::TaskDispatcher { public: @@ -429,10 +451,14 @@ public: } }; -void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atomic& abortProcessing, int face) { - assert(image.format() == QIMAGE_HDR_FORMAT); +void generateHDRMips(gpu::Texture* texture, QImage&& image, const std::atomic& abortProcessing, int face) { + // 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 + QImage localCopy = std::move(image); - const int width = image.width(), height = image.height(); + assert(localCopy.format() == QIMAGE_HDR_FORMAT); + + const int width = localCopy.width(), height = localCopy.height(); std::vector data; std::vector::iterator dataIt; auto mipFormat = texture->getStoredMipFormat(); @@ -471,10 +497,10 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom return; } - data.resize(width*height); + data.resize(width * height); dataIt = data.begin(); for (auto lineNb = 0; lineNb < height; lineNb++) { - const uint32* srcPixelIt = reinterpret_cast( image.constScanLine(lineNb) ); + const uint32* srcPixelIt = reinterpret_cast(localCopy.constScanLine(lineNb)); const uint32* srcPixelEnd = srcPixelIt + width; while (srcPixelIt < srcPixelEnd) { @@ -485,6 +511,9 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom } assert(dataIt == data.end()); + // We're done with the localCopy, free up the memory to avoid bloating the heap + localCopy = QImage(); // QImage doesn't have a clear function, so override it with an empty one. + nvtt::OutputOptions outputOptions; outputOptions.setOutputHeader(false); std::unique_ptr outputHandler; @@ -497,7 +526,7 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom // Don't use NVTT (at least version 2.1) as it outputs wrong RGB9E5 and R11G11B10F values from floats outputHandler.reset(new PackedFloatOutputHandler(texture, face, mipFormat)); } else { - outputHandler.reset( new OutputHandler(texture, face) ); + outputHandler.reset(new OutputHandler(texture, face)); } outputOptions.setOutputHandler(outputHandler.get()); @@ -518,13 +547,17 @@ void generateHDRMips(gpu::Texture* texture, const QImage& image, const std::atom } } -void generateLDRMips(gpu::Texture* texture, QImage& image, const std::atomic& abortProcessing, int face) { - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); +void generateLDRMips(gpu::Texture* texture, QImage&& image, const std::atomic& abortProcessing, int face) { + // 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 + QImage localCopy = std::move(image); + + if (localCopy.format() != QImage::Format_ARGB32) { + localCopy = localCopy.convertToFormat(QImage::Format_ARGB32); } - const int width = image.width(), height = image.height(); - const void* data = static_cast(image.constBits()); + const int width = localCopy.width(), height = localCopy.height(); + const void* data = static_cast(localCopy.constBits()); nvtt::TextureType textureType = nvtt::TextureType_2D; nvtt::InputFormat inputFormat = nvtt::InputFormat_BGRA_8UB; @@ -537,7 +570,11 @@ void generateLDRMips(gpu::Texture* texture, QImage& image, const std::atomic& abortProcessing = false, int face = -1) { +#endif + + + +void generateMips(gpu::Texture* texture, QImage&& image, const std::atomic& abortProcessing = false, int face = -1) { #if CPU_MIPMAPS PROFILE_RANGE(resource_parse, "generateMips"); if (image.format() == QIMAGE_HDR_FORMAT) { - generateHDRMips(texture, image, abortProcessing, face); + generateHDRMips(texture, std::move(image), abortProcessing, face); } else { - generateLDRMips(texture, image, abortProcessing, face); + generateLDRMips(texture, std::move(image), abortProcessing, face); } #else texture->setAutoGenerateMips(true); @@ -678,10 +719,11 @@ void processTextureAlpha(const QImage& srcImage, bool& validAlpha, bool& alphaAs validAlpha = (numOpaques != NUM_PIXELS); } -gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool isStrict, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureColorFromImage"); - QImage image = processSourceImage(srcImage, false); + QImage image = processSourceImage(std::move(srcImage), false); + bool validAlpha = image.hasAlphaChannel(); bool alphaAsMask = false; @@ -727,7 +769,7 @@ gpu::TexturePointer TextureUsage::process2DTextureColorFromImage(const QImage& s } theTexture->setUsage(usage.build()); theTexture->setStoredMipFormat(formatMip); - generateMips(theTexture.get(), image, abortProcessing); + generateMips(theTexture.get(), std::move(image), abortProcessing); } return theTexture; @@ -745,16 +787,20 @@ double mapComponent(double sobelValue) { return (sobelValue + 1.0) * factor; } -QImage processBumpMap(QImage& image) { - if (image.format() != QImage::Format_Grayscale8) { - image = image.convertToFormat(QImage::Format_Grayscale8); +QImage processBumpMap(QImage&& image) { + // 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 + QImage localCopy = std::move(image); + + if (localCopy.format() != QImage::Format_Grayscale8) { + localCopy = localCopy.convertToFormat(QImage::Format_Grayscale8); } // PR 5540 by AlessandroSigna integrated here as a specialized TextureLoader for bumpmaps // The conversion is done using the Sobel Filter to calculate the derivatives from the grayscale image const double pStrength = 2.0; - int width = image.width(); - int height = image.height(); + int width = localCopy.width(); + int height = localCopy.height(); QImage result(width, height, QImage::Format_ARGB32); @@ -767,14 +813,14 @@ QImage processBumpMap(QImage& image) { const int jPrevClamped = clampPixelCoordinate(j - 1, height - 1); // surrounding pixels - const QRgb topLeft = image.pixel(iPrevClamped, jPrevClamped); - const QRgb top = image.pixel(iPrevClamped, j); - const QRgb topRight = image.pixel(iPrevClamped, jNextClamped); - const QRgb right = image.pixel(i, jNextClamped); - const QRgb bottomRight = image.pixel(iNextClamped, jNextClamped); - const QRgb bottom = image.pixel(iNextClamped, j); - const QRgb bottomLeft = image.pixel(iNextClamped, jPrevClamped); - const QRgb left = image.pixel(i, jPrevClamped); + const QRgb topLeft = localCopy.pixel(iPrevClamped, jPrevClamped); + const QRgb top = localCopy.pixel(iPrevClamped, j); + const QRgb topRight = localCopy.pixel(iPrevClamped, jNextClamped); + const QRgb right = localCopy.pixel(i, jNextClamped); + const QRgb bottomRight = localCopy.pixel(iNextClamped, jNextClamped); + const QRgb bottom = localCopy.pixel(iNextClamped, j); + const QRgb bottomLeft = localCopy.pixel(iNextClamped, jPrevClamped); + const QRgb left = localCopy.pixel(i, jPrevClamped); // take their gray intensities // since it's a grayscale image, the value of each component RGB is the same @@ -803,13 +849,13 @@ QImage processBumpMap(QImage& image) { return result; } -gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, bool isBumpMap, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureNormalMapFromImage"); - QImage image = processSourceImage(srcImage, false); + QImage image = processSourceImage(std::move(srcImage), false); if (isBumpMap) { - image = processBumpMap(image); + image = processBumpMap(std::move(image)); } // Make sure the normal map source image is ARGB32 @@ -829,17 +875,17 @@ gpu::TexturePointer TextureUsage::process2DTextureNormalMapFromImage(const QImag theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); theTexture->setSource(srcImageName); theTexture->setStoredMipFormat(formatMip); - generateMips(theTexture.get(), image, abortProcessing); + generateMips(theTexture.get(), std::move(image), abortProcessing); } return theTexture; } -gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool isInvertedPixels, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "process2DTextureGrayscaleFromImage"); - QImage image = processSourceImage(srcImage, false); + QImage image = processSourceImage(std::move(srcImage), false); if (image.format() != QImage::Format_ARGB32) { image = image.convertToFormat(QImage::Format_ARGB32); @@ -865,7 +911,7 @@ gpu::TexturePointer TextureUsage::process2DTextureGrayscaleFromImage(const QImag theTexture = gpu::Texture::create2D(formatGPU, image.width(), image.height(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)); theTexture->setSource(srcImageName); theTexture->setStoredMipFormat(formatMip); - generateMips(theTexture.get(), image, abortProcessing); + generateMips(theTexture.get(), std::move(image), abortProcessing); } return theTexture; @@ -935,7 +981,7 @@ public: static QImage extractEquirectangularFace(const QImage& source, gpu::Texture::CubeFace face, int faceWidth) { QImage image(faceWidth, faceWidth, source.format()); - glm::vec2 dstInvSize(1.0f / (float)image.width(), 1.0f / (float)image.height()); + glm::vec2 dstInvSize(1.0f / faceWidth); struct CubeToXYZ { gpu::Texture::CubeFace _face; @@ -1138,8 +1184,12 @@ const int CubeLayout::NUM_CUBEMAP_LAYOUTS = sizeof(CubeLayout::CUBEMAP_LAYOUTS) //#define DEBUG_COLOR_PACKING -QImage convertToHDRFormat(QImage srcImage, gpu::Element format) { - QImage hdrImage(srcImage.width(), srcImage.height(), (QImage::Format)QIMAGE_HDR_FORMAT); +QImage convertToHDRFormat(QImage&& srcImage, gpu::Element format) { + // 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 + QImage localCopy = std::move(srcImage); + + QImage hdrImage(localCopy.width(), localCopy.height(), (QImage::Format)QIMAGE_HDR_FORMAT); std::function packFunc; #ifdef DEBUG_COLOR_PACKING std::function unpackFunc; @@ -1161,13 +1211,13 @@ QImage convertToHDRFormat(QImage srcImage, gpu::Element format) { default: qCWarning(imagelogging) << "Unsupported HDR format"; Q_UNREACHABLE(); - return srcImage; + return localCopy; } - srcImage = srcImage.convertToFormat(QImage::Format_ARGB32); - for (auto y = 0; y < srcImage.height(); y++) { - const QRgb* srcLineIt = reinterpret_cast( srcImage.constScanLine(y) ); - const QRgb* srcLineEnd = srcLineIt + srcImage.width(); + localCopy = localCopy.convertToFormat(QImage::Format_ARGB32); + for (auto y = 0; y < localCopy.height(); y++) { + const QRgb* srcLineIt = reinterpret_cast( localCopy.constScanLine(y) ); + const QRgb* srcLineEnd = srcLineIt + localCopy.width(); uint32* hdrLineIt = reinterpret_cast( hdrImage.scanLine(y) ); glm::vec3 color; @@ -1192,86 +1242,99 @@ QImage convertToHDRFormat(QImage srcImage, gpu::Element format) { return hdrImage; } -gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, +gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool generateIrradiance, const std::atomic& abortProcessing) { PROFILE_RANGE(resource_parse, "processCubeTextureColorFromImage"); + // 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 + QImage localCopy = std::move(srcImage); + + int originalWidth = localCopy.width(); + int originalHeight = localCopy.height(); + if ((originalWidth <= 0) && (originalHeight <= 0)) { + return nullptr; + } + gpu::TexturePointer theTexture = nullptr; - if ((srcImage.width() > 0) && (srcImage.height() > 0)) { - QImage image = processSourceImage(srcImage, true); - if (image.format() != QIMAGE_HDR_FORMAT) { - image = convertToHDRFormat(image, HDR_FORMAT); + QImage image = processSourceImage(std::move(localCopy), true); + + if (image.format() != QIMAGE_HDR_FORMAT) { + image = convertToHDRFormat(std::move(image), HDR_FORMAT); + } + + gpu::Element formatMip; + gpu::Element formatGPU; + if (isCubeTexturesCompressionEnabled()) { + formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB; + formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB; + } else { + formatMip = HDR_FORMAT; + formatGPU = HDR_FORMAT; + } + + // Find the layout of the cubemap in the 2D image + // Use the original image size since processSourceImage may have altered the size / aspect ratio + int foundLayout = CubeLayout::findLayout(originalWidth, originalHeight); + + if (foundLayout < 0) { + qCDebug(imagelogging) << "Failed to find a known cube map layout from this image:" << QString(srcImageName.c_str()); + return nullptr; + } + + std::vector faces; + + // If found, go extract the faces as separate images + auto& layout = CubeLayout::CUBEMAP_LAYOUTS[foundLayout]; + if (layout._type == CubeLayout::FLAT) { + int faceWidth = image.width() / layout._widthRatio; + + faces.push_back(image.copy(QRect(layout._faceXPos._x * faceWidth, layout._faceXPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXPos._horizontalMirror, layout._faceXPos._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceXNeg._x * faceWidth, layout._faceXNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXNeg._horizontalMirror, layout._faceXNeg._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceYPos._x * faceWidth, layout._faceYPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYPos._horizontalMirror, layout._faceYPos._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceYNeg._x * faceWidth, layout._faceYNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYNeg._horizontalMirror, layout._faceYNeg._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceZPos._x * faceWidth, layout._faceZPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZPos._horizontalMirror, layout._faceZPos._verticalMirror)); + faces.push_back(image.copy(QRect(layout._faceZNeg._x * faceWidth, layout._faceZNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZNeg._horizontalMirror, layout._faceZNeg._verticalMirror)); + } else if (layout._type == CubeLayout::EQUIRECTANGULAR) { + // THe face width is estimated from the input image + const int EQUIRECT_FACE_RATIO_TO_WIDTH = 4; + const int EQUIRECT_MAX_FACE_WIDTH = 2048; + int faceWidth = std::min(image.width() / EQUIRECT_FACE_RATIO_TO_WIDTH, EQUIRECT_MAX_FACE_WIDTH); + for (int face = gpu::Texture::CUBE_FACE_RIGHT_POS_X; face < gpu::Texture::NUM_CUBE_FACES; face++) { + QImage faceImage = CubeLayout::extractEquirectangularFace(std::move(image), (gpu::Texture::CubeFace) face, faceWidth); + faces.push_back(std::move(faceImage)); } + } - gpu::Element formatMip; - gpu::Element formatGPU; - if (isCubeTexturesCompressionEnabled()) { - formatMip = gpu::Element::COLOR_COMPRESSED_HDR_RGB; - formatGPU = gpu::Element::COLOR_COMPRESSED_HDR_RGB; - } else { - formatMip = HDR_FORMAT; - formatGPU = HDR_FORMAT; - } + // free up the memory afterward to avoid bloating the heap + image = QImage(); // QImage doesn't have a clear function, so override it with an empty one. - // Find the layout of the cubemap in the 2D image - // Use the original image size since processSourceImage may have altered the size / aspect ratio - int foundLayout = CubeLayout::findLayout(srcImage.width(), srcImage.height()); - - std::vector faces; - // If found, go extract the faces as separate images - if (foundLayout >= 0) { - auto& layout = CubeLayout::CUBEMAP_LAYOUTS[foundLayout]; - if (layout._type == CubeLayout::FLAT) { - int faceWidth = image.width() / layout._widthRatio; - - faces.push_back(image.copy(QRect(layout._faceXPos._x * faceWidth, layout._faceXPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXPos._horizontalMirror, layout._faceXPos._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceXNeg._x * faceWidth, layout._faceXNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceXNeg._horizontalMirror, layout._faceXNeg._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceYPos._x * faceWidth, layout._faceYPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYPos._horizontalMirror, layout._faceYPos._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceYNeg._x * faceWidth, layout._faceYNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceYNeg._horizontalMirror, layout._faceYNeg._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceZPos._x * faceWidth, layout._faceZPos._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZPos._horizontalMirror, layout._faceZPos._verticalMirror)); - faces.push_back(image.copy(QRect(layout._faceZNeg._x * faceWidth, layout._faceZNeg._y * faceWidth, faceWidth, faceWidth)).mirrored(layout._faceZNeg._horizontalMirror, layout._faceZNeg._verticalMirror)); - } else if (layout._type == CubeLayout::EQUIRECTANGULAR) { - // THe face width is estimated from the input image - const int EQUIRECT_FACE_RATIO_TO_WIDTH = 4; - const int EQUIRECT_MAX_FACE_WIDTH = 2048; - int faceWidth = std::min(image.width() / EQUIRECT_FACE_RATIO_TO_WIDTH, EQUIRECT_MAX_FACE_WIDTH); - for (int face = gpu::Texture::CUBE_FACE_RIGHT_POS_X; face < gpu::Texture::NUM_CUBE_FACES; face++) { - QImage faceImage = CubeLayout::extractEquirectangularFace(image, (gpu::Texture::CubeFace) face, faceWidth); - faces.push_back(faceImage); - } - } - } else { - qCDebug(imagelogging) << "Failed to find a known cube map layout from this image:" << QString(srcImageName.c_str()); - return nullptr; - } - - // If the 6 faces have been created go on and define the true Texture - if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) { - theTexture = gpu::Texture::createCube(formatGPU, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); - theTexture->setSource(srcImageName); - theTexture->setStoredMipFormat(formatMip); + // If the 6 faces have been created go on and define the true Texture + if (faces.size() == gpu::Texture::NUM_FACES_PER_TYPE[gpu::Texture::TEX_CUBE]) { + theTexture = gpu::Texture::createCube(formatGPU, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); + theTexture->setSource(srcImageName); + theTexture->setStoredMipFormat(formatMip); + // Generate irradiance while we are at it + if (generateIrradiance) { + PROFILE_RANGE(resource_parse, "generateIrradiance"); + auto irradianceTexture = gpu::Texture::createCube(HDR_FORMAT, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); + irradianceTexture->setSource(srcImageName); + irradianceTexture->setStoredMipFormat(HDR_FORMAT); for (uint8 face = 0; face < faces.size(); ++face) { - generateMips(theTexture.get(), faces[face], abortProcessing, face); + irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits()); } - // Generate irradiance while we are at it - if (generateIrradiance) { - PROFILE_RANGE(resource_parse, "generateIrradiance"); - auto irradianceTexture = gpu::Texture::createCube(HDR_FORMAT, faces[0].width(), gpu::Texture::MAX_NUM_MIPS, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR, gpu::Sampler::WRAP_CLAMP)); - irradianceTexture->setSource(srcImageName); - irradianceTexture->setStoredMipFormat(HDR_FORMAT); - for (uint8 face = 0; face < faces.size(); ++face) { - irradianceTexture->assignStoredMipFace(0, face, faces[face].byteCount(), faces[face].constBits()); - } + irradianceTexture->generateIrradiance(); - irradianceTexture->generateIrradiance(); + auto irradiance = irradianceTexture->getIrradiance(); + theTexture->overrideIrradiance(irradiance); + } - auto irradiance = irradianceTexture->getIrradiance(); - theTexture->overrideIrradiance(irradiance); - } + for (uint8 face = 0; face < faces.size(); ++face) { + generateMips(theTexture.get(), std::move(faces[face]), abortProcessing, face); } } diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h index 856dc009cf..39f5ea3bab 100644 --- a/libraries/image/src/image/Image.h +++ b/libraries/image/src/image/Image.h @@ -41,45 +41,47 @@ enum Type { UNUSED_TEXTURE }; -using TextureLoader = std::function&)>; +using TextureLoader = std::function&)>; TextureLoader getTextureLoaderForType(Type type, const QVariantMap& options = QVariantMap()); -gpu::TexturePointer create2DTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer create2DTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createStrict2DTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createStrict2DTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createAlbedoTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createAlbedoTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createEmissiveTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createEmissiveTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createNormalTextureFromNormalImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createNormalTextureFromNormalImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createNormalTextureFromBumpImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createNormalTextureFromBumpImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createRoughnessTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createRoughnessTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createRoughnessTextureFromGlossImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createRoughnessTextureFromGlossImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createMetallicTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createMetallicTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createCubeTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createCubeTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createCubeTextureFromImageWithoutIrradiance(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer createLightmapTextureFromImage(const QImage& image, const std::string& srcImageName, +gpu::TexturePointer createLightmapTextureFromImage(QImage&& image, const std::string& srcImageName, const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool isStrict, +gpu::TexturePointer process2DTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool isStrict, const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureNormalMapFromImage(const QImage& srcImage, const std::string& srcImageName, bool isBumpMap, +gpu::TexturePointer process2DTextureNormalMapFromImage(QImage&& srcImage, const std::string& srcImageName, bool isBumpMap, const std::atomic& abortProcessing); -gpu::TexturePointer process2DTextureGrayscaleFromImage(const QImage& srcImage, const std::string& srcImageName, bool isInvertedPixels, +gpu::TexturePointer process2DTextureGrayscaleFromImage(QImage&& srcImage, const std::string& srcImageName, bool isInvertedPixels, const std::atomic& abortProcessing); -gpu::TexturePointer processCubeTextureColorFromImage(const QImage& srcImage, const std::string& srcImageName, bool generateIrradiance, +gpu::TexturePointer processCubeTextureColorFromImage(QImage&& srcImage, const std::string& srcImageName, bool generateIrradiance, const std::atomic& abortProcessing); } // namespace TextureUsage +const QStringList getSupportedFormats(); + bool isColorTexturesCompressionEnabled(); bool isNormalTexturesCompressionEnabled(); bool isGrayscaleTexturesCompressionEnabled(); @@ -90,7 +92,7 @@ void setNormalTexturesCompressionEnabled(bool enabled); void setGrayscaleTexturesCompressionEnabled(bool enabled); void setCubeTexturesCompressionEnabled(bool enabled); -gpu::TexturePointer processImage(const QByteArray& content, const std::string& url, +gpu::TexturePointer processImage(QByteArray&& content, const std::string& url, int maxNumPixels, TextureUsage::Type textureType, const std::atomic& abortProcessing = false); diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index 4184351c2d..b8f81dc7a4 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -264,7 +264,7 @@ gpu::TexturePointer getFallbackTextureForType(image::TextureUsage::Type type) { gpu::TexturePointer TextureCache::getImageTexture(const QString& path, image::TextureUsage::Type type, QVariantMap options) { QImage image = QImage(path); auto loader = image::TextureUsage::getTextureLoaderForType(type, options); - return gpu::TexturePointer(loader(image, QUrl::fromLocalFile(path).fileName().toStdString(), false)); + return gpu::TexturePointer(loader(std::move(image), QUrl::fromLocalFile(path).fileName().toStdString(), false)); } QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, @@ -954,7 +954,9 @@ void ImageReader::read() { gpu::TexturePointer texture; { PROFILE_RANGE_EX(resource_parse_image_raw, __FUNCTION__, 0xffff0000, 0); - texture = image::processImage(_content, _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType()); + + // IMPORTANT: _content is empty past this point + texture = image::processImage(std::move(_content), _url.toString().toStdString(), _maxNumPixels, networkTexture->getTextureType()); if (!texture) { qCWarning(modelnetworking) << "Could not process:" << _url; @@ -1006,14 +1008,11 @@ NetworkTexturePointer TextureCache::getResourceTexture(QUrl resourceTextureUrl) if (!_spectatorCameraNetworkTexture) { _spectatorCameraNetworkTexture.reset(new NetworkTexture(resourceTextureUrl)); } - if (_spectatorCameraFramebuffer) { - texture = _spectatorCameraFramebuffer->getRenderBuffer(0); - if (texture) { - texture->setSource(SPECTATOR_CAMERA_FRAME_URL.toString().toStdString()); - _spectatorCameraNetworkTexture->setImage(texture, texture->getWidth(), texture->getHeight()); - return _spectatorCameraNetworkTexture; - } + if (!_spectatorCameraFramebuffer) { + getSpectatorCameraFramebuffer(); // initialize frame buffer } + updateSpectatorCameraNetworkTexture(); + return _spectatorCameraNetworkTexture; } // FIXME: Generalize this, DRY up this code if (resourceTextureUrl == HMD_PREVIEW_FRAME_URL) { @@ -1052,7 +1051,18 @@ const gpu::FramebufferPointer& TextureCache::getSpectatorCameraFramebuffer(int w // If we aren't taking a screenshot, we might need to resize or create the camera buffer if (!_spectatorCameraFramebuffer || _spectatorCameraFramebuffer->getWidth() != width || _spectatorCameraFramebuffer->getHeight() != height) { _spectatorCameraFramebuffer.reset(gpu::Framebuffer::create("spectatorCamera", gpu::Element::COLOR_SRGBA_32, width, height)); + updateSpectatorCameraNetworkTexture(); emit spectatorCameraFramebufferReset(); } return _spectatorCameraFramebuffer; } + +void TextureCache::updateSpectatorCameraNetworkTexture() { + if (_spectatorCameraFramebuffer && _spectatorCameraNetworkTexture) { + gpu::TexturePointer texture = _spectatorCameraFramebuffer->getRenderBuffer(0); + if (texture) { + texture->setSource(SPECTATOR_CAMERA_FRAME_URL.toString().toStdString()); + _spectatorCameraNetworkTexture->setImage(texture, texture->getWidth(), texture->getHeight()); + } + } +} diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index 1102694f86..5a96fcf5e6 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -171,6 +171,7 @@ public: const gpu::FramebufferPointer& getHmdPreviewFramebuffer(int width, int height); const gpu::FramebufferPointer& getSpectatorCameraFramebuffer(); const gpu::FramebufferPointer& getSpectatorCameraFramebuffer(int width, int height); + void updateSpectatorCameraNetworkTexture(); static const int DEFAULT_SPECTATOR_CAM_WIDTH { 2048 }; static const int DEFAULT_SPECTATOR_CAM_HEIGHT { 1024 }; diff --git a/libraries/networking/src/AssetUtils.cpp b/libraries/networking/src/AssetUtils.cpp index 3af0b1df47..76fda6aed4 100644 --- a/libraries/networking/src/AssetUtils.cpp +++ b/libraries/networking/src/AssetUtils.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include "NetworkAccessManager.h" diff --git a/libraries/networking/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp index 6c6f3eb90c..51f93d13b0 100644 --- a/libraries/networking/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -12,9 +12,10 @@ #include #include -#include +#include #include #include +#include #include diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 1e682f367a..94bc4eeff6 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp index 61f2071c5f..0965c9834f 100644 --- a/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.cpp @@ -88,3 +88,56 @@ void UserActivityLoggerScriptingInterface::doLogAction(QString action, QJsonObje Q_ARG(QString, action), Q_ARG(QJsonObject, details)); } + +void UserActivityLoggerScriptingInterface::commercePurchaseSuccess(QString marketplaceID, int cost, bool firstPurchaseOfThisItem) { + QJsonObject payload; + payload["marketplaceID"] = marketplaceID; + payload["cost"] = cost; + payload["firstPurchaseOfThisItem"] = firstPurchaseOfThisItem; + doLogAction("commercePurchaseSuccess", payload); +} + +void UserActivityLoggerScriptingInterface::commercePurchaseFailure(QString marketplaceID, int cost, bool firstPurchaseOfThisItem, QString errorDetails) { + QJsonObject payload; + payload["marketplaceID"] = marketplaceID; + payload["cost"] = cost; + payload["firstPurchaseOfThisItem"] = firstPurchaseOfThisItem; + payload["errorDetails"] = errorDetails; + doLogAction("commercePurchaseFailure", payload); +} + +void UserActivityLoggerScriptingInterface::commerceEntityRezzed(QString marketplaceID, QString source, QString type) { + QJsonObject payload; + payload["marketplaceID"] = marketplaceID; + payload["source"] = source; + payload["type"] = type; + doLogAction("commerceEntityRezzed", payload); +} + +void UserActivityLoggerScriptingInterface::commerceWalletSetupStarted(int timestamp, QString setupAttemptID, int setupFlowVersion, QString referrer, QString currentDomain) { + QJsonObject payload; + payload["timestamp"] = timestamp; + payload["setupAttemptID"] = setupAttemptID; + payload["setupFlowVersion"] = setupFlowVersion; + payload["referrer"] = referrer; + payload["currentDomain"] = currentDomain; + doLogAction("commerceWalletSetupStarted", payload); +} + +void UserActivityLoggerScriptingInterface::commerceWalletSetupProgress(int timestamp, QString setupAttemptID, int secondsElapsed, int currentStepNumber, QString currentStepName) { + QJsonObject payload; + payload["timestamp"] = timestamp; + payload["setupAttemptID"] = setupAttemptID; + payload["secondsElapsed"] = secondsElapsed; + payload["currentStepNumber"] = currentStepNumber; + payload["currentStepName"] = currentStepName; + doLogAction("commerceWalletSetupProgress", payload); +} + +void UserActivityLoggerScriptingInterface::commerceWalletSetupFinished(int timestamp, QString setupAttemptID, int secondsToComplete) { + QJsonObject payload; + payload["timestamp"] = timestamp; + payload["setupAttemptID"] = setupAttemptID; + payload["secondsToComplete"] = secondsToComplete; + doLogAction("commerceWalletSetupFinished", payload); +} diff --git a/libraries/networking/src/UserActivityLoggerScriptingInterface.h b/libraries/networking/src/UserActivityLoggerScriptingInterface.h index 885f637a62..e71723f03c 100644 --- a/libraries/networking/src/UserActivityLoggerScriptingInterface.h +++ b/libraries/networking/src/UserActivityLoggerScriptingInterface.h @@ -33,6 +33,12 @@ public: Q_INVOKABLE void bubbleToggled(bool newValue); Q_INVOKABLE void bubbleActivated(); Q_INVOKABLE void logAction(QString action, QVariantMap details = QVariantMap{}); + Q_INVOKABLE void commercePurchaseSuccess(QString marketplaceID, int cost, bool firstPurchaseOfThisItem); + Q_INVOKABLE void commercePurchaseFailure(QString marketplaceID, int cost, bool firstPurchaseOfThisItem, QString errorDetails); + Q_INVOKABLE void commerceEntityRezzed(QString marketplaceID, QString source, QString type); + Q_INVOKABLE void commerceWalletSetupStarted(int timestamp, QString setupAttemptID, int setupFlowVersion, QString referrer, QString currentDomain); + Q_INVOKABLE void commerceWalletSetupProgress(int timestamp, QString setupAttemptID, int secondsElapsed, int currentStepNumber, QString currentStepName); + Q_INVOKABLE void commerceWalletSetupFinished(int timestamp, QString setupAttemptID, int secondsToComplete); private: void doLogAction(QString action, QJsonObject details = {}); }; diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index f42049f107..2f57523f79 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -11,6 +11,8 @@ #include "Connection.h" +#include + #include #include @@ -60,6 +62,15 @@ Connection::Connection(Socket* parentSocket, HifiSockAddr destination, std::uniq _ack2Packet = ControlPacket::create(ControlPacket::ACK2, ACK2_PAYLOAD_BYTES); _lossReport = ControlPacket::create(ControlPacket::NAK, NAK_PACKET_PAYLOAD_BYTES); _handshakeACK = ControlPacket::create(ControlPacket::HandshakeACK, HANDSHAKE_ACK_PAYLOAD_BYTES); + + + // setup psuedo-random number generation shared by all connections + static std::random_device rd; + static std::mt19937 generator(rd()); + static std::uniform_int_distribution<> distribution(0, SequenceNumber::MAX); + + // randomize the intial sequence number + _initialSequenceNumber = SequenceNumber(distribution(generator)); } Connection::~Connection() { @@ -79,11 +90,11 @@ void Connection::stopSendQueue() { // tell the send queue to stop and be deleted sendQueue->stop(); + + _lastMessageNumber = sendQueue->getCurrentMessageNumber(); + sendQueue->deleteLater(); - // since we're stopping the send queue we should consider our handshake ACK not receieved - _hasReceivedHandshakeACK = false; - // wait on the send queue thread so we know the send queue is gone sendQueueThread->quit(); sendQueueThread->wait(); @@ -101,13 +112,19 @@ void Connection::setMaxBandwidth(int maxBandwidth) { SendQueue& Connection::getSendQueue() { if (!_sendQueue) { - // we may have a sequence number from the previous inactive queue - re-use that so that the // receiver is getting the sequence numbers it expects (given that the connection must still be active) // Lasily create send queue - _sendQueue = SendQueue::create(_parentSocket, _destination); - _lastReceivedACK = _sendQueue->getCurrentSequenceNumber(); + + if (!_hasReceivedHandshakeACK) { + // First time creating a send queue for this connection + _sendQueue = SendQueue::create(_parentSocket, _destination, _initialSequenceNumber - 1, _lastMessageNumber, _hasReceivedHandshakeACK); + _lastReceivedACK = _sendQueue->getCurrentSequenceNumber(); + } else { + // Connection already has a handshake from a previous send queue + _sendQueue = SendQueue::create(_parentSocket, _destination, _lastReceivedACK, _lastMessageNumber, _hasReceivedHandshakeACK); + } #ifdef UDT_CONNECTION_DEBUG qCDebug(networking) << "Created SendQueue for connection to" << _destination; @@ -142,14 +159,6 @@ void Connection::queueInactive() { #ifdef UDT_CONNECTION_DEBUG qCDebug(networking) << "Connection to" << _destination << "has stopped its SendQueue."; #endif - - if (!_hasReceivedHandshake || !_isReceivingData) { -#ifdef UDT_CONNECTION_DEBUG - qCDebug(networking) << "Connection SendQueue to" << _destination << "stopped and no data is being received - stopping connection."; -#endif - - deactivate(); - } } void Connection::queueTimeout() { @@ -184,11 +193,16 @@ void Connection::queueReceivedMessagePacket(std::unique_ptr packet) { while (pendingMessage.hasAvailablePackets()) { auto packet = pendingMessage.removeNextPacket(); - _parentSocket->messageReceived(std::move(packet)); - } - if (pendingMessage.isComplete()) { - _pendingReceivedMessages.erase(messageNumber); + auto packetPosition = packet->getPacketPosition(); + + _parentSocket->messageReceived(std::move(packet)); + + // if this was the last or only packet, then we can remove the pending message from our hash + if (packetPosition == Packet::PacketPosition::LAST || + packetPosition == Packet::PacketPosition::ONLY) { + _pendingReceivedMessages.erase(messageNumber); + } } } @@ -208,19 +222,6 @@ void Connection::sync() { && duration_cast(sincePacketReceive).count() >= MIN_SECONDS_BEFORE_EXPIRY ) { // the receive side of this connection is expired _isReceivingData = false; - - // if we don't have a send queue that means the whole connection has expired and we can emit our signal - // otherwise we'll wait for it to also timeout before cleaning up - if (!_sendQueue) { - -#ifdef UDT_CONNECTION_DEBUG - qCDebug(networking) << "Connection to" << _destination << "no longer receiving any data and there is currently no send queue - stopping connection."; -#endif - - deactivate(); - - return; - } } // reset the number of light ACKs or non SYN ACKs during this sync interval @@ -242,26 +243,6 @@ void Connection::sync() { sendTimeoutNAK(); } } - } else if (!_sendQueue) { - // we haven't received a packet and we're not sending - // this most likely means we were started erroneously - // check the start time for this connection and auto expire it after 5 seconds of not receiving or sending any data - static const int CONNECTION_NOT_USED_EXPIRY_SECONDS = 5; - auto secondsSinceStart = duration_cast(p_high_resolution_clock::now() - _connectionStart).count(); - - if (secondsSinceStart >= CONNECTION_NOT_USED_EXPIRY_SECONDS) { - // it's been CONNECTION_NOT_USED_EXPIRY_SECONDS and nothing has actually happened with this connection - // consider it inactive and emit our inactivity signal - -#ifdef UDT_CONNECTION_DEBUG - qCDebug(networking) << "Connection to" << _destination << "did not receive or send any data in last" - << CONNECTION_NOT_USED_EXPIRY_SECONDS << "seconds - stopping connection."; -#endif - - deactivate(); - - return; - } } } @@ -444,7 +425,6 @@ void Connection::sendHandshakeRequest() { } bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, int packetSize, int payloadSize) { - if (!_hasReceivedHandshake) { // Refuse to process any packets until we've received the handshake // Send handshake request to re-request a handshake @@ -536,7 +516,7 @@ bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, in } else { _stats.recordReceivedPackets(payloadSize, packetSize); } - + return !wasDuplicate; } @@ -827,11 +807,13 @@ void Connection::processHandshakeACK(ControlPacketPointer controlPacket) { SequenceNumber initialSequenceNumber; controlPacket->readPrimitive(&initialSequenceNumber); - // hand off this handshake ACK to the send queue so it knows it can start sending - getSendQueue().handshakeACK(initialSequenceNumber); - - // indicate that handshake ACK was received - _hasReceivedHandshakeACK = true; + if (initialSequenceNumber == _initialSequenceNumber) { + // hand off this handshake ACK to the send queue so it knows it can start sending + getSendQueue().handshakeACK(); + + // indicate that handshake ACK was received + _hasReceivedHandshakeACK = true; + } } } diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h index c134081dde..0017eb204a 100644 --- a/libraries/networking/src/udt/Connection.h +++ b/libraries/networking/src/udt/Connection.h @@ -37,7 +37,6 @@ class Socket; class PendingReceivedMessage { public: void enqueuePacket(std::unique_ptr packet); - bool isComplete() const { return _hasLastPacket && _numPackets == _packets.size(); } bool hasAvailablePackets() const; std::unique_ptr removeNextPacket(); @@ -72,8 +71,6 @@ public: void queueReceivedMessagePacket(std::unique_ptr packet); ConnectionStats::Stats sampleStats() { return _stats.sample(); } - - bool isActive() const { return _isActive; } HifiSockAddr getDestination() const { return _destination; } @@ -83,7 +80,6 @@ public: signals: void packetSent(); - void connectionInactive(const HifiSockAddr& sockAddr); void receiverHandshakeRequestComplete(const HifiSockAddr& sockAddr); private slots: @@ -112,8 +108,6 @@ private: void resetReceiveState(); void resetRTT(); - void deactivate() { _isActive = false; emit connectionInactive(_destination); } - SendQueue& getSendQueue(); SequenceNumber nextACK() const; void updateRTT(int rtt); @@ -138,9 +132,11 @@ private: p_high_resolution_clock::time_point _lastReceiveTime; // holds the last time we received anything from sender bool _isReceivingData { false }; // flag used for expiry of receipt portion of connection - bool _isActive { true }; // flag used for inactivity of connection - SequenceNumber _initialReceiveSequenceNumber; // Randomized by peer SendQueue on creation, identifies connection during re-connect requests + SequenceNumber _initialSequenceNumber; // Randomized on Connection creation, identifies connection during re-connect requests + SequenceNumber _initialReceiveSequenceNumber; // Randomized by peer Connection on creation, identifies connection during re-connect requests + + MessageNumber _lastMessageNumber { 0 }; LossList _lossList; // List of all missing packets SequenceNumber _lastReceivedSequenceNumber; // The largest sequence number received from the peer diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index fa5028e1bb..207ddf6bbb 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -74,6 +74,8 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(AudioVersion::HighDynamicRangeVolume); case PacketType::ICEPing: return static_cast(IcePingVersion::SendICEPeerID); + case PacketType::DomainSettings: + return 18; // replace min_avatar_scale and max_avatar_scale with min_avatar_height and max_avatar_height default: return 17; } diff --git a/libraries/networking/src/udt/PacketQueue.cpp b/libraries/networking/src/udt/PacketQueue.cpp index 9560f2f187..0560855ecb 100644 --- a/libraries/networking/src/udt/PacketQueue.cpp +++ b/libraries/networking/src/udt/PacketQueue.cpp @@ -15,7 +15,7 @@ using namespace udt; -PacketQueue::PacketQueue() { +PacketQueue::PacketQueue(MessageNumber messageNumber) : _currentMessageNumber(messageNumber) { _channels.emplace_back(new std::list()); } diff --git a/libraries/networking/src/udt/PacketQueue.h b/libraries/networking/src/udt/PacketQueue.h index 2b3d3a4b5b..bc4c5e3432 100644 --- a/libraries/networking/src/udt/PacketQueue.h +++ b/libraries/networking/src/udt/PacketQueue.h @@ -34,7 +34,7 @@ class PacketQueue { using Channels = std::vector; public: - PacketQueue(); + PacketQueue(MessageNumber messageNumber = 0); void queuePacket(PacketPointer packet); void queuePacketList(PacketListPointer packetList); @@ -42,6 +42,8 @@ public: PacketPointer takePacket(); Mutex& getLock() { return _packetsLock; } + + MessageNumber getCurrentMessageNumber() const { return _currentMessageNumber; } private: MessageNumber getNextMessageNumber(); diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index 0c029751aa..4a0500f642 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -12,7 +12,6 @@ #include "SendQueue.h" #include -#include #include #include @@ -62,10 +61,12 @@ private: Mutex2& _mutex2; }; -std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destination) { +std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destination, SequenceNumber currentSequenceNumber, + MessageNumber currentMessageNumber, bool hasReceivedHandshakeACK) { Q_ASSERT_X(socket, "SendQueue::create", "Must be called with a valid Socket*"); - auto queue = std::unique_ptr(new SendQueue(socket, destination)); + auto queue = std::unique_ptr(new SendQueue(socket, destination, currentSequenceNumber, + currentMessageNumber, hasReceivedHandshakeACK)); // Setup queue private thread QThread* thread = new QThread; @@ -84,22 +85,18 @@ std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destin return queue; } -SendQueue::SendQueue(Socket* socket, HifiSockAddr dest) : +SendQueue::SendQueue(Socket* socket, HifiSockAddr dest, SequenceNumber currentSequenceNumber, + MessageNumber currentMessageNumber, bool hasReceivedHandshakeACK) : + _packets(currentMessageNumber), _socket(socket), _destination(dest) { - // setup psuedo-random number generation for all instances of SendQueue - static std::random_device rd; - static std::mt19937 generator(rd()); - static std::uniform_int_distribution<> distribution(0, SequenceNumber::MAX); - - // randomize the intial sequence number - _initialSequenceNumber = SequenceNumber(distribution(generator)); - - // set our member variables from randomized initial number - _currentSequenceNumber = _initialSequenceNumber - 1; + // set our member variables from current sequence number + _currentSequenceNumber = currentSequenceNumber; _atomicCurrentSequenceNumber = uint32_t(_currentSequenceNumber); - _lastACKSequenceNumber = uint32_t(_currentSequenceNumber) - 1; + _lastACKSequenceNumber = uint32_t(_currentSequenceNumber); + + _hasReceivedHandshakeACK = hasReceivedHandshakeACK; // default the last receiver response to the current time _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); @@ -114,8 +111,8 @@ void SendQueue::queuePacket(std::unique_ptr packet) { // call notify_one on the condition_variable_any in case the send thread is sleeping waiting for packets _emptyCondition.notify_one(); - if (!this->thread()->isRunning() && _state == State::NotStarted) { - this->thread()->start(); + if (!thread()->isRunning() && _state == State::NotStarted) { + thread()->start(); } } @@ -125,8 +122,8 @@ void SendQueue::queuePacketList(std::unique_ptr packetList) { // call notify_one on the condition_variable_any in case the send thread is sleeping waiting for packets _emptyCondition.notify_one(); - if (!this->thread()->isRunning() && _state == State::NotStarted) { - this->thread()->start(); + if (!thread()->isRunning() && _state == State::NotStarted) { + thread()->start(); } } @@ -225,8 +222,11 @@ void SendQueue::sendHandshake() { std::unique_lock handshakeLock { _handshakeMutex }; if (!_hasReceivedHandshakeACK) { // we haven't received a handshake ACK from the client, send another now + // if the handshake hasn't been completed, then the initial sequence number + // should be the current sequence number + 1 + SequenceNumber initialSequenceNumber = _currentSequenceNumber + 1; auto handshakePacket = ControlPacket::create(ControlPacket::Handshake, sizeof(SequenceNumber)); - handshakePacket->writePrimitive(_initialSequenceNumber); + handshakePacket->writePrimitive(initialSequenceNumber); _socket->writeBasePacket(*handshakePacket, _destination); // we wait for the ACK or the re-send interval to expire @@ -235,18 +235,16 @@ void SendQueue::sendHandshake() { } } -void SendQueue::handshakeACK(SequenceNumber initialSequenceNumber) { - if (initialSequenceNumber == _initialSequenceNumber) { - { - std::lock_guard locker { _handshakeMutex }; - _hasReceivedHandshakeACK = true; - } - - _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); - - // Notify on the handshake ACK condition - _handshakeACKCondition.notify_one(); +void SendQueue::handshakeACK() { + { + std::lock_guard locker { _handshakeMutex }; + _hasReceivedHandshakeACK = true; } + + _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); + + // Notify on the handshake ACK condition + _handshakeACKCondition.notify_one(); } SequenceNumber SendQueue::getNextSequenceNumber() { @@ -540,28 +538,6 @@ bool SendQueue::maybeResendPacket() { bool SendQueue::isInactive(bool attemptedToSendPacket) { // check for connection timeout first - // that will be the case if we have had 16 timeouts since hearing back from the client, and it has been - // at least 5 seconds - static const int NUM_TIMEOUTS_BEFORE_INACTIVE = 16; - static const int MIN_MS_BEFORE_INACTIVE = 5 * 1000; - - auto sinceLastResponse = (QDateTime::currentMSecsSinceEpoch() - _lastReceiverResponse); - - if (sinceLastResponse > 0 && - sinceLastResponse >= int64_t(NUM_TIMEOUTS_BEFORE_INACTIVE * (_estimatedTimeout / USECS_PER_MSEC)) && - sinceLastResponse > MIN_MS_BEFORE_INACTIVE) { - // If the flow window has been full for over CONSIDER_INACTIVE_AFTER, - // then signal the queue is inactive and return so it can be cleaned up - -#ifdef UDT_CONNECTION_DEBUG - qCDebug(networking) << "SendQueue to" << _destination << "reached" << NUM_TIMEOUTS_BEFORE_INACTIVE << "timeouts" - << "and" << MIN_MS_BEFORE_INACTIVE << "milliseconds before receiving any ACK/NAK and is now inactive. Stopping."; -#endif - - deactivate(); - return true; - } - if (!attemptedToSendPacket) { // During our processing above we didn't send any packets diff --git a/libraries/networking/src/udt/SendQueue.h b/libraries/networking/src/udt/SendQueue.h index 484afcb88e..b33180c1e2 100644 --- a/libraries/networking/src/udt/SendQueue.h +++ b/libraries/networking/src/udt/SendQueue.h @@ -50,7 +50,9 @@ public: Stopped }; - static std::unique_ptr create(Socket* socket, HifiSockAddr destination); + static std::unique_ptr create(Socket* socket, HifiSockAddr destination, + SequenceNumber currentSequenceNumber, MessageNumber currentMessageNumber, + bool hasReceivedHandshakeACK); virtual ~SendQueue(); @@ -58,6 +60,7 @@ public: void queuePacketList(std::unique_ptr packetList); SequenceNumber getCurrentSequenceNumber() const { return SequenceNumber(_atomicCurrentSequenceNumber); } + MessageNumber getCurrentMessageNumber() const { return _packets.getCurrentMessageNumber(); } void setFlowWindowSize(int flowWindowSize) { _flowWindowSize = flowWindowSize; } @@ -76,7 +79,7 @@ public slots: void nak(SequenceNumber start, SequenceNumber end); void fastRetransmit(SequenceNumber ack); void overrideNAKListFromPacket(ControlPacket& packet); - void handshakeACK(SequenceNumber initialSequenceNumber); + void handshakeACK(); signals: void packetSent(int wireSize, int payloadSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint); @@ -91,7 +94,8 @@ private slots: void run(); private: - SendQueue(Socket* socket, HifiSockAddr dest); + SendQueue(Socket* socket, HifiSockAddr dest, SequenceNumber currentSequenceNumber, + MessageNumber currentMessageNumber, bool hasReceivedHandshakeACK); SendQueue(SendQueue& other) = delete; SendQueue(SendQueue&& other) = delete; @@ -115,8 +119,6 @@ private: Socket* _socket { nullptr }; // Socket to send packet on HifiSockAddr _destination; // Destination addr - - SequenceNumber _initialSequenceNumber; // Randomized on SendQueue creation, identifies connection during re-connect requests std::atomic _lastACKSequenceNumber { 0 }; // Last ACKed sequence number diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index a3374a0f47..55643985c8 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -257,9 +257,6 @@ Connection* Socket::findOrCreateConnection(const HifiSockAddr& sockAddr) { congestionControl->setMaxBandwidth(_maxBandwidth); auto connection = std::unique_ptr(new Connection(this, sockAddr, std::move(congestionControl))); - // we queue the connection to cleanup connection in case it asks for it during its own rate control sync - QObject::connect(connection.get(), &Connection::connectionInactive, this, &Socket::cleanupConnection); - // allow higher-level classes to find out when connections have completed a handshake QObject::connect(connection.get(), &Connection::receiverHandshakeRequestComplete, this, &Socket::clientHandshakeRequestComplete); diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 8ebce9f811..7e8b431ceb 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -700,7 +700,7 @@ uint32_t EntityMotionState::getIncomingDirtyFlags() { void EntityMotionState::clearIncomingDirtyFlags() { assert(entityTreeIsLocked()); if (_body && _entity) { - _entity->clearDirtyFlags(); + _entity->clearDirtyFlags(DIRTY_PHYSICS_FLAGS); } } diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index 69e9768ccd..7b718515a8 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 30121a232d..c437a8c556 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -211,6 +211,6 @@ void CauterizedModel::updateRenderItems() { } const Model::MeshState& CauterizedModel::getCauterizeMeshState(int index) const { - assert(index < _meshStates.size()); + assert((size_t)index < _meshStates.size()); return _cauterizeMeshStates.at(index); } diff --git a/libraries/render-utils/src/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index d334a53fa1..02a5496151 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.cpp +++ b/libraries/render-utils/src/DebugDeferredBuffer.cpp @@ -11,7 +11,8 @@ #include "DebugDeferredBuffer.h" -#include +#include +#include #include #include diff --git a/libraries/render-utils/src/DrawHaze.cpp b/libraries/render-utils/src/DrawHaze.cpp index 4431c1bbc3..da07f5bd9b 100644 --- a/libraries/render-utils/src/DrawHaze.cpp +++ b/libraries/render-utils/src/DrawHaze.cpp @@ -169,7 +169,12 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu auto hazeStage = args->_scene->getStage(); if (hazeStage && hazeStage->_currentFrame._hazes.size() > 0) { model::HazePointer hazePointer = hazeStage->getHaze(hazeStage->_currentFrame._hazes.front()); - batch.setUniformBuffer(HazeEffect_ParamsSlot, hazePointer->getHazeParametersBuffer()); + if (hazePointer) { + batch.setUniformBuffer(HazeEffect_ParamsSlot, hazePointer->getHazeParametersBuffer()); + } else { + // Something is wrong, so just quit Haze + return; + } } batch.setUniformBuffer(HazeEffect_TransformBufferSlot, transformBuffer->getFrameTransformBuffer()); @@ -178,7 +183,7 @@ void DrawHaze::run(const render::RenderContextPointer& renderContext, const Inpu if (lightStage) { model::LightPointer keyLight; keyLight = lightStage->getCurrentKeyLight(); - if (keyLight != nullptr) { + if (keyLight) { batch.setUniformBuffer(HazeEffect_LightingMapSlot, keyLight->getLightSchemaBuffer()); } } diff --git a/libraries/render-utils/src/Highlight.slh b/libraries/render-utils/src/Highlight.slh index 2faa10682e..f4d4ad0e04 100644 --- a/libraries/render-utils/src/Highlight.slh +++ b/libraries/render-utils/src/Highlight.slh @@ -16,7 +16,7 @@ <@include Highlight_shared.slh@> uniform highlightParamsBuffer { - HighlightParameters params; + HighlightParameters params; }; uniform sampler2D sceneDepthMap; @@ -35,8 +35,7 @@ void main(void) { // We offset by half a texel to be centered on the depth sample. If we don't do this // the blur will have a different width between the left / right sides and top / bottom // sides of the silhouette - float highlightedDepth = texture(highlightedDepthMap, varTexCoord0).x; - float intensity = 0.0; + float highlightedDepth = texture(highlightedDepthMap, varTexCoord0).x; if (highlightedDepth < FAR_Z) { // We're not on the far plane so we are on the highlighted object, thus no outline to do! @@ -47,10 +46,13 @@ void main(void) { highlightedDepth = -evalZeyeFromZdb(highlightedDepth); sceneDepth = -evalZeyeFromZdb(sceneDepth); - // Are we occluded? - intensity = sceneDepth < (highlightedDepth-LINEAR_DEPTH_BIAS) ? params._occludedFillOpacity : params._unoccludedFillOpacity; + if (sceneDepth < (highlightedDepth-LINEAR_DEPTH_BIAS)) { + outFragColor = vec4(params._fillOccludedColor, params._fillOccludedAlpha); + } else { + outFragColor = vec4(params._fillUnoccludedColor, params._fillUnoccludedAlpha); + } <@else@> - discard; + discard; <@endif@> } else { vec2 halfTexel = getInvWidthHeight() / 2; @@ -62,6 +64,10 @@ void main(void) { int x; int y; + float intensity = 0.0; + float outlinedDepth = 0.0; + float sumOutlineDepth = 0.0; + for (y=0 ; y=0.0 && uv.x<=1.0) { - highlightedDepth = texture(highlightedDepthMap, uv).x; - intensity += (highlightedDepth < FAR_Z) ? 1.0 : 0.0; + outlinedDepth = texture(highlightedDepthMap, uv).x; + float touch = (outlinedDepth < FAR_Z) ? 1.0 : 0.0; + sumOutlineDepth = max(outlinedDepth * touch, sumOutlineDepth); + intensity += touch; weight += 1.0; } uv.x += deltaUv.x; @@ -79,15 +87,32 @@ void main(void) { } } + if (intensity > 0) { + // sumOutlineDepth /= intensity; + } else { + sumOutlineDepth = FAR_Z; + } + intensity /= weight; if (intensity < OPACITY_EPSILON) { discard; } + intensity = min(1.0, intensity / params._threshold); - intensity = min(1.0, intensity / params._threshold) * params._intensity; + // But we need to check the scene depth against the depth of the outline + float sceneDepth = texture(sceneDepthMap, texCoord0).x; + + // Transform to linear depth for better precision + outlinedDepth = -evalZeyeFromZdb(sumOutlineDepth); + sceneDepth = -evalZeyeFromZdb(sceneDepth); + + // Are we occluded? + if (sceneDepth < (outlinedDepth/*-LINEAR_DEPTH_BIAS*/)) { + outFragColor = vec4(params._outlineOccludedColor, intensity * params._outlineOccludedAlpha); + } else { + outFragColor = vec4(params._outlineUnoccludedColor, intensity * params._outlineUnoccludedAlpha); + } } - - outFragColor = vec4(params._color.rgb, intensity); } <@endfunc@> diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index 7c58e5ba66..fee1f4a568 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -88,7 +88,7 @@ HighlightSharedParameters::HighlightSharedParameters() { } float HighlightSharedParameters::getBlurPixelWidth(const render::HighlightStyle& style, int frameBufferHeight) { - return ceilf(style.outlineWidth * frameBufferHeight / 400.0f); + return ceilf(style._outlineWidth * frameBufferHeight / 400.0f); } PrepareDrawHighlight::PrepareDrawHighlight() { @@ -267,14 +267,19 @@ void DrawHighlight::run(const render::RenderContextPointer& renderContext, const { auto& shaderParameters = _configuration.edit(); - shaderParameters._color = highlight._style.color; - shaderParameters._intensity = highlight._style.outlineIntensity * (highlight._style.isOutlineSmooth ? 2.0f : 1.0f); - shaderParameters._unoccludedFillOpacity = highlight._style.unoccludedFillOpacity; - shaderParameters._occludedFillOpacity = highlight._style.occludedFillOpacity; - shaderParameters._threshold = highlight._style.isOutlineSmooth ? 1.0f : 1e-3f; - shaderParameters._blurKernelSize = std::min(7, std::max(2, (int)floorf(highlight._style.outlineWidth * 3 + 0.5f))); + shaderParameters._outlineUnoccludedColor = highlight._style._outlineUnoccluded.color; + shaderParameters._outlineUnoccludedAlpha = highlight._style._outlineUnoccluded.alpha * (highlight._style._isOutlineSmooth ? 2.0f : 1.0f); + shaderParameters._outlineOccludedColor = highlight._style._outlineOccluded.color; + shaderParameters._outlineOccludedAlpha = highlight._style._outlineOccluded.alpha * (highlight._style._isOutlineSmooth ? 2.0f : 1.0f); + shaderParameters._fillUnoccludedColor = highlight._style._fillUnoccluded.color; + shaderParameters._fillUnoccludedAlpha = highlight._style._fillUnoccluded.alpha; + shaderParameters._fillOccludedColor = highlight._style._fillOccluded.color; + shaderParameters._fillOccludedAlpha = highlight._style._fillOccluded.alpha; + + shaderParameters._threshold = highlight._style._isOutlineSmooth ? 1.0f : 1e-3f; + shaderParameters._blurKernelSize = std::min(7, std::max(2, (int)floorf(highlight._style._outlineWidth * 3 + 0.5f))); // Size is in normalized screen height. We decide that for highlight width = 1, this is equal to 1/400. - auto size = highlight._style.outlineWidth / 400.0f; + auto size = highlight._style._outlineWidth / 400.0f; shaderParameters._size.x = (size * framebufferSize.y) / framebufferSize.x; shaderParameters._size.y = size; } @@ -432,19 +437,20 @@ void SelectionToHighlight::run(const render::RenderContextPointer& renderContext outputs.clear(); _sharedParameters->_highlightIds.fill(render::HighlightStage::INVALID_INDEX); - for (auto i = 0; i < HighlightSharedParameters::MAX_PASS_COUNT; i++) { - std::ostringstream stream; - if (i > 0) { - stream << "highlightList" << i; - } else { - stream << "contextOverlayHighlightList"; - } - auto selectionName = stream.str(); - if (!scene->isSelectionEmpty(selectionName)) { - auto highlightId = highlightStage->getHighlightIdBySelection(selectionName); - if (!render::HighlightStage::isIndexInvalid(highlightId)) { - _sharedParameters->_highlightIds[outputs.size()] = highlightId; - outputs.emplace_back(selectionName); + int numLayers = 0; + auto highlightList = highlightStage->getActiveHighlightIds(); + + for (auto styleId : highlightList) { + auto highlight = highlightStage->getHighlight(styleId); + + if (!scene->isSelectionEmpty(highlight._selectionName)) { + auto highlightId = highlightStage->getHighlightIdBySelection(highlight._selectionName); + _sharedParameters->_highlightIds[outputs.size()] = highlightId; + outputs.emplace_back(highlight._selectionName); + numLayers++; + + if (numLayers == HighlightSharedParameters::MAX_PASS_COUNT) { + break; } } } diff --git a/libraries/render-utils/src/Highlight_shared.slh b/libraries/render-utils/src/Highlight_shared.slh index 5efbde4d52..edc51e4ecb 100644 --- a/libraries/render-utils/src/Highlight_shared.slh +++ b/libraries/render-utils/src/Highlight_shared.slh @@ -11,17 +11,18 @@ struct HighlightParameters { - TVEC3 _color; - float _intensity; + TVEC3 _outlineUnoccludedColor; + float _outlineUnoccludedAlpha; + TVEC3 _outlineOccludedColor; + float _outlineOccludedAlpha; + TVEC3 _fillUnoccludedColor; + float _fillUnoccludedAlpha; + TVEC3 _fillOccludedColor; + float _fillOccludedAlpha; - TVEC2 _size; - float _unoccludedFillOpacity; - float _occludedFillOpacity; - - float _threshold; int _blurKernelSize; - float padding2; - float padding3; + float _threshold; + TVEC2 _size; }; // <@if 1@> diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index ba705e56cb..bf2dce85e8 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -180,7 +180,7 @@ LightStage::LightPointer LightStage::removeLight(Index index) { _lightMap.erase(removedLight); _descs[index] = Desc(); } - assert(_descs.size() <= index || _descs[index].shadowId == INVALID_INDEX); + assert(_descs.size() <= (size_t)index || _descs[index].shadowId == INVALID_INDEX); return removedLight; } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 428fcc7a54..c4bc435691 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -163,7 +163,7 @@ void Model::setScale(const glm::vec3& scale) { _scaledToFit = false; } -const float SCALE_CHANGE_EPSILON = 0.01f; +const float SCALE_CHANGE_EPSILON = 0.001f; void Model::setScaleInternal(const glm::vec3& scale) { if (glm::distance(_scale, scale) > SCALE_CHANGE_EPSILON) { @@ -286,6 +286,7 @@ void Model::reset() { if (isLoaded()) { const FBXGeometry& geometry = getFBXGeometry(); _rig.reset(geometry); + emit rigReset(); } } @@ -322,6 +323,7 @@ bool Model::updateGeometry() { _blendedVertexBuffers.push_back(buffer); } needFullUpdate = true; + emit rigReady(); } return needFullUpdate; } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index c537a928b3..7568a17342 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -204,6 +204,9 @@ public: /// Returns the extents of the model's mesh Extents getMeshExtents() const; + /// Returns the unscaled extents of the model's mesh + Extents getUnscaledMeshExtents() const; + void setTranslation(const glm::vec3& translation); void setRotation(const glm::quat& rotation); void setTransformNoUpdateRenderItems(const Transform& transform); // temporary HACK @@ -270,15 +273,14 @@ signals: void setURLFinished(bool success); void setCollisionModelURLFinished(bool success); void requestRenderUpdate(); + void rigReady(); + void rigReset(); protected: void setBlendshapeCoefficients(const QVector& coefficients) { _blendshapeCoefficients = coefficients; } const QVector& getBlendshapeCoefficients() const { return _blendshapeCoefficients; } - /// Returns the unscaled extents of the model's mesh - Extents getUnscaledMeshExtents() const; - /// Clear the joint states void clearJointState(int index); diff --git a/libraries/render/src/render/HighlightStage.cpp b/libraries/render/src/render/HighlightStage.cpp index ade3844321..c9f097b387 100644 --- a/libraries/render/src/render/HighlightStage.cpp +++ b/libraries/render/src/render/HighlightStage.cpp @@ -61,42 +61,42 @@ void HighlightStageConfig::setSelectionName(const QString& name) { } void HighlightStageConfig::setOutlineSmooth(bool isSmooth) { - editStyle().isOutlineSmooth = isSmooth; + editStyle()._isOutlineSmooth = isSmooth; emit dirty(); } void HighlightStageConfig::setColorRed(float value) { - editStyle().color.r = value; + editStyle()._outlineUnoccluded.color.r = value; emit dirty(); } void HighlightStageConfig::setColorGreen(float value) { - editStyle().color.g = value; + editStyle()._outlineUnoccluded.color.g = value; emit dirty(); } void HighlightStageConfig::setColorBlue(float value) { - editStyle().color.b = value; + editStyle()._outlineUnoccluded.color.b = value; emit dirty(); } void HighlightStageConfig::setOutlineWidth(float value) { - editStyle().outlineWidth = value; + editStyle()._outlineWidth = value; emit dirty(); } void HighlightStageConfig::setOutlineIntensity(float value) { - editStyle().outlineIntensity = value; + editStyle()._outlineUnoccluded.alpha = value; emit dirty(); } void HighlightStageConfig::setUnoccludedFillOpacity(float value) { - editStyle().unoccludedFillOpacity = value; + editStyle()._fillUnoccluded.alpha = value; emit dirty(); } void HighlightStageConfig::setOccludedFillOpacity(float value) { - editStyle().occludedFillOpacity = value; + editStyle()._fillOccluded.alpha = value; emit dirty(); } diff --git a/libraries/render/src/render/HighlightStage.h b/libraries/render/src/render/HighlightStage.h index b35fff654c..5e6574840f 100644 --- a/libraries/render/src/render/HighlightStage.h +++ b/libraries/render/src/render/HighlightStage.h @@ -51,6 +51,7 @@ namespace render { HighlightIdList::iterator begin() { return _activeHighlightIds.begin(); } HighlightIdList::iterator end() { return _activeHighlightIds.end(); } + const HighlightIdList& getActiveHighlightIds() const { return _activeHighlightIds; } private: @@ -82,28 +83,28 @@ namespace render { QString getSelectionName() const { return QString(_selectionName.c_str()); } void setSelectionName(const QString& name); - bool isOutlineSmooth() const { return getStyle().isOutlineSmooth; } + bool isOutlineSmooth() const { return getStyle()._isOutlineSmooth; } void setOutlineSmooth(bool isSmooth); - float getColorRed() const { return getStyle().color.r; } + float getColorRed() const { return getStyle()._outlineUnoccluded.color.r; } void setColorRed(float value); - float getColorGreen() const { return getStyle().color.g; } + float getColorGreen() const { return getStyle()._outlineUnoccluded.color.g; } void setColorGreen(float value); - float getColorBlue() const { return getStyle().color.b; } + float getColorBlue() const { return getStyle()._outlineUnoccluded.color.b; } void setColorBlue(float value); - float getOutlineWidth() const { return getStyle().outlineWidth; } + float getOutlineWidth() const { return getStyle()._outlineWidth; } void setOutlineWidth(float value); - float getOutlineIntensity() const { return getStyle().outlineIntensity; } + float getOutlineIntensity() const { return getStyle()._outlineUnoccluded.alpha; } void setOutlineIntensity(float value); - float getUnoccludedFillOpacity() const { return getStyle().unoccludedFillOpacity; } + float getUnoccludedFillOpacity() const { return getStyle()._fillUnoccluded.alpha; } void setUnoccludedFillOpacity(float value); - float getOccludedFillOpacity() const { return getStyle().occludedFillOpacity; } + float getOccludedFillOpacity() const { return getStyle()._fillOccluded.alpha; } void setOccludedFillOpacity(float value); std::string _selectionName{ "contextOverlayHighlightList" }; diff --git a/libraries/render/src/render/HighlightStyle.h b/libraries/render/src/render/HighlightStyle.h index 6e7373c78b..8bef5c33c3 100644 --- a/libraries/render/src/render/HighlightStyle.h +++ b/libraries/render/src/render/HighlightStyle.h @@ -20,17 +20,24 @@ namespace render { // This holds the configuration for a particular outline style class HighlightStyle { public: + struct RGBA { + glm::vec3 color{ 1.0f, 0.7f, 0.2f }; + float alpha{ 0.9f }; + + RGBA(const glm::vec3& c, float a) : color(c), alpha(a) {} + }; + + RGBA _outlineUnoccluded{ { 1.0f, 0.7f, 0.2f }, 0.9f }; + RGBA _outlineOccluded{ { 1.0f, 0.7f, 0.2f }, 0.9f }; + RGBA _fillUnoccluded{ { 0.2f, 0.7f, 1.0f }, 0.0f }; + RGBA _fillOccluded{ { 0.2f, 0.7f, 1.0f }, 0.0f }; + + float _outlineWidth{ 2.0f }; + bool _isOutlineSmooth{ false }; bool isFilled() const { - return unoccludedFillOpacity > 5e-3f || occludedFillOpacity > 5e-3f; + return _fillUnoccluded.alpha > 5e-3f || _fillOccluded.alpha > 5e-3f; } - - glm::vec3 color{ 1.f, 0.7f, 0.2f }; - float outlineWidth{ 2.0f }; - float outlineIntensity{ 0.9f }; - float unoccludedFillOpacity{ 0.0f }; - float occludedFillOpacity{ 0.0f }; - bool isOutlineSmooth{ false }; }; } diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index 88e25b6d27..bc75e5ad21 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -32,23 +32,23 @@ void Transaction::removeItem(ItemID id) { } void Transaction::addTransitionToItem(ItemID id, Transition::Type transition, ItemID boundId) { - _addedTransitions.emplace_back(TransitionAdd{ id, transition, boundId }); + _addedTransitions.emplace_back(id, transition, boundId); } void Transaction::removeTransitionFromItem(ItemID id) { - _addedTransitions.emplace_back(TransitionAdd{ id, Transition::NONE, render::Item::INVALID_ITEM_ID }); + _addedTransitions.emplace_back(id, Transition::NONE, render::Item::INVALID_ITEM_ID); } void Transaction::reApplyTransitionToItem(ItemID id) { - _reAppliedTransitions.emplace_back(TransitionReApply{ id }); + _reAppliedTransitions.emplace_back(id); } void Transaction::queryTransitionOnItem(ItemID id, TransitionQueryFunc func) { - _queriedTransitions.emplace_back(TransitionQuery{ id, func }); + _queriedTransitions.emplace_back(id, func); } void Transaction::updateItem(ItemID id, const UpdateFunctorPointer& functor) { - _updatedItems.emplace_back(Update{ id, functor }); + _updatedItems.emplace_back(id, functor); } void Transaction::resetSelection(const Selection& selection) { @@ -56,28 +56,122 @@ void Transaction::resetSelection(const Selection& selection) { } void Transaction::resetSelectionHighlight(const std::string& selectionName, const HighlightStyle& style) { - _highlightResets.emplace_back(HighlightReset{ selectionName, style }); + _highlightResets.emplace_back(selectionName, style ); } void Transaction::removeHighlightFromSelection(const std::string& selectionName) { _highlightRemoves.emplace_back(selectionName); } -void Transaction::querySelectionHighlight(const std::string& selectionName, SelectionHighlightQueryFunc func) { - _highlightQueries.emplace_back(HighlightQuery{ selectionName, func }); +void Transaction::querySelectionHighlight(const std::string& selectionName, const SelectionHighlightQueryFunc& func) { + _highlightQueries.emplace_back(selectionName, func); +} + +void Transaction::reserve(const std::vector& transactionContainer) { + size_t resetItemsCount = 0; + size_t removedItemsCount = 0; + size_t updatedItemsCount = 0; + size_t resetSelectionsCount = 0; + size_t addedTransitionsCount = 0; + size_t queriedTransitionsCount = 0; + size_t reAppliedTransitionsCount = 0; + size_t highlightResetsCount = 0; + size_t highlightRemovesCount = 0; + size_t highlightQueriesCount = 0; + + for (const auto& transaction : transactionContainer) { + resetItemsCount += transaction._resetItems.size(); + removedItemsCount += transaction._removedItems.size(); + updatedItemsCount += transaction._updatedItems.size(); + resetSelectionsCount += transaction._resetSelections.size(); + addedTransitionsCount += transaction._addedTransitions.size(); + queriedTransitionsCount += transaction._queriedTransitions.size(); + reAppliedTransitionsCount += transaction._reAppliedTransitions.size(); + highlightResetsCount += transaction._highlightResets.size(); + highlightRemovesCount += transaction._highlightRemoves.size(); + highlightQueriesCount += transaction._highlightQueries.size(); + } + + _resetItems.reserve(resetItemsCount); + _removedItems.reserve(removedItemsCount); + _updatedItems.reserve(updatedItemsCount); + _resetSelections.reserve(resetSelectionsCount); + _addedTransitions.reserve(addedTransitionsCount); + _queriedTransitions.reserve(queriedTransitionsCount); + _reAppliedTransitions.reserve(reAppliedTransitionsCount); + _highlightResets.reserve(highlightResetsCount); + _highlightRemoves.reserve(highlightRemovesCount); + _highlightQueries.reserve(highlightQueriesCount); +} + +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::merge(Transaction&& transaction) { + moveElements(_resetItems, transaction._resetItems); + moveElements(_removedItems, transaction._removedItems); + moveElements(_updatedItems, transaction._updatedItems); + moveElements(_resetSelections, transaction._resetSelections); + moveElements(_addedTransitions, transaction._addedTransitions); + moveElements(_queriedTransitions, transaction._queriedTransitions); + moveElements(_reAppliedTransitions, transaction._reAppliedTransitions); + moveElements(_highlightResets, transaction._highlightResets); + moveElements(_highlightRemoves, transaction._highlightRemoves); + moveElements(_highlightQueries, transaction._highlightQueries); } void Transaction::merge(const Transaction& transaction) { - _resetItems.insert(_resetItems.end(), transaction._resetItems.begin(), transaction._resetItems.end()); - _removedItems.insert(_removedItems.end(), transaction._removedItems.begin(), transaction._removedItems.end()); - _updatedItems.insert(_updatedItems.end(), transaction._updatedItems.begin(), transaction._updatedItems.end()); - _resetSelections.insert(_resetSelections.end(), transaction._resetSelections.begin(), transaction._resetSelections.end()); - _addedTransitions.insert(_addedTransitions.end(), transaction._addedTransitions.begin(), transaction._addedTransitions.end()); - _queriedTransitions.insert(_queriedTransitions.end(), transaction._queriedTransitions.begin(), transaction._queriedTransitions.end()); - _reAppliedTransitions.insert(_reAppliedTransitions.end(), transaction._reAppliedTransitions.begin(), transaction._reAppliedTransitions.end()); - _highlightResets.insert(_highlightResets.end(), transaction._highlightResets.begin(), transaction._highlightResets.end()); - _highlightRemoves.insert(_highlightRemoves.end(), transaction._highlightRemoves.begin(), transaction._highlightRemoves.end()); - _highlightQueries.insert(_highlightQueries.end(), transaction._highlightQueries.begin(), transaction._highlightQueries.end()); + copyElements(_resetItems, transaction._resetItems); + copyElements(_removedItems, transaction._removedItems); + copyElements(_updatedItems, transaction._updatedItems); + copyElements(_resetSelections, transaction._resetSelections); + copyElements(_addedTransitions, transaction._addedTransitions); + copyElements(_queriedTransitions, transaction._queriedTransitions); + copyElements(_reAppliedTransitions, transaction._reAppliedTransitions); + copyElements(_highlightResets, transaction._highlightResets); + copyElements(_highlightRemoves, transaction._highlightRemoves); + copyElements(_highlightQueries, transaction._highlightQueries); +} + +void Transaction::clear() { + _resetItems.clear(); + _removedItems.clear(); + _updatedItems.clear(); + _resetSelections.clear(); + _addedTransitions.clear(); + _queriedTransitions.clear(); + _reAppliedTransitions.clear(); + _highlightResets.clear(); + _highlightRemoves.clear(); + _highlightQueries.clear(); } @@ -102,54 +196,50 @@ bool Scene::isAllocatedID(const ItemID& id) const { /// Enqueue change batch to the scene void Scene::enqueueTransaction(const Transaction& transaction) { - _transactionQueueMutex.lock(); - _transactionQueue.push(transaction); - _transactionQueueMutex.unlock(); + std::unique_lock lock(_transactionQueueMutex); + _transactionQueue.emplace_back(transaction); } -void consolidateTransaction(TransactionQueue& queue, Transaction& singleBatch) { - while (!queue.empty()) { - const auto& transaction = queue.front(); - singleBatch.merge(transaction); - queue.pop(); - }; +void Scene::enqueueTransaction(Transaction&& transaction) { + std::unique_lock lock(_transactionQueueMutex); + _transactionQueue.emplace_back(std::move(transaction)); } uint32_t Scene::enqueueFrame() { PROFILE_RANGE(render, __FUNCTION__); - Transaction consolidatedTransaction; + TransactionQueue localTransactionQueue; { std::unique_lock lock(_transactionQueueMutex); - consolidateTransaction(_transactionQueue, consolidatedTransaction); + localTransactionQueue.swap(_transactionQueue); } - uint32_t frameNumber = 0; + Transaction consolidatedTransaction; + consolidatedTransaction.merge(std::move(localTransactionQueue)); { std::unique_lock lock(_transactionFramesMutex); _transactionFrames.push_back(consolidatedTransaction); - _transactionFrameNumber++; - frameNumber = _transactionFrameNumber; } - return frameNumber; + return ++_transactionFrameNumber; } void Scene::processTransactionQueue() { PROFILE_RANGE(render, __FUNCTION__); - TransactionFrames queuedFrames; + static TransactionFrames queuedFrames; { // capture the queued frames and clear the queue std::unique_lock lock(_transactionFramesMutex); - queuedFrames = _transactionFrames; - _transactionFrames.clear(); + queuedFrames.swap(_transactionFrames); } // go through the queue of frames and process them for (auto& frame : queuedFrames) { processTransactionFrame(frame); } + + queuedFrames.clear(); } void Scene::processTransactionFrame(const Transaction& transaction) { @@ -442,7 +532,7 @@ bool Scene::isSelectionEmpty(const Selection::Name& name) const { std::unique_lock lock(_selectionsMutex); auto found = _selections.find(name); if (found == _selections.end()) { - return false; + return true; } else { return (*found).second.isEmpty(); } diff --git a/libraries/render/src/render/Scene.h b/libraries/render/src/render/Scene.h index af6204acb4..2d8bc7f4dd 100644 --- a/libraries/render/src/render/Scene.h +++ b/libraries/render/src/render/Scene.h @@ -65,9 +65,14 @@ public: void resetSelectionHighlight(const std::string& selectionName, const HighlightStyle& style = HighlightStyle()); void removeHighlightFromSelection(const std::string& selectionName); - void querySelectionHighlight(const std::string& selectionName, SelectionHighlightQueryFunc func); + void querySelectionHighlight(const std::string& selectionName, const SelectionHighlightQueryFunc& func); + 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(); // Checkers if there is work to do when processing the transaction bool touchTransactions() const { return !_resetSelections.empty(); } @@ -107,7 +112,7 @@ protected: HighlightRemoves _highlightRemoves; HighlightQueries _highlightQueries; }; -typedef std::queue TransactionQueue; +typedef std::vector TransactionQueue; // Scene is a container for Items @@ -133,6 +138,9 @@ public: // Enqueue transaction to the scene void enqueueTransaction(const Transaction& transaction); + // Enqueue transaction to the scene + void enqueueTransaction(Transaction&& transaction); + // Enqueue end of frame transactions boundary uint32_t enqueueFrame(); @@ -187,7 +195,7 @@ protected: std::mutex _transactionFramesMutex; - using TransactionFrames = std::list; + using TransactionFrames = std::vector; TransactionFrames _transactionFrames; uint32_t _transactionFrameNumber{ 0 }; diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index a7a80471be..4942c63e27 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -12,6 +12,8 @@ #ifndef hifi_AvatarConstants_h #define hifi_AvatarConstants_h +#include "GLMHelpers.h" + // 50th Percentile Man const float DEFAULT_AVATAR_HEIGHT = 1.755f; // meters const float DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD = 0.11f; // meters @@ -52,5 +54,10 @@ const float DEFAULT_AVATAR_JUMP_HEIGHT = (DEFAULT_AVATAR_JUMP_SPEED * DEFAULT_AV const float DEFAULT_AVATAR_FALL_HEIGHT = 20.0f; // meters const float DEFAULT_AVATAR_MIN_HOVER_HEIGHT = 2.5f; // meters +static const float MAX_AVATAR_SCALE = 1000.0f; +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 #endif // hifi_AvatarConstants_h diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index 39fec45d90..ff1d29eed1 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -432,6 +432,12 @@ glm::vec3 toGlm(const xColor& color) { return glm::vec3(color.red, color.green, color.blue) / MAX_COLOR; } +xColor xColorFromGlm(const glm::vec3 & color) { + static const float MAX_COLOR = 255.0f; + return { (uint8_t)(color.x * MAX_COLOR), (uint8_t)(color.y * MAX_COLOR), (uint8_t)(color.z * MAX_COLOR) }; +} + + glm::vec4 toGlm(const QColor& color) { return glm::vec4(color.redF(), color.greenF(), color.blueF(), color.alphaF()); } diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 7248f4cb46..973998b927 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -177,6 +177,8 @@ vec4 toGlm(const QColor& color); ivec4 toGlm(const QRect& rect); vec4 toGlm(const xColor& color, float alpha); +xColor xColorFromGlm(const glm::vec3 & c); + QSize fromGlm(const glm::ivec2 & v); QMatrix4x4 fromGlm(const glm::mat4 & m); diff --git a/libraries/shared/src/PathUtils.cpp b/libraries/shared/src/PathUtils.cpp index 46613f1283..6f2e7d8b19 100644 --- a/libraries/shared/src/PathUtils.cpp +++ b/libraries/shared/src/PathUtils.cpp @@ -19,8 +19,14 @@ #include #include #include +#include #include // std::once #include "shared/GlobalAppProperties.h" +#include "SharedUtil.h" + +// Format: AppName-PID-Timestamp +// Example: ... +QString TEMP_DIR_FORMAT { "%1-%2-%3" }; const QString& PathUtils::resourcesPath() { #ifdef Q_OS_MAC @@ -60,7 +66,8 @@ QString PathUtils::generateTemporaryDir() { QString appName = qApp->applicationName(); for (auto i = 0; i < 64; ++i) { auto now = std::chrono::system_clock::now().time_since_epoch().count(); - QDir tempDir = rootTempDir.filePath(appName + "-" + QString::number(now)); + auto dirName = TEMP_DIR_FORMAT.arg(appName).arg(qApp->applicationPid()).arg(now); + QDir tempDir = rootTempDir.filePath(dirName); if (tempDir.mkpath(".")) { return tempDir.absolutePath(); } @@ -68,6 +75,39 @@ QString PathUtils::generateTemporaryDir() { return ""; } +// Delete all temporary directories for an application +int PathUtils::removeTemporaryApplicationDirs(QString appName) { + if (appName.isNull()) { + appName = qApp->applicationName(); + } + + auto dirName = TEMP_DIR_FORMAT.arg(appName).arg("*").arg("*"); + + QDir rootTempDir = QDir::tempPath(); + auto dirs = rootTempDir.entryInfoList({ dirName }, QDir::Dirs); + int removed = 0; + for (auto& dir : dirs) { + auto dirName = dir.fileName(); + auto absoluteDirPath = QDir(dir.absoluteFilePath()); + QRegularExpression re { "^" + QRegularExpression::escape(appName) + "\\-(?\\d+)\\-(?\\d+)$" }; + + auto match = re.match(dirName); + if (match.hasMatch()) { + auto pid = match.capturedRef("pid").toLongLong(); + auto timestamp = match.capturedRef("timestamp"); + if (!processIsRunning(pid)) { + qDebug() << " Removing old temporary directory: " << dir.absoluteFilePath(); + absoluteDirPath.removeRecursively(); + removed++; + } else { + qDebug() << " Not removing (process is running): " << dir.absoluteFilePath(); + } + } + } + + return removed; +} + QString fileNameWithoutExtension(const QString& fileName, const QVector possibleExtensions) { QString fileNameLowered = fileName.toLower(); foreach (const QString possibleExtension, possibleExtensions) { diff --git a/libraries/shared/src/PathUtils.h b/libraries/shared/src/PathUtils.h index 8c4bcf2394..f3ba5910c4 100644 --- a/libraries/shared/src/PathUtils.h +++ b/libraries/shared/src/PathUtils.h @@ -39,6 +39,8 @@ public: static QString generateTemporaryDir(); + static int removeTemporaryApplicationDirs(QString appName = QString::null); + static Qt::CaseSensitivity getFSCaseSensitivity(); static QString stripFilename(const QUrl& url); // note: this is FS-case-sensitive version of parentURL.isParentOf(childURL) diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 1d11a04265..dc6a877bb9 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -12,6 +12,9 @@ #define hifi_PrioritySortUtil_h #include +#include + +#include "NumericalConstants.h" #include "ViewFrustum.h" /* PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. To use: @@ -32,11 +35,10 @@ (2) Make a PrioritySortUtil::PriorityQueue and add them to the queue: - PrioritySortUtil::Prioritizer prioritizer(viewFrustum); + PrioritySortUtil::PriorityQueue sortedThings(viewFrustum); std::priority_queue< PrioritySortUtil::Sortable > sortedThings; for (thing in things) { - float priority = prioritizer.computePriority(PrioritySortUtil::PrioritizableThing(thing)); - sortedThings.push(PrioritySortUtil::Sortable entry(thing, priority)); + sortedThings.push(SortableWrapper(thing)); } (3) Loop over your priority queue and do timeboxed work: @@ -65,6 +67,7 @@ namespace PrioritySortUtil { virtual uint64_t getTimestamp() const = 0; void setPriority(float priority) { _priority = priority; } + float getPriority() const { return _priority; } bool operator<(const Sortable& other) const { return _priority < other._priority; } private: float _priority { 0.0f }; @@ -109,11 +112,19 @@ namespace PrioritySortUtil { glm::vec3 position = thing.getPosition(); glm::vec3 offset = position - _view.getPosition(); float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero - float radius = thing.getRadius(); + const float MIN_RADIUS = 0.1f; // WORKAROUND for zero size objects (we still want them to sort by distance) + float radius = glm::min(thing.getRadius(), MIN_RADIUS); + float cosineAngle = (glm::dot(offset, _view.getDirection()) / distance); + float age = (float)(usecTimestampNow() - thing.getTimestamp()); - float priority = _angularWeight * (radius / distance) - + _centerWeight * (glm::dot(offset, _view.getDirection()) / distance) - + _ageWeight * (float)(usecTimestampNow() - thing.getTimestamp()); + // we modulatate "age" drift rate by the cosineAngle term to make periphrial objects sort forward + // at a reduced rate but we don't want the "age" term to go zero or negative so we clamp it + const float MIN_COSINE_ANGLE_FACTOR = 0.1f; + float cosineAngleFactor = glm::max(cosineAngle, MIN_COSINE_ANGLE_FACTOR); + + float priority = _angularWeight * glm::max(radius, MIN_RADIUS) / distance + + _centerWeight * cosineAngle + + _ageWeight * cosineAngleFactor * age; // decrement priority of things outside keyhole if (distance - radius > _view.getCenterRadius()) { diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 38a7a3165f..2d2ec7c28f 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -44,6 +44,12 @@ extern "C" FILE * __cdecl __iob_func(void) { #include #endif + +#if defined(Q_OS_LINUX) || defined(Q_OS_MAC) +#include +#include +#endif + #include #include #include @@ -1078,6 +1084,24 @@ void setMaxCores(uint8_t maxCores) { #endif } +bool processIsRunning(int64_t pid) { +#ifdef Q_OS_WIN + HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid); + if (process) { + DWORD exitCode; + if (GetExitCodeProcess(process, &exitCode) != 0) { + return exitCode == STILL_ACTIVE; + } + } + return false; +#else + if (kill(pid, 0) == -1) { + return errno != ESRCH; + } + return true; +#endif +} + void quitWithParentProcess() { if (qApp) { qDebug() << "Parent process died, quitting"; diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 25051d45ac..6cf5a4755d 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -238,6 +238,8 @@ void setMaxCores(uint8_t maxCores); const QString PARENT_PID_OPTION = "parent-pid"; void watchParentProcess(int parentPID); +bool processIsRunning(int64_t pid); + #ifdef Q_OS_WIN void* createProcessGroup(); diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 9a591018f5..942c9f71a5 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -1149,6 +1149,7 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QT touchEvent.setTarget(_rootItem); touchEvent.setTouchPoints(touchPoints); touchEvent.setTouchPointStates(touchPointStates); + touchEvent.setTimestamp((ulong)QDateTime::currentMSecsSinceEpoch()); touchEvent.ignore(); } diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 8889e58f0c..c58e97cc06 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -337,6 +337,7 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle _poseStateMap.clear(); _buttonPressedMap.clear(); _validTrackedObjects.clear(); + _trackedControllers = 0; // While the keyboard is open, we defer strictly to the keyboard values if (isOpenVrKeyboardShown()) { @@ -369,14 +370,12 @@ void ViveControllerManager::InputDevice::update(float deltaTime, const controlle } } - int numTrackedControllers = 0; if (leftHandDeviceIndex != vr::k_unTrackedDeviceIndexInvalid) { - numTrackedControllers++; + _trackedControllers++; } if (rightHandDeviceIndex != vr::k_unTrackedDeviceIndexInvalid) { - numTrackedControllers++; + _trackedControllers++; } - _trackedControllers = numTrackedControllers; calibrateFromHandController(inputCalibrationData); calibrateFromUI(inputCalibrationData); @@ -527,6 +526,7 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde // but _validTrackedObjects remain in sensor frame _validTrackedObjects.push_back(std::make_pair(poseIndex, pose)); + _trackedControllers++; } else { controller::Pose invalidPose; _poseStateMap[poseIndex] = invalidPose; @@ -758,9 +758,9 @@ void ViveControllerManager::InputDevice::handleHmd(uint32_t deviceIndex, const c } else { const mat4& mat = mat4(); const vec3 zero = vec3(); - handleHeadPoseEvent(inputCalibrationData, mat, zero, zero); } + _trackedControllers++; } } diff --git a/scripts/developer/utilities/audio/audioScope.js b/scripts/developer/utilities/audio/audioScope.js new file mode 100644 index 0000000000..00c9e4b725 --- /dev/null +++ b/scripts/developer/utilities/audio/audioScope.js @@ -0,0 +1,17 @@ +var qml = Script.resourcesPath() + '/qml/AudioScope.qml'; +var window = new OverlayWindow({ + title: 'Audio Scope', + source: qml, + width: 1200, + height: 500 +}); +window.closed.connect(function () { + if (Audio.getRecording()) { + Audio.stopRecording(); + } + AudioScope.setVisible(false); + AudioScope.setLocalEcho(false); + AudioScope.setServerEcho(false); + AudioScope.selectAudioScopeFiveFrames(); + Script.stop(); +}); \ No newline at end of file diff --git a/scripts/developer/utilities/lib/plotperf/Color.qml b/scripts/developer/utilities/lib/plotperf/Color.qml new file mode 100644 index 0000000000..15d7f9fcc9 --- /dev/null +++ b/scripts/developer/utilities/lib/plotperf/Color.qml @@ -0,0 +1,212 @@ +// +// Color.qml +// +// Created by Sam Gateau 12/4/2017 +// Copyright 2017 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 as Original +import QtQuick.Controls.Styles 1.4 + +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls + + +Item { + HifiConstants { id: hifi } + id: root + + height: 24 + + property var _color: Qt.rgba(1.0, 1.0, 1.0, 1.0 ); + property var zoneWidth: width / 3; + property var hoveredOn: 0.0; + property var sliderHeight: height / 2; + + signal newColor(color __color) + + function setColor(color) { + _color = Qt.rgba(color.r, color.g, color.b, 1.0) + updateColor() + } + function setRed(r) { + _color.r = r; + updateColor() + } + function setGreen(g) { + _color.g = g; + updateColor() + } + function setBlue(b) { + _color.b = b; + updateColor() + } + + function updateColor() { + repaint() + newColor(_color) + } + function repaint() { + current.color = _color + } + + function resetSliders() { + redZone.set(_color.r) + greenZone.set(_color.g) + blueZone.set(_color.b) + } + + function setXColor(xcolor) { + setColor(Qt.rgba(xcolor.red/255, xcolor.green/255, color.blue/255, 1.0)) + } + function getXColor() { + return {red:_color.r * 255, green:_color.g * 255, blue:_color.b * 255} + } + + Rectangle { + id: current + anchors.fill: root + color: root._color; + } + Rectangle { + id: sliderBack + height: root.sliderHeight + anchors.bottom: root.bottom + anchors.left: root.left + anchors.right: root.right + color: Qt.rgba(0.2, 0.2, 0.2, 1) + opacity: root.hoveredOn * 0.5 + } + + MouseArea { + id: all + anchors.fill: root + hoverEnabled: true + onEntered: { + root.hoveredOn = 1.0; + resetSliders(); + } + onExited: { + root.hoveredOn = 0.0; + } + } + + Component.onCompleted: { + } + + Item { + id: redZone + anchors.top: root.top + anchors.bottom: root.bottom + anchors.left: root.left + width: root.zoneWidth + + function update(r) { + if (r < 0.0) { + r = 0.0 + } else if (r > 1.0) { + r = 1.0 + } + root.setRed(r) + set(r) + } + function set(r) { + redRect.width = r * redZone.width + redRect.color = Qt.rgba(r, 0, 0, 1) + } + + Rectangle { + id: redRect + anchors.bottom: parent.bottom + anchors.left: parent.left + height: root.sliderHeight + opacity: root.hoveredOn + } + + MouseArea { + id: redArea + anchors.fill: parent + onPositionChanged: { + redZone.update(mouse.x / redArea.width) + } + } + } + Item { + id: greenZone + anchors.top: root.top + anchors.bottom: root.bottom + anchors.horizontalCenter: root.horizontalCenter + width: root.zoneWidth + + function update(g) { + if (g < 0.0) { + g = 0.0 + } else if (g > 1.0) { + g = 1.0 + } + root.setGreen(g) + set(g) + } + function set(g) { + greenRect.width = g * greenZone.width + greenRect.color = Qt.rgba(0, g, 0, 1) + } + + Rectangle { + id: greenRect + anchors.bottom: parent.bottom + anchors.left: parent.left + height: root.sliderHeight + opacity: root.hoveredOn + } + + MouseArea { + id: greenArea + anchors.fill: parent + onPositionChanged: { + greenZone.update(mouse.x / greenArea.width) + } + } + } + Item { + id: blueZone + anchors.top: root.top + anchors.bottom: root.bottom + anchors.right: root.right + width: root.zoneWidth + + function update(b) { + if (b < 0.0) { + b = 0.0 + } else if (b > 1.0) { + b = 1.0 + } + root.setBlue(b) + set(b) + } + function set(b) { + blueRect.width = b * blueZone.width + blueRect.color = Qt.rgba(0, 0, b, 1) + } + + Rectangle { + id: blueRect + anchors.bottom: parent.bottom + anchors.left: parent.left + height: root.sliderHeight + opacity: root.hoveredOn + } + + MouseArea { + id: blueArea + anchors.fill: parent + onPositionChanged: { + blueZone.update(mouse.x / blueArea.width) + } + } + } +} diff --git a/scripts/developer/utilities/lib/plotperf/qmldir b/scripts/developer/utilities/lib/plotperf/qmldir index 5668f5034c..20b3fc9fcf 100644 --- a/scripts/developer/utilities/lib/plotperf/qmldir +++ b/scripts/developer/utilities/lib/plotperf/qmldir @@ -1 +1,2 @@ -PlotPerf 1.0 PlotPerf.qml \ No newline at end of file +PlotPerf 1.0 PlotPerf.qml +Color 1.0 Color.qml diff --git a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml index 830dc6de23..87e0e51726 100644 --- a/scripts/developer/utilities/render/configSlider/ConfigSlider.qml +++ b/scripts/developer/utilities/render/configSlider/ConfigSlider.qml @@ -30,6 +30,8 @@ Item { property alias min: sliderControl.minimumValue property alias max: sliderControl.maximumValue + signal valueChanged(real value) + Component.onCompleted: { // Binding favors qml value, so set it first sliderControl.value = root.config[root.property]; @@ -69,5 +71,7 @@ Item { anchors.rightMargin: 0 anchors.top: root.top anchors.topMargin: 0 + + onValueChanged: { root.valueChanged(value) } } } diff --git a/scripts/developer/utilities/render/debugHighlight.js b/scripts/developer/utilities/render/debugHighlight.js index 5175761978..e70565cec2 100644 --- a/scripts/developer/utilities/render/debugHighlight.js +++ b/scripts/developer/utilities/render/debugHighlight.js @@ -9,152 +9,162 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// Set up the qml ui -var qml = Script.resolvePath('highlight.qml'); -var window = new OverlayWindow({ - title: 'Highlight', - source: qml, - width: 400, - height: 400, -}); -window.closed.connect(function() { Script.stop(); }); - "use strict"; -// Created by Sam Gondelman on 9/7/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 +(function() { + var TABLET_BUTTON_NAME = "Highlight"; + var QMLAPP_URL = Script.resolvePath("./highlight.qml"); + var ICON_URL = Script.resolvePath("../../../system/assets/images/luci-i.svg"); + var ACTIVE_ICON_URL = Script.resolvePath("../../../system/assets/images/luci-a.svg"); -(function() { // BEGIN LOCAL_SCOPE + + var onLuciScreen = false; -var END_DIMENSIONS = { - x: 0.15, - y: 0.15, - z: 0.15 -}; -var COLOR = {red: 97, green: 247, blue: 255}; -var end = { - type: "sphere", - dimensions: END_DIMENSIONS, - color: COLOR, - ignoreRayIntersection: true, - alpha: 1.0, - visible: true -} - -var COLOR2 = {red: 247, green: 97, blue: 255}; -var end2 = { - type: "sphere", - dimensions: END_DIMENSIONS, - color: COLOR2, - ignoreRayIntersection: true, - alpha: 1.0, - visible: true -} - -var highlightGroupIndex = 0 -var isSelectionAddEnabled = false -var isSelectionEnabled = false -var renderStates = [{name: "test", end: end}]; -var defaultRenderStates = [{name: "test", distance: 20.0, end: end2}]; -var time = 0 - -var ray = LaserPointers.createLaserPointer({ - joint: "Mouse", - filter: RayPick.PICK_ENTITIES | RayPick.PICK_OVERLAYS | RayPick.PICK_AVATARS | RayPick.PICK_INVISIBLE | RayPick.PICK_NONCOLLIDABLE, - renderStates: renderStates, - defaultRenderStates: defaultRenderStates, - enabled: false -}); - -function getSelectionName() { - var selectionName = "contextOverlayHighlightList" - - if (highlightGroupIndex>0) { - selectionName += highlightGroupIndex - } - return selectionName -} - -function fromQml(message) { - tokens = message.split(' ') - print("Received '"+message+"' from hightlight.qml") - if (tokens[0]=="highlight") { - highlightGroupIndex = parseInt(tokens[1]) - print("Switching to highlight group "+highlightGroupIndex) - } else if (tokens[0]=="pick") { - isSelectionEnabled = tokens[1]=='true' - print("Ray picking set to "+isSelectionEnabled.toString()) - if (isSelectionEnabled) { - LaserPointers.enableLaserPointer(ray) + function onClicked() { + if (onLuciScreen) { + tablet.gotoHomeScreen(); } else { - LaserPointers.disableLaserPointer(ray) - } - time = 0 - } else if (tokens[0]=="add") { - isSelectionAddEnabled = tokens[1]=='true' - print("Add to selection set to "+isSelectionAddEnabled.toString()) - if (!isSelectionAddEnabled) { - Selection.clearSelectedItemsList(getSelectionName()) + tablet.loadQMLSource(QMLAPP_URL); } } -} -window.fromQml.connect(fromQml); + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + text: TABLET_BUTTON_NAME, + icon: ICON_URL, + activeIcon: ACTIVE_ICON_URL, + sortOrder: 1 + }); -function cleanup() { - LaserPointers.removeLaserPointer(ray); -} -Script.scriptEnding.connect(cleanup); + var hasEventBridge = false; -var prevID = 0 -var prevType = "" -var selectedID = 0 -var selectedType = "" -function update(deltaTime) { - - // you have to do this repeatedly because there's a bug but I'll fix it - LaserPointers.setRenderState(ray, "test"); - - var result = LaserPointers.getPrevRayPickResult(ray); - var selectionName = getSelectionName() - - if (isSelectionEnabled && result.type != RayPick.INTERSECTED_NONE) { - time += deltaTime - if (result.objectID != prevID) { - var typeName = "" - if (result.type == RayPick.INTERSECTED_ENTITY) { - typeName = "entity" - } else if (result.type == RayPick.INTERSECTED_OVERLAY) { - typeName = "overlay" - } else if (result.type == RayPick.INTERSECTED_AVATAR) { - typeName = "avatar" + 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) { + onLuciScreen = true; + } else { + onLuciScreen = false; + } + + button.editProperties({isActive: onLuciScreen}); + wireEventBridge(onLuciScreen); + } + + button.clicked.connect(onClicked); + tablet.screenChanged.connect(onScreenChanged); + + Script.scriptEnding.connect(function () { + if (onLuciScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + tablet.removeButton(button); + }); + + // Create a Laser pointer used to pick and add objects to selections + var END_DIMENSIONS = { x: 0.05, y: 0.05, z: 0.05 }; + var COLOR1 = {red: 255, green: 0, blue: 0}; + var COLOR2 = {red: 0, green: 255, blue: 0}; + var end1 = { + type: "sphere", + dimensions: END_DIMENSIONS, + color: COLOR1, + ignoreRayIntersection: true + } + var end2 = { + type: "sphere", + dimensions: END_DIMENSIONS, + color: COLOR2, + ignoreRayIntersection: true + } + var laser = Pointers.createPointer(PickType.Ray, { + joint: "Mouse", + filter: Picks.PICK_ENTITIES, + renderStates: [{name: "one", end: end1}], + defaultRenderStates: [{name: "one", end: end2, distance: 2.0}], + enabled: true + }); + Pointers.setRenderState(laser, "one"); + + var HoveringList = "Hovering" + var hoveringStyle = { + isOutlineSmooth: true, + outlineWidth: 5, + outlineUnoccludedColor: {red: 255, green: 128, blue: 128}, + outlineUnoccludedAlpha: 0.88, + outlineOccludedColor: {red: 255, green: 128, blue: 128}, + outlineOccludedAlpha:0.5, + fillUnoccludedColor: {red: 26, green: 0, blue: 0}, + fillUnoccludedAlpha: 0.0, + fillOccludedColor: {red: 26, green: 0, blue: 0}, + fillOccludedAlpha: 0.0 + } + Selection.enableListHighlight(HoveringList, hoveringStyle) + + var currentSelectionName = "" + var isSelectionEnabled = false + Pointers.disablePointer(laser) + + function fromQml(message) { + tokens = message.split(' ') + print("Received '"+message+"' from hightlight.qml") + if (tokens[0]=="highlight") { + currentSelectionName = tokens[1]; + print("Switching to highlight name "+currentSelectionName) + } else if (tokens[0]=="pick") { + isSelectionEnabled = tokens[1]=='true' + print("Ray picking set to "+isSelectionEnabled.toString()) + if (isSelectionEnabled) { + Pointers.enablePointer(laser) + } else { + Pointers.disablePointer(laser) + Selection.clearSelectedItemsList(HoveringList) } - - prevID = result.objectID; - prevType = typeName; time = 0 - } else if (time>1.0 && prevID!=selectedID) { - if (prevID != 0 && !isSelectionAddEnabled) { - Selection.removeFromSelectedItemsList(selectionName, selectedType, selectedID) - } - selectedID = prevID - selectedType = prevType - Selection.addToSelectedItemsList(selectionName, selectedType, selectedID) - print("HIGHLIGHT " + highlightGroupIndex + " picked type: " + result.type + ", id: " + result.objectID); } - } else { - if (prevID != 0 && !isSelectionAddEnabled) { - Selection.removeFromSelectedItemsList(selectionName, prevType, prevID) - } - prevID = 0 - selectedID = 0 - time = 0 } -} + + Entities.hoverEnterEntity.connect(function (id, event) { + // print("hoverEnterEntity"); + if (isSelectionEnabled) Selection.addToSelectedItemsList(HoveringList, "entity", id) + }) + + Entities.hoverOverEntity.connect(function (id, event) { + // print("hoverOverEntity"); + }) + + + Entities.hoverLeaveEntity.connect(function (id, event) { + if (isSelectionEnabled) Selection.removeFromSelectedItemsList(HoveringList, "entity", id) + // print("hoverLeaveEntity"); + }) + + function cleanup() { + Pointers.removePointer(ray); + Selection.disableListHighlight(HoveringList) + Selection.removeListFromMap(HoveringList) + + } + Script.scriptEnding.connect(cleanup); + +}()); -Script.update.connect(update); -}()); // END LOCAL_SCOPE \ No newline at end of file diff --git a/scripts/developer/utilities/render/highlight.qml b/scripts/developer/utilities/render/highlight.qml index 6be74fcf40..88d6a807ae 100644 --- a/scripts/developer/utilities/render/highlight.qml +++ b/scripts/developer/utilities/render/highlight.qml @@ -15,162 +15,184 @@ import QtQuick.Layouts 1.3 import "qrc:///qml/styles-uit" import "qrc:///qml/controls-uit" as HifiControls import "configSlider" +import "../lib/plotperf" +import "highlight" -Rectangle { +Item { id: root HifiConstants { id: hifi;} - color: hifi.colors.baseGray; - anchors.margins: hifi.dimensions.contentMargin.x - - property var debugConfig: Render.getConfig("RenderMainView.HighlightDebug") - property var highlightConfig: Render.getConfig("UpdateScene.HighlightStageSetup") + anchors.margins: 0 + property var listName: "contextOverlayHighlightList" + property var styleList: Selection.getHighlightedListNames() + signal sendToScript(var message); + Component.onCompleted: { + // Connect the signal from Selection when any selection content change and use it to refresh the current selection view + Selection.selectedItemsListChanged.connect(resetSelectionView) + } + + function resetSelectionView() { + if (selectionView !== undefined) { + selectionView.resetSelectionView(); + } + } + Column { id: col - spacing: 10 - anchors.left: parent.left - anchors.right: parent.right + spacing: 5 + anchors.fill: root anchors.margins: hifi.dimensions.contentMargin.x Row { + id: controlbar spacing: 10 anchors.left: parent.left anchors.right: parent.right + height: 24 - HifiControls.CheckBox { + HifiControls.Button { id: debug - text: "View Mask" - checked: root.debugConfig["viewMask"] - onCheckedChanged: { - root.debugConfig["viewMask"] = checked; + text: "Refresh" + height: 24 + width: 82 + onClicked: { + print("list of highlight styles") + root.styleList = Selection.getHighlightedListNames() + + print(root.styleList) + styleSelectorLoader.sourceComponent = undefined; + styleSelectorLoader.sourceComponent = selectorWidget; } } - HifiControls.CheckBox { - text: "Hover select" - checked: false - onCheckedChanged: { - sendToScript("pick "+checked.toString()) - } - } - HifiControls.CheckBox { - text: "Add to selection" - checked: false - onCheckedChanged: { - sendToScript("add "+checked.toString()) + + Loader { + id: styleSelectorLoader + sourceComponent: selectorWidget + width: 300 + //anchors.right: parent.right + } + Component { + id: selectorWidget + HifiControls.ComboBox { + id: box + z: 999 + editable: true + colorScheme: hifi.colorSchemes.dark + model: root.styleList + label: "" + + Timer { + id: postpone + interval: 100; running: false; repeat: false + onTriggered: { + styleWidgetLoader.sourceComponent = styleWidget + resetSelectionView(); + } + } + onCurrentIndexChanged: { + root.listName = model[currentIndex]; + // This is a hack to be sure the widgets below properly reflect the change of category: delete the Component + // by setting the loader source to Null and then recreate it 100ms later + styleWidgetLoader.sourceComponent = undefined; + postpone.interval = 100 + postpone.start() + } } } } - HifiControls.ComboBox { - id: box - width: 350 - z: 999 - editable: true - colorScheme: hifi.colorSchemes.dark - model: [ - "contextOverlayHighlightList", - "highlightList1", - "highlightList2", - "highlightList3", - "highlightList4"] - label: "" - - Timer { - id: postpone - interval: 100; running: false; repeat: false - onTriggered: { paramWidgetLoader.sourceComponent = paramWidgets } - } - onCurrentIndexChanged: { - root.highlightConfig["selectionName"] = model[currentIndex]; - sendToScript("highlight "+currentIndex) - // This is a hack to be sure the widgets below properly reflect the change of category: delete the Component - // by setting the loader source to Null and then recreate it 100ms later - paramWidgetLoader.sourceComponent = undefined; - postpone.interval = 100 - postpone.start() - } - } - + Separator {} Loader { - id: paramWidgetLoader - sourceComponent: paramWidgets - width: 350 + id: styleWidgetLoader + sourceComponent: styleWidget + anchors.left: parent.left + anchors.right: parent.right + height: 240 + } + + Separator {} + + HifiControls.CheckBox { + text: "Highlight Hovered" + checked: false + onCheckedChanged: { + if (checked) { + root.sendToScript("pick true") + } else { + root.sendToScript("pick false") + } + } } + Separator {} + Rectangle { + id: selectionView + anchors.left: parent.left + anchors.right: parent.right + height: 250 + color: hifi.colors.lightGray - Component { - id: paramWidgets - - Column { - spacing: 10 - anchors.margins: hifi.dimensions.contentMargin.x - - HifiControls.Label { - text: "Outline" - } - Column { - spacing: 10 - anchors.left: parent.left - anchors.right: parent.right - HifiControls.CheckBox { - text: "Smooth" - checked: root.highlightConfig["isOutlineSmooth"] - onCheckedChanged: { - root.highlightConfig["isOutlineSmooth"] = checked; - } - } - Repeater { - model: ["Width:outlineWidth:5.0:0.0", - "Intensity:outlineIntensity:1.0:0.0" - ] - ConfigSlider { - label: qsTr(modelData.split(":")[0]) - integral: false - config: root.highlightConfig - property: modelData.split(":")[1] - max: modelData.split(":")[2] - min: modelData.split(":")[3] - } + function resetSelectionView() { + myModel.clear() + var entities = Selection.getSelectedItemsList(root.listName)["entities"] + if (entities !== undefined) { + myModel.append({ "objectID": "Entities" }) + for (var i = 0; i < entities.length; i++) { + myModel.append({ "objectID": JSON.stringify(entities[i]) }) } } - - Separator {} - HifiControls.Label { - text: "Color" - } - Repeater { - model: ["Red:colorR:1.0:0.0", - "Green:colorG:1.0:0.0", - "Blue:colorB:1.0:0.0" - ] - ConfigSlider { - label: qsTr(modelData.split(":")[0]) - integral: false - config: root.highlightConfig - property: modelData.split(":")[1] - max: modelData.split(":")[2] - min: modelData.split(":")[3] + var overlays = Selection.getSelectedItemsList(root.listName)["overlays"] + if (overlays !== undefined) { + myModel.append({ "objectID": "Overlays" }) + for (var i = 0; i < overlays.length; i++) { + myModel.append({ "objectID": JSON.stringify(overlays[i]) }) } } - - Separator {} - HifiControls.Label { - text: "Fill Opacity" - } - Repeater { - model: ["Unoccluded:unoccludedFillOpacity:1.0:0.0", - "Occluded:occludedFillOpacity:1.0:0.0" - ] - ConfigSlider { - label: qsTr(modelData.split(":")[0]) - integral: false - config: root.highlightConfig - property: modelData.split(":")[1] - max: modelData.split(":")[2] - min: modelData.split(":")[3] + var avatars = Selection.getSelectedItemsList(root.listName)["avatars"] + if (avatars !== undefined) { + myModel.append({ "objectID": "Avatars" }) + for (var i = 0; i < avatars.length; i++) { + myModel.append({ "objectID": JSON.stringify(avatars[i]) }) } } + } + + ListModel { + id: myModel + } + + Component { + id: myDelegate + Row { + id: fruit + Text { text: objectID } + } + } + + ListView { + id: selectionListView + anchors.fill: parent + anchors.topMargin: 30 + model: myModel + delegate: myDelegate + } + } + } + + Component { + id: styleWidget + + HighlightStyle { + id: highlightStyle + anchors.left: parent.left + anchors.right: parent.right + highlightStyle: Selection.getListHighlightStyle(root.listName) + + onNewStyle: { + var style = getStyle() + // print("new style " + JSON.stringify(style) ) + Selection.enableListHighlight(root.listName, style) } } } diff --git a/scripts/developer/utilities/render/highlight/HighlightStyle.qml b/scripts/developer/utilities/render/highlight/HighlightStyle.qml new file mode 100644 index 0000000000..371b7e81f7 --- /dev/null +++ b/scripts/developer/utilities/render/highlight/HighlightStyle.qml @@ -0,0 +1,105 @@ +// +// highlightStyle.qml +// +// Created by Sam Gateau 12/4/2017 +// Copyright 2017 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 "../configSlider" +import "../../lib/plotperf" +import "qrc:///qml/styles-uit" +import "qrc:///qml/controls-uit" as HifiControls + +Item { + id: root + property var highlightStyle + height: 48 + + anchors.margins: 0 + + signal newStyle() + + function getStyle() { + return highlightStyle; + } + + Component.onCompleted: { + } + + Column { + spacing: 5 + anchors.left: root.left + anchors.right: root.right + anchors.margins: 0 + + + + ConfigSlider { + label: "Outline Width" + integral: false + config: root.highlightStyle + property: "outlineWidth" + max: 10 + min: 0 + + anchors.left: parent.left + anchors.right: parent.right + + onValueChanged: { root.highlightStyle["outlineWidth"] = value; newStyle() } + } + HifiControls.CheckBox { + id: isOutlineSmooth + text: "Smooth Outline" + checked: root.highlightStyle["isOutlineSmooth"] + onCheckedChanged: { + root.highlightStyle["isOutlineSmooth"] = checked; + newStyle(); + } + } + + Repeater { + model: [ + "Outline Unoccluded:outlineUnoccludedColor:outlineUnoccludedAlpha", + "Outline Occluded:outlineOccludedColor:outlineOccludedAlpha", + "Fill Unoccluded:fillUnoccludedColor:fillUnoccludedAlpha", + "Fill Occluded:fillOccludedColor:fillOccludedAlpha"] + Column { + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 0 + + Color { + height: 20 + anchors.right: parent.right + width: root.width / 2 + _color: Qt.rgba(root.highlightStyle[modelData.split(":")[1]].red / 255, root.highlightStyle[modelData.split(":")[1]].green / 255, root.highlightStyle[modelData.split(":")[1]].blue / 255, 1.0) + onNewColor: { + root.highlightStyle[modelData.split(":")[1]] = getXColor() + newStyle() + } + } + + ConfigSlider { + label: qsTr(modelData.split(":")[0]) + integral: false + config: root.highlightStyle + property: modelData.split(":")[2] + max: 1.0 + min: 0.0 + + anchors.left: parent.left + anchors.right: parent.right + + onValueChanged: { root.highlightStyle[modelData.split(":")[2]] = value; newStyle() } + } + + } + } + + } +} diff --git a/scripts/developer/utilities/render/highlight/qmldir b/scripts/developer/utilities/render/highlight/qmldir new file mode 100644 index 0000000000..31fc576bbe --- /dev/null +++ b/scripts/developer/utilities/render/highlight/qmldir @@ -0,0 +1 @@ +HighlightStyle 1.0 HighlightStyle.qml \ No newline at end of file diff --git a/scripts/developer/utilities/render/highlightPage/HighlightPage.qml b/scripts/developer/utilities/render/highlightPage/HighlightPage.qml deleted file mode 100644 index 5669f90628..0000000000 --- a/scripts/developer/utilities/render/highlightPage/HighlightPage.qml +++ /dev/null @@ -1,116 +0,0 @@ -// -// highlightPage.qml -// developer/utilities/render -// -// Olivier Prat, created on 08/08/2017. -// Copyright 2017 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 "../configSlider" -import "qrc:///qml/styles-uit" -import "qrc:///qml/controls-uit" as HifiControls - -Rectangle { - id: root - property var highlightIndex: 0 - property var drawConfig: Render.getConfig("RenderMainView.HighlightEffect"+highlightIndex) - - HifiConstants { id: hifi;} - anchors.margins: hifi.dimensions.contentMargin.x - - Column { - spacing: 5 - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: hifi.dimensions.contentMargin.x - - HifiControls.CheckBox { - id: glow - text: "Glow" - checked: root.drawConfig["glow"] - onCheckedChanged: { - root.drawConfig["glow"] = checked; - } - } - Repeater { - model: ["Width:width:5.0:0.0", - "Intensity:intensity:1.0:0.0" - ] - ConfigSlider { - label: qsTr(modelData.split(":")[0]) - integral: false - config: root.drawConfig - property: modelData.split(":")[1] - max: modelData.split(":")[2] - min: modelData.split(":")[3] - - anchors.left: parent.left - anchors.right: parent.right - } - } - - GroupBox { - title: "Color" - anchors.left: parent.left - anchors.right: parent.right - Column { - spacing: 10 - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: hifi.dimensions.contentMargin.x - - Repeater { - model: ["Red:colorR:1.0:0.0", - "Green:colorG:1.0:0.0", - "Blue:colorB:1.0:0.0" - ] - ConfigSlider { - label: qsTr(modelData.split(":")[0]) - integral: false - config: root.drawConfig - property: modelData.split(":")[1] - max: modelData.split(":")[2] - min: modelData.split(":")[3] - - anchors.left: parent.left - anchors.right: parent.right - } - } - } - } - - GroupBox { - title: "Fill Opacity" - anchors.left: parent.left - anchors.right: parent.right - Column { - spacing: 10 - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: hifi.dimensions.contentMargin.x - - Repeater { - model: ["Unoccluded:unoccludedFillOpacity:1.0:0.0", - "Occluded:occludedFillOpacity:1.0:0.0" - ] - ConfigSlider { - label: qsTr(modelData.split(":")[0]) - integral: false - config: root.drawConfig - property: modelData.split(":")[1] - max: modelData.split(":")[2] - min: modelData.split(":")[3] - - anchors.left: parent.left - anchors.right: parent.right - } - } - } - } - } -} diff --git a/scripts/developer/utilities/render/highlightPage/qmldir b/scripts/developer/utilities/render/highlightPage/qmldir deleted file mode 100644 index bb3de24b84..0000000000 --- a/scripts/developer/utilities/render/highlightPage/qmldir +++ /dev/null @@ -1 +0,0 @@ -HighlightPage 1.0 HighlightPage.qml \ No newline at end of file diff --git a/scripts/system/controllers/controllerDispatcher.js b/scripts/system/controllers/controllerDispatcher.js index bad6793995..915b3b3680 100644 --- a/scripts/system/controllers/controllerDispatcher.js +++ b/scripts/system/controllers/controllerDispatcher.js @@ -154,6 +154,15 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); }; this.update = function () { + try { + _this.updateInternal(); + } catch (e) { + print(e); + } + Script.setTimeout(_this.update, BASIC_TIMER_INTERVAL_MS); + }; + + this.updateInternal = function () { if (PROFILE) { Script.beginProfileRange("dispatch.pre"); } @@ -470,7 +479,6 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); }; this.cleanup = function () { - Script.update.disconnect(_this.update); Controller.disableMapping(MAPPING_NAME); _this.pointerManager.removePointers(); Pointers.removePointer(this.mouseRayPick); @@ -501,5 +509,5 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Messages.subscribe('Hifi-Hand-RayPick-Blacklist'); Messages.messageReceived.connect(controllerDispatcher.handleHandMessage); Script.scriptEnding.connect(controllerDispatcher.cleanup); - Script.update.connect(controllerDispatcher.update); + Script.setTimeout(controllerDispatcher.update, BASIC_TIMER_INTERVAL_MS); }()); diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index b8c20d5bd6..a250f77b2e 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -10,7 +10,7 @@ getControllerJointIndex, enableDispatcherModule, disableDispatcherModule, Messages, makeDispatcherModuleParameters, makeRunningValues, Settings, entityHasActions, Vec3, Overlays, flatten, Xform, getControllerWorldLocation, ensureDynamic, entityIsCloneable, - cloneEntity, DISPATCHER_PROPERTIES + cloneEntity, DISPATCHER_PROPERTIES, TEAR_AWAY_DISTANCE */ Script.include("/~/system/libraries/Xform.js"); @@ -138,9 +138,9 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var dimensions; if (overlayInfoSet.type === "sphere") { - dimensions = overlayInfoSet.hotspot.radius * 2 * overlayInfoSet.currentSize * EQUIP_SPHERE_SCALE_FACTOR; + dimensions = (overlayInfoSet.hotspot.radius / 2) * overlayInfoSet.currentSize * EQUIP_SPHERE_SCALE_FACTOR; } else { - dimensions = overlayInfoSet.hotspot.radius * 2 * overlayInfoSet.currentSize; + dimensions = (overlayInfoSet.hotspot.radius / 2) * overlayInfoSet.currentSize; } overlayInfoSet.overlays.forEach(function(overlay) { @@ -162,7 +162,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var ATTACH_POINT_SETTINGS = "io.highfidelity.attachPoints"; - var EQUIP_RADIUS = 0.2; // radius used for palm vs equip-hotspot for equipping. + var EQUIP_RADIUS = 1.0; // radius used for palm vs equip-hotspot for equipping. var HAPTIC_PULSE_STRENGTH = 1.0; var HAPTIC_PULSE_DURATION = 13.0; @@ -322,7 +322,9 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa } } else { var wearableProps = getWearableData(props); + var sensorToScaleFactor = MyAvatar.sensorToWorldScale; if (wearableProps && wearableProps.joints) { + result.push({ key: entityID.toString() + "0", entityID: entityID, @@ -332,7 +334,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa z: 0 }, worldPosition: entityXform.pos, - radius: EQUIP_RADIUS, + radius: EQUIP_RADIUS * sensorToScaleFactor, joints: wearableProps.joints, modelURL: null, modelScale: null diff --git a/scripts/system/controllers/controllerModules/scaleAvatar.js b/scripts/system/controllers/controllerModules/scaleAvatar.js index e4ae3654e1..1868b0228a 100644 --- a/scripts/system/controllers/controllerModules/scaleAvatar.js +++ b/scripts/system/controllers/controllerModules/scaleAvatar.js @@ -12,6 +12,10 @@ (function () { var dispatcherUtils = Script.require("/~/system/libraries/controllerDispatcherUtils.js"); + function clamp(val, min, max) { + return Math.max(min, Math.min(max, val)); + } + function ScaleAvatar(hand) { this.hand = hand; this.scalingStartAvatarScale = 0; @@ -61,7 +65,7 @@ controllerData.controllerLocations[this.otherHand()].position)); var newAvatarScale = (scalingCurrentDistance / this.scalingStartDistance) * this.scalingStartAvatarScale; - MyAvatar.scale = newAvatarScale; + MyAvatar.scale = clamp(newAvatarScale, MyAvatar.getDomainMinScale(), MyAvatar.getDomainMaxScale()); MyAvatar.scaleChanged(); } return dispatcherUtils.makeRunningValues(true, [], []); diff --git a/scripts/system/directory.js b/scripts/system/directory.js index 8b9ec17f05..f84429ab95 100644 --- a/scripts/system/directory.js +++ b/scripts/system/directory.js @@ -63,7 +63,7 @@ var toolBar = (function() { y: -TOOLBAR_MARGIN_Y - toolHeight }); browseDirectoryButton = toolBar.addTool({ - imageURL: toolIconUrl + "directory-01.svg", + imageURL: toolIconUrl + "directory.svg", subImage: { x: 0, y: Tool.IMAGE_WIDTH, diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 80fba6fc13..8a7e36465d 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -112,8 +112,8 @@ var DEFAULT_LIGHT_DIMENSIONS = Vec3.multiply(20, DEFAULT_DIMENSIONS); var MENU_AUTO_FOCUS_ON_SELECT = "Auto Focus on Select"; var MENU_EASE_ON_FOCUS = "Ease Orientation on Focus"; -var MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "Show Lights and Particle Systems in Edit Mode"; -var MENU_SHOW_ZONES_IN_EDIT_MODE = "Show Zones in Edit Mode"; +var MENU_SHOW_LIGHTS_AND_PARTICLES_IN_EDIT_MODE = "Show Lights and Particle Systems in Create Mode"; +var MENU_SHOW_ZONES_IN_EDIT_MODE = "Show Zones in Create Mode"; var SETTING_AUTO_FOCUS_ON_SELECT = "autoFocusOnSelect"; var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus"; diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index 6aacaa5333..736d42d593 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1415,6 +1415,9 @@ input#reset-to-natural-dimensions { } /* ----- Order of Menu items for Primitive ----- */ +/* Entity Menu classes are specified by selected entity + within entityProperties.js +*/ #properties-list.ShapeMenu #general, #properties-list.BoxMenu #general, #properties-list.SphereMenu #general { @@ -1469,6 +1472,34 @@ input#reset-to-natural-dimensions { display: none; } +/* ----- ParticleEffectMenu ----- */ +#properties-list.ParticleEffectMenu #general { + order: 1; +} +#properties-list.ParticleEffectMenu #collision-info { + order: 2; +} +#properties-list.ParticleEffectMenu #physical { + order: 3; +} +#properties-list.ParticleEffectMenu #spatial { + order: 4; +} +#properties-list.ParticleEffectMenu #behavior { + order: 5; +} + +/* items to hide */ +#properties-list.ParticleEffectMenu #base-color-section, +#properties-list.ParticleEffectMenu #hyperlink, +#properties-list.ParticleEffectMenu #light, +#properties-list.ParticleEffectMenu #model, +#properties-list.ParticleEffectMenu #shape-list, +#properties-list.ParticleEffectMenu #text, +#properties-list.ParticleEffectMenu #web, +#properties-list.ParticleEffectMenu #zone { + display: none; +} /* ----- Order of Menu items for Light ----- */ #properties-list.LightMenu #general { @@ -1500,8 +1531,8 @@ input#reset-to-natural-dimensions { display: none; } /* items to hide */ -#properties-list.LightMenu .shape-group.shape-section.property.dropdown, -#properties-list.LightMenu color-section.color-control1 { +#properties-list.LightMenu #shape-list, +#properties-list.LightMenu #base-color-section { display: none } @@ -1536,8 +1567,8 @@ input#reset-to-natural-dimensions { display: none; } /* items to hide */ -#properties-list.ModelMenu .shape-group.shape-section.property.dropdown, -#properties-list.ModelMenu .color-section.color-control1 { +#properties-list.ModelMenu #shape-list, +#properties-list.ModelMenu #base-color-section { display: none } @@ -1572,8 +1603,8 @@ input#reset-to-natural-dimensions { display: none; } /* items to hide */ -#properties-list.ZoneMenu .shape-group.shape-section.property.dropdown, -#properties-list.ZoneMenu .color-section.color-control1 { +#properties-list.ZoneMenu #shape-list, +#properties-list.ZoneMenu #base-color-section { display: none } @@ -1608,8 +1639,8 @@ input#reset-to-natural-dimensions { display: none; } /* items to hide */ -#properties-list.WebMenu .shape-group.shape-section.property.dropdown, -#properties-list.WebMenu .color-section.color-control1 { +#properties-list.WebMenu #shape-list, +#properties-list.WebMenu #base-color-section { display: none; } @@ -1645,8 +1676,8 @@ input#reset-to-natural-dimensions { display: none; } /* items to hide */ -#properties-list.TextMenu .shape-group.shape-section.property.dropdown, -#properties-list.TextMenu .color-section.color-control1 { +#properties-list.TextMenu #shape-list, +#properties-list.TextMenu #base-color-section { display: none } diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index 8b2a088d83..2ccad2c169 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -44,7 +44,7 @@
-
-
+ +
+ CollisionM
@@ -216,17 +223,6 @@
-
-
-
- -
-
-
-
-
-
-
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index f54394a353..0ab9f7a9cb 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -6,6 +6,9 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +/* global alert, augmentSpinButtons, clearTimeout, console, document, Element, EventBridge, + HifiEntityUI, JSONEditor, openEventBridge, setUpKeyboardControl, setTimeout, window, _ $ */ + var PI = 3.14159265358979; var DEGREES_TO_RADIANS = PI / 180.0; var RADIANS_TO_DEGREES = 180.0 / PI; @@ -22,33 +25,33 @@ var ICON_FOR_TYPE = { PolyVox: "", Multiple: "", PolyLine: "" -} +}; var EDITOR_TIMEOUT_DURATION = 1500; -const KEY_P = 80; //Key code for letter p used for Parenting hotkey. +var KEY_P = 80; // Key code for letter p used for Parenting hotkey. var colorPickers = []; var lastEntityID = null; -debugPrint = function(message) { +function debugPrint(message) { EventBridge.emitWebEvent( JSON.stringify({ type: "print", message: message }) ); -}; +} function enableChildren(el, selector) { - els = el.querySelectorAll(selector); - for (var i = 0; i < els.length; i++) { - els[i].removeAttribute('disabled'); + var elSelectors = el.querySelectorAll(selector); + for (var selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { + elSelectors[selectorIndex].removeAttribute('disabled'); } } function disableChildren(el, selector) { - els = el.querySelectorAll(selector); - for (var i = 0; i < els.length; i++) { - els[i].setAttribute('disabled', 'disabled'); + var elSelectors = el.querySelectorAll(selector); + for (var selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { + elSelectors[selectorIndex].setAttribute('disabled', 'disabled'); } } @@ -103,16 +106,6 @@ function createEmitCheckedPropertyUpdateFunction(propertyName) { }; } -function createEmitCheckedToStringPropertyUpdateFunction(checkboxElement, name, propertyName) { - var newString = ""; - if (checkboxElement.checked) { - newString += name + ""; - } else { - - } - -} - function createEmitGroupCheckedPropertyUpdateFunction(group, propertyName) { return function() { var properties = {}; @@ -123,7 +116,7 @@ function createEmitGroupCheckedPropertyUpdateFunction(group, propertyName) { } function createEmitNumberPropertyUpdateFunction(propertyName, decimals) { - decimals = decimals == undefined ? 4 : decimals; + decimals = ((decimals === undefined) ? 4 : decimals); return function() { var value = parseFloat(this.value).toFixed(decimals); updateProperty(propertyName, value); @@ -146,7 +139,9 @@ function createEmitTextPropertyUpdateFunction(propertyName) { }; } -function createZoneComponentModeChangedFunction(zoneComponent, zoneComponentModeInherit, zoneComponentModeDisabled, zoneComponentModeEnabled) { +function createZoneComponentModeChangedFunction(zoneComponent, zoneComponentModeInherit, + zoneComponentModeDisabled, zoneComponentModeEnabled) { + return function() { var zoneComponentMode; @@ -159,7 +154,7 @@ function createZoneComponentModeChangedFunction(zoneComponent, zoneComponentMode } updateProperty(zoneComponent, zoneComponentMode); - } + }; } function createEmitGroupTextPropertyUpdateFunction(group, propertyName) { @@ -177,11 +172,11 @@ function createEmitVec3PropertyUpdateFunction(property, elX, elY, elZ) { properties[property] = { x: elX.value, y: elY.value, - z: elZ.value, + z: elZ.value }; updateProperties(properties); - } -}; + }; +} function createEmitGroupVec3PropertyUpdateFunction(group, property, elX, elY, elZ) { return function() { @@ -190,11 +185,11 @@ function createEmitGroupVec3PropertyUpdateFunction(group, property, elX, elY, el properties[group][property] = { x: elX.value, y: elY.value, - z: elZ ? elZ.value : 0, + z: elZ ? elZ.value : 0 }; updateProperties(properties); - } -}; + }; +} function createEmitVec3PropertyUpdateFunctionWithMultiplier(property, elX, elY, elZ, multiplier) { return function() { @@ -202,17 +197,17 @@ function createEmitVec3PropertyUpdateFunctionWithMultiplier(property, elX, elY, properties[property] = { x: elX.value * multiplier, y: elY.value * multiplier, - z: elZ.value * multiplier, + z: elZ.value * multiplier }; updateProperties(properties); - } -}; + }; +} function createEmitColorPropertyUpdateFunction(property, elRed, elGreen, elBlue) { return function() { emitColorPropertyUpdate(property, elRed.value, elGreen.value, elBlue.value); - } -}; + }; +} function emitColorPropertyUpdate(property, red, green, blue, group) { var properties = {}; @@ -221,17 +216,17 @@ function emitColorPropertyUpdate(property, red, green, blue, group) { properties[group][property] = { red: red, green: green, - blue: blue, + blue: blue }; } else { properties[property] = { red: red, green: green, - blue: blue, + blue: blue }; } updateProperties(properties); -}; +} function createEmitGroupColorPropertyUpdateFunction(group, property, elRed, elGreen, elBlue) { @@ -241,11 +236,11 @@ function createEmitGroupColorPropertyUpdateFunction(group, property, elRed, elGr properties[group][property] = { red: elRed.value, green: elGreen.value, - blue: elBlue.value, + blue: elBlue.value }; updateProperties(properties); - } -}; + }; +} function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElement, subPropertyString) { if (subPropertyElement.checked) { @@ -264,12 +259,12 @@ function setUserDataFromEditor(noUpdate) { try { json = editor.get(); } catch (e) { - alert('Invalid JSON code - look for red X in your code ', +e) + alert('Invalid JSON code - look for red X in your code ', +e); } if (json === null) { return; } else { - var text = editor.getText() + var text = editor.getText(); if (noUpdate === true) { EventBridge.emitWebEvent( JSON.stringify({ @@ -277,7 +272,7 @@ function setUserDataFromEditor(noUpdate) { type: "saveUserData", properties: { userData: text - }, + } }) ); return; @@ -292,22 +287,24 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) { var parsedData = {}; try { if ($('#userdata-editor').css('height') !== "0px") { - //if there is an expanded, we want to use its json. + // if there is an expanded, we want to use its json. parsedData = getEditorJSON(); } else { parsedData = JSON.parse(userDataElement.value); } - } catch (e) {} + } catch (e) { + // TODO: Should an alert go here? + } if (!(groupName in parsedData)) { - parsedData[groupName] = {} + parsedData[groupName] = {}; } var keys = Object.keys(updateKeyPair); keys.forEach(function (key) { delete parsedData[groupName][key]; if (updateKeyPair[key] !== null && updateKeyPair[key] !== "null") { if (updateKeyPair[key] instanceof Element) { - if(updateKeyPair[key].type === "checkbox") { + if (updateKeyPair[key].type === "checkbox") { if (updateKeyPair[key].checked !== defaults[key]) { parsedData[groupName][key] = updateKeyPair[key].checked; } @@ -322,16 +319,16 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) { } } }); - if (Object.keys(parsedData[groupName]).length == 0) { + if (Object.keys(parsedData[groupName]).length === 0) { delete parsedData[groupName]; } if (Object.keys(parsedData).length > 0) { - properties['userData'] = JSON.stringify(parsedData); + properties.userData = JSON.stringify(parsedData); } else { - properties['userData'] = ''; + properties.userData = ''; } - userDataElement.value = properties['userData']; + userDataElement.value = properties.userData; updateProperties(properties); } @@ -340,13 +337,12 @@ function userDataChanger(groupName, keyName, values, userDataElement, defaultVal val[keyName] = values; def[keyName] = defaultValue; multiDataUpdater(groupName, val, userDataElement, def); -}; +} function setTextareaScrolling(element) { var isScrolling = element.scrollHeight > element.offsetHeight; element.setAttribute("scrolling", isScrolling ? "true" : "false"); -}; - +} var editor = null; @@ -364,7 +360,7 @@ function createJSONEditor() { $('.jsoneditor-poweredBy').remove(); }, onError: function(e) { - alert('JSON editor:' + e) + alert('JSON editor:' + e); }, onChange: function() { var currentJSONString = editor.getText(); @@ -372,22 +368,22 @@ function createJSONEditor() { if (currentJSONString === '{"":""}') { return; } - $('#userdata-save').attr('disabled', false) + $('#userdata-save').attr('disabled', false); } }; editor = new JSONEditor(container, options); -}; +} function hideNewJSONEditorButton() { $('#userdata-new-editor').hide(); -}; +} function hideClearUserDataButton() { $('#userdata-clear').hide(); -}; +} function showSaveUserDataButton() { $('#userdata-save').show(); @@ -401,65 +397,65 @@ function hideSaveUserDataButton() { function showNewJSONEditorButton() { $('#userdata-new-editor').show(); -}; +} function showClearUserDataButton() { $('#userdata-clear').show(); -}; +} function showUserDataTextArea() { $('#property-user-data').show(); -}; +} function hideUserDataTextArea() { $('#property-user-data').hide(); -}; +} function showStaticUserData() { if (editor !== null) { $('#static-userdata').show(); - $('#static-userdata').css('height', $('#userdata-editor').height()) + $('#static-userdata').css('height', $('#userdata-editor').height()); $('#static-userdata').text(editor.getText()); } -}; +} function removeStaticUserData() { $('#static-userdata').hide(); -}; +} function setEditorJSON(json) { - editor.set(json) + editor.set(json); if (editor.hasOwnProperty('expandAll')) { editor.expandAll(); } -}; +} function getEditorJSON() { return editor.get(); -}; +} function deleteJSONEditor() { if (editor !== null) { editor.destroy(); editor = null; } -}; +} var savedJSONTimer = null; function saveJSONUserData(noUpdate) { setUserDataFromEditor(noUpdate); $('#userdata-saved').show(); - $('#userdata-save').attr('disabled', true) + $('#userdata-save').attr('disabled', true); if (savedJSONTimer !== null) { clearTimeout(savedJSONTimer); } savedJSONTimer = setTimeout(function() { $('#userdata-saved').hide(); - }, 1500) + }, EDITOR_TIMEOUT_DURATION); } function bindAllNonJSONEditorElements() { @@ -468,6 +464,8 @@ function bindAllNonJSONEditorElements() { for (i = 0; i < inputs.length; i++) { var input = inputs[i]; var field = $(input); + // TODO FIXME: (JSHint) Functions declared within loops referencing + // an outer scoped variable may lead to confusing semantics. field.on('focus', function(e) { if (e.target.id === "userdata-new-editor" || e.target.id === "userdata-clear") { return; @@ -477,7 +475,7 @@ function bindAllNonJSONEditorElements() { } } - }) + }); } } @@ -492,18 +490,17 @@ function unbindAllInputs() { } function clearSelection() { - if(document.selection && document.selection.empty) { - document.selection.empty(); - } else if(window.getSelection) { - var sel = window.getSelection(); - sel.removeAllRanges(); - } + if (document.selection && document.selection.empty) { + document.selection.empty(); + } else if (window.getSelection) { + var sel = window.getSelection(); + sel.removeAllRanges(); + } } function loaded() { openEventBridge(function() { - var allSections = []; var elPropertiesList = document.getElementById("properties-list"); var elID = document.getElementById("property-id"); var elType = document.getElementById("property-type"); @@ -589,21 +586,14 @@ function loaded() { var elUserData = document.getElementById("property-user-data"); var elClearUserData = document.getElementById("userdata-clear"); var elSaveUserData = document.getElementById("userdata-save"); - var elJSONEditor = document.getElementById("userdata-editor"); var elNewJSONEditor = document.getElementById('userdata-new-editor'); - var elColorSections = document.querySelectorAll(".color-section"); - var elColorControl1 = document.getElementById("property-color-control1"); - var elColorControl2 = document.getElementById("property-color-control2"); + var elColorControlVariant2 = document.getElementById("property-color-control2"); var elColorRed = document.getElementById("property-color-red"); var elColorGreen = document.getElementById("property-color-green"); var elColorBlue = document.getElementById("property-color-blue"); - var elShapeSections = document.querySelectorAll(".shape-section"); - allSections.push(elShapeSections); var elShape = document.getElementById("property-shape"); - var elLightSections = document.querySelectorAll(".light-section"); - allSections.push(elLightSections); var elLightSpotLight = document.getElementById("property-light-spot-light"); var elLightColor = document.getElementById("property-light-color"); var elLightColorRed = document.getElementById("property-light-color-red"); @@ -615,8 +605,6 @@ function loaded() { var elLightExponent = document.getElementById("property-light-exponent"); var elLightCutoff = document.getElementById("property-light-cutoff"); - var elModelSections = document.querySelectorAll(".model-section"); - allSections.push(elModelSections); var elModelURL = document.getElementById("property-model-url"); var elShapeType = document.getElementById("property-shape-type"); var elCompoundShapeURL = document.getElementById("property-compound-shape-url"); @@ -632,8 +620,6 @@ function loaded() { var elModelTextures = document.getElementById("property-model-textures"); var elModelOriginalTextures = document.getElementById("property-model-original-textures"); - var elWebSections = document.querySelectorAll(".web-section"); - allSections.push(elWebSections); var elWebSourceURL = document.getElementById("property-web-source-url"); var elWebDPI = document.getElementById("property-web-dpi"); @@ -641,11 +627,7 @@ function loaded() { var elHyperlinkHref = document.getElementById("property-hyperlink-href"); - var elHyperlinkSections = document.querySelectorAll(".hyperlink-section"); - - var elTextSections = document.querySelectorAll(".text-section"); - allSections.push(elTextSections); var elTextText = document.getElementById("property-text-text"); var elTextLineHeight = document.getElementById("property-text-line-height"); var elTextTextColor = document.getElementById("property-text-text-color"); @@ -653,13 +635,10 @@ 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"); - var elZoneSections = document.querySelectorAll(".zone-section"); - allSections.push(elZoneSections); var elZoneStageSunModelEnabled = document.getElementById("property-zone-stage-sun-model-enabled"); var elZoneKeyLightColor = document.getElementById("property-zone-key-light-color"); @@ -670,7 +649,6 @@ function loaded() { var elZoneKeyLightAmbientIntensity = document.getElementById("property-zone-key-ambient-intensity"); var elZoneKeyLightDirectionX = document.getElementById("property-zone-key-light-direction-x"); var elZoneKeyLightDirectionY = document.getElementById("property-zone-key-light-direction-y"); - var elZoneKeyLightDirectionZ = document.getElementById("property-zone-key-light-direction-z"); var elZoneKeyLightAmbientURL = document.getElementById("property-zone-key-ambient-url"); var elZoneHazeModeInherit = document.getElementById("property-zone-haze-mode-inherit"); @@ -694,11 +672,7 @@ function loaded() { var elZoneHazeCeiling = document.getElementById("property-zone-haze-ceiling"); var elZoneHazeBackgroundBlend = document.getElementById("property-zone-haze-background-blend"); - - var elZoneHazeAttenuateKeyLight = document.getElementById("property-zone-haze-attenuate-keylight"); - var elZoneHazeKeyLightRange = document.getElementById("property-zone-haze-keylight-range"); - var elZoneHazeKeyLightAltitude = document.getElementById("property-zone-haze-keylight-altitude"); - + var elZoneStageLatitude = document.getElementById("property-zone-stage-latitude"); var elZoneStageLongitude = document.getElementById("property-zone-stage-longitude"); var elZoneStageAltitude = document.getElementById("property-zone-stage-altitude"); @@ -718,8 +692,6 @@ function loaded() { var elZoneGhostingAllowed = document.getElementById("property-zone-ghosting-allowed"); var elZoneFilterURL = document.getElementById("property-zone-filter-url"); - var elPolyVoxSections = document.querySelectorAll(".poly-vox-section"); - allSections.push(elPolyVoxSections); var elVoxelVolumeSizeX = document.getElementById("property-voxel-volume-size-x"); var elVoxelVolumeSizeY = document.getElementById("property-voxel-volume-size-y"); var elVoxelVolumeSizeZ = document.getElementById("property-voxel-volume-size-z"); @@ -732,10 +704,10 @@ function loaded() { var properties; EventBridge.scriptEventReceived.connect(function(data) { data = JSON.parse(data); - if (data.type == "server_script_status") { + if (data.type === "server_script_status") { elServerScriptError.value = data.errorInfo; // If we just set elServerScriptError's diplay to block or none, we still end up with - //it's parent contributing 21px bottom padding even when elServerScriptError is display:none. + // it's parent contributing 21px bottom padding even when elServerScriptError is display:none. // So set it's parent to block or none elServerScriptError.parentElement.style.display = data.errorInfo ? "block" : "none"; if (data.statusRetrieved === false) { @@ -744,18 +716,18 @@ function loaded() { var ENTITY_SCRIPT_STATUS = { pending: "Pending", loading: "Loading", - error_loading_script: "Error loading script", - error_running_script: "Error running script", + error_loading_script: "Error loading script", // eslint-disable-line camelcase + error_running_script: "Error running script", // eslint-disable-line camelcase running: "Running", - unloaded: "Unloaded", + unloaded: "Unloaded" }; elServerScriptStatus.innerText = ENTITY_SCRIPT_STATUS[data.status] || data.status; } else { elServerScriptStatus.innerText = "Not running"; } - } else if (data.type == "update") { + } else if (data.type === "update") { - if (!data.selections || data.selections.length == 0) { + if (!data.selections || data.selections.length === 0) { if (editor !== null && lastEntityID !== null) { saveJSONUserData(true); deleteJSONEditor(); @@ -775,20 +747,19 @@ function loaded() { for (var i = 0; i < selections.length; i++) { ids.push(selections[i].id); - var type = selections[i].properties.type; - if (types[type] === undefined) { - types[type] = 0; + var currentSelectedType = selections[i].properties.type; + if (types[currentSelectedType] === undefined) { + types[currentSelectedType] = 0; numTypes += 1; } - types[type]++; + types[currentSelectedType]++; } - var type; + var type = "Multiple"; if (numTypes === 1) { type = selections[0].properties.type; - } else { - type = "Multiple"; } + elType.innerHTML = type + " (" + data.selections.length + ")"; elTypeIcon.innerHTML = ICON_FOR_TYPE[type]; elTypeIcon.style.display = "inline-block"; @@ -804,12 +775,14 @@ function loaded() { if (lastEntityID !== '"' + properties.id + '"' && lastEntityID !== null && editor !== null) { saveJSONUserData(true); } - //the event bridge and json parsing handle our avatar id string differently. + // the event bridge and json parsing handle our avatar id string differently. lastEntityID = '"' + properties.id + '"'; elID.value = properties.id; + // Create class name for css ruleset filtering elPropertiesList.className = properties.type + 'Menu'; + elType.innerHTML = properties.type; elTypeIcon.innerHTML = ICON_FOR_TYPE[properties.type]; elTypeIcon.style.display = "inline-block"; @@ -883,13 +856,13 @@ function loaded() { elCloneableLifetime.value = 300; var grabbablesSet = false; - var parsedUserData = {} + var parsedUserData = {}; try { parsedUserData = JSON.parse(properties.userData); if ("grabbableKey" in parsedUserData) { grabbablesSet = true; - var grabbableData = parsedUserData["grabbableKey"]; + var grabbableData = parsedUserData.grabbableKey; if ("grabbable" in grabbableData) { elGrabbable.checked = grabbableData.grabbable; } else { @@ -907,27 +880,28 @@ function loaded() { } if ("cloneable" in grabbableData) { elCloneable.checked = grabbableData.cloneable; - elCloneableGroup.style.display = elCloneable.checked ? "block": "none"; + elCloneableGroup.style.display = elCloneable.checked ? "block" : "none"; elCloneableDynamic.checked = grabbableData.cloneDynamic ? grabbableData.cloneDynamic : properties.dynamic; if (elCloneable.checked) { - if ("cloneLifetime" in grabbableData) { - elCloneableLifetime.value = - grabbableData.cloneLifetime ? grabbableData.cloneLifetime : 300; - } - if ("cloneLimit" in grabbableData) { - elCloneableLimit.value = grabbableData.cloneLimit ? grabbableData.cloneLimit : 0; - } - if ("cloneAvatarEntity" in grabbableData) { - elCloneableAvatarEntity.checked = - grabbableData.cloneAvatarEntity ? grabbableData.cloneAvatarEntity : false; - } + if ("cloneLifetime" in grabbableData) { + elCloneableLifetime.value = + grabbableData.cloneLifetime ? grabbableData.cloneLifetime : 300; + } + if ("cloneLimit" in grabbableData) { + elCloneableLimit.value = grabbableData.cloneLimit ? grabbableData.cloneLimit : 0; + } + if ("cloneAvatarEntity" in grabbableData) { + elCloneableAvatarEntity.checked = + grabbableData.cloneAvatarEntity ? grabbableData.cloneAvatarEntity : false; + } } } else { elCloneable.checked = false; } } } catch (e) { + // TODO: What should go here? } if (!grabbablesSet) { elGrabbable.checked = true; @@ -946,7 +920,7 @@ function loaded() { try { json = JSON.parse(properties.userData); } catch (e) { - //normal text + // normal text deleteJSONEditor(); elUserData.value = properties.userData; showUserDataTextArea(); @@ -968,19 +942,21 @@ function loaded() { elDescription.value = properties.description; - if (properties.type == "Shape" || properties.type == "Box" || properties.type == "Sphere") { + if (properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") { elShape.value = properties.shape; setDropdownText(elShape); } - if (properties.type == "Shape" || properties.type == "Box" || properties.type == "Sphere" || properties.type == "ParticleEffect") { + if (properties.type === "Shape" || properties.type === "Box" || + properties.type === "Sphere" || properties.type === "ParticleEffect") { elColorRed.value = properties.color.red; elColorGreen.value = properties.color.green; elColorBlue.value = properties.color.blue; - elColorControl1.style.backgroundColor = elColorControl2.style.backgroundColor = "rgb(" + properties.color.red + "," + properties.color.green + "," + properties.color.blue + ")"; + elColorControlVariant2.style.backgroundColor = "rgb(" + properties.color.red + "," + + properties.color.green + "," + properties.color.blue + ")"; } - if (properties.type == "Model") { + if (properties.type === "Model") { elModelURL.value = properties.modelURL; elShapeType.value = properties.shapeType; setDropdownText(elShapeType); @@ -998,24 +974,26 @@ function loaded() { setTextareaScrolling(elModelTextures); elModelOriginalTextures.value = properties.originalTextures; setTextareaScrolling(elModelOriginalTextures); - } else if (properties.type == "Web") { + } else if (properties.type === "Web") { elWebSourceURL.value = properties.sourceUrl; elWebDPI.value = properties.dpi; - } else if (properties.type == "Text") { + } else if (properties.type === "Text") { elTextText.value = properties.text; elTextLineHeight.value = properties.lineHeight.toFixed(4); elTextFaceCamera.checked = properties.faceCamera; - elTextTextColor.style.backgroundColor = "rgb(" + properties.textColor.red + "," + properties.textColor.green + "," + properties.textColor.blue + ")"; + elTextTextColor.style.backgroundColor = "rgb(" + properties.textColor.red + "," + + properties.textColor.green + "," + properties.textColor.blue + ")"; elTextTextColorRed.value = properties.textColor.red; elTextTextColorGreen.value = properties.textColor.green; elTextTextColorBlue.value = properties.textColor.blue; elTextBackgroundColorRed.value = properties.backgroundColor.red; elTextBackgroundColorGreen.value = properties.backgroundColor.green; elTextBackgroundColorBlue.value = properties.backgroundColor.blue; - } else if (properties.type == "Light") { + } else if (properties.type === "Light") { elLightSpotLight.checked = properties.isSpotlight; - elLightColor.style.backgroundColor = "rgb(" + properties.color.red + "," + properties.color.green + "," + properties.color.blue + ")"; + elLightColor.style.backgroundColor = "rgb(" + properties.color.red + "," + + properties.color.green + "," + properties.color.blue + ")"; elLightColorRed.value = properties.color.red; elLightColorGreen.value = properties.color.green; elLightColorBlue.value = properties.color.blue; @@ -1024,9 +1002,10 @@ function loaded() { elLightFalloffRadius.value = properties.falloffRadius.toFixed(1); elLightExponent.value = properties.exponent.toFixed(2); elLightCutoff.value = properties.cutoff.toFixed(2); - } else if (properties.type == "Zone") { + } else if (properties.type === "Zone") { elZoneStageSunModelEnabled.checked = properties.stage.sunModelEnabled; - elZoneKeyLightColor.style.backgroundColor = "rgb(" + properties.keyLight.color.red + "," + properties.keyLight.color.green + "," + properties.keyLight.color.blue + ")"; + elZoneKeyLightColor.style.backgroundColor = "rgb(" + properties.keyLight.color.red + "," + + properties.keyLight.color.green + "," + properties.keyLight.color.blue + ")"; elZoneKeyLightColorRed.value = properties.keyLight.color.red; elZoneKeyLightColorGreen.value = properties.keyLight.color.green; elZoneKeyLightColorBlue.value = properties.keyLight.color.blue; @@ -1036,9 +1015,9 @@ function loaded() { elZoneKeyLightDirectionY.value = properties.keyLight.direction.y.toFixed(2); elZoneKeyLightAmbientURL.value = properties.keyLight.ambientURL; - elZoneHazeModeInherit.checked = (properties.hazeMode == 'inherit'); - elZoneHazeModeDisabled.checked = (properties.hazeMode == 'disabled'); - elZoneHazeModeEnabled.checked = (properties.hazeMode == 'enabled'); + elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit'); + elZoneHazeModeDisabled.checked = (properties.hazeMode === 'disabled'); + elZoneHazeModeEnabled.checked = (properties.hazeMode === 'enabled'); elZoneHazeRange.value = properties.haze.hazeRange.toFixed(0); elZoneHazeColor.style.backgroundColor = "rgb(" + @@ -1069,10 +1048,6 @@ function loaded() { elZoneHazeBackgroundBlend.value = properties.haze.hazeBackgroundBlend.toFixed(2); -// elZoneHazeAttenuateKeyLight.checked = properties.haze.hazeAttenuateKeyLight; -// elZoneHazeKeyLightRange.value = properties.haze.hazeKeyLightRange.toFixed(0); -// elZoneHazeKeyLightAltitude.value = properties.haze.hazeKeyLightAltitude.toFixed(0); - elZoneStageLatitude.value = properties.stage.latitude.toFixed(2); elZoneStageLongitude.value = properties.stage.longitude.toFixed(2); elZoneStageAltitude.value = properties.stage.altitude.toFixed(2); @@ -1085,7 +1060,8 @@ function loaded() { elZoneBackgroundMode.value = properties.backgroundMode; setDropdownText(elZoneBackgroundMode); - elZoneSkyboxColor.style.backgroundColor = "rgb(" + properties.skybox.color.red + "," + properties.skybox.color.green + "," + properties.skybox.color.blue + ")"; + elZoneSkyboxColor.style.backgroundColor = "rgb(" + properties.skybox.color.red + "," + + properties.skybox.color.green + "," + properties.skybox.color.blue + ")"; elZoneSkyboxColorRed.value = properties.skybox.color.red; elZoneSkyboxColorGreen.value = properties.skybox.color.green; elZoneSkyboxColorBlue.value = properties.skybox.color.blue; @@ -1095,8 +1071,9 @@ function loaded() { elZoneGhostingAllowed.checked = properties.ghostingAllowed; elZoneFilterURL.value = properties.filterURL; - showElements(document.getElementsByClassName('skybox-section'), elZoneBackgroundMode.value == 'skybox'); - } else if (properties.type == "PolyVox") { + showElements(document.getElementsByClassName('skybox-section'), + elZoneBackgroundMode.value === 'skybox'); + } else if (properties.type === "PolyVox") { elVoxelVolumeSizeX.value = properties.voxelVolumeSize.x.toFixed(2); elVoxelVolumeSizeY.value = properties.voxelVolumeSize.y.toFixed(2); elVoxelVolumeSizeZ.value = properties.voxelVolumeSize.z.toFixed(2); @@ -1121,7 +1098,7 @@ function loaded() { activeElement.select(); } } - clearSelection(); + clearSelection(); } }); } @@ -1228,29 +1205,30 @@ function loaded() { var checked = event.target.checked; if (checked) { multiDataUpdater("grabbableKey", { - cloneLifetime: elCloneableLifetime, - cloneLimit: elCloneableLimit, - cloneDynamic: elCloneableDynamic, - cloneAvatarEntity: elCloneableAvatarEntity, - cloneable: event.target, - grabbable: null - }, elUserData, {}); + cloneLifetime: elCloneableLifetime, + cloneLimit: elCloneableLimit, + cloneDynamic: elCloneableDynamic, + cloneAvatarEntity: elCloneableAvatarEntity, + cloneable: event.target, + grabbable: null + }, elUserData, {}); elCloneableGroup.style.display = "block"; updateProperty('dynamic', false); } else { multiDataUpdater("grabbableKey", { - cloneLifetime: null, - cloneLimit: null, - cloneDynamic: null, - cloneAvatarEntity: null, - cloneable: false - }, elUserData, {}); + cloneLifetime: null, + cloneLimit: null, + cloneDynamic: null, + cloneAvatarEntity: null, + cloneable: false + }, elUserData, {}); elCloneableGroup.style.display = "none"; } }); var numberListener = function (event) { - userDataChanger("grabbableKey", event.target.getAttribute("data-user-data-type"), parseInt(event.target.value), elUserData, false); + userDataChanger("grabbableKey", + event.target.getAttribute("data-user-data-type"), parseInt(event.target.value), elUserData, false); }; elCloneableLifetime.addEventListener('change', numberListener); elCloneableLimit.addEventListener('change', numberListener); @@ -1279,7 +1257,7 @@ function loaded() { showUserDataTextArea(); showNewJSONEditorButton(); hideSaveUserDataButton(); - updateProperty('userData', elUserData.value) + updateProperty('userData', elUserData.value); }); elSaveUserData.addEventListener("click", function() { @@ -1303,24 +1281,6 @@ function loaded() { elColorRed.addEventListener('change', colorChangeFunction); elColorGreen.addEventListener('change', colorChangeFunction); elColorBlue.addEventListener('change', colorChangeFunction); - colorPickers.push($('#property-color-control1').colpick({ - colorScheme: 'dark', - layout: 'hex', - color: '000000', - onShow: function(colpick) { - $('#property-color-control1').attr('active', 'true'); - }, - onHide: function(colpick) { - $('#property-color-control1').attr('active', 'false'); - }, - onSubmit: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).colpickHide(); - emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); - // Keep the companion control in sync - elColorControl2.style.backgroundColor = "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")"; - } - })); colorPickers.push($('#property-color-control2').colpick({ colorScheme: 'dark', layout: 'hex', @@ -1335,9 +1295,6 @@ function loaded() { $(el).css('background-color', '#' + hex); $(el).colpickHide(); emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); - // Keep the companion control in sync - elColorControl1.style.backgroundColor = "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")"; - } })); @@ -1380,14 +1337,18 @@ function loaded() { elCompoundShapeURL.addEventListener('change', createEmitTextPropertyUpdateFunction('compoundShapeURL')); elModelAnimationURL.addEventListener('change', createEmitGroupTextPropertyUpdateFunction('animation', 'url')); - elModelAnimationPlaying.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'running')); + elModelAnimationPlaying.addEventListener('change',createEmitGroupCheckedPropertyUpdateFunction('animation', 'running')); elModelAnimationFPS.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('animation', 'fps')); - elModelAnimationFrame.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('animation', 'currentFrame')); - elModelAnimationFirstFrame.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('animation', 'firstFrame')); - elModelAnimationLastFrame.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('animation', 'lastFrame')); + elModelAnimationFrame.addEventListener('change', + createEmitGroupNumberPropertyUpdateFunction('animation', 'currentFrame')); + elModelAnimationFirstFrame.addEventListener('change', + createEmitGroupNumberPropertyUpdateFunction('animation', 'firstFrame')); + elModelAnimationLastFrame.addEventListener('change', + createEmitGroupNumberPropertyUpdateFunction('animation', 'lastFrame')); elModelAnimationLoop.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'loop')); elModelAnimationHold.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'hold')); - elModelAnimationAllowTranslation.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'allowTranslation')); + elModelAnimationAllowTranslation.addEventListener('change', + createEmitGroupCheckedPropertyUpdateFunction('animation', 'allowTranslation')); elModelTextures.addEventListener('change', createEmitTextPropertyUpdateFunction('textures')); @@ -1439,7 +1400,8 @@ function loaded() { } })); - elZoneStageSunModelEnabled.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('stage', 'sunModelEnabled')); + elZoneStageSunModelEnabled.addEventListener('change', + createEmitGroupCheckedPropertyUpdateFunction('stage', 'sunModelEnabled')); colorPickers.push($('#property-zone-key-light-color').colpick({ colorScheme: 'dark', layout: 'hex', @@ -1456,18 +1418,26 @@ function loaded() { emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'keyLight'); } })); - var zoneKeyLightColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('keyLight', 'color', elZoneKeyLightColorRed, elZoneKeyLightColorGreen, elZoneKeyLightColorBlue); + var zoneKeyLightColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('keyLight', 'color', + elZoneKeyLightColorRed, elZoneKeyLightColorGreen, elZoneKeyLightColorBlue); elZoneKeyLightColorRed.addEventListener('change', zoneKeyLightColorChangeFunction); elZoneKeyLightColorGreen.addEventListener('change', zoneKeyLightColorChangeFunction); elZoneKeyLightColorBlue.addEventListener('change', zoneKeyLightColorChangeFunction); - elZoneKeyLightIntensity.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('keyLight', 'intensity')); - elZoneKeyLightAmbientIntensity.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('keyLight', 'ambientIntensity')); - elZoneKeyLightAmbientURL.addEventListener('change', createEmitGroupTextPropertyUpdateFunction('keyLight', 'ambientURL')); - var zoneKeyLightDirectionChangeFunction = createEmitGroupVec3PropertyUpdateFunction('keyLight', 'direction', elZoneKeyLightDirectionX, elZoneKeyLightDirectionY); + elZoneKeyLightIntensity.addEventListener('change', + createEmitGroupNumberPropertyUpdateFunction('keyLight', 'intensity')); + elZoneKeyLightAmbientIntensity.addEventListener('change', + createEmitGroupNumberPropertyUpdateFunction('keyLight', 'ambientIntensity')); + elZoneKeyLightAmbientURL.addEventListener('change', + createEmitGroupTextPropertyUpdateFunction('keyLight', 'ambientURL')); + var zoneKeyLightDirectionChangeFunction = + createEmitGroupVec3PropertyUpdateFunction('keyLight', 'direction', + elZoneKeyLightDirectionX, elZoneKeyLightDirectionY); elZoneKeyLightDirectionX.addEventListener('change', zoneKeyLightDirectionChangeFunction); elZoneKeyLightDirectionY.addEventListener('change', zoneKeyLightDirectionChangeFunction); - var hazeModeChanged = createZoneComponentModeChangedFunction('hazeMode', elZoneHazeModeInherit, elZoneHazeModeDisabled, elZoneHazeModeEnabled) + var hazeModeChanged = + createZoneComponentModeChangedFunction('hazeMode', elZoneHazeModeInherit, + elZoneHazeModeDisabled, elZoneHazeModeEnabled); elZoneHazeModeInherit.addEventListener('change', hazeModeChanged); elZoneHazeModeDisabled.addEventListener('change', hazeModeChanged); elZoneHazeModeEnabled.addEventListener('change', hazeModeChanged); @@ -1524,23 +1494,23 @@ function loaded() { elZoneHazeGlareColorGreen.addEventListener('change', zoneHazeGlareColorChangeFunction); elZoneHazeGlareColorBlue.addEventListener('change', zoneHazeGlareColorChangeFunction); - elZoneHazeEnableGlare.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeEnableGlare')); + elZoneHazeEnableGlare.addEventListener('change', + createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeEnableGlare')); elZonehazeGlareAngle.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeGlareAngle')); - elZoneHazeAltitudeEffect.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeAltitudeEffect')); + elZoneHazeAltitudeEffect.addEventListener('change', + createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeAltitudeEffect')); elZoneHazeCeiling.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeCeiling')); elZoneHazeBaseRef.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeBaseRef')); - elZoneHazeBackgroundBlend.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeBackgroundBlend')); - -// elZoneHazeAttenuateKeyLight.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeAttenuateKeyLight')); -// elZoneHazeKeyLightRange.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeKeyLightRange')); -// elZoneHazeKeyLightAltitude.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeKeyLightAltitude')); + elZoneHazeBackgroundBlend.addEventListener('change', + createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeBackgroundBlend')); elZoneStageLatitude.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'latitude')); elZoneStageLongitude.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'longitude')); elZoneStageAltitude.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'altitude')); - elZoneStageAutomaticHourDay.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('stage', 'automaticHourDay')); + elZoneStageAutomaticHourDay.addEventListener('change', + createEmitGroupCheckedPropertyUpdateFunction('stage', 'automaticHourDay')); elZoneStageDay.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'day')); elZoneStageHour.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'hour')); @@ -1587,26 +1557,26 @@ function loaded() { elMoveSelectionToGrid.addEventListener("click", function() { EventBridge.emitWebEvent(JSON.stringify({ type: "action", - action: "moveSelectionToGrid", + action: "moveSelectionToGrid" })); }); elMoveAllToGrid.addEventListener("click", function() { EventBridge.emitWebEvent(JSON.stringify({ type: "action", - action: "moveAllToGrid", + action: "moveAllToGrid" })); }); elResetToNaturalDimensions.addEventListener("click", function() { EventBridge.emitWebEvent(JSON.stringify({ type: "action", - action: "resetToNaturalDimensions", + action: "resetToNaturalDimensions" })); }); elRescaleDimensionsButton.addEventListener("click", function() { EventBridge.emitWebEvent(JSON.stringify({ type: "action", action: "rescaleDimensions", - percentage: parseFloat(elRescaleDimensionsPct.value), + percentage: parseFloat(elRescaleDimensionsPct.value) })); }); elReloadScriptsButton.addEventListener("click", function() { @@ -1625,20 +1595,20 @@ function loaded() { }); document.addEventListener("keydown", function (keyDown) { - if (keyDown.keyCode === KEY_P && keyDown.ctrlKey) { - if (keyDown.shiftKey) { - EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); - } else { - EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' })); - } - } + if (keyDown.keyCode === KEY_P && keyDown.ctrlKey) { + if (keyDown.shiftKey) { + EventBridge.emitWebEvent(JSON.stringify({ type: 'unparent' })); + } else { + EventBridge.emitWebEvent(JSON.stringify({ type: 'parent' })); + } + } }); window.onblur = function() { // Fake a change event var ev = document.createEvent("HTMLEvents"); ev.initEvent("change", true, true); document.activeElement.dispatchEvent(ev); - } + }; // For input and textarea elements, select all of the text on focus // WebKit-based browsers, such as is used with QWebView, have a quirk @@ -1653,13 +1623,17 @@ function loaded() { for (var i = 0; i < els.length; i++) { var clicked = false; var originalText; + // TODO FIXME: (JSHint) Functions declared within loops referencing + // an outer scoped variable may lead to confusing semantics. els[i].onfocus = function(e) { originalText = this.value; this.select(); clicked = false; }; + // TODO FIXME: (JSHint) Functions declared within loops referencing + // an outer scoped variable may lead to confusing semantics. els[i].onmouseup = function(e) { - if (!clicked && originalText == this.value) { + if (!clicked && originalText === this.value) { e.preventDefault(); } clicked = true; @@ -1674,15 +1648,15 @@ function loaded() { var toggleCollapsedEvent = function(event) { var element = event.target.parentNode.parentNode; var isCollapsed = element.dataset.collapsed !== "true"; - element.dataset.collapsed = isCollapsed ? "true" : false + element.dataset.collapsed = isCollapsed ? "true" : false; element.setAttribute("collapsed", isCollapsed ? "true" : "false"); element.getElementsByTagName("span")[0].textContent = isCollapsed ? "L" : "M"; }; - for (var i = 0, length = elCollapsible.length; i < length; i++) { - var element = elCollapsible[i]; - element.addEventListener("click", toggleCollapsedEvent, true); - }; + for (var collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) { + var curCollapsibleElement = elCollapsible[collapseIndex]; + curCollapsibleElement.addEventListener("click", toggleCollapsedEvent, true); + } // Textarea scrollbars @@ -1690,17 +1664,17 @@ function loaded() { var textareaOnChangeEvent = function(event) { setTextareaScrolling(event.target); - } + }; - for (var i = 0, length = elTextareas.length; i < length; i++) { - var element = elTextareas[i]; - setTextareaScrolling(element); - element.addEventListener("input", textareaOnChangeEvent, false); - element.addEventListener("change", textareaOnChangeEvent, false); + for (var textAreaIndex = 0, numTextAreas = elTextareas.length; textAreaIndex < numTextAreas; ++textAreaIndex) { + var curTextAreaElement = elTextareas[textAreaIndex]; + setTextareaScrolling(curTextAreaElement); + curTextAreaElement.addEventListener("input", textareaOnChangeEvent, false); + curTextAreaElement.addEventListener("change", textareaOnChangeEvent, false); /* FIXME: Detect and update textarea scrolling attribute on resize. Unfortunately textarea doesn't have a resize event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */ - element.addEventListener("mouseup", textareaOnChangeEvent, false); - }; + curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false); + } // Dropdowns // For each dropdown the following replacement is created in place of the oriringal dropdown... @@ -1749,22 +1723,23 @@ function loaded() { } var elDropdowns = document.getElementsByTagName("select"); - for (var i = 0; i < elDropdowns.length; i++) { - var options = elDropdowns[i].getElementsByTagName("option"); + for (var dropDownIndex = 0; dropDownIndex < elDropdowns.length; ++dropDownIndex) { + var options = elDropdowns[dropDownIndex].getElementsByTagName("option"); var selectedOption = 0; - for (var j = 0; j < options.length; j++) { - if (options[j].getAttribute("selected") === "selected") { - selectedOption = j; + for (var optionIndex = 0; optionIndex < options.length; ++optionIndex) { + if (options[optionIndex].getAttribute("selected") === "selected") { + selectedOption = optionIndex; + // TODO: Shouldn't there be a break here? } } - var div = elDropdowns[i].parentNode; + var div = elDropdowns[dropDownIndex].parentNode; var dl = document.createElement("dl"); div.appendChild(dl); var dt = document.createElement("dt"); - dt.name = elDropdowns[i].name; - dt.id = elDropdowns[i].id; + dt.name = elDropdowns[dropDownIndex].name; + dt.id = elDropdowns[dropDownIndex].id; dt.addEventListener("click", toggleDropdown, true); dl.appendChild(dt); @@ -1773,9 +1748,9 @@ function loaded() { span.textContent = options[selectedOption].firstChild.textContent; dt.appendChild(span); - var span = document.createElement("span"); - span.textContent = "5"; // caratDn - dt.appendChild(span); + var spanCaratDown = document.createElement("span"); + spanCaratDown.textContent = "5"; // caratDn + dt.appendChild(spanCaratDown); var dd = document.createElement("dd"); dl.appendChild(dd); @@ -1783,10 +1758,10 @@ function loaded() { var ul = document.createElement("ul"); dd.appendChild(ul); - for (var j = 0; j < options.length; j++) { + for (var listOptionIndex = 0; listOptionIndex < options.length; ++listOptionIndex) { var li = document.createElement("li"); - li.setAttribute("value", options[j].value); - li.textContent = options[j].firstChild.textContent; + li.setAttribute("value", options[listOptionIndex].value); + li.textContent = options[listOptionIndex].firstChild.textContent; li.addEventListener("click", setDropdownValue); ul.appendChild(li); } diff --git a/scripts/system/html/js/keyboardControl.js b/scripts/system/html/js/keyboardControl.js index f2937bc63a..7a8a314c62 100644 --- a/scripts/system/html/js/keyboardControl.js +++ b/scripts/system/html/js/keyboardControl.js @@ -15,6 +15,9 @@ function setUpKeyboardControl() { var KEYBOARD_HEIGHT = 200; function raiseKeyboard() { + window.isKeyboardRaised = true; + window.isNumericKeyboard = this.type === "number"; + if (lowerTimer !== null) { clearTimeout(lowerTimer); lowerTimer = null; @@ -35,6 +38,9 @@ function setUpKeyboardControl() { } function doLowerKeyboard() { + window.isKeyboardRaised = false; + window.isNumericKeyboard = false; + EventBridge.emitWebEvent("_LOWER_KEYBOARD"); lowerTimer = null; isRaised = false; diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 1346bcd750..878c3b51f1 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -327,6 +327,19 @@ }); } + // fix for 10108 - marketplace category cannot scroll + function injectAddScrollbarToCategories() { + $('#categories-dropdown').on('show.bs.dropdown', function () { + $('body > div.container').css('display', 'none') + $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': 'auto', 'height': 'calc(100vh - 110px)' }) + }); + + $('#categories-dropdown').on('hide.bs.dropdown', function () { + $('body > div.container').css('display', '') + $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': '', 'height': '' }) + }); + } + function injectHiFiCode() { if (commerceMode) { maybeAddLogInButton(); @@ -358,6 +371,7 @@ } injectUnfocusOnSearch(); + injectAddScrollbarToCategories(); } function injectHiFiItemPageCode() { diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index 57b17f3d72..4217ec503e 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -118,15 +118,16 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { Overlays.deleteOverlay(this.webOverlayID); } - var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2) * (1 / sensorScaleFactor); - var WEB_ENTITY_Y_OFFSET = 0.004 * (1 / sensorScaleFactor); - + var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) / sensorScaleFactor; + var WEB_ENTITY_Y_OFFSET = 0.004; + var screenWidth = 0.82 * tabletWidth; + var screenHeight = 0.81 * tabletHeight; this.webOverlayID = Overlays.addOverlay("web3d", { name: "WebTablet Web", url: url, localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, localRotation: Quat.angleAxis(180, Y_AXIS), - resolution: this.getTabletTextureResolution(), + dimensions: {x: screenWidth, y: screenHeight, z: 0.1}, dpi: tabletDpi, color: { red: 255, green: 255, blue: 255 }, alpha: 1.0, @@ -136,12 +137,15 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { visible: visible }); - var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20)) * (1 / sensorScaleFactor); - this.homeButtonID = Overlays.addOverlay("sphere", { + var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20)) * (1 / sensorScaleFactor) - 0.003; + // FIXME: Circle3D overlays currently at the wrong dimensions, so we need to account for that here + var homeButtonDim = 4.0 * tabletScaleFactor / 3.0; + this.homeButtonID = Overlays.addOverlay("circle3d", { name: "homeButton", - localPosition: {x: -0.001, y: -HOME_BUTTON_Y_OFFSET, z: 0.0}, - localRotation: {x: 0, y: 1, z: 0, w: 0}, - dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor}, + localPosition: { x: 0.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 }, + solid: true, alpha: 0.0, visible: visible, drawInFront: false, @@ -151,14 +155,14 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { this.homeButtonHighlightID = Overlays.addOverlay("circle3d", { name: "homeButtonHighlight", - localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET + 0.003, z: -0.0158 }, + localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, localRotation: { x: 0, y: 1, z: 0, w: 0 }, - dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor }, + dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }, + color: { red: 255, green: 255, blue: 255 }, solid: true, innerRadius: 0.9, ignoreIntersection: true, alpha: 1.0, - color: { red: 255, green: 255, blue: 255 }, visible: visible, drawInFront: false, parentID: this.tabletEntityID, @@ -265,11 +269,16 @@ WebTablet.prototype.setLandscape = function(newLandscapeValue) { this.landscape = newLandscapeValue; Overlays.editOverlay(this.tabletEntityID, - { rotation: this.landscape ? Quat.multiply(Camera.orientation, ROT_LANDSCAPE) : - Quat.multiply(Camera.orientation, ROT_Y_180) }); + { rotation: Quat.multiply(Camera.orientation, this.landscape ? ROT_LANDSCAPE : ROT_Y_180) }); + + var tabletWidth = getTabletWidthFromSettings() * MyAvatar.sensorToWorldScale; + var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x; + var tabletHeight = TABLET_NATURAL_DIMENSIONS.y * tabletScaleFactor; + var screenWidth = 0.82 * tabletWidth; + var screenHeight = 0.81 * tabletHeight; Overlays.editOverlay(this.webOverlayID, { - resolution: this.getTabletTextureResolution(), - rotation: Quat.multiply(Camera.orientation, ROT_LANDSCAPE_WINDOW) + rotation: Quat.multiply(Camera.orientation, ROT_LANDSCAPE_WINDOW), + dimensions: {x: this.landscape ? screenHeight : screenWidth, y: this.landscape ? screenWidth : screenHeight, z: 0.1} }); }; @@ -505,31 +514,17 @@ WebTablet.prototype.getPosition = function () { }; WebTablet.prototype.mousePressEvent = function (event) { - var pickRay = Camera.computePickRay(event.x, event.y); - var entityPickResults; - 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.webOverlayID, this.homeButtonID], []); - if (overlayPickResults.intersects && overlayPickResults.overlayID === this.homeButtonID) { - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - var onHomeScreen = tablet.onHomeScreen(); - var isMessageOpen = tablet.isMessageDialogOpen(); - if (onHomeScreen) { - if (isMessageOpen === false) { - HMD.closeTablet(); - } - } else { - if (isMessageOpen === false) { - tablet.gotoHomeScreen(); - this.setHomeButtonTexture(); - } + if (!HMD.active) { + var pickRay = Camera.computePickRay(event.x, event.y); + var tabletBackPickResults = Overlays.findRayIntersection(pickRay, true, [this.tabletEntityID]); + if (tabletBackPickResults.intersects) { + var overlayPickResults = Overlays.findRayIntersection(pickRay, true, [this.webOverlayID, this.homeButtonID]); + if (!overlayPickResults.intersects) { + this.dragging = true; + var invCameraXform = new Xform(Camera.orientation, Camera.position).inv(); + this.initialLocalIntersectionPoint = invCameraXform.xformPoint(tabletBackPickResults.intersection); + this.initialLocalPosition = Overlays.getProperty(this.tabletEntityID, "localPosition"); } - } else if (!HMD.active && (!overlayPickResults.intersects || overlayPickResults.overlayID !== this.webOverlayID)) { - this.dragging = true; - var invCameraXform = new Xform(Camera.orientation, Camera.position).inv(); - this.initialLocalIntersectionPoint = invCameraXform.xformPoint(entityPickResults.intersection); - this.initialLocalPosition = Overlays.getProperty(this.tabletEntityID, "localPosition"); } } }; diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index fd0db91fec..e0971201e6 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -272,22 +272,8 @@ projectOntoEntityXYPlane = function (entityID, worldPos, props) { projectOntoOverlayXYPlane = function projectOntoOverlayXYPlane(overlayID, worldPos) { var position = Overlays.getProperty(overlayID, "position"); var rotation = Overlays.getProperty(overlayID, "rotation"); - var dimensions; - - var dpi = Overlays.getProperty(overlayID, "dpi"); - if (dpi) { - // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property is used as a scale. - var resolution = Overlays.getProperty(overlayID, "resolution"); - resolution.z = 1; // Circumvent divide-by-zero. - var scale = Overlays.getProperty(overlayID, "dimensions"); - scale.z = 0.01; // overlay dimensions are 2D, not 3D. - dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); - } else { - dimensions = Overlays.getProperty(overlayID, "dimensions"); - if (dimensions.z) { - dimensions.z = 0.01; // overlay dimensions are 2D, not 3D. - } - } + var dimensions = Overlays.getProperty(overlayID, "dimensions"); + dimensions.z = 0.01; // we are projecting onto the XY plane of the overlay, so ignore the z dimension return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT); }; diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index d947a1d397..b8ba146757 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1031,6 +1031,71 @@ SelectionDisplay = (function() { that.updateHandles(); }; + // Function: Calculate New Bound Extremes + // uses dot product to discover new top and bottom on the new referential (max and min) + that.calculateNewBoundExtremes = function(boundPointList, referenceVector) { + + if (boundPointList.length < 2) { + return [null, null]; + } + + var refMax = boundPointList[0]; + var refMin = boundPointList[1]; + + var dotMax = Vec3.dot(boundPointList[0], referenceVector); + var dotMin = Vec3.dot(boundPointList[1], referenceVector); + + if (dotMin > dotMax) { + dotMax = dotMin; + dotMin = Vec3.dot(boundPointList[0], referenceVector); + refMax = boundPointList[1]; + refMin = boundPointList[0]; + } + + for (var i = 2; i < boundPointList.length ; i++) { + var dotAux = Vec3.dot(boundPointList[i], referenceVector); + if (dotAux > dotMax) { + dotMax = dotAux; + refMax = boundPointList[i]; + } else if (dotAux < dotMin) { + dotMin = dotAux; + refMin = boundPointList[i]; + } + } + return [refMin, refMax]; + } + + // Function: Project Bounding Box Points + // Projects all 6 bounding box points: Top, Bottom, Left, Right, Near, Far (assumes center 0,0,0) onto + // one of the basis of the new avatar referencial + // dimensions - dimensions of the AABB (axis aligned bounding box) on the standard basis + // [1, 0, 0], [0, 1, 0], [0, 0, 1] + // v - projection vector + // rotateHandleOffset - offset for the rotation handle gizmo position + that.projectBoundingBoxPoints = function(dimensions, v, rotateHandleOffset) { + var projT_v = Vec3.dot(Vec3.multiply((dimensions.y / 2) + rotateHandleOffset, Vec3.UNIT_Y), v); + projT_v = Vec3.multiply(projT_v, v); + + var projB_v = Vec3.dot(Vec3.multiply(-(dimensions.y / 2) - rotateHandleOffset, Vec3.UNIT_Y), v); + projB_v = Vec3.multiply(projB_v, v); + + var projL_v = Vec3.dot(Vec3.multiply((dimensions.x / 2) + rotateHandleOffset, Vec3.UNIT_X), v); + projL_v = Vec3.multiply(projL_v, v); + + var projR_v = Vec3.dot(Vec3.multiply(-1.0 * (dimensions.x / 2) - 1.0 * rotateHandleOffset, Vec3.UNIT_X), v); + projR_v = Vec3.multiply(projR_v, v); + + var projN_v = Vec3.dot(Vec3.multiply((dimensions.z / 2) + rotateHandleOffset, Vec3.FRONT), v); + projN_v = Vec3.multiply(projN_v, v); + + var projF_v = Vec3.dot(Vec3.multiply(-1.0 * (dimensions.z / 2) - 1.0 * rotateHandleOffset, Vec3.FRONT), v); + projF_v = Vec3.multiply(projF_v, v); + + var projList = [projT_v, projB_v, projL_v, projR_v, projN_v, projF_v]; + + return that.calculateNewBoundExtremes(projList, v); + }; + // FUNCTION: UPDATE ROTATION HANDLES that.updateRotationHandles = function() { var diagonal = (Vec3.length(SelectionManager.worldDimensions) / 2) * 1.1; @@ -1043,10 +1108,10 @@ SelectionDisplay = (function() { } else { outerAlpha = 0.5; } - + // prev 0.05 var rotateHandleOffset = 0.05; - var top, far, left, bottom, near, right, boundsCenter, objectCenter, BLN, BRN, BLF, TLN, TRN, TLF, TRF; + var boundsCenter, objectCenter; var dimensions, rotation; if (spaceMode === SPACE_LOCAL) { @@ -1058,280 +1123,66 @@ SelectionDisplay = (function() { dimensions = SelectionManager.worldDimensions; var position = objectCenter; - top = objectCenter.y + (dimensions.y / 2); - far = objectCenter.z + (dimensions.z / 2); - left = objectCenter.x + (dimensions.x / 2); - - bottom = objectCenter.y - (dimensions.y / 2); - near = objectCenter.z - (dimensions.z / 2); - right = objectCenter.x - (dimensions.x / 2); - boundsCenter = objectCenter; var yawCorner; var pitchCorner; var rollCorner; - // determine which bottom corner we are closest to - /*------------------------------ - example: - - BRF +--------+ BLF - | | - | | - BRN +--------+ BLN - - * - - ------------------------------*/ - var cameraPosition = Camera.getPosition(); - if (cameraPosition.x > objectCenter.x) { - // must be BRF or BRN - if (cameraPosition.z < objectCenter.z) { + var look = Vec3.normalize(Vec3.subtract(cameraPosition, objectCenter)); - yawHandleRotation = Quat.fromVec3Degrees({ - x: 270, - y: 90, - z: 0 - }); - pitchHandleRotation = Quat.fromVec3Degrees({ - x: 0, - y: 90, - z: 0 - }); - rollHandleRotation = Quat.fromVec3Degrees({ - x: 0, - y: 0, - z: 0 - }); + // place yaw, pitch and roll rotations on the avatar referential + + var avatarReferential = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({ + x: 0, + y: 180, + z: 0 + })); + var upVector = Quat.getUp(avatarReferential); + var rightVector = Vec3.multiply(-1, Quat.getRight(avatarReferential)); + var frontVector = Quat.getFront(avatarReferential); + + // project all 6 bounding box points: Top, Bottom, Left, Right, Near, Far (assumes center 0,0,0) + // onto the new avatar referential - yawCorner = { - x: left + rotateHandleOffset, - y: bottom - rotateHandleOffset, - z: near - rotateHandleOffset - }; - - pitchCorner = { - x: right - rotateHandleOffset, - y: top + rotateHandleOffset, - z: near - rotateHandleOffset - }; - - rollCorner = { - x: left + rotateHandleOffset, - y: top + rotateHandleOffset, - z: far + rotateHandleOffset - }; - - yawCenter = { - x: boundsCenter.x, - y: bottom, - z: boundsCenter.z - }; - pitchCenter = { - x: right, - y: boundsCenter.y, - z: boundsCenter.z - }; - rollCenter = { - x: boundsCenter.x, - y: boundsCenter.y, - z: far - }; - - - Overlays.editOverlay(pitchHandle, { - url: ROTATE_ARROW_WEST_SOUTH_URL - }); - Overlays.editOverlay(rollHandle, { - url: ROTATE_ARROW_WEST_SOUTH_URL - }); - - - } else { - - yawHandleRotation = Quat.fromVec3Degrees({ - x: 270, - y: 0, - z: 0 - }); - pitchHandleRotation = Quat.fromVec3Degrees({ - x: 180, - y: 270, - z: 0 - }); - rollHandleRotation = Quat.fromVec3Degrees({ - x: 0, - y: 0, - z: 90 - }); - - yawCorner = { - x: left + rotateHandleOffset, - y: bottom - rotateHandleOffset, - z: far + rotateHandleOffset - }; - - pitchCorner = { - x: right - rotateHandleOffset, - y: top + rotateHandleOffset, - z: far + rotateHandleOffset - }; - - rollCorner = { - x: left + rotateHandleOffset, - y: top + rotateHandleOffset, - z: near - rotateHandleOffset - }; - - - yawCenter = { - x: boundsCenter.x, - y: bottom, - z: boundsCenter.z - }; - pitchCenter = { - x: right, - y: boundsCenter.y, - z: boundsCenter.z - }; - rollCenter = { - x: boundsCenter.x, - y: boundsCenter.y, - z: near - }; - - Overlays.editOverlay(pitchHandle, { - url: ROTATE_ARROW_WEST_NORTH_URL - }); - Overlays.editOverlay(rollHandle, { - url: ROTATE_ARROW_WEST_NORTH_URL - }); - } - } else { - - // must be BLF or BLN - if (cameraPosition.z < objectCenter.z) { - - yawHandleRotation = Quat.fromVec3Degrees({ - x: 270, - y: 180, - z: 0 - }); - pitchHandleRotation = Quat.fromVec3Degrees({ - x: 90, - y: 0, - z: 90 - }); - rollHandleRotation = Quat.fromVec3Degrees({ - x: 0, - y: 0, - z: 180 - }); - - yawCorner = { - x: right - rotateHandleOffset, - y: bottom - rotateHandleOffset, - z: near - rotateHandleOffset - }; - - pitchCorner = { - x: left + rotateHandleOffset, - y: top + rotateHandleOffset, - z: near - rotateHandleOffset - }; - - rollCorner = { - x: right - rotateHandleOffset, - y: top + rotateHandleOffset, - z: far + rotateHandleOffset - }; - - yawCenter = { - x: boundsCenter.x, - y: bottom, - z: boundsCenter.z - }; - pitchCenter = { - x: left, - y: boundsCenter.y, - z: boundsCenter.z - }; - rollCenter = { - x: boundsCenter.x, - y: boundsCenter.y, - z: far - }; - - Overlays.editOverlay(pitchHandle, { - url: ROTATE_ARROW_WEST_NORTH_URL - }); - Overlays.editOverlay(rollHandle, { - url: ROTATE_ARROW_WEST_NORTH_URL - }); - - } else { - - yawHandleRotation = Quat.fromVec3Degrees({ - x: 270, - y: 270, - z: 0 - }); - pitchHandleRotation = Quat.fromVec3Degrees({ - x: 180, - y: 270, - z: 0 - }); - rollHandleRotation = Quat.fromVec3Degrees({ - x: 0, - y: 0, - z: 180 - }); - - yawCorner = { - x: right - rotateHandleOffset, - y: bottom - rotateHandleOffset, - z: far + rotateHandleOffset - }; - - rollCorner = { - x: right - rotateHandleOffset, - y: top + rotateHandleOffset, - z: near - rotateHandleOffset - }; - - pitchCorner = { - x: left + rotateHandleOffset, - y: top + rotateHandleOffset, - z: far + rotateHandleOffset - }; - - yawCenter = { - x: boundsCenter.x, - y: bottom, - z: boundsCenter.z - }; - rollCenter = { - x: boundsCenter.x, - y: boundsCenter.y, - z: near - }; - pitchCenter = { - x: left, - y: boundsCenter.y, - z: boundsCenter.z - }; - - Overlays.editOverlay(pitchHandle, { - url: ROTATE_ARROW_WEST_NORTH_URL - }); - Overlays.editOverlay(rollHandle, { - url: ROTATE_ARROW_WEST_NORTH_URL - }); - - } - } + // UP + var projUP = that.projectBoundingBoxPoints(dimensions, upVector, rotateHandleOffset); + // RIGHT + var projRIGHT = that.projectBoundingBoxPoints(dimensions, rightVector, rotateHandleOffset); + // FRONT + var projFRONT = that.projectBoundingBoxPoints(dimensions, frontVector, rotateHandleOffset); + + // YAW + yawCenter = Vec3.sum(boundsCenter, projUP[0]); + yawCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[0], projRIGHT[1]), projFRONT[1])); + + yawHandleRotation = Quat.lookAt( + yawCorner, + Vec3.sum(yawCorner, upVector), + Vec3.subtract(yawCenter,yawCorner)); + yawHandleRotation = Quat.multiply(Quat.angleAxis(45, upVector), yawHandleRotation); + + // PTCH + pitchCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[1], projRIGHT[0]), projFRONT[1])); + pitchCenter = Vec3.sum(boundsCenter, projRIGHT[0]); + + pitchHandleRotation = Quat.lookAt( + pitchCorner, + Vec3.sum(pitchCorner, rightVector), + Vec3.subtract(pitchCenter,pitchCorner)); + pitchHandleRotation = Quat.multiply(Quat.angleAxis(45, rightVector), pitchHandleRotation); + + // ROLL + rollCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[1], projRIGHT[1]), projFRONT[0])); + rollCenter = Vec3.sum(boundsCenter, projFRONT[0]); + + rollHandleRotation = Quat.lookAt( + rollCorner, + Vec3.sum(rollCorner, frontVector), + Vec3.subtract(rollCenter,rollCorner)); + rollHandleRotation = Quat.multiply(Quat.angleAxis(45, frontVector), rollHandleRotation); + var rotateHandlesVisible = true; var rotationOverlaysVisible = false; @@ -1382,6 +1233,8 @@ SelectionDisplay = (function() { position: rollCorner, rotation: rollHandleRotation }); + + }; // FUNCTION: UPDATE HANDLE SIZES @@ -3422,7 +3275,7 @@ SelectionDisplay = (function() { y: innerRadius * ROTATION_DISPLAY_SIZE_Y_MULTIPLIER }, lineHeight: innerRadius * ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER, - text: normalizeDegrees(angleFromZero) + "°" + text: normalizeDegrees(-angleFromZero) + "°" }; if (wantDebug) { print(" TranslatedPos: " + position.x + ", " + position.y + ", " + position.z); @@ -3483,6 +3336,13 @@ SelectionDisplay = (function() { initialPosition = SelectionManager.worldPosition; rotationNormal = { x: 0, y: 0, z: 0 }; rotationNormal[rotAroundAxis] = 1; + //get the correct axis according to the avatar referencial + var avatarReferential = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({ + x: 0, + y: 0, + z: 0 + })); + rotationNormal = Vec3.multiplyQbyV(avatarReferential, rotationNormal); // Size the overlays to the current selection size var diagonal = (Vec3.length(SelectionManager.worldDimensions) / 2) * 1.1; @@ -3584,11 +3444,11 @@ SelectionDisplay = (function() { var snapAngle = snapToInner ? innerSnapAngle : 1.0; angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; - var vec3Degrees = { x: 0, y: 0, z: 0 }; - vec3Degrees[rotAroundAxis] = angleFromZero; - var rotChange = Quat.fromVec3Degrees(vec3Degrees); + + var rotChange = Quat.angleAxis(angleFromZero, rotationNormal); updateSelectionsRotation(rotChange); - + //present angle in avatar referencial + angleFromZero = -angleFromZero; updateRotationDegreesOverlay(angleFromZero, handleRotation, rotCenter); // update the rotation display accordingly... diff --git a/scripts/system/libraries/gridTool.js b/scripts/system/libraries/gridTool.js index 2c417a9dde..19d4417a12 100644 --- a/scripts/system/libraries/gridTool.js +++ b/scripts/system/libraries/gridTool.js @@ -242,7 +242,9 @@ GridTool = function(opts) { horizontalGrid.addListener(function(data) { webView.emitScriptEvent(JSON.stringify(data)); - selectionDisplay.updateHandles(); + if (selectionDisplay) { + selectionDisplay.updateHandles(); + } }); webView.webEventReceived.connect(function(data) { diff --git a/scripts/system/libraries/touchEventUtils.js b/scripts/system/libraries/touchEventUtils.js index c9fd825a09..f0f7ec46fe 100644 --- a/scripts/system/libraries/touchEventUtils.js +++ b/scripts/system/libraries/touchEventUtils.js @@ -169,32 +169,12 @@ function calculateTouchTargetFromOverlay(touchTip, overlayID) { // calclulate normalized position var invRot = Quat.inverse(overlayRotation); var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, overlayPosition)); - var dpi = Overlays.getProperty(overlayID, "dpi"); - var dimensions; - if (dpi) { - // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property - // is used as a scale. - var resolution = Overlays.getProperty(overlayID, "resolution"); - if (resolution === undefined) { - return; - } - resolution.z = 1; // Circumvent divide-by-zero. - var scale = Overlays.getProperty(overlayID, "dimensions"); - if (scale === undefined) { - return; - } - scale.z = 0.01; // overlay dimensions are 2D, not 3D. - dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); - } else { - dimensions = Overlays.getProperty(overlayID, "dimensions"); - if (dimensions === undefined) { - return; - } - if (!dimensions.z) { - dimensions.z = 0.01; // sometimes overlay dimensions are 2D, not 3D. - } + var dimensions = Overlays.getProperty(overlayID, "dimensions"); + if (dimensions === undefined) { + return; } + dimensions.z = 0.01; // we are projecting onto the XY plane of the overlay, so ignore the z dimension var invDimensions = { x: 1 / dimensions.x, y: 1 / dimensions.y, z: 1 / dimensions.z }; var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), DEFAULT_REGISTRATION_POINT); diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index 9706073081..4a1fcdf301 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -185,7 +185,7 @@ logTrace = function(str) { // (the vector that would move the point outside the sphere) // otherwise returns false findSphereHit = function(point, sphereRadius) { - var EPSILON = 0.000001; //smallish positive number - used as margin of error for some computations + var EPSILON = 0.000001; //smallish positive number - used as margin of error for some computations var vectorLength = Vec3.length(point); if (vectorLength < EPSILON) { return true; @@ -400,25 +400,28 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) }); // update webOverlay - var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2) * sensorScaleOffsetOverride; - var WEB_ENTITY_Y_OFFSET = 0.004 * sensorScaleOffsetOverride; + var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) * sensorScaleOffsetOverride; + var WEB_ENTITY_Y_OFFSET = 0.004 * sensorScaleFactor * sensorScaleOffsetOverride; + var screenWidth = 0.82 * tabletWidth; + var screenHeight = 0.81 * 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 }, + 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)) * sensorScaleOffsetOverride; - var homeButtonDim = 4 * tabletScaleFactor; + 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; Overlays.editOverlay(HMD.homeButtonID, { - localPosition: {x: -0.001, y: -HOME_BUTTON_Y_OFFSET, z: 0.0}, - dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim} + localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, + dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } }); - // Circle3D overlays render at 1.5x their proper dimensions - var highlightDim = homeButtonDim / 3.0; Overlays.editOverlay(HMD.homeButtonHighlightID, { - localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET + 0.003, z: -0.0158 }, - dimensions: { x: highlightDim, y: highlightDim, z: highlightDim } + localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, + dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } }); }; diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 646e5452df..004375bff7 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -11,9 +11,12 @@ /* global Tablet, Script, HMD, UserActivityLogger, Entities */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ +var selectionDisplay = null; // for gridTool.js to ignore + (function () { // BEGIN LOCAL_SCOPE - Script.include("../libraries/WebTablet.js"); + Script.include("/~/system/libraries/WebTablet.js"); + Script.include("/~/system/libraries/gridTool.js"); var METAVERSE_SERVER_URL = Account.metaverseServerURL; var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace"; @@ -110,8 +113,10 @@ var filterText; // Used for updating Purchases QML function onScreenChanged(type, url) { onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1; - onCommerceScreen = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH_BASE) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH || url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1); - wireEventBridge(onCommerceScreen); + onWalletScreen = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1; + onCommerceScreen = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH_BASE) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH + || url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1); + wireEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen); if (url === MARKETPLACE_PURCHASES_QML_PATH) { tablet.sendToQml({ @@ -122,7 +127,7 @@ } // for toolbar mode: change button to active when window is first openend, false otherwise. - marketplaceButton.editProperties({ isActive: onMarketplaceScreen || onCommerceScreen }); + marketplaceButton.editProperties({ isActive: (onMarketplaceScreen || onCommerceScreen) && !onWalletScreen }); if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) { ContextOverlay.isInMarketplaceInspectionMode = true; } else { @@ -168,6 +173,33 @@ })); } + var grid = new Grid(); + function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { + // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original + // position in the given direction. + var CORNERS = [ + { x: 0, y: 0, z: 0 }, + { x: 0, y: 0, z: 1 }, + { x: 0, y: 1, z: 0 }, + { x: 0, y: 1, z: 1 }, + { x: 1, y: 0, z: 0 }, + { x: 1, y: 0, z: 1 }, + { x: 1, y: 1, z: 0 }, + { x: 1, y: 1, z: 1 }, + ]; + + // Go through all corners and find least (most negative) distance in front of position. + var distance = 0; + for (var i = 0, length = CORNERS.length; i < length; i++) { + var cornerVector = + Vec3.multiplyQbyV(orientation, Vec3.multiplyVbyV(Vec3.subtract(CORNERS[i], registration), dimensions)); + var cornerDistance = Vec3.dot(cornerVector, direction); + distance = Math.min(cornerDistance, distance); + } + position = Vec3.sum(Vec3.multiply(distance, direction), position); + return position; + } + var HALF_TREE_SCALE = 16384; function getPositionToCreateEntity(extra) { var CREATE_DISTANCE = 2; @@ -256,10 +288,6 @@ } } } - - if (isActive) { - selectionManager.setSelections(pastedEntityIDs); - } } else { Window.notifyEditError("Can't import entities: entities would be out of bounds."); } @@ -322,6 +350,11 @@ } else if (parsedJsonMessage.type === "LOGIN") { openLoginWindow(); } else if (parsedJsonMessage.type === "WALLET_SETUP") { + wireEventBridge(true); + tablet.sendToQml({ + method: 'updateWalletReferrer', + referrer: "marketplace cta" + }); openWallet(); } else if (parsedJsonMessage.type === "MY_ITEMS") { referrerURL = MARKETPLACE_URL_INITIAL; @@ -399,6 +432,7 @@ referrer: "purchases" }); openWallet(); + break; case 'checkout_walletNotSetUp': wireEventBridge(true); tablet.sendToQml({ @@ -420,8 +454,10 @@ tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); break; case 'checkout_itemLinkClicked': - case 'checkout_continueShopping': tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'checkout_continueShopping': + tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); //tablet.popFromStack(); break; case 'purchases_itemInfoClicked': diff --git a/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index 17821c737e..36a1cbcdd9 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -47,7 +47,7 @@ } return false; } - if (Overlays.getProperty(HMD.homeButtonID, "type") != "sphere" || + if (Overlays.getProperty(HMD.homeButtonID, "type") != "circle3d" || Overlays.getProperty(HMD.tabletScreenID, "type") != "web3d") { if (debugTablet) { print("TABLET is invalid due to other"); diff --git a/server-console/package-lock.json b/server-console/package-lock.json new file mode 100644 index 0000000000..51b7f2c268 --- /dev/null +++ b/server-console/package-lock.json @@ -0,0 +1,2250 @@ +{ + "name": "HighFidelitySandbox", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "abbrev": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz", + "integrity": "sha1-W2A1su6dT7XPhZ8Iqb6BsghJGEM=", + "dev": true + }, + "always-tail": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/always-tail/-/always-tail-0.2.0.tgz", + "integrity": "sha1-M5sa9E1QJQqgeg6H7Mw6JOxET/4=", + "requires": { + "debug": "0.7.4" + } + }, + "ansi-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz", + "integrity": "sha1-xQYbbg74qBd15Q9dZhUb9r83EQc=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "ansicolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.2.1.tgz", + "integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8=" + }, + "array-find-index": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.1.tgz", + "integrity": "sha1-C8Jd2slB7IpJauJY/UrBiAA+868=", + "dev": true + }, + "asar": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/asar/-/asar-0.11.0.tgz", + "integrity": "sha1-uSbnksMV+MBIxDNx4yWwnJenZGQ=", + "dev": true, + "requires": { + "chromium-pickle-js": "0.1.0", + "commander": "2.9.0", + "cuint": "0.2.1", + "glob": "6.0.4", + "minimatch": "3.0.0", + "mkdirp": "0.5.1", + "mksnapshot": "0.3.0" + }, + "dependencies": { + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "dev": true, + "requires": { + "inflight": "1.0.4", + "inherits": "2.0.1", + "minimatch": "3.0.0", + "once": "1.3.3", + "path-is-absolute": "1.0.0" + } + } + } + }, + "asn1": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", + "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + }, + "assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=" + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=" + }, + "aws4": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.3.2.tgz", + "integrity": "sha1-054L7kEs7Q6O2Uoj4xTzE6lbn9E=", + "requires": { + "lru-cache": "4.0.1" + } + }, + "balanced-match": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.3.0.tgz", + "integrity": "sha1-qRzdHr7xqGZZ5w/03vAWJfwtZ1Y=" + }, + "base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=", + "dev": true + }, + "binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=", + "dev": true, + "requires": { + "buffers": "0.1.1", + "chainsaw": "0.1.0" + } + }, + "bl": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", + "integrity": "sha1-/cqHGplxOqANGeO7ukHER4emU5g=", + "requires": { + "readable-stream": "2.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.1", + "isarray": "1.0.0", + "process-nextick-args": "1.0.6", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + } + } + } + }, + "bluebird": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.10.2.tgz", + "integrity": "sha1-AkpVFylTCIV/FPkfEQb8O1VfRGs=", + "dev": true + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.3.tgz", + "integrity": "sha1-Rr/1ARXUf8mriYVKu4fZgHihCZE=", + "requires": { + "balanced-match": "0.3.0", + "concat-map": "0.0.1" + } + }, + "buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s=", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "dev": true, + "requires": { + "camelcase": "2.1.1", + "map-obj": "1.0.1" + } + }, + "cardinal": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-0.5.0.tgz", + "integrity": "sha1-ANX2YdvUqr/ffUHOSKWlm8o1opE=", + "requires": { + "ansicolors": "0.2.1", + "redeyed": "0.5.0" + } + }, + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=" + }, + "chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=", + "dev": true, + "requires": { + "traverse": "0.3.9" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" + } + }, + "cheerio": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.19.0.tgz", + "integrity": "sha1-dy5wFfLuKZZQltcepBdbdas1SSU=", + "requires": { + "css-select": "1.0.0", + "dom-serializer": "0.1.0", + "entities": "1.1.1", + "htmlparser2": "3.8.3", + "lodash": "3.10.1" + } + }, + "chromium-pickle-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.1.0.tgz", + "integrity": "sha1-HUixB9ghJqLz4hHC6iX4A7pVGyE=", + "dev": true + }, + "cli-table": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", + "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", + "requires": { + "colors": "1.0.3" + } + }, + "cli-usage": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/cli-usage/-/cli-usage-0.1.2.tgz", + "integrity": "sha1-SXwg6vEuwneTk6m/rCJcX2y5FS0=", + "requires": { + "marked": "0.3.5", + "marked-terminal": "1.6.1", + "minimist": "0.2.0" + }, + "dependencies": { + "minimist": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.0.tgz", + "integrity": "sha1-Tf/lJdriuGTGbC4jxicdev3s784=" + } + } + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "1.0.1", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.0.0" + } + }, + "code-point-at": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.0.0.tgz", + "integrity": "sha1-9psZLT99keOC5Lcb3bd4eGGasMY=", + "requires": { + "number-is-nan": "1.0.0" + } + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" + }, + "combined-stream": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "requires": { + "delayed-stream": "1.0.0" + } + }, + "commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "requires": { + "graceful-readlink": "1.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz", + "integrity": "sha1-U/fUPFHF5D+ByP3QMyHGMb5o1hE=", + "dev": true, + "requires": { + "inherits": "2.0.1", + "readable-stream": "2.0.6", + "typedarray": "0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.1", + "isarray": "1.0.0", + "process-nextick-args": "1.0.6", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + } + } + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", + "requires": { + "boom": "2.10.1" + } + }, + "css-select": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.0.0.tgz", + "integrity": "sha1-sRIcpRhI3SZOIkTQWM7iVN7rRLA=", + "requires": { + "boolbase": "1.0.0", + "css-what": "1.0.0", + "domutils": "1.4.3", + "nth-check": "1.0.1" + } + }, + "css-what": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-1.0.0.tgz", + "integrity": "sha1-18wt9FGAZm+Z0rFEYmOUaeAPc2w=" + }, + "ctype": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz", + "integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8=", + "dev": true + }, + "cuint": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.1.tgz", + "integrity": "sha1-VlBFzoEnxwxr80D1kcAEin1M/rw=", + "dev": true + }, + "dashdash": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.13.0.tgz", + "integrity": "sha1-parm/Z2OFWYk6w3ZJZ6xK6JFOFo=", + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + } + } + }, + "debug": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz", + "integrity": "sha1-BuHqgILCyxTjmAbiLi9vdX+Srzk=" + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decompress-zip": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/decompress-zip/-/decompress-zip-0.3.0.tgz", + "integrity": "sha1-rjvLfjTGWHmt/nfhnDD4ZgK0vbA=", + "dev": true, + "requires": { + "binary": "0.3.0", + "graceful-fs": "4.1.3", + "mkpath": "0.1.0", + "nopt": "3.0.6", + "q": "1.4.1", + "readable-stream": "1.1.14", + "touch": "0.0.3" + } + }, + "deep-extend": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.1.tgz", + "integrity": "sha1-7+QRPQgIX05vlod1mBD4B0aeIlM=", + "dev": true + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "dom-serializer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", + "requires": { + "domelementtype": "1.1.3", + "entities": "1.1.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" + } + } + }, + "domelementtype": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" + }, + "domhandler": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", + "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", + "requires": { + "domelementtype": "1.3.0" + } + }, + "domutils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.4.3.tgz", + "integrity": "sha1-CGVRN5bGswYDGFDhdVFrr4C3Km8=", + "requires": { + "domelementtype": "1.3.0" + } + }, + "ecc-jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", + "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "optional": true, + "requires": { + "jsbn": "0.1.0" + } + }, + "electron-download": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/electron-download/-/electron-download-2.1.1.tgz", + "integrity": "sha1-AH07HyrTco0nzP5PhJayY/kTijE=", + "dev": true, + "requires": { + "debug": "2.2.0", + "home-path": "1.0.3", + "minimist": "1.2.0", + "mkdirp": "0.5.1", + "mv": "2.1.1", + "nugget": "1.6.2", + "path-exists": "1.0.0", + "rc": "1.1.6" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + } + } + }, + "electron-log": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-1.1.1.tgz", + "integrity": "sha1-DboCXtM9DkW/j0DG6b487i+YbCg=" + }, + "electron-osx-sign": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.3.0.tgz", + "integrity": "sha1-SXIB38g1OMVLNPGkBexuIaAf904=", + "dev": true, + "requires": { + "minimist": "1.2.0", + "run-series": "1.1.4" + } + }, + "electron-packager": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/electron-packager/-/electron-packager-6.0.2.tgz", + "integrity": "sha1-juAGaf6KNjCVAnMvz+0RfX7prCk=", + "dev": true, + "requires": { + "asar": "0.11.0", + "electron-download": "2.1.1", + "electron-osx-sign": "0.3.0", + "extract-zip": "1.5.0", + "fs-extra": "0.26.7", + "get-package-info": "0.0.2", + "minimist": "1.2.0", + "mkdirp": "0.5.1", + "mv": "2.1.1", + "ncp": "2.0.0", + "object-assign": "4.0.1", + "plist": "1.2.0", + "rcedit": "0.5.0", + "resolve": "1.1.7", + "rimraf": "2.5.2", + "run-series": "1.1.4" + } + }, + "electron-prebuilt": { + "version": "0.37.5", + "resolved": "https://registry.npmjs.org/electron-prebuilt/-/electron-prebuilt-0.37.5.tgz", + "integrity": "sha1-OkGJgod4FdOnrB+bLi9KcPQg/3A=", + "dev": true, + "requires": { + "electron-download": "2.1.1", + "extract-zip": "1.5.0" + } + }, + "end-of-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.1.0.tgz", + "integrity": "sha1-6TUyWLqpEIll78QcsO+K3i88+wc=", + "requires": { + "once": "1.3.3" + } + }, + "entities": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", + "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=" + }, + "error-ex": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.0.tgz", + "integrity": "sha1-5ntD8+gsluo6WE/+4Ln8MyXYAtk=", + "dev": true, + "requires": { + "is-arrayish": "0.2.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "esprima-fb": { + "version": "12001.1.0-dev-harmony-fb", + "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-12001.1.0-dev-harmony-fb.tgz", + "integrity": "sha1-2EQAOEupXOJnjGF60kp/QICNqRU=" + }, + "extend": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz", + "integrity": "sha1-WkdDU7nzNT3dgXbf03uRyDpG8dQ=" + }, + "extract-zip": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.5.0.tgz", + "integrity": "sha1-ksz22B73Cp+kwXRxFMzvbYaIpsQ=", + "dev": true, + "requires": { + "concat-stream": "1.5.0", + "debug": "0.7.4", + "mkdirp": "0.5.0", + "yauzl": "2.4.1" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", + "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + } + } + }, + "extsprintf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", + "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=" + }, + "fd-slicer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", + "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "dev": true, + "requires": { + "pend": "1.2.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "2.1.0", + "pinkie-promise": "2.0.1" + }, + "dependencies": { + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "2.0.1" + } + } + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "1.0.0-rc4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc4.tgz", + "integrity": "sha1-BaxrwiIntD5EYfSIFhVUaZ1Pi14=", + "requires": { + "async": "1.5.2", + "combined-stream": "1.0.5", + "mime-types": "2.1.10" + } + }, + "fs-extra": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz", + "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=", + "requires": { + "graceful-fs": "4.1.3", + "jsonfile": "2.2.3", + "klaw": "1.1.3", + "path-is-absolute": "1.0.0", + "rimraf": "2.5.2" + } + }, + "generate-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=" + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "requires": { + "is-property": "1.0.2" + } + }, + "get-package-info": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/get-package-info/-/get-package-info-0.0.2.tgz", + "integrity": "sha1-csOPvuLnZyhCSgDcFOJN0aKMI5E=", + "dev": true, + "requires": { + "bluebird": "3.3.5", + "lodash.get": "4.2.1", + "resolve": "1.1.7" + }, + "dependencies": { + "bluebird": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.3.5.tgz", + "integrity": "sha1-XudH8ce9lnZYtoOTZDCu51OVWjQ=", + "dev": true + } + } + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", + "dev": true + }, + "glob": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.3.tgz", + "integrity": "sha1-CqI1kxpKlqwT1g/6wvuHe9btT1g=", + "requires": { + "inflight": "1.0.4", + "inherits": "2.0.1", + "minimatch": "3.0.0", + "once": "1.3.3", + "path-is-absolute": "1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.3.tgz", + "integrity": "sha1-kgM84RETxB4mKNYf36QLwQ3AFVw=" + }, + "graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=" + }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=" + }, + "har-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "requires": { + "chalk": "1.1.3", + "commander": "2.9.0", + "is-my-json-valid": "2.13.1", + "pinkie-promise": "2.0.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "2.0.0" + } + }, + "hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=" + }, + "home-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/home-path/-/home-path-1.0.3.tgz", + "integrity": "sha1-ns5Z/sPwMubRC1Q0/uJk30wt4y8=", + "dev": true + }, + "hosted-git-info": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.1.4.tgz", + "integrity": "sha1-2elTsmmIvogJbEbpJklNlgTDAPg=", + "dev": true + }, + "htmlparser2": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", + "requires": { + "domelementtype": "1.3.0", + "domhandler": "2.3.0", + "domutils": "1.5.1", + "entities": "1.0.0", + "readable-stream": "1.1.14" + }, + "dependencies": { + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0.1.0", + "domelementtype": "1.3.0" + } + }, + "entities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=" + } + } + }, + "http-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.2.2", + "sshpk": "1.7.4" + } + }, + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "2.0.1" + }, + "dependencies": { + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "dev": true, + "requires": { + "is-finite": "1.0.1" + } + } + } + }, + "inflight": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz", + "integrity": "sha1-bLtFIevVHODsCpNr/XZX736bFyo=", + "requires": { + "once": "1.3.3", + "wrappy": "1.0.1" + } + }, + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + }, + "ini": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", + "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", + "dev": true + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, + "is-absolute": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.1.7.tgz", + "integrity": "sha1-hHSREZ/MtftDYhfMc39/qtUPYD8=", + "requires": { + "is-relative": "0.1.3" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "1.1.1" + } + }, + "is-finite": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz", + "integrity": "sha1-ZDhgPq6+J5OUj/SkJi7I2z1iWXs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "1.0.0" + } + }, + "is-my-json-valid": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.13.1.tgz", + "integrity": "sha1-1Vd4qC/rawlj/0vhEdXRaE6JBwc=", + "requires": { + "generate-function": "2.0.0", + "generate-object-property": "1.2.0", + "jsonpointer": "2.0.0", + "xtend": "4.0.1" + } + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=" + }, + "is-relative": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.1.3.tgz", + "integrity": "sha1-kF/uiuhvRbPsYUvDwVyGnfCHboI=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "isexe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-1.1.2.tgz", + "integrity": "sha1-NvPiLmB1CSD15yQaR2qMakInWtA=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jodid25519": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", + "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=", + "optional": true, + "requires": { + "jsbn": "0.1.0" + } + }, + "jsbn": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.0.tgz", + "integrity": "sha1-ZQmH2g3XT06/WhE3eiqi0nPpff0=", + "optional": true + }, + "json-schema": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz", + "integrity": "sha1-UDVPGfYDkXxpX3C4Wvp3w7DyNQY=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsonfile": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.2.3.tgz", + "integrity": "sha1-4lK5mmr5AdPsQfMyWJyQUJp7xgU=" + }, + "jsonpointer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz", + "integrity": "sha1-OvHdIP6FRjkQ1GmjheMwF9KgMNk=" + }, + "jsprim": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.2.2.tgz", + "integrity": "sha1-8gyQaskqvVjjt5rIvHCkiDJRLaE=", + "requires": { + "extsprintf": "1.0.2", + "json-schema": "0.2.2", + "verror": "1.3.6" + } + }, + "klaw": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.1.3.tgz", + "integrity": "sha1-faM8a0L5s9yc7ADRfxOvAX/MJyE=" + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "1.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "4.1.3", + "parse-json": "2.2.0", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "strip-bom": "2.0.0" + } + }, + "lodash": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" + }, + "lodash._arraycopy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz", + "integrity": "sha1-due3wfH7klRzdIeKVi7Qaj5Q9uE=" + }, + "lodash._arrayeach": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", + "integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=" + }, + "lodash._baseassign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", + "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", + "requires": { + "lodash._basecopy": "3.0.1", + "lodash.keys": "3.1.2" + } + }, + "lodash._baseclone": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz", + "integrity": "sha1-MDUZv2OT/n5C802LYw73eU41Qrc=", + "requires": { + "lodash._arraycopy": "3.0.0", + "lodash._arrayeach": "3.0.0", + "lodash._baseassign": "3.2.0", + "lodash._basefor": "3.0.3", + "lodash.isarray": "3.0.4", + "lodash.keys": "3.1.2" + } + }, + "lodash._basecopy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", + "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=" + }, + "lodash._basefor": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz", + "integrity": "sha1-dVC06SGO8J+tJDQ7YSAhx5tMIMI=" + }, + "lodash._bindcallback": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", + "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=" + }, + "lodash._createassigner": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz", + "integrity": "sha1-g4pbri/aymOsIt7o4Z+k5taXCxE=", + "requires": { + "lodash._bindcallback": "3.0.1", + "lodash._isiterateecall": "3.0.9", + "lodash.restparam": "3.6.1" + } + }, + "lodash._getnative": { + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", + "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=" + }, + "lodash._stringtopath": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/lodash._stringtopath/-/lodash._stringtopath-4.7.1.tgz", + "integrity": "sha1-1GYKFaWZeYj6WTAaO/2gXJpt494=", + "dev": true + }, + "lodash.assign": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz", + "integrity": "sha1-POnwI0tLIiPilrj6CsH+6OvKZPo=", + "requires": { + "lodash._baseassign": "3.2.0", + "lodash._createassigner": "3.1.1", + "lodash.keys": "3.1.2" + } + }, + "lodash.clonedeep": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-3.0.2.tgz", + "integrity": "sha1-oKHkDYKl6on/WxR7hETtY9koJ9s=", + "requires": { + "lodash._baseclone": "3.3.0", + "lodash._bindcallback": "3.0.1" + } + }, + "lodash.get": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.2.1.tgz", + "integrity": "sha1-bpr8h7imwCFgZnpw9sjOEHiusRs=", + "dev": true, + "requires": { + "lodash._stringtopath": "4.7.1" + } + }, + "lodash.isarguments": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.0.8.tgz", + "integrity": "sha1-W/jaiH8B8qnknAoXXNrrMYoOQ9w=" + }, + "lodash.isarray": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", + "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" + }, + "lodash.keys": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", + "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", + "requires": { + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.0.8", + "lodash.isarray": "3.0.4" + } + }, + "lodash.restparam": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", + "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=" + }, + "loud-rejection": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.3.0.tgz", + "integrity": "sha1-8omjkvF9K6rPGU0KZzAEOUQzsRU=", + "dev": true, + "requires": { + "array-find-index": "1.0.1", + "signal-exit": "2.1.2" + } + }, + "lru-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.1.tgz", + "integrity": "sha1-E0OVXtry432bnn7nJB4nxLn7cr4=", + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.0.0" + } + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + }, + "marked": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.5.tgz", + "integrity": "sha1-QROhWsXXvKFYpargciRYe5+hW5Q=" + }, + "marked-terminal": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-1.6.1.tgz", + "integrity": "sha1-BM0cfIsO9I2z9oAQ1zpXqWYcbM8=", + "requires": { + "cardinal": "0.5.0", + "chalk": "1.1.3", + "cli-table": "0.3.1", + "lodash.assign": "3.2.0", + "node-emoji": "0.1.0" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "dev": true, + "requires": { + "camelcase-keys": "2.1.0", + "decamelize": "1.2.0", + "loud-rejection": "1.3.0", + "map-obj": "1.0.1", + "minimist": "1.2.0", + "normalize-package-data": "2.3.5", + "object-assign": "4.0.1", + "read-pkg-up": "1.0.1", + "redent": "1.0.0", + "trim-newlines": "1.0.0" + } + }, + "mime-db": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.22.0.tgz", + "integrity": "sha1-qyOmNy3J2G09yRIb0OvTgQWhkEo=" + }, + "mime-types": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.10.tgz", + "integrity": "sha1-uTx8tDYuFtQQcqflRTj7TUMHCDc=", + "requires": { + "mime-db": "1.22.0" + } + }, + "minimatch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz", + "integrity": "sha1-UjYVelHk8ATBd/s8Un/33Xjw74M=", + "requires": { + "brace-expansion": "1.1.3" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + } + } + }, + "mkpath": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/mkpath/-/mkpath-0.1.0.tgz", + "integrity": "sha1-dVSm+Nhxg0zJe1RisSLEwSTW3pE=", + "dev": true + }, + "mksnapshot": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mksnapshot/-/mksnapshot-0.3.0.tgz", + "integrity": "sha1-MuqYStb1MjJMaj+uZACHa4WChAc=", + "dev": true, + "requires": { + "decompress-zip": "0.3.0", + "fs-extra": "0.26.7", + "request": "2.55.0" + }, + "dependencies": { + "asn1": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", + "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=", + "dev": true + }, + "assert-plus": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", + "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=", + "dev": true + }, + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + }, + "aws-sign2": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", + "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=", + "dev": true + }, + "bl": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz", + "integrity": "sha1-wGt5evCF6gC8Unr8jvzxHeIjIFQ=", + "dev": true, + "requires": { + "readable-stream": "1.0.34" + } + }, + "caseless": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.9.0.tgz", + "integrity": "sha1-t7Zc5r8UE4hlOc/VM/CzDv+pz4g=", + "dev": true + }, + "combined-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", + "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", + "dev": true, + "requires": { + "delayed-stream": "0.0.5" + } + }, + "delayed-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", + "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=", + "dev": true + }, + "form-data": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.2.0.tgz", + "integrity": "sha1-Jvi8JtpkQOKZy9z7aQNcT3em5GY=", + "dev": true, + "requires": { + "async": "0.9.2", + "combined-stream": "0.0.7", + "mime-types": "2.0.14" + } + }, + "har-validator": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-1.8.0.tgz", + "integrity": "sha1-2DhCsOtMQ1lgrrEIoGejqpTA7rI=", + "dev": true, + "requires": { + "bluebird": "2.10.2", + "chalk": "1.1.3", + "commander": "2.9.0", + "is-my-json-valid": "2.13.1" + } + }, + "hawk": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-2.3.1.tgz", + "integrity": "sha1-HnMc45RH+h0PbXB/e87r7A/R7B8=", + "dev": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "http-signature": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", + "integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=", + "dev": true, + "requires": { + "asn1": "0.1.11", + "assert-plus": "0.1.5", + "ctype": "0.5.3" + } + }, + "mime-db": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.12.0.tgz", + "integrity": "sha1-PQxjGA9FjrENMlqqN9fFiuMS6dc=", + "dev": true + }, + "mime-types": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.0.14.tgz", + "integrity": "sha1-MQ4VnbI+B3+Lsit0jav6SVcUCqY=", + "dev": true, + "requires": { + "mime-db": "1.12.0" + } + }, + "oauth-sign": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.6.0.tgz", + "integrity": "sha1-fb6uRPbKRU4fFoRR1jB0ZzWBPOM=", + "dev": true + }, + "qs": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-2.4.2.tgz", + "integrity": "sha1-9854jld33wtQENp/fE5zujJHD1o=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.1", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "request": { + "version": "2.55.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.55.0.tgz", + "integrity": "sha1-11wc32eddrsQD5v/4f5VG1wk6T0=", + "dev": true, + "requires": { + "aws-sign2": "0.5.0", + "bl": "0.9.5", + "caseless": "0.9.0", + "combined-stream": "0.0.7", + "forever-agent": "0.6.1", + "form-data": "0.2.0", + "har-validator": "1.8.0", + "hawk": "2.3.1", + "http-signature": "0.10.1", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.0.14", + "node-uuid": "1.4.7", + "oauth-sign": "0.6.0", + "qs": "2.4.2", + "stringstream": "0.0.5", + "tough-cookie": "2.2.2", + "tunnel-agent": "0.4.2" + } + } + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "mv": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", + "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", + "dev": true, + "requires": { + "mkdirp": "0.5.1", + "ncp": "2.0.0", + "rimraf": "2.4.5" + }, + "dependencies": { + "glob": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", + "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "dev": true, + "requires": { + "inflight": "1.0.4", + "inherits": "2.0.1", + "minimatch": "3.0.0", + "once": "1.3.3", + "path-is-absolute": "1.0.0" + } + }, + "rimraf": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", + "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "dev": true, + "requires": { + "glob": "6.0.4" + } + } + } + }, + "ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "dev": true + }, + "node-emoji": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-0.1.0.tgz", + "integrity": "sha1-P0QkpVuo7VDCVKE4WLJEVPQYJgI=" + }, + "node-notifier": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-4.5.0.tgz", + "integrity": "sha1-Ap7pjXqbxOlsnLUb6dTzYTI/6ps=", + "requires": { + "cli-usage": "0.1.2", + "growly": "1.3.0", + "lodash.clonedeep": "3.0.2", + "minimist": "1.2.0", + "semver": "5.1.0", + "shellwords": "0.1.0", + "which": "1.2.4" + } + }, + "node-uuid": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.7.tgz", + "integrity": "sha1-baWhdmjEs91ZYjvaEc9/pMH2Cm8=" + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "dev": true, + "requires": { + "abbrev": "1.0.7" + } + }, + "normalize-package-data": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.5.tgz", + "integrity": "sha1-jZJPFClg4Xd+f/4XBUNjHMfLAt8=", + "dev": true, + "requires": { + "hosted-git-info": "2.1.4", + "is-builtin-module": "1.0.0", + "semver": "5.1.0", + "validate-npm-package-license": "3.0.1" + } + }, + "nth-check": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", + "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "requires": { + "boolbase": "1.0.0" + } + }, + "nugget": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/nugget/-/nugget-1.6.2.tgz", + "integrity": "sha1-iMpuA7pXBqmRc/XaCQJZPWvK4Qc=", + "dev": true, + "requires": { + "debug": "2.2.0", + "minimist": "1.2.0", + "pretty-bytes": "1.0.4", + "progress-stream": "1.2.0", + "request": "2.71.0", + "single-line-log": "0.4.1", + "throttleit": "0.0.2" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "throttleit": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-0.0.2.tgz", + "integrity": "sha1-z+34jmDADdlpe2H90qg0OptoDq8=", + "dev": true + } + } + }, + "number-is-nan": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz", + "integrity": "sha1-wCD1KcUoKt/dIz2R1LGBw9aG3Es=" + }, + "oauth-sign": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.1.tgz", + "integrity": "sha1-GCQ5vbkTeL90YOdcZOpD5kSN7wY=" + }, + "object-assign": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz", + "integrity": "sha1-mVBEVsNZi1ytT8WcJuipuxB/4L0=", + "dev": true + }, + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=", + "dev": true + }, + "once": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", + "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", + "requires": { + "wrappy": "1.0.1" + } + }, + "os-homedir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.1.tgz", + "integrity": "sha1-DWK99EuRb9O73PLKsZGUj7CU8Ac=" + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "1.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "1.3.0" + } + }, + "path-exists": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-1.0.0.tgz", + "integrity": "sha1-1aiZjrce83p0w06w2eum6HjuoIE=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz", + "integrity": "sha1-Jj2tpmqz8vsQv3+dJN2PPlcO+RI=" + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "4.1.3", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" + } + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "2.0.4" + } + }, + "plist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-1.2.0.tgz", + "integrity": "sha1-CEtQk93JJQbiWfh0uNmxr7jHlZM=", + "dev": true, + "requires": { + "base64-js": "0.0.8", + "util-deprecate": "1.0.2", + "xmlbuilder": "4.0.0", + "xmldom": "0.1.22" + } + }, + "pretty-bytes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz", + "integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=", + "dev": true, + "requires": { + "get-stdin": "4.0.1", + "meow": "3.7.0" + } + }, + "process-nextick-args": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.6.tgz", + "integrity": "sha1-D5awAc6pCxJZLOVm7bl+wR5pvQU=" + }, + "progress-stream": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/progress-stream/-/progress-stream-1.2.0.tgz", + "integrity": "sha1-LNPP6jO6OonJwSHsM0er6asSX3c=", + "dev": true, + "requires": { + "speedometer": "0.1.4", + "through2": "0.2.3" + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "pump": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.1.tgz", + "integrity": "sha1-8fFAn7m9EIW721drQ7hOxLXq3Bo=", + "requires": { + "end-of-stream": "1.1.0", + "once": "1.3.3" + } + }, + "q": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=", + "dev": true + }, + "qs": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.1.0.tgz", + "integrity": "sha1-7B0WJrJCeNmfD99FSeUk4k7O6yY=" + }, + "rc": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.1.6.tgz", + "integrity": "sha1-Q2UbdrauU7XIAvEVH6P8OwWZack=", + "dev": true, + "requires": { + "deep-extend": "0.4.1", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "1.0.4" + } + }, + "rcedit": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/rcedit/-/rcedit-0.5.0.tgz", + "integrity": "sha1-72a1p/AxB1IUGjTiLyPCJDPBzxU=", + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "1.1.0", + "normalize-package-data": "2.3.5", + "path-type": "1.1.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "1.1.2", + "read-pkg": "1.1.0" + } + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.1", + "isarray": "0.0.1", + "string_decoder": "0.10.31" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "dev": true, + "requires": { + "indent-string": "2.1.0", + "strip-indent": "1.0.1" + } + }, + "redeyed": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-0.5.0.tgz", + "integrity": "sha1-erAA5g7jh1rBFdKe2zLBQDxsJdE=", + "requires": { + "esprima-fb": "12001.1.0-dev-harmony-fb" + } + }, + "request": { + "version": "2.71.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.71.0.tgz", + "integrity": "sha1-bxRkPJxaZ8ruapXPjvBHfVYDvZE=", + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.3.2", + "bl": "1.1.2", + "caseless": "0.11.0", + "combined-stream": "1.0.5", + "extend": "3.0.0", + "forever-agent": "0.6.1", + "form-data": "1.0.0-rc4", + "har-validator": "2.0.6", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.10", + "node-uuid": "1.4.7", + "oauth-sign": "0.8.1", + "qs": "6.1.0", + "stringstream": "0.0.5", + "tough-cookie": "2.2.2", + "tunnel-agent": "0.4.2" + } + }, + "request-progress": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-1.0.2.tgz", + "integrity": "sha1-XUBvCBMJ32G0qKqDzVc032Pxi/U=", + "requires": { + "throttleit": "1.0.0" + } + }, + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + }, + "rimraf": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.2.tgz", + "integrity": "sha1-YrqUf6TAtDY4Oa7+zU8PutYFlyY=", + "requires": { + "glob": "7.0.3" + } + }, + "run-series": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/run-series/-/run-series-1.1.4.tgz", + "integrity": "sha1-iac93F51ye+KtjIMChYA1qQRebk=", + "dev": true + }, + "semver": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.1.0.tgz", + "integrity": "sha1-hfLPhVBGXE3wAM99hvawVBBqueU=" + }, + "shellwords": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.0.tgz", + "integrity": "sha1-Zq/Ue2oSky2Qccv9mKUueFzQuhQ=" + }, + "signal-exit": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-2.1.2.tgz", + "integrity": "sha1-N1h5sfkuvDszRIDQONxUam1VhWQ=", + "dev": true + }, + "single-line-log": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/single-line-log/-/single-line-log-0.4.1.tgz", + "integrity": "sha1-h6VWSfdJ14PsDc2AToFA2Yc8fO4=", + "dev": true + }, + "sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "requires": { + "hoek": "2.16.3" + } + }, + "spdx-correct": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "dev": true, + "requires": { + "spdx-license-ids": "1.2.1" + } + }, + "spdx-exceptions": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-1.0.4.tgz", + "integrity": "sha1-IguEI5EZrpBFqJLbgag/TOFvgP0=", + "dev": true + }, + "spdx-expression-parse": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.2.tgz", + "integrity": "sha1-1SsUtelnB3FECvIlvLVjEirEUvY=", + "dev": true, + "requires": { + "spdx-exceptions": "1.0.4", + "spdx-license-ids": "1.2.1" + } + }, + "spdx-license-ids": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.1.tgz", + "integrity": "sha1-0H6hek0v2TUfnZTi/5zsdBgP6PM=", + "dev": true + }, + "speedometer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-0.1.4.tgz", + "integrity": "sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0=", + "dev": true + }, + "sshpk": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.7.4.tgz", + "integrity": "sha1-rXtH3vymHIQV2WQkO2KwzmD7yjg=", + "requires": { + "asn1": "0.2.3", + "assert-plus": "0.2.0", + "dashdash": "1.13.0", + "ecc-jsbn": "0.1.1", + "jodid25519": "1.0.2", + "jsbn": "0.1.0", + "tweetnacl": "0.14.3" + } + }, + "string-width": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.1.tgz", + "integrity": "sha1-ySEptvHX9SrPmvQkom44ZKBc6wo=", + "requires": { + "code-point-at": "1.0.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "stringstream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", + "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "0.2.1" + } + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "dev": true, + "requires": { + "get-stdin": "4.0.1" + } + }, + "strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "tar-fs": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.12.0.tgz", + "integrity": "sha1-pqgFU9ilTHPeHQrg553ncDVgXh0=", + "requires": { + "mkdirp": "0.5.1", + "pump": "1.0.1", + "tar-stream": "1.5.1" + } + }, + "tar-stream": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.1.tgz", + "integrity": "sha1-UWx00b6j4THMC5NIkpyag/CirRE=", + "requires": { + "bl": "1.1.2", + "end-of-stream": "1.1.0", + "readable-stream": "2.0.6", + "xtend": "4.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.1", + "isarray": "1.0.0", + "process-nextick-args": "1.0.6", + "string_decoder": "0.10.31", + "util-deprecate": "1.0.2" + } + } + } + }, + "throttleit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", + "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=" + }, + "through2": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.2.3.tgz", + "integrity": "sha1-6zKE2k6jEbbMis42U3SKUqvyWj8=", + "dev": true, + "requires": { + "readable-stream": "1.1.14", + "xtend": "2.1.2" + }, + "dependencies": { + "xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=", + "dev": true, + "requires": { + "object-keys": "0.4.0" + } + } + } + }, + "touch": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/touch/-/touch-0.0.3.tgz", + "integrity": "sha1-Ua7z1ElXHU8oel2Hyci0kYGg2x0=", + "dev": true, + "requires": { + "nopt": "1.0.10" + }, + "dependencies": { + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1.0.7" + } + } + } + }, + "tough-cookie": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz", + "integrity": "sha1-yDoYMPTl7wuT7yo0iOck+N4Basc=" + }, + "traverse": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", + "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=", + "dev": true + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", + "dev": true + }, + "tunnel-agent": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.2.tgz", + "integrity": "sha1-EQTj82rIcSXChycAZ9WC0YEzv+4=" + }, + "tweetnacl": { + "version": "0.14.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.3.tgz", + "integrity": "sha1-PaOC9nDyXe1417PReSEZvKC3Ey0=", + "optional": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "validate-npm-package-license": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "dev": true, + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.2" + } + }, + "verror": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", + "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", + "requires": { + "extsprintf": "1.0.2" + } + }, + "which": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/which/-/which-1.2.4.tgz", + "integrity": "sha1-FVf5YIBgTlsRs1meufRbUKnv1yI=", + "requires": { + "is-absolute": "0.1.7", + "isexe": "1.1.2" + } + }, + "window-size": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", + "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=" + }, + "wrap-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.0.0.tgz", + "integrity": "sha1-fTD4+HP5pbvDpk2ryNF34HGuQm8=", + "requires": { + "string-width": "1.0.1" + } + }, + "wrappy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz", + "integrity": "sha1-HmWWmWXMvC20VIxrhKbyxa7dRzk=" + }, + "xmlbuilder": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.0.0.tgz", + "integrity": "sha1-mLj2UcowqmJANvEn0RzGbce5B6M=", + "dev": true, + "requires": { + "lodash": "3.10.1" + } + }, + "xmldom": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.22.tgz", + "integrity": "sha1-EN5OXpZJgfA8jMcvrcCNFLbDqiY=", + "dev": true + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + }, + "yallist": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.0.0.tgz", + "integrity": "sha1-MGxUODXwnuGkyyO3vOmrNByRzdQ=" + }, + "yargs": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", + "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", + "requires": { + "camelcase": "2.1.1", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "os-locale": "1.4.0", + "string-width": "1.0.1", + "window-size": "0.1.4", + "y18n": "3.2.1" + } + }, + "yauzl": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz", + "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", + "dev": true, + "requires": { + "fd-slicer": "1.0.1" + } + } + } +} diff --git a/server-console/package.json b/server-console/package.json index f72ffc347f..8d2a177e0a 100644 --- a/server-console/package.json +++ b/server-console/package.json @@ -8,7 +8,6 @@ "" ], "devDependencies": { - "electron-compilers": "^1.0.1", "electron-packager": "^6.0.2", "electron-prebuilt": "0.37.5" }, diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index cf11ef9e7a..16446c5071 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -23,4 +23,8 @@ if (BUILD_TOOLS) add_subdirectory(oven) set_target_properties(oven PROPERTIES FOLDER "Tools") + + add_subdirectory(auto-tester) + set_target_properties(auto-tester PROPERTIES FOLDER "Tools") endif() + diff --git a/tools/auto-tester/CMakeLists.txt b/tools/auto-tester/CMakeLists.txt new file mode 100644 index 0000000000..e5f2c1fb97 --- /dev/null +++ b/tools/auto-tester/CMakeLists.txt @@ -0,0 +1,57 @@ +set(TARGET_NAME auto-tester) +project(${TARGET_NAME}) + +# Automatically run UIC and MOC. This replaces the older WRAP macros +SET(CMAKE_AUTOUIC ON) +SET(CMAKE_AUTOMOC ON) + +setup_hifi_project(Core Widgets) +link_hifi_libraries() + +# FIX: Qt was built with -reduce-relocations +if (Qt5_POSITION_INDEPENDENT_CODE) + SET(CMAKE_POSITION_INDEPENDENT_CODE ON) +endif() + +# Qt includes +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +include_directories(${Qt5Core_INCLUDE_DIRS}) +include_directories(${Qt5Widgets_INCLUDE_DIRS}) + +set(QT_LIBRARIES Qt5::Core Qt5::Widgets) + +# Find all sources files +file (GLOB_RECURSE SOURCES src/*.cpp) +file (GLOB_RECURSE HEADERS src/*.h) +file (GLOB_RECURSE UIS src/ui/*.ui) + +if (WIN32) + # Do not show Console + set_property(TARGET auto-tester PROPERTY WIN32_EXECUTABLE true) +endif() + +add_executable(PROJECT_NAME ${SOURCES} ${HEADERS} ${UIS}) + +target_link_libraries(PROJECT_NAME ${QT_LIBRARIES}) + +# Copy required dll's. +add_custom_command(TARGET auto-tester POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $ +) + +if (WIN32) + find_program(WINDEPLOYQT_COMMAND windeployqt PATHS ${QT_DIR}/bin NO_DEFAULT_PATH) + + if (NOT WINDEPLOYQT_COMMAND) + message(FATAL_ERROR "Could not find windeployqt at ${QT_DIR}/bin. windeployqt is required.") + endif () + + # add a post-build command to call windeployqt to copy Qt plugins + add_custom_command( + TARGET ${TARGET_NAME} + POST_BUILD + COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} ${EXTRA_DEPLOY_OPTIONS} $<$,$,$>:--release> \"$\"" + ) +endif () \ No newline at end of file diff --git a/tools/auto-tester/ReadMe.md b/tools/auto-tester/ReadMe.md new file mode 100644 index 0000000000..57ec7ea623 --- /dev/null +++ b/tools/auto-tester/ReadMe.md @@ -0,0 +1,7 @@ +After building auto-tester, it needs to be deployed to Amazon SW + +* In folder hifi/build/tools/auto-tester + * Right click on the Release folder + * Select 7-Zip -> Add to archive + * Select Option ```Create SFX archive``` to create Release.exe +* Use Cyberduck (or any other AWS S3 client) to copy Release.exe to hifi-content/nissim/autoTester/ \ No newline at end of file diff --git a/tools/auto-tester/src/ImageComparer.cpp b/tools/auto-tester/src/ImageComparer.cpp new file mode 100644 index 0000000000..121c98e16e --- /dev/null +++ b/tools/auto-tester/src/ImageComparer.cpp @@ -0,0 +1,119 @@ +// +// ImageComparer.cpp +// +// Created by Nissim Hadar on 18 Nov 2017. +// Copyright 2013 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 "ImageComparer.h" + +#include + +// 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_RGB32) { + 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); + const double c2 = pow((K2 * L), 2); + + // Coefficients for luminosity calculation + const double R_Y = 0.212655f; + const double G_Y = 0.715158f; + const double B_Y = 0.072187f; + + // First go over all full 8x8 blocks + // This is done in 3 loops + // 1) Read the pixels into a linear array (an optimization) + // 2) Calculate mean + // 3) Calculate variance and covariance + // + // p - pixel in expected image + // q - pixel in result image + // + const int WIN_SIZE = 8; + int x{ 0 }; // column index (start of block) + int y{ 0 }; // row index (start of block + + // Pixels are processed in square blocks + double p[WIN_SIZE * WIN_SIZE]; + double q[WIN_SIZE * WIN_SIZE]; + + int windowCounter{ 0 }; + double ssim{ 0.0 }; + while (x < expectedImage.width()) { + int lastX = x + WIN_SIZE - 1; + if (lastX > expectedImage.width() - 1) { + x -= (lastX - expectedImage.width()); + } + + while (y < expectedImage.height()) { + int lastY = y + WIN_SIZE - 1; + if (lastY > expectedImage.height() - 1) { + y -= (lastY - expectedImage.height()); + } + + // Collect pixels into linear arrays + int i{ 0 }; + for (int xx = 0; xx < WIN_SIZE; ++xx) { + for (int yy = 0; yy < WIN_SIZE; ++yy) { + // Get pixels + QRgb pixelP = expectedImage.pixel(QPoint(x + xx, y + yy)); + QRgb pixelQ = resultImage.pixel(QPoint(x + xx, y + yy)); + + // Convert to luminance + p[i] = R_Y * qRed(pixelP) + G_Y * qGreen(pixelP) + B_Y * qBlue(pixelP); + q[i] = R_Y * qRed(pixelQ) + G_Y * qGreen(pixelQ) + B_Y * qBlue(pixelQ); + + ++i; + } + } + + // Calculate mean + double mP{ 0.0 }; // average value of expected pixel + double mQ{ 0.0 }; // average value of result pixel + for (int j = 0; j < WIN_SIZE * WIN_SIZE; ++j) { + mP += p[j]; + mQ += q[j]; + } + mP /= (WIN_SIZE * WIN_SIZE); + mQ /= (WIN_SIZE * WIN_SIZE); + + // Calculate variance and covariance + double sigsqP{ 0.0 }; + double sigsqQ{ 0.0 }; + double sigPQ{ 0.0 }; + for (int j = 0; j < WIN_SIZE * WIN_SIZE; ++j) { + sigsqP += pow((p[j] - mP), 2); + sigsqQ += pow((q[j] - mQ), 2); + + sigPQ += (p[j] - mP) * (q[j] - mQ); + } + sigsqP /= (WIN_SIZE * WIN_SIZE); + sigsqQ /= (WIN_SIZE * WIN_SIZE); + sigPQ /= (WIN_SIZE * WIN_SIZE); + + double numerator = (2.0 * mP * mQ + c1) * (2.0 * sigPQ + c2); + double denominator = (mP * mP + mQ * mQ + c1) * (sigsqP + sigsqQ + c2); + + ssim += numerator / denominator; + ++windowCounter; + + y += WIN_SIZE; + } + + x += WIN_SIZE; + y = 0; + } + + return ssim / windowCounter; +}; diff --git a/tools/auto-tester/src/ImageComparer.h b/tools/auto-tester/src/ImageComparer.h new file mode 100644 index 0000000000..7b7b8b0b74 --- /dev/null +++ b/tools/auto-tester/src/ImageComparer.h @@ -0,0 +1,21 @@ +// +// ImageComparer.h +// +// Created by Nissim Hadar on 18 Nov 2017. +// Copyright 2013 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_ImageComparer_h +#define hifi_ImageComparer_h + +#include +#include + +class ImageComparer { +public: + double compareImages(QImage resultImage, QImage expectedImage) const; +}; + +#endif // hifi_ImageComparer_h diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp new file mode 100644 index 0000000000..8cb36fcfca --- /dev/null +++ b/tools/auto-tester/src/Test.cpp @@ -0,0 +1,383 @@ +// +// Test.cpp +// +// Created by Nissim Hadar on 2 Nov 2017. +// Copyright 2013 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 "Test.h" + +#include +#include +#include + +Test::Test() { + snapshotFilenameFormat = QRegularExpression("hifi-snap-by-.+-on-\\d\\d\\d\\d-\\d\\d-\\d\\d_\\d\\d-\\d\\d-\\d\\d.jpg"); + + expectedImageFilenameFormat = QRegularExpression("ExpectedImage_\\d+.jpg"); + + mismatchWindow.setModal(true); +} + +bool Test::compareImageLists(QStringList expectedImages, QStringList resultImages) { + // 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.999 }; + bool success{ true }; + bool keepOn{ true }; + for (int i = 0; keepOn && i < expectedImages.length(); ++i) { + // First check that images are the same size + QImage resultImage(resultImages[i]); + QImage expectedImage(expectedImages[i]); + if (resultImage.width() != expectedImage.width() || resultImage.height() != expectedImage.height()) { + messageBox.critical(0, "Internal error", "Images are not the same size"); + exit(-1); + } + + double similarityIndex; // in [-1.0 .. 1.0], where 1.0 means images are identical + try { + similarityIndex = imageComparer.compareImages(resultImage, expectedImage); + } catch (...) { + messageBox.critical(0, "Internal error", "Image not in expected format"); + exit(-1); + } + + if (similarityIndex < THRESHOLD) { + mismatchWindow.setTestFailure(TestFailure{ + (float)similarityIndex, + expectedImages[i].left(expectedImages[i].lastIndexOf("/") + 1), // path to the test (including trailing /) + QFileInfo(expectedImages[i].toStdString().c_str()).fileName(), // filename of expected image + QFileInfo(resultImages[i].toStdString().c_str()).fileName() // filename of result image + }); + + mismatchWindow.exec(); + + switch (mismatchWindow.getUserResponse()) { + case USER_RESPONSE_PASS: + break; + case USE_RESPONSE_FAIL: + success = false; + break; + case USER_RESPONSE_ABORT: + keepOn = false; + success = false; + break; + default: + assert(false); + break; + } + } + } + + return success; +} + +void Test::evaluateTests() { + // Get list of JPEG images in folder, sorted by name + QString pathToImageDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); + if (pathToImageDirectory == "") { + return; + } + + QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(pathToImageDirectory); + + // Separate images into two lists. The first is the expected images, the second is the test results + // Images that are in the wrong format are ignored. + QStringList expectedImages; + QStringList resultImages; + foreach(QString currentFilename, sortedImageFilenames) { + QString fullCurrentFilename = pathToImageDirectory + "/" + currentFilename; + if (isInExpectedImageFilenameFormat(currentFilename)) { + expectedImages << fullCurrentFilename; + } else if (isInSnapshotFilenameFormat(currentFilename)) { + resultImages << fullCurrentFilename; + } + } + + // The number of images in each list should be identical + if (expectedImages.length() != resultImages.length()) { + messageBox.critical(0, + "Test failed", + "Found " + QString::number(resultImages.length()) + " images in directory" + + "\nExpected to find " + QString::number(expectedImages.length()) + " images" + ); + + exit(-1); + } + + bool success = compareImageLists(expectedImages, resultImages); + + if (success) { + messageBox.information(0, "Success", "All images are as expected"); + } else { + messageBox.information(0, "Failure", "One or more images are not as expected"); + } +} + +// Two criteria are used to decide if a folder contains valid test results. +// 1) a 'test'js' file exists in the folder +// 2) the folder has the same number of actual and expected images +void Test::evaluateTestsRecursively() { + // Select folder to start recursing from + QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", ".", QFileDialog::ShowDirsOnly); + if (topLevelDirectory == "") { + return; + } + + bool success{ true }; + QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); + while (it.hasNext()) { + QString directory = it.next(); + if (directory[directory.length() - 1] == '.') { + // ignore '.', '..' directories + continue; + } + + // + const QString testPathname{ directory + "/" + testFilename }; + QFileInfo fileInfo(testPathname); + if (!fileInfo.exists()) { + // Folder does not contain 'test.js' + continue; + } + + QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(directory); + + // Separate images into two lists. The first is the expected images, the second is the test results + // Images that are in the wrong format are ignored. + QStringList expectedImages; + QStringList resultImages; + foreach(QString currentFilename, sortedImageFilenames) { + QString fullCurrentFilename = directory + "/" + currentFilename; + if (isInExpectedImageFilenameFormat(currentFilename)) { + expectedImages << fullCurrentFilename; + } else if (isInSnapshotFilenameFormat(currentFilename)) { + resultImages << fullCurrentFilename; + } + } + + if (expectedImages.length() != resultImages.length()) { + // Number of images doesn't match + continue; + } + + // Set success to false if any test has failed + success &= compareImageLists(expectedImages, resultImages); + } + + if (success) { + messageBox.information(0, "Success", "All images are as expected"); + } else { + messageBox.information(0, "Failure", "One or more images are not as expected"); + } +} + +void Test::importTest(QTextStream& textStream, const QString& testPathname, int testNumber) { + textStream << "var test" << testNumber << " = Script.require(\"" << "file:///" << testPathname + "\");" << endl; +} + +// Creates a single script in a user-selected folder. +// This script will run all text.js scripts in every applicable sub-folder +void Test::createRecursiveScript() { + // Select folder to start recursing from + QString topLevelDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder that will contain the top level test script", ".", QFileDialog::ShowDirsOnly); + if (topLevelDirectory == "") { + return; + } + + QFile allTestsFilename(topLevelDirectory + "/" + "allTests.js"); + if (!allTestsFilename.open(QIODevice::WriteOnly | QIODevice::Text)) { + messageBox.critical(0, + "Internal Error", + "Failed to create \"allTests.js\" in directory \"" + topLevelDirectory + "\""); + + exit(-1); + } + + QTextStream textStream(&allTestsFilename); + textStream << "// This is an automatically generated file, created by auto-tester" << endl; + + // The main will call each test after the previous test is completed + // This is implemented with an interval timer that periodically tests if a + // running test has increment a testNumber variable that it received as an input. + int testNumber = 1; + QVector testPathnames; + + // First test if top-level folder has a test.js file + const QString testPathname{ topLevelDirectory + "/" + testFilename }; + QFileInfo fileInfo(testPathname); + if (fileInfo.exists()) { + // Current folder contains a test + importTest(textStream, testPathname, testNumber); + ++testNumber; + + testPathnames << testPathname; + } + + QDirIterator it(topLevelDirectory.toStdString().c_str(), QDirIterator::Subdirectories); + while (it.hasNext()) { + QString directory = it.next(); + if (directory[directory.length() - 1] == '.') { + // ignore '.', '..' directories + continue; + } + + const QString testPathname{ directory + "/" + testFilename }; + QFileInfo fileInfo(testPathname); + if (fileInfo.exists()) { + // Current folder contains a test + importTest(textStream, testPathname, testNumber); + ++testNumber; + + testPathnames << testPathname; + } + } + + if (testPathnames.length() <= 0) { + messageBox.information(0, "Failure", "No \"test.js\" files found"); + allTestsFilename.close(); + return; + } + + textStream << endl; + + // Define flags for each test + for (int i = 1; i <= testPathnames.length(); ++i) { + textStream << "var test" << i << "HasNotStarted = true;" << endl; + } + + // Leave a blank line in the main + textStream << endl; + + const int TEST_PERIOD = 1000; // in milliseconds + const QString tab = " "; + + textStream << "// Check every second if the current test is complete and the next test can be run" << endl; + textStream << "var testTimer = Script.setInterval(" << endl; + textStream << tab << "function() {" << endl; + + const QString testFunction = "test"; + for (int i = 1; i <= testPathnames.length(); ++i) { + // First test starts immediately, all other tests wait for the previous test to complete. + // The script produced will look as follows: + // if (test1HasNotStarted) { + // test1HasNotStarted = false; + // test1.test(); + // print("******started test 1******"); + // } + // | + // | + // if (test5.complete && test6HasNotStarted) { + // test6HasNotStarted = false; + // test7.test(); + // print("******started test 6******"); + // } + // | + // | + // if (test12.complete) { + // print("******stopping******"); + // Script.stop(); + // } + // + if (i == 1) { + textStream << tab << tab << "if (test1HasNotStarted) {" << endl; + } else { + textStream << tab << tab << "if (test" << i - 1 << ".complete && test" << i << "HasNotStarted) {" << endl; + } + textStream << tab << tab << tab << "test" << i << "HasNotStarted = false;" << endl; + textStream << tab << tab << tab << "test" << i << "." << testFunction << "();" << endl; + textStream << tab << tab << tab << "print(\"******started test " << i << "******\");" << endl; + + textStream << tab << tab << "}" << endl << endl; + + } + + // Add extra step to stop the script + textStream << tab << tab << "if (test" << testPathnames.length() << ".complete) {" << endl; + textStream << tab << tab << tab << "print(\"******stopping******\");" << endl; + textStream << tab << tab << tab << "Script.stop();" << endl; + textStream << tab << tab << "}" << endl << endl; + + textStream << tab << "}," << endl; + textStream << endl; + textStream << tab << TEST_PERIOD << endl; + textStream << ");" << endl << endl; + + textStream << "// Stop the timer and clear the module cache" << endl; + textStream << "Script.scriptEnding.connect(" << endl; + textStream << tab << "function() {" << endl; + textStream << tab << tab << "Script.clearInterval(testTimer);" << endl; + textStream << tab << tab << "Script.require.cache = {};" << endl; + textStream << tab << "}" << endl; + textStream << ");" << endl; + + allTestsFilename.close(); + + messageBox.information(0, "Success", "Script has been created"); +} + +void Test::createTest() { + // Rename files sequentially, as ExpectedResult_1.jpeg, ExpectedResult_2.jpg and so on + // Any existing expected result images will be deleted + QString pathToImageDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); + if (pathToImageDirectory == "") { + return; + } + + QStringList sortedImageFilenames = createListOfAllJPEGimagesInDirectory(pathToImageDirectory); + + int i = 1; + foreach (QString currentFilename, sortedImageFilenames) { + QString fullCurrentFilename = pathToImageDirectory + "/" + currentFilename; + if (isInExpectedImageFilenameFormat(currentFilename)) { + if (!QFile::remove(fullCurrentFilename)) { + messageBox.critical(0, "Error", "Could not delete existing file: " + currentFilename + "\nTest creation aborted"); + exit(-1); + } + } else if (isInSnapshotFilenameFormat(currentFilename)) { + const int MAX_IMAGES = 100000; + if (i >= MAX_IMAGES) { + messageBox.critical(0, "Error", "More than 100,000 images not supported"); + exit(-1); + } + QString newFilename = "ExpectedImage_" + QString::number(i-1).rightJustified(5, '0') + ".jpg"; + QString fullNewFileName = pathToImageDirectory + "/" + newFilename; + + if (!imageDirectory.rename(fullCurrentFilename, newFilename)) { + if (!QFile::exists(fullCurrentFilename)) { + messageBox.critical(0, "Error", "Could not rename file: " + fullCurrentFilename + " to: " + newFilename + "\n" + + fullCurrentFilename + " not found" + + "\nTest creation aborted" + ); + exit(-1); + } else { + messageBox.critical(0, "Error", "Could not rename file: " + fullCurrentFilename + " to: " + newFilename + "\n" + + "unknown error" + "\nTest creation aborted" + ); + exit(-1); + } + } + ++i; + } + } + + messageBox.information(0, "Success", "Test images have been created"); +} + +QStringList Test::createListOfAllJPEGimagesInDirectory(QString pathToImageDirectory) { + imageDirectory = QDir(pathToImageDirectory); + QStringList nameFilters; + nameFilters << "*.jpg"; + + return imageDirectory.entryList(nameFilters, QDir::Files, QDir::Name); +} + +bool Test::isInSnapshotFilenameFormat(QString filename) { + return (snapshotFilenameFormat.match(filename).hasMatch()); +} + +bool Test::isInExpectedImageFilenameFormat(QString filename) { + return (expectedImageFilenameFormat.match(filename).hasMatch()); +} \ No newline at end of file diff --git a/tools/auto-tester/src/Test.h b/tools/auto-tester/src/Test.h new file mode 100644 index 0000000000..1f7b1e92a7 --- /dev/null +++ b/tools/auto-tester/src/Test.h @@ -0,0 +1,55 @@ +// +// Test.h +// zone/ambientLightInheritence +// +// Created by Nissim Hadar on 2 Nov 2017. +// Copyright 2013 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_test_h +#define hifi_test_h + +#include +#include +#include + +#include "ImageComparer.h" +#include "ui/MismatchWindow.h" + +class Test { +public: + Test(); + + void evaluateTests(); + void evaluateTestsRecursively(); + void createRecursiveScript(); + void createTest(); + + QStringList createListOfAllJPEGimagesInDirectory(QString pathToImageDirectory); + + bool isInSnapshotFilenameFormat(QString filename); + bool isInExpectedImageFilenameFormat(QString filename); + + void importTest(QTextStream& textStream, const QString& testPathname, int testNumber); + +private: + const QString testFilename{ "test.js" }; + + QMessageBox messageBox; + + QDir imageDirectory; + + QRegularExpression snapshotFilenameFormat; + QRegularExpression expectedImageFilenameFormat; + + MismatchWindow mismatchWindow; + + ImageComparer imageComparer; + + bool compareImageLists(QStringList expectedImages, QStringList resultImages); +}; + +#endif // hifi_test_h diff --git a/tools/auto-tester/src/common.h b/tools/auto-tester/src/common.h new file mode 100644 index 0000000000..126177358f --- /dev/null +++ b/tools/auto-tester/src/common.h @@ -0,0 +1,37 @@ +// +// common.h +// +// Created by Nissim Hadar on 10 Nov 2017. +// Copyright 2013 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_common_h +#define hifi_common_h + +#include + +class TestFailure { +public: + TestFailure(float error, QString pathname, QString expectedImageFilename, QString actualImageFilename) : + _error(error), + _pathname(pathname), + _expectedImageFilename(expectedImageFilename), + _actualImageFilename(actualImageFilename) + {} + + double _error; + QString _pathname; + QString _expectedImageFilename; + QString _actualImageFilename; +}; + +enum UserResponse { + USER_RESPONSE_INVALID, + USER_RESPONSE_PASS, + USE_RESPONSE_FAIL, + USER_RESPONSE_ABORT +}; + +#endif // hifi_common_h diff --git a/tools/auto-tester/src/main.cpp b/tools/auto-tester/src/main.cpp new file mode 100644 index 0000000000..6e5e06b732 --- /dev/null +++ b/tools/auto-tester/src/main.cpp @@ -0,0 +1,20 @@ +// +// main.cpp +// +// Created by Nissim Hadar on 2 Nov 2017. +// Copyright 2013 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 +#include "ui/AutoTester.h" + +int main(int argc, char *argv[]) { + QApplication application(argc, argv); + + AutoTester autoTester; + autoTester.show(); + + return application.exec(); +} diff --git a/tools/auto-tester/src/ui/AutoTester.cpp b/tools/auto-tester/src/ui/AutoTester.cpp new file mode 100644 index 0000000000..105baddb92 --- /dev/null +++ b/tools/auto-tester/src/ui/AutoTester.cpp @@ -0,0 +1,35 @@ +// +// AutoTester.cpp +// zone/ambientLightInheritence +// +// Created by Nissim Hadar on 2 Nov 2017. +// Copyright 2013 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 "AutoTester.h" + +AutoTester::AutoTester(QWidget *parent) : QMainWindow(parent) { + ui.setupUi(this); +} + +void AutoTester::on_evaluateTestsButton_clicked() { + test.evaluateTests(); +} + +void AutoTester::on_evaluateTestsRecursivelyButton_clicked() { + test.evaluateTestsRecursively(); +} + +void AutoTester::on_createRecursiveScriptButton_clicked() { + test.createRecursiveScript(); +} + +void AutoTester::on_createTestButton_clicked() { + test.createTest(); +} + +void AutoTester::on_closeButton_clicked() { + exit(0); +} \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.h b/tools/auto-tester/src/ui/AutoTester.h new file mode 100644 index 0000000000..acfea32ba1 --- /dev/null +++ b/tools/auto-tester/src/ui/AutoTester.h @@ -0,0 +1,37 @@ +// +// AutoTester.h +// zone/ambientLightInheritence +// +// Created by Nissim Hadar on 2 Nov 2017. +// Copyright 2013 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_AutoTester_h +#define hifi_AutoTester_h + +#include +#include "ui_AutoTester.h" +#include "../Test.h" + +class AutoTester : public QMainWindow { + Q_OBJECT + +public: + AutoTester(QWidget *parent = Q_NULLPTR); + +private slots: +void on_evaluateTestsButton_clicked(); +void on_evaluateTestsRecursivelyButton_clicked(); +void on_createRecursiveScriptButton_clicked(); + void on_createTestButton_clicked(); + void on_closeButton_clicked(); + +private: + Ui::AutoTesterClass ui; + + Test test; +}; + +#endif // hifi_AutoTester_h \ No newline at end of file diff --git a/tools/auto-tester/src/ui/AutoTester.ui b/tools/auto-tester/src/ui/AutoTester.ui new file mode 100644 index 0000000000..7032ef9710 --- /dev/null +++ b/tools/auto-tester/src/ui/AutoTester.ui @@ -0,0 +1,106 @@ + + + AutoTesterClass + + + + 0 + 0 + 286 + 470 + + + + AutoTester + + + + + + 60 + 360 + 160 + 40 + + + + Close + + + + + + 60 + 270 + 160 + 40 + + + + Create Test + + + + + + 60 + 20 + 160 + 40 + + + + Evaluate Test + + + + + + 60 + 210 + 160 + 40 + + + + Create Recursive Script + + + + + + 60 + 75 + 160 + 40 + + + + Evaluate Tests Recursively + + + + + + + 0 + 0 + 286 + 21 + + + + + + TopToolBarArea + + + false + + + + + + + + diff --git a/tools/auto-tester/src/ui/MismatchWindow.cpp b/tools/auto-tester/src/ui/MismatchWindow.cpp new file mode 100644 index 0000000000..07664a1667 --- /dev/null +++ b/tools/auto-tester/src/ui/MismatchWindow.cpp @@ -0,0 +1,46 @@ +// +// MismatchWindow.cpp +// +// Created by Nissim Hadar on 9 Nov 2017. +// Copyright 2013 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 "MismatchWindow.h" + +#include + +MismatchWindow::MismatchWindow(QWidget *parent) : QDialog(parent) { + setupUi(this); + + expectedImage->setScaledContents(true); + resultImage->setScaledContents(true); +} + +void MismatchWindow::setTestFailure(TestFailure testFailure) { + errorLabel->setText("Similarity: " + QString::number(testFailure._error)); + + imagePath->setText("Path to test: " + testFailure._pathname); + + expectedFilename->setText(testFailure._expectedImageFilename); + expectedImage->setPixmap(QPixmap(testFailure._pathname + testFailure._expectedImageFilename)); + + resultFilename->setText(testFailure._actualImageFilename); + resultImage->setPixmap(QPixmap(testFailure._pathname + testFailure._actualImageFilename)); +} + +void MismatchWindow::on_passTestButton_clicked() { + _userResponse = USER_RESPONSE_PASS; + close(); +} + +void MismatchWindow::on_failTestButton_clicked() { + _userResponse = USE_RESPONSE_FAIL; + close(); +} + +void MismatchWindow::on_abortTestsButton_clicked() { + _userResponse = USER_RESPONSE_ABORT; + close(); +} diff --git a/tools/auto-tester/src/ui/MismatchWindow.h b/tools/auto-tester/src/ui/MismatchWindow.h new file mode 100644 index 0000000000..7c72b7b0b7 --- /dev/null +++ b/tools/auto-tester/src/ui/MismatchWindow.h @@ -0,0 +1,38 @@ +// +// MismatchWindow.h +// +// Created by Nissim Hadar on 9 Nov 2017. +// Copyright 2013 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_MismatchWindow_h +#define hifi_MismatchWindow_h + +#include "ui_MismatchWindow.h" + +#include "../common.h" + +class MismatchWindow : public QDialog, public Ui::MismatchWindow +{ + Q_OBJECT + +public: + MismatchWindow(QWidget *parent = Q_NULLPTR); + + void setTestFailure(TestFailure testFailure); + + UserResponse getUserResponse() { return _userResponse; } + +private slots: + void on_passTestButton_clicked(); + void on_failTestButton_clicked(); + void on_abortTestsButton_clicked(); + +private: + UserResponse _userResponse{ USER_RESPONSE_INVALID }; +}; + + +#endif // hifi_MismatchWindow_h diff --git a/tools/auto-tester/src/ui/MismatchWindow.ui b/tools/auto-tester/src/ui/MismatchWindow.ui new file mode 100644 index 0000000000..cab6c61e1c --- /dev/null +++ b/tools/auto-tester/src/ui/MismatchWindow.ui @@ -0,0 +1,157 @@ + + + MismatchWindow + + + + 0 + 0 + 1585 + 694 + + + + MismatchWindow + + + + + 20 + 170 + 720 + 362 + + + + expected image + + + + + + 760 + 170 + 720 + 362 + + + + result image + + + + + + 760 + 90 + 800 + 28 + + + + + 16 + + + + result image filename + + + + + + 40 + 90 + 700 + 28 + + + + + 16 + + + + expected image filename + + + + + + 40 + 30 + 1200 + 28 + + + + + 16 + + + + image path + + + + + + 30 + 600 + 75 + 23 + + + + Pass + + + + + + 330 + 600 + 75 + 23 + + + + Fail + + + + + + 630 + 600 + 75 + 23 + + + + Abort Tests + + + + + + 810 + 600 + 720 + 28 + + + + + 16 + + + + similarity + + + + + + + diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index 1022c204c5..00344179bd 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -8,13 +8,14 @@ setup_memory_debugger() if (WIN32) package_libraries_for_deployment() -endif () - -if (UNIX) +elseif (UNIX AND NOT APPLE) find_package(Threads REQUIRED) if(THREADS_HAVE_PTHREAD_ARG) target_compile_options(PUBLIC oven "-pthread") endif() -endif () +elseif (APPLE) + # Fix up the rpath so macdeployqt works + set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks") +endif() -set_target_properties(${TARGET_NAME} PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE) +install_beside_console() diff --git a/tools/oven/src/BakerCLI.cpp b/tools/oven/src/BakerCLI.cpp index 5ab995be95..5af65c4dc0 100644 --- a/tools/oven/src/BakerCLI.cpp +++ b/tools/oven/src/BakerCLI.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include "ModelBakingLoggingCategory.h" #include "Oven.h" @@ -22,22 +23,30 @@ BakerCLI::BakerCLI(Oven* parent) : QObject(parent) { } -void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) { +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"); } - static const QString MODEL_EXTENSION { ".fbx" }; + qDebug() << "Baking file type: " << type; + + static const QString MODEL_EXTENSION { "fbx" }; + + QString extension = type; + + if (extension.isNull()) { + auto url = inputUrl.toDisplayString(); + extension = url.mid(url.lastIndexOf('.')); + } // check what kind of baker we should be creating - bool isFBX = inputUrl.toDisplayString().endsWith(MODEL_EXTENSION, Qt::CaseInsensitive); - bool isSupportedImage = false; + bool isFBX = extension == MODEL_EXTENSION; - for (QByteArray format : QImageReader::supportedImageFormats()) { - isSupportedImage |= inputUrl.toDisplayString().endsWith(format, Qt::CaseInsensitive); - } + bool isSupportedImage = QImageReader::supportedImageFormats().contains(extension.toLatin1()); + + _outputPath = outputPath; // create our appropiate baker if (isFBX) { @@ -48,7 +57,7 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) { _baker->moveToThread(qApp->getNextWorkerThread()); } else { qCDebug(model_baking) << "Failed to determine baker type for file" << inputUrl; - QApplication::exit(1); + QApplication::exit(OVEN_STATUS_CODE_FAIL); } // invoke the bake method on the baker thread @@ -60,5 +69,17 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) { void BakerCLI::handleFinishedBaker() { qCDebug(model_baking) << "Finished baking file."; - QApplication::exit(_baker.get()->hasErrors()); + int exitCode = OVEN_STATUS_CODE_SUCCESS; + // Do we need this? + if (_baker->wasAborted()) { + exitCode = OVEN_STATUS_CODE_ABORT; + } else if (_baker->hasErrors()) { + exitCode = OVEN_STATUS_CODE_FAIL; + QFile errorFile { _outputPath.absoluteFilePath(OVEN_ERROR_FILENAME) }; + if (errorFile.open(QFile::WriteOnly)) { + errorFile.write(_baker->getErrors().join('\n').toUtf8()); + errorFile.close(); + } + } + QApplication::exit(exitCode); } diff --git a/tools/oven/src/BakerCLI.h b/tools/oven/src/BakerCLI.h index cb2b908059..7d362eb898 100644 --- a/tools/oven/src/BakerCLI.h +++ b/tools/oven/src/BakerCLI.h @@ -13,22 +13,32 @@ #define hifi_BakerCLI_h #include +#include + +#include #include "Baker.h" #include "Oven.h" +static const int OVEN_STATUS_CODE_SUCCESS { 0 }; +static const int OVEN_STATUS_CODE_FAIL { 1 }; +static const int OVEN_STATUS_CODE_ABORT { 2 }; + +static const QString OVEN_ERROR_FILENAME = "errors.txt"; + class BakerCLI : public QObject { Q_OBJECT public: BakerCLI(Oven* parent); - void bakeFile(QUrl inputUrl, const QString outputPath); + void bakeFile(QUrl inputUrl, const QString& outputPath, const QString& type = QString::null); private slots: void handleFinishedBaker(); private: + QDir _outputPath; std::unique_ptr _baker; }; -#endif // hifi_BakerCLI_h \ No newline at end of file +#endif // hifi_BakerCLI_h diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index d91206a592..9de06a35bb 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -24,6 +24,7 @@ static const QString OUTPUT_FOLDER = "/Users/birarda/code/hifi/lod/test-oven/exp static const QString CLI_INPUT_PARAMETER = "i"; static const QString CLI_OUTPUT_PARAMETER = "o"; +static const QString CLI_TYPE_PARAMETER = "t"; Oven::Oven(int argc, char* argv[]) : QApplication(argc, argv) @@ -39,7 +40,8 @@ Oven::Oven(int argc, char* argv[]) : parser.addOptions({ { CLI_INPUT_PARAMETER, "Path to file that you would like to bake.", "input" }, - { CLI_OUTPUT_PARAMETER, "Path to folder that will be used as output.", "output" } + { CLI_OUTPUT_PARAMETER, "Path to folder that will be used as output.", "output" }, + { CLI_TYPE_PARAMETER, "Type of asset.", "type" } }); parser.addHelpOption(); parser.process(*this); @@ -59,7 +61,8 @@ Oven::Oven(int argc, char* argv[]) : BakerCLI* cli = new BakerCLI(this); QUrl inputUrl(QDir::fromNativeSeparators(parser.value(CLI_INPUT_PARAMETER))); QUrl outputUrl(QDir::fromNativeSeparators(parser.value(CLI_OUTPUT_PARAMETER))); - cli->bakeFile(inputUrl, outputUrl.toString()); + QString type = parser.isSet(CLI_TYPE_PARAMETER) ? parser.value(CLI_TYPE_PARAMETER) : QString::null; + cli->bakeFile(inputUrl, outputUrl.toString(), type); } else { parser.showHelp(); QApplication::quit(); diff --git a/tools/oven/src/ui/BakeWidget.h b/tools/oven/src/ui/BakeWidget.h index 00996128ed..34cced537a 100644 --- a/tools/oven/src/ui/BakeWidget.h +++ b/tools/oven/src/ui/BakeWidget.h @@ -14,6 +14,8 @@ #include +#include + #include class BakeWidget : public QWidget { diff --git a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js index f0b943ad92..9eb543e768 100644 --- a/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js +++ b/unpublishedScripts/marketplace/spectator-camera/spectatorCamera.js @@ -311,7 +311,10 @@ viewFinderOverlayDim = { x: glassPaneWidth, y: -glassPaneWidth, z: 0 }; } updateOverlay(); - spectatorCameraConfig.resetSizeSpectatorCamera(geometryChanged.width, geometryChanged.height); + // if secondary camera is currently being used for mirror projection then don't update it's aspect ratio (will be done in spectatorCameraOn) + if (!spectatorCameraConfig.mirrorProjection) { + spectatorCameraConfig.resetSizeSpectatorCamera(geometryChanged.width, geometryChanged.height); + } setDisplay(monitorShowsCameraView); }