Merge branch 'master' into feature/domain-limited-height

This commit is contained in:
Anthony J. Thibault 2017-12-07 10:01:33 -08:00
commit fbcecbfc89
265 changed files with 6948 additions and 6114 deletions

4
.gitignore vendored
View file

@ -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

View file

@ -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=<your_home_directory>/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

View file

@ -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()

View file

@ -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')

View file

@ -7,12 +7,10 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.sensor.accelerometer" android:required="true"/>
<uses-feature android:name="android.hardware.sensor.gyroscope" android:required="true"/>
<uses-feature android:name="android.software.vr.mode" android:required="false"/>
<uses-feature android:name="android.hardware.vr.high_performance" android:required="false"/>
<application
android:allowBackup="true"
android:theme="@style/VrActivityTheme"
android:theme="@style/NoSystemUI"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round">
<activity
@ -20,17 +18,10 @@
android:label="@string/app_name"
android:screenOrientation="landscape"
android:configChanges="orientation|keyboardHidden|screenSize"
android:enableVrMode="@string/gvr_vr_mode_component"
android:resizeableActivity="false">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="com.google.intent.category.DAYDREAM"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="com.google.intent.category.CARDBOARD" />
</intent-filter>
</activity>
</application>

View file

@ -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<gvr_context *>(native_gvr_api);
return toJni(new NativeRenderer(gvrContext));
#else
return toJni(new NativeRenderer(nullptr));
#endif
return toJni(new NativeRenderer());
}
JNI_METHOD(void, nativeDestroyRenderer)

View file

@ -1,138 +1,14 @@
#include "renderer.h"
#include <mutex>
#include <glm/gtc/matrix_transform.hpp>
#include <QtCore/QDebug>
#include <gl/Config.h>
#include "GoogleVRHelpers.h"
#include <gl/GLShaders.h>
#include <shared/Bilateral.h>
#include <gpu/DrawTransformUnitQuad_vert.h>
#include <gpu/DrawTexcoordRectTransformUnitQuad_vert.h>
#include <gpu/DrawViewportQuadTransformTexcoord_vert.h>
#include <gpu/DrawTexture_frag.h>
#include <gpu/DrawTextureOpaque_frag.h>
#include <gpu/DrawColoredTexture_frag.h>
#include <render-utils/simple_vert.h>
#include <render-utils/simple_frag.h>
#include <render-utils/simple_textured_frag.h>
#include <render-utils/simple_textured_unlit_frag.h>
#include <render-utils/deferred_light_vert.h>
#include <render-utils/deferred_light_point_vert.h>
#include <render-utils/deferred_light_spot_vert.h>
#include <render-utils/directional_ambient_light_frag.h>
#include <render-utils/directional_skybox_light_frag.h>
#include <render-utils/standardTransformPNTC_vert.h>
#include <render-utils/standardDrawTexture_frag.h>
#include <render-utils/model_vert.h>
#include <render-utils/model_shadow_vert.h>
#include <render-utils/model_normal_map_vert.h>
#include <render-utils/model_lightmap_vert.h>
#include <render-utils/model_lightmap_normal_map_vert.h>
#include <render-utils/skin_model_vert.h>
#include <render-utils/skin_model_shadow_vert.h>
#include <render-utils/skin_model_normal_map_vert.h>
#include <render-utils/model_frag.h>
#include <render-utils/model_shadow_frag.h>
#include <render-utils/model_normal_map_frag.h>
#include <render-utils/model_normal_specular_map_frag.h>
#include <render-utils/model_specular_map_frag.h>
#include <render-utils/model_lightmap_frag.h>
#include <render-utils/model_lightmap_normal_map_frag.h>
#include <render-utils/model_lightmap_normal_specular_map_frag.h>
#include <render-utils/model_lightmap_specular_map_frag.h>
#include <render-utils/model_translucent_frag.h>
#include <render-utils/overlay3D_vert.h>
#include <render-utils/overlay3D_frag.h>
#include <render-utils/sdf_text3D_vert.h>
#include <render-utils/sdf_text3D_frag.h>
#if 0
#include <model/skybox_vert.h>
#include <model/skybox_frag.h>
#include <entities-renderer/textured_particle_frag.h>
#include <entities-renderer/textured_particle_vert.h>
#include <entities-renderer/paintStroke_vert.h>
#include <entities-renderer/paintStroke_frag.h>
#include <entities-renderer/polyvox_vert.h>
#include <entities-renderer/polyvox_frag.h>
#endif
template <typename F>
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<float, 108> 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<float, 108> 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<float, 108> 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<float, 9> TRIANGLE_VERTS {{
-0.5f, -0.5f, 0.0f,
@ -327,229 +60,36 @@ namespace triangle {
}};
}
std::array<gvr::BufferViewport, 2> buildViewports(const std::unique_ptr<gvr::GvrApi> &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<gvr::BufferSpec> 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<std::chrono::milliseconds>(
@ -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();
}

View file

@ -4,21 +4,8 @@
#include <array>
#include <glm/glm.hpp>
#define GVR
#if defined(GVR)
#include <vr/gvr/capi/include/gvr.h>
#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<std::chrono::system_clock> start { std::chrono::system_clock::now() };
std::chrono::time_point<std::chrono::system_clock> start;
#if defined(GVR)
void InitializeVR();
void PrepareFramebuffer();
std::unique_ptr<gvr::GvrApi> _gvrapi;
gvr::ViewerType _gvr_viewer_type;
std::unique_ptr<gvr::BufferViewportList> _viewportlist;
std::unique_ptr<gvr::SwapChain> _swapchain;
std::array<gvr::BufferViewport, 2> _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<glm::mat4, 2> _modelview_cube;
std::array<glm::mat4, 2> _modelview_floor;
std::array<glm::mat4, 2> _modelview_projection_cube;
std::array<glm::mat4, 2> _modelview_projection_floor;
std::array<glm::vec3, 2> _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 };
};

View file

@ -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));
}

View file

@ -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)

41
android/setupGVR.gradle Normal file
View file

@ -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]) {
}

View file

@ -441,7 +441,7 @@ void Agent::executeScript() {
Transform audioTransform;
auto headOrientation = scriptedAvatar->getHeadOrientation();
audioTransform.setTranslation(scriptedAvatar->getPosition());
audioTransform.setTranslation(scriptedAvatar->getWorldPosition());
audioTransform.setRotation(headOrientation);
QByteArray encodedBuffer;
@ -452,7 +452,7 @@ void Agent::executeScript() {
}
AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber,
audioTransform, scriptedAvatar->getPosition(), glm::vec3(0),
audioTransform, scriptedAvatar->getWorldPosition(), glm::vec3(0),
packetType, _selectedCodecName);
});
@ -742,10 +742,10 @@ void Agent::processAgentAvatarAudio() {
audioPacket->writePrimitive(numAvailableSamples);
// use the orientation and position of this avatar for the source of this audio
audioPacket->writePrimitive(scriptedAvatar->getPosition());
audioPacket->writePrimitive(scriptedAvatar->getWorldPosition());
glm::quat headOrientation = scriptedAvatar->getHeadOrientation();
audioPacket->writePrimitive(headOrientation);
audioPacket->writePrimitive(scriptedAvatar->getPosition());
audioPacket->writePrimitive(scriptedAvatar->getWorldPosition());
audioPacket->writePrimitive(glm::vec3(0));
// no matter what, the loudness should be set to 0
@ -759,10 +759,10 @@ void Agent::processAgentAvatarAudio() {
audioPacket->writePrimitive((quint8)0);
// use the orientation and position of this avatar for the source of this audio
audioPacket->writePrimitive(scriptedAvatar->getPosition());
audioPacket->writePrimitive(scriptedAvatar->getWorldPosition());
glm::quat headOrientation = scriptedAvatar->getHeadOrientation();
audioPacket->writePrimitive(headOrientation);
audioPacket->writePrimitive(scriptedAvatar->getPosition());
audioPacket->writePrimitive(scriptedAvatar->getWorldPosition()); // HUH? why do we write this twice??
audioPacket->writePrimitive(glm::vec3(0));
QByteArray encodedBuffer;

View file

@ -25,6 +25,23 @@ AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID) :
_avatar->setID(nodeID);
}
uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(QUuid otherAvatar) const {
std::unordered_map<QUuid, uint64_t>::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<QUuid, uint64_t>::iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar);
if (itr != _lastOtherAvatarEncodeTime.end()) {
itr->second = time;
} else {
_lastOtherAvatarEncodeTime.emplace(std::pair<QUuid, uint64_t>(otherAvatar, time));
}
}
void AvatarMixerClientData::queuePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node) {
if (!_packetQueue.node) {
_packetQueue.node = node;

View file

@ -91,7 +91,7 @@ public:
void loadJSONStats(QJsonObject& jsonObject) const;
glm::vec3 getPosition() const { return _avatar ? _avatar->getPosition() : glm::vec3(0); }
glm::vec3 getPosition() const { return _avatar ? _avatar->getWorldPosition() : glm::vec3(0); }
glm::vec3 getGlobalBoundingBoxCorner() const { return _avatar ? _avatar->getGlobalBoundingBoxCorner() : glm::vec3(0); }
bool isRadiusIgnoring(const QUuid& other) const { return _radiusIgnoredOthers.find(other) != _radiusIgnoredOthers.end(); }
void addToRadiusIgnoringSet(const QUuid& other) { _radiusIgnoredOthers.insert(other); }
@ -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<JointData>& 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<QUuid, quint64> _lastOtherAvatarEncodeTime;
std::unordered_map<QUuid, uint64_t> _lastOtherAvatarEncodeTime;
std::unordered_map<QUuid, QVector<JointData>> _lastOtherAvatarSentJoints;
uint64_t _identityChangeTimestamp;

View file

@ -22,6 +22,7 @@
#include <NodeList.h>
#include <Node.h>
#include <OctreeConstants.h>
#include <PrioritySortUtil.h>
#include <udt/PacketHeaders.h>
#include <SharedUtil.h>
#include <StDev.h>
@ -32,6 +33,10 @@
#include "AvatarMixerClientData.h"
#include "AvatarMixerSlave.h"
namespace PrioritySortUtil {
// we declare this callback here but override it later
std::function<uint64_t(const AvatarSharedPointer&)> getAvatarAgeCallback = [] (const AvatarSharedPointer& avatar) { return 0; };
}
void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) {
_begin = begin;
@ -184,10 +189,8 @@ 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<AvatarSharedPointer> avatarList;
std::vector<AvatarSharedPointer> avatarsToSort;
std::unordered_map<AvatarSharedPointer, SharedNodePointer> avatarDataToNodes;
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 +198,61 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData());
AvatarSharedPointer otherAvatar = otherNodeData->getAvatarSharedPointer();
avatarList << otherAvatar;
avatarsToSort.push_back(otherAvatar);
avatarDataToNodes[otherAvatar] = otherNode;
}
});
AvatarSharedPointer thisAvatar = nodeData->getAvatarSharedPointer();
ViewFrustum cameraView = nodeData->getViewFrustom();
std::priority_queue<AvatarPriority> 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->getPosition() - avatar->getGlobalBoundingBoxCorner() * avatar->getSensorToWorldScale());
return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z));
}, [&](AvatarSharedPointer avatar)->bool {
// now that we've assembled the avatarDataToNodes map we can replace PrioritySortUtil::getAvatarAgeCallback
// with the true implementation
PrioritySortUtil::getAvatarAgeCallback = [&] (const AvatarSharedPointer& avatar) {
auto avatarNode = avatarDataToNodes[avatar];
assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map
return nodeData->getLastOtherAvatarEncodeTime(avatarNode->getUUID());
};
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 {
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 {
// use the callback implemented above
return PrioritySortUtil::getAvatarAgeCallback(_avatar);
}
const AvatarSharedPointer& getAvatar() const { return _avatar; }
private:
AvatarSharedPointer _avatar;
};
// prepare to sort
ViewFrustum cameraView = nodeData->getViewFrustum();
PrioritySortUtil::PriorityQueue<SortableAvatar> 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<const AvatarMixerClientData*>(avatarNode->getLinkedData());
@ -240,7 +268,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 +294,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 +317,21 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
++numAvatarsWithSkippedFrames;
}
}
return shouldIgnore;
});
quint64 endIgnoreCalculation = usecTimestampNow();
_stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation);
if (!shouldIgnore) {
// sort this one for later
sortedAvatars.push(SortableAvatar(avatar));
}
}
// 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 +358,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 +429,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();

View file

@ -20,7 +20,7 @@
QByteArray ScriptableAvatar::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) {
_globalPosition = getPosition();
_globalPosition = getWorldPosition();
return AvatarData::toByteArrayStateful(dataDetail);
}

View file

@ -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()

View file

@ -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()

View file

@ -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}")

View file

@ -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)

View file

@ -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()

18
cmake/macros/TargetDraco.cmake Executable file
View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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)

View file

@ -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")

View file

@ -916,6 +916,14 @@
"default": false
}
]
},
{
"name": "multi_kick_logged_in",
"type": "checkbox",
"label": "Multi-Kick for Logged In Users",
"help": "Kick logged in users by machine fingerprint (in addition to the default kick by username)",
"default": false,
"advanced": true
}
]
},

View file

@ -183,6 +183,11 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin
#ifdef WANT_DEBUG
qDebug() << "| user-permissions: specific MAC matches, so:" << userPerms;
#endif
} else if (_server->_settingsManager.hasPermissionsForMachineFingerprint(machineFingerprint)) {
userPerms = _server->_settingsManager.getPermissionsForMachineFingerprint(machineFingerprint);
#ifdef WANT_DEBUG
qDebug(() << "| user-permissions: specific Machine Fingerprint matches, so: " << userPerms;
#endif
} else if (_server->_settingsManager.hasPermissionsForIP(senderAddress)) {
// this user comes from an IP we have in our permissions table, apply those permissions

View file

@ -570,6 +570,7 @@ void DomainServerSettingsManager::unpackPermissions() {
} else {
// anonymous, logged in, and friend users get connect permissions by default
perms->set(NodePermissions::Permission::canConnectToDomain);
perms->set(NodePermissions::Permission::canRezTemporaryCertifiedEntities);
}
// add the permissions to the standard map
@ -691,7 +692,7 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<Re
bool newPermissions = false;
if (!verifiedUsername.isEmpty()) {
// if we have a verified user name for this user, we apply the kick to the username
// if we have a verified user name for this user, we first apply the kick to the username
// check if there were already permissions
bool hadPermissions = havePermissionsForName(verifiedUsername);
@ -703,7 +704,14 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<Re
// ensure that the connect permission is clear
userPermissions->clear(NodePermissions::Permission::canConnectToDomain);
} else {
}
// if we didn't have a username, or this domain-server uses the "multi-kick" setting to
// kick logged in users via username AND machine fingerprint (or IP as fallback)
// then we remove connect permissions for the machine fingerprint (or IP as fallback)
const QString MULTI_KICK_SETTINGS_KEYPATH = "security.multi_kick_logged_in";
if (verifiedUsername.isEmpty() || valueOrDefaultValueForKeyPath(MULTI_KICK_SETTINGS_KEYPATH).toBool()) {
// remove connect permissions for the machine fingerprint
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(matchingNode->getLinkedData());
if (nodeData) {
@ -738,8 +746,8 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer<Re
// TODO: soon we will have feedback (in the form of a message to the client) after we kick. When we
// do, we will have a success flag, and perhaps a reason for failure. For now, just don't do it.
if (kickAddress == limitedNodeList->getPublicSockAddr().getAddress() ||
kickAddress == limitedNodeList->getLocalSockAddr().getAddress() ||
kickAddress.isLoopback() ) {
kickAddress == limitedNodeList->getLocalSockAddr().getAddress() ||
kickAddress.isLoopback() ) {
qWarning() << "attempt to kick node running on same machine as domain server, ignoring KickRequest";
return;
}

View file

@ -33,6 +33,56 @@ var EventBridge;
// replace the TempEventBridge with the real one.
var tempEventBridge = EventBridge;
EventBridge = channel.objects.eventBridge;
EventBridge.audioOutputDeviceChanged.connect(function(deviceName) {
navigator.mediaDevices.getUserMedia({ audio: true, video: true }).then(function(mediaStream) {
navigator.mediaDevices.enumerateDevices().then(function(devices) {
devices.forEach(function(device) {
if (device.kind == "audiooutput") {
if (device.label == deviceName){
console.log("Changing HTML audio output to device " + device.label);
var deviceId = device.deviceId;
var videos = document.getElementsByTagName("video");
for (var i = 0; i < videos.length; i++){
videos[i].setSinkId(deviceId);
}
var audios = document.getElementsByTagName("audio");
for (var i = 0; i < audios.length; i++){
audios[i].setSinkId(deviceId);
}
}
}
});
}).catch(function(err) {
console.log("Error getting media devices"+ err.name + ": " + err.message);
});
}).catch(function(err) {
console.log("Error getting user media"+ err.name + ": " + err.message);
});
});
// To be able to update the state of the output device selection for every element added to the DOM
// we need to listen to events that might precede the addition of this elements.
// A more robust hack will be to add a setInterval that look for DOM changes every 100-300 ms (low performance?)
window.addEventListener("load",function(event) {
setTimeout(function() {
EventBridge.forceHtmlAudioOutputDeviceUpdate();
}, 1200);
}, false);
document.addEventListener("click",function(){
setTimeout(function() {
EventBridge.forceHtmlAudioOutputDeviceUpdate();
}, 1200);
}, false);
document.addEventListener("change",function(){
setTimeout(function() {
EventBridge.forceHtmlAudioOutputDeviceUpdate();
}, 1200);
}, false);
tempEventBridge._callbacks.forEach(function (callback) {
EventBridge.scriptEventReceived.connect(callback);
});

View file

@ -1,127 +0,0 @@
<!-- Copyright 2016 High Fidelity, Inc. -->
<html>
<head>
<meta charset="utf-8"/>
<input type="hidden" id="version" value="1"/>
<title>Welcome to Interface</title>
<style>
body {
background: black;
width: 100%;
overflow-x: hidden;
margin: 0;
padding: 0;
}
#kbm_button {
position: absolute;
left: 70;
top: 118;
width: 297;
height: 80;
}
#hand_controllers_button {
position: absolute;
left: 367;
top: 118;
width: 267;
height: 80;
}
#game_controller_button {
position: absolute;
left: 634;
top: 118;
width: 297;
height: 80;
}
#image_area {
width: 1024;
height: 720;
margin: auto;
position: absolute;
top: 0; left: 0; bottom: 0; right: 0;
}
</style>
<script>
var handControllerImageURL = null;
function showKbm() {
document.getElementById("main_image").setAttribute("src", "img/controls-help-keyboard.png");
}
function showHandControllers() {
document.getElementById("main_image").setAttribute("src", handControllerImageURL);
}
function showGamepad() {
document.getElementById("main_image").setAttribute("src", "img/controls-help-gamepad.png");
}
// This is not meant to be a complete or hardened query string parser - it only
// needs to handle the values we send in and have control over.
//
// queryString is a string of the form "key1=value1&key2=value2&key3&key4=value4"
function parseQueryString(queryString) {
var params = {};
var paramsParts = queryString.split("&");
for (var i = 0; i < paramsParts.length; ++i) {
var paramKeyValue = paramsParts[i].split("=");
if (paramKeyValue.length == 1) {
params[paramKeyValue[0]] = undefined;
} else if (paramKeyValue.length == 2) {
params[paramKeyValue[0]] = paramKeyValue[1];
} else {
console.error("Error parsing param keyvalue: ", paramParts);
}
}
return params;
}
function load() {
var parts = window.location.href.split("?");
var params = {};
if (parts.length > 0) {
params = parseQueryString(parts[1]);
}
switch (params.handControllerName) {
case "oculus":
handControllerImageURL = "img/controls-help-oculus.png";
break;
case "vive":
default:
handControllerImageURL = "img/controls-help-vive.png";
}
switch (params.defaultTab) {
case "gamepad":
showGamepad();
break;
case "handControllers":
showHandControllers();
break;
case "kbm":
default:
showKbm();
}
}
</script>
</head>
<body onload="load()">
<div id="image_area">
<img id="main_image" src="img/controls-help-keyboard.png" width="1024px" height="720px"></img>
<a href="#" id="kbm_button" onmousedown="showKbm()"></a>
<a href="#" id="hand_controllers_button" onmousedown="showHandControllers()"></a>
<a href="#" id="game_controller_button" onmousedown="showGamepad()"></a>
</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 604 KiB

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 503 KiB

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 585 KiB

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 547 KiB

After

Width:  |  Height:  |  Size: 254 KiB

View file

@ -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;
});
})();

View file

@ -212,7 +212,7 @@ ScrollingWindow {
WebEngineScript {
id: createGlobalEventBridge
sourceCode: eventBridgeJavaScriptToInject
injectionPoint: WebEngineScript.DocumentCreation
injectionPoint: WebEngineScript.Deferred
worldId: WebEngineScript.MainWorld
}
@ -233,9 +233,13 @@ ScrollingWindow {
anchors.right: parent.right
onFeaturePermissionRequested: {
permissionsBar.securityOrigin = securityOrigin;
permissionsBar.feature = feature;
root.showPermissionsBar();
if (feature == 2) { // QWebEnginePage::MediaAudioCapture
grantFeaturePermission(securityOrigin, feature, true);
} else {
permissionsBar.securityOrigin = securityOrigin;
permissionsBar.feature = feature;
root.showPermissionsBar();
}
}
onLoadingChanged: {

View file

@ -70,11 +70,12 @@ Item {
keyItem.state = "mouseOver";
var globalPosition = keyItem.mapToGlobal(mouseArea1.mouseX, mouseArea1.mouseY);
var deviceId = Web3DOverlay.deviceIdByTouchPoint(globalPosition.x, globalPosition.y);
var hand = deviceId - 1; // based on touchEventUtils.js, deviceId is 'hand + 1', so 'hand' is 'deviceId' - 1
var pointerID = Web3DOverlay.deviceIdByTouchPoint(globalPosition.x, globalPosition.y);
if (hand == leftHand || hand == rightHand) {
Controller.triggerHapticPulse(_HAPTIC_STRENGTH, _HAPTIC_DURATION, hand);
if (Pointers.isLeftHand(pointerID)) {
Controller.triggerHapticPulse(_HAPTIC_STRENGTH, _HAPTIC_DURATION, leftHand);
} else if (Pointers.isRightHand(pointerID)) {
Controller.triggerHapticPulse(_HAPTIC_STRENGTH, _HAPTIC_DURATION, rightHand);
}
}

View file

@ -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 {

View file

@ -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
}

View file

@ -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 {

View file

@ -430,7 +430,7 @@ Rectangle {
var a = new Date(timestamp);
var year = a.getFullYear();
var month = addLeadingZero(a.getMonth());
var month = addLeadingZero(a.getMonth() + 1);
var day = addLeadingZero(a.getDate());
var hour = a.getHours();
var drawnHour = hour;

View file

@ -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 {

View file

@ -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() {

View file

@ -59,6 +59,11 @@ Item {
ListModel {
id: helpModel;
ListElement {
isExpanded: false;
question: "How can I get HFC?"
answer: qsTr("High Fidelity commerce is in closed beta right now.<br><br>To request entry and get free HFC, <b>please contact info@highfidelity.com with your High Fidelity account username and the email address registered to that account.</b>");
}
ListElement {
isExpanded: false;
question: "What are private keys?"
@ -87,12 +92,7 @@ Item {
ListElement {
isExpanded: false;
question: "My HFC balance isn't what I expect it to be. Why?"
answer: qsTr('High Fidelity Coin (HFC) transactions are backed by a <b>blockchain</b>, which takes time to update. The status of a transaction usually updates within 90 seconds.<br><br><b><font color="#0093C5"><a href="#blockchain">Tap here to learn more about the blockchain.</a></font></b>');
}
ListElement {
isExpanded: false;
question: "My friend purchased my item from the Marketplace, but I still haven't received the money from the sale. Why not?"
answer: qsTr('High Fidelity Coin (HFC) transactions are backed by a <b>blockchain</b>, which takes time to update. The status of a transaction usually updates within 90 seconds, at which point you will receive your money.<br><br><b><font color="#0093C5"><a href="#blockchain">Tap here to learn more about the blockchain.</a></font></b>');
answer: qsTr('High Fidelity Coin (HFC) transactions are backed by a <b>blockchain</b>, which takes time to update. The status of a transaction usually updates within a few seconds.<br><br><b><font color="#0093C5"><a href="#blockchain">Tap here to learn more about the blockchain.</a></font></b>');
}
ListElement {
isExpanded: false;
@ -212,7 +212,7 @@ Item {
if (link === "#privateKeyPath") {
Qt.openUrlExternally("file:///" + root.keyFilePath.substring(0, root.keyFilePath.lastIndexOf('/')));
} else if (link === "#blockchain") {
Qt.openUrlExternally("https://www.highfidelity.com/");
Qt.openUrlExternally("https://docs.highfidelity.com/high-fidelity-commerce");
}
}
}

View file

@ -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;

View file

@ -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});
}
}

View file

@ -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
//

View file

@ -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;
}
@ -339,7 +360,7 @@ Item {
var a = new Date(timestamp);
var year = a.getFullYear();
var month = addLeadingZero(a.getMonth());
var month = addLeadingZero(a.getMonth() + 1);
var day = addLeadingZero(a.getDate());
var hour = a.getHours();
var drawnHour = hour;

View file

@ -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));
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -36,7 +36,9 @@ Rectangle {
readonly property bool hmdHead: headBox.checked
readonly property bool headPuck: headPuckBox.checked
readonly property bool handController: handBox.checked
readonly property bool handPuck: handPuckBox.checked
readonly property bool hmdDesktop: hmdInDesktop.checked
property int state: buttonState.disabled
property var lastConfiguration: null
@ -53,10 +55,6 @@ Rectangle {
}
MouseArea {
id: mouseArea
@ -99,6 +97,7 @@ Rectangle {
onClicked: {
if (checked) {
headPuckBox.checked = false;
hmdInDesktop.checked = false;
} else {
checked = true;
}
@ -121,6 +120,7 @@ Rectangle {
onClicked: {
if (checked) {
headBox.checked = false;
hmdInDesktop.checked = false;
} else {
checked = true;
}
@ -133,6 +133,36 @@ Rectangle {
text: "Tracker"
color: hifi.colors.lightGrayText
}
HifiControls.CheckBox {
id: hmdInDesktop
width: 15
height: 15
boxRadius: 7
visible: viveInDesktop.checked
anchors.top: viveInDesktop.bottom
anchors.topMargin: 5
anchors.left: openVrConfiguration.left
anchors.leftMargin: leftMargin + 10
onClicked: {
if (checked) {
headBox.checked = false;
headPuckBox.checked = false;
} else {
checked = true;
}
sendConfigurationSettings();
}
}
RalewayBold {
size: 12
visible: viveInDesktop.checked
text: "None"
color: hifi.colors.lightGrayText
}
}
Row {
@ -773,6 +803,11 @@ Rectangle {
anchors.leftMargin: leftMargin + 10
onClicked: {
if (!checked & hmdInDesktop.checked) {
headBox.checked = true;
headPuckBox.checked = false;
hmdInDesktop.checked = false;
}
sendConfigurationSettings();
}
}
@ -789,6 +824,7 @@ Rectangle {
verticalCenter: viveInDesktop.verticalCenter
}
}
NumberAnimation {
id: numberAnimation
@ -797,6 +833,7 @@ Rectangle {
to: 0
}
function logAction(action, status) {
console.log("calibrated from ui");
var data = {
@ -877,6 +914,7 @@ Rectangle {
var HmdHead = settings["HMDHead"];
var viveController = settings["handController"];
var desktopMode = settings["desktopMode"];
var hmdDesktopPosition = settings["hmdDesktopTracking"];
armCircumference.value = settings.armCircumference;
shoulderWidth.value = settings.shoulderWidth;
@ -898,6 +936,7 @@ Rectangle {
}
viveInDesktop.checked = desktopMode;
hmdInDesktop.checked = hmdDesktopPosition;
initializeButtonState();
updateCalibrationText();
@ -1058,7 +1097,8 @@ Rectangle {
"handConfiguration": handObject,
"armCircumference": armCircumference.value,
"shoulderWidth": shoulderWidth.value,
"desktopMode": viveInDesktop.checked
"desktopMode": viveInDesktop.checked,
"hmdDesktopTracking": hmdInDesktop.checked
}
return settingsObject;

View file

@ -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
}

View file

@ -194,8 +194,13 @@
#include <EntityScriptClient.h>
#include <ModelScriptingInterface.h>
#include <PickManager.h>
#include <PointerManager.h>
#include <raypick/RayPickScriptingInterface.h>
#include <raypick/LaserPointerScriptingInterface.h>
#include <raypick/PickScriptingInterface.h>
#include <raypick/PointerScriptingInterface.h>
#include <raypick/MouseRayPick.h>
#include <FadeEffect.h>
@ -620,6 +625,12 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::registerInheritance<SpatialParentFinder, InterfaceParentFinder>();
// Set dependencies
DependencyManager::set<PickManager>();
DependencyManager::set<PointerManager>();
DependencyManager::set<LaserPointerScriptingInterface>();
DependencyManager::set<RayPickScriptingInterface>();
DependencyManager::set<PointerScriptingInterface>();
DependencyManager::set<PickScriptingInterface>();
DependencyManager::set<Cursor::Manager>();
DependencyManager::set<AccountManager>(std::bind(&Application::getUserAgent, qApp));
DependencyManager::set<StatTracker>();
@ -700,9 +711,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<FadeEffect>();
DependencyManager::set<LaserPointerScriptingInterface>();
DependencyManager::set<RayPickScriptingInterface>();
return previousSessionCrashed;
}
@ -895,7 +903,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(audioIO.data(), &AudioClient::muteEnvironmentRequested, [](glm::vec3 position, float radius) {
auto audioClient = DependencyManager::get<AudioClient>();
auto audioScriptingInterface = DependencyManager::get<AudioScriptingInterface>();
auto myAvatarPosition = DependencyManager::get<AvatarManager>()->getMyAvatar()->getPosition();
auto myAvatarPosition = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldPosition();
float distance = glm::distance(myAvatarPosition, position);
bool shouldMute = !audioClient->isMuted() && (distance < radius);
@ -968,8 +976,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
auto addressManager = DependencyManager::get<AddressManager>();
// use our MyAvatar position and quat for address manager path
addressManager->setPositionGetter([this]{ return getMyAvatar()->getPosition(); });
addressManager->setOrientationGetter([this]{ return getMyAvatar()->getOrientation(); });
addressManager->setPositionGetter([this]{ return getMyAvatar()->getWorldPosition(); });
addressManager->setOrientationGetter([this]{ return getMyAvatar()->getWorldOrientation(); });
connect(addressManager.data(), &AddressManager::hostChanged, this, &Application::updateWindowTitle);
connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress);
@ -1470,13 +1478,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// If the user clicks an an entity, we will check that it's an unlocked web entity, and if so, set the focus to it
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity,
connect(entityScriptingInterface.data(), &EntityScriptingInterface::mousePressOnEntity,
[this](const EntityItemID& entityItemID, const PointerEvent& event) {
if (getEntities()->wantsKeyboardFocus(entityItemID)) {
setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID);
setKeyboardFocusEntity(entityItemID);
} else {
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
if (event.shouldFocus()) {
if (getEntities()->wantsKeyboardFocus(entityItemID)) {
setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID);
setKeyboardFocusEntity(entityItemID);
} else {
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
}
}
});
@ -1507,7 +1517,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
static const QString FAST_STATS_ARG = "--fast-heartbeat";
static int SEND_STATS_INTERVAL_MS = arguments().indexOf(FAST_STATS_ARG) != -1 ? 1000 : 10000;
static glm::vec3 lastAvatarPosition = myAvatar->getPosition();
static glm::vec3 lastAvatarPosition = myAvatar->getWorldPosition();
static glm::mat4 lastHMDHeadPose = getHMDSensorPose();
static controller::Pose lastLeftHandPose = myAvatar->getLeftHandPose();
static controller::Pose lastRightHandPose = myAvatar->getRightHandPose();
@ -1635,7 +1645,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
properties["bytes_downloaded"] = bytesDownloaded;
auto myAvatar = getMyAvatar();
glm::vec3 avatarPosition = myAvatar->getPosition();
glm::vec3 avatarPosition = myAvatar->getWorldPosition();
properties["avatar_has_moved"] = lastAvatarPosition != avatarPosition;
lastAvatarPosition = avatarPosition;
@ -1715,7 +1725,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
checkNearbyAvatarsTimer->setInterval(CHECK_NEARBY_AVATARS_INTERVAL_MS); // 10 seconds, Qt::CoarseTimer ok
connect(checkNearbyAvatarsTimer, &QTimer::timeout, this, [this]() {
auto avatarManager = DependencyManager::get<AvatarManager>();
int nearbyAvatars = avatarManager->numberOfAvatarsInRange(avatarManager->getMyAvatar()->getPosition(),
int nearbyAvatars = avatarManager->numberOfAvatarsInRange(avatarManager->getMyAvatar()->getWorldPosition(),
NEARBY_AVATAR_RADIUS_METERS) - 1;
if (nearbyAvatars != lastCountOfNearbyAvatars) {
lastCountOfNearbyAvatars = nearbyAvatars;
@ -1804,25 +1814,35 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(&_myCamera, &Camera::modeUpdated, this, &Application::cameraModeChanged);
DependencyManager::get<PickManager>()->setShouldPickHUDOperator([&]() { return DependencyManager::get<HMDScriptingInterface>()->isHMDMode(); });
DependencyManager::get<PickManager>()->setCalculatePos2DFromHUDOperator([&](const glm::vec3& intersection) {
const glm::vec2 MARGIN(25.0f);
glm::vec2 maxPos = _controllerScriptingInterface->getViewportDimensions() - MARGIN;
glm::vec2 pos2D = DependencyManager::get<HMDScriptingInterface>()->overlayFromWorldPoint(intersection);
return glm::max(MARGIN, glm::min(pos2D, maxPos));
});
// Setup the mouse ray pick and related operators
DependencyManager::get<EntityTreeRenderer>()->setMouseRayPickID(_rayPickManager.createRayPick(
RayPickFilter(DependencyManager::get<RayPickScriptingInterface>()->PICK_ENTITIES() | DependencyManager::get<RayPickScriptingInterface>()->PICK_INCLUDE_NONCOLLIDABLE()),
0.0f, true));
DependencyManager::get<EntityTreeRenderer>()->setMouseRayPickResultOperator([&](QUuid rayPickID) {
DependencyManager::get<EntityTreeRenderer>()->setMouseRayPickID(DependencyManager::get<PickManager>()->addPick(PickQuery::Ray, std::make_shared<MouseRayPick>(
PickFilter(PickScriptingInterface::PICK_ENTITIES() | PickScriptingInterface::PICK_INCLUDE_NONCOLLIDABLE()), 0.0f, true)));
DependencyManager::get<EntityTreeRenderer>()->setMouseRayPickResultOperator([&](unsigned int rayPickID) {
RayToEntityIntersectionResult entityResult;
RayPickResult result = _rayPickManager.getPrevRayPickResult(rayPickID);
entityResult.intersects = result.type != DependencyManager::get<RayPickScriptingInterface>()->INTERSECTED_NONE();
if (entityResult.intersects) {
entityResult.intersection = result.intersection;
entityResult.distance = result.distance;
entityResult.surfaceNormal = result.surfaceNormal;
entityResult.entityID = result.objectID;
entityResult.entity = DependencyManager::get<EntityTreeRenderer>()->getTree()->findEntityByID(entityResult.entityID);
entityResult.intersects = false;
auto pickResult = DependencyManager::get<PickManager>()->getPrevPickResultTyped<RayPickResult>(rayPickID);
if (pickResult) {
entityResult.intersects = pickResult->type != IntersectionType::NONE;
if (entityResult.intersects) {
entityResult.intersection = pickResult->intersection;
entityResult.distance = pickResult->distance;
entityResult.surfaceNormal = pickResult->surfaceNormal;
entityResult.entityID = pickResult->objectID;
entityResult.entity = DependencyManager::get<EntityTreeRenderer>()->getTree()->findEntityByID(entityResult.entityID);
}
}
return entityResult;
});
DependencyManager::get<EntityTreeRenderer>()->setSetPrecisionPickingOperator([&](QUuid rayPickID, bool value) {
_rayPickManager.setPrecisionPicking(rayPickID, value);
DependencyManager::get<EntityTreeRenderer>()->setSetPrecisionPickingOperator([&](unsigned int rayPickID, bool value) {
DependencyManager::get<PickManager>()->setPrecisionPicking(rayPickID, value);
});
// Preload Tablet sounds
@ -2408,7 +2428,7 @@ void Application::updateCamera(RenderArgs& renderArgs) {
auto hmdWorldMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix();
_myCamera.setOrientation(glm::normalize(glmExtractRotation(hmdWorldMat)));
_myCamera.setPosition(extractTranslation(hmdWorldMat) +
myAvatar->getOrientation() * boomOffset);
myAvatar->getWorldOrientation() * boomOffset);
}
else {
_myCamera.setOrientation(myAvatar->getHead()->getOrientation());
@ -2418,13 +2438,13 @@ void Application::updateCamera(RenderArgs& renderArgs) {
}
else {
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ myAvatar->getOrientation() * boomOffset);
+ myAvatar->getWorldOrientation() * boomOffset);
}
}
}
else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
if (isHMDMode()) {
auto mirrorBodyOrientation = myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f));
auto mirrorBodyOrientation = myAvatar->getWorldOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f));
glm::quat hmdRotation = extractRotation(myAvatar->getHMDSensorMatrix());
// Mirror HMD yaw and roll
@ -2447,11 +2467,11 @@ void Application::updateCamera(RenderArgs& renderArgs) {
+ mirrorBodyOrientation * hmdOffset);
}
else {
_myCamera.setOrientation(myAvatar->getOrientation()
_myCamera.setOrientation(myAvatar->getWorldOrientation()
* glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ glm::vec3(0, _raiseMirror * myAvatar->getModelScale(), 0)
+ (myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) *
+ (myAvatar->getWorldOrientation() * glm::quat(glm::vec3(0.0f, _rotateMirror, 0.0f))) *
glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
}
renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE;
@ -2461,13 +2481,13 @@ void Application::updateCamera(RenderArgs& renderArgs) {
if (cameraEntity != nullptr) {
if (isHMDMode()) {
glm::quat hmdRotation = extractRotation(myAvatar->getHMDSensorMatrix());
_myCamera.setOrientation(cameraEntity->getRotation() * hmdRotation);
_myCamera.setOrientation(cameraEntity->getWorldOrientation() * hmdRotation);
glm::vec3 hmdOffset = extractTranslation(myAvatar->getHMDSensorMatrix());
_myCamera.setPosition(cameraEntity->getPosition() + (hmdRotation * hmdOffset));
_myCamera.setPosition(cameraEntity->getWorldPosition() + (hmdRotation * hmdOffset));
}
else {
_myCamera.setOrientation(cameraEntity->getRotation());
_myCamera.setPosition(cameraEntity->getPosition());
_myCamera.setOrientation(cameraEntity->getWorldOrientation());
_myCamera.setPosition(cameraEntity->getWorldPosition());
}
}
}
@ -3283,7 +3303,7 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto eventPosition = compositor.getMouseEventPosition(event);
QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition, _glWidget);
QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition);
auto button = event->button();
auto buttons = event->buttons();
// Determine if the ReticleClick Action is 1 and if so, fake include the LeftMouseButton
@ -3329,7 +3349,7 @@ void Application::mousePressEvent(QMouseEvent* event) {
offscreenUi->unfocusWindows();
auto eventPosition = getApplicationCompositor().getMouseEventPosition(event);
QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition, _glWidget);
QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition);
QMouseEvent mappedEvent(event->type(),
transformedPos,
event->screenPos(), event->button(),
@ -3359,7 +3379,7 @@ void Application::mousePressEvent(QMouseEvent* event) {
void Application::mouseDoublePressEvent(QMouseEvent* event) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto eventPosition = getApplicationCompositor().getMouseEventPosition(event);
QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition, _glWidget);
QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition);
QMouseEvent mappedEvent(event->type(),
transformedPos,
event->screenPos(), event->button(),
@ -3385,7 +3405,7 @@ void Application::mouseReleaseEvent(QMouseEvent* event) {
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto eventPosition = getApplicationCompositor().getMouseEventPosition(event);
QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition, _glWidget);
QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition);
QMouseEvent mappedEvent(event->type(),
transformedPos,
event->screenPos(), event->button(),
@ -3878,16 +3898,16 @@ void Application::idle() {
if (!_keyboardFocusedEntity.get().isInvalidID()) {
auto entity = getEntities()->getTree()->findEntityByID(_keyboardFocusedEntity.get());
if (entity && _keyboardFocusHighlight) {
_keyboardFocusHighlight->setRotation(entity->getRotation());
_keyboardFocusHighlight->setPosition(entity->getPosition());
_keyboardFocusHighlight->setWorldOrientation(entity->getWorldOrientation());
_keyboardFocusHighlight->setWorldPosition(entity->getWorldPosition());
}
} else {
// Only Web overlays can have focus.
auto overlay =
std::dynamic_pointer_cast<Web3DOverlay>(getOverlays().getOverlay(_keyboardFocusedOverlay.get()));
if (overlay && _keyboardFocusHighlight) {
_keyboardFocusHighlight->setRotation(overlay->getRotation());
_keyboardFocusHighlight->setPosition(overlay->getPosition());
_keyboardFocusHighlight->setWorldOrientation(overlay->getWorldOrientation());
_keyboardFocusHighlight->setWorldPosition(overlay->getWorldPosition());
}
}
}
@ -4022,7 +4042,7 @@ bool Application::exportEntities(const QString& filename,
!entityIDs.contains(parentID) ||
!entityTree->findEntityByEntityItemID(parentID))) {
// If parent wasn't selected, we want absolute position, which isn't in properties.
auto position = entityItem->getPosition();
auto position = entityItem->getWorldPosition();
root.x = glm::min(root.x, position.x);
root.y = glm::min(root.y, position.y);
root.z = glm::min(root.z, position.z);
@ -4225,7 +4245,7 @@ void Application::init() {
return 0.0f;
}
auto distance = glm::distance(getMyAvatar()->getPosition(), item.getPosition());
auto distance = glm::distance(getMyAvatar()->getWorldPosition(), item.getWorldPosition());
return atan2(maxSize, distance);
});
@ -4302,7 +4322,7 @@ void Application::updateMyAvatarLookAtPosition() {
// TODO -- this code is probably wrong, getHeadPose() returns something in sensor frame, not avatar
glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose();
glm::quat hmdRotation = glm::quat_cast(headPose);
lookAtSpot = _myCamera.getPosition() + myAvatar->getOrientation() * (hmdRotation * lookAtPosition);
lookAtSpot = _myCamera.getPosition() + myAvatar->getWorldOrientation() * (hmdRotation * lookAtPosition);
} else {
lookAtSpot = myAvatar->getHead()->getEyePosition()
+ (myAvatar->getHead()->getFinalOrientationInWorldFrame() * lookAtPosition);
@ -4531,8 +4551,8 @@ void Application::setKeyboardFocusHighlight(const glm::vec3& position, const glm
}
// Position focus
_keyboardFocusHighlight->setRotation(rotation);
_keyboardFocusHighlight->setPosition(position);
_keyboardFocusHighlight->setWorldOrientation(rotation);
_keyboardFocusHighlight->setWorldPosition(position);
_keyboardFocusHighlight->setDimensions(dimensions);
_keyboardFocusHighlight->setVisible(true);
}
@ -4569,7 +4589,7 @@ void Application::setKeyboardFocusEntity(const EntityItemID& entityItemID) {
}
_lastAcceptedKeyPress = usecTimestampNow();
setKeyboardFocusHighlight(entity->getPosition(), entity->getRotation(),
setKeyboardFocusHighlight(entity->getWorldPosition(), entity->getWorldOrientation(),
entity->getDimensions() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR);
}
}
@ -4606,7 +4626,7 @@ void Application::setKeyboardFocusOverlay(const OverlayID& overlayID) {
if (overlay->getProperty("showKeyboardFocusHighlight").toBool()) {
auto size = overlay->getSize() * FOCUS_HIGHLIGHT_EXPANSION_FACTOR;
const float OVERLAY_DEPTH = 0.0105f;
setKeyboardFocusHighlight(overlay->getPosition(), overlay->getRotation(), glm::vec3(size.x, size.y, OVERLAY_DEPTH));
setKeyboardFocusHighlight(overlay->getWorldPosition(), overlay->getWorldOrientation(), glm::vec3(size.x, size.y, OVERLAY_DEPTH));
} else if (_keyboardFocusHighlight) {
_keyboardFocusHighlight->setVisible(false);
}
@ -4698,7 +4718,7 @@ void Application::update(float deltaTime) {
controller::InputCalibrationData calibrationData = {
myAvatar->getSensorToWorldMatrix(),
createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition()),
createMatFromQuatAndPos(myAvatar->getWorldOrientation(), myAvatar->getWorldPosition()),
myAvatar->getHMDSensorMatrix(),
myAvatar->getCenterEyeCalibrationMat(),
myAvatar->getHeadCalibrationMat(),
@ -4808,7 +4828,7 @@ void Application::update(float deltaTime) {
};
// copy controller poses from userInputMapper to myAvatar.
glm::mat4 myAvatarMatrix = createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition());
glm::mat4 myAvatarMatrix = createMatFromQuatAndPos(myAvatar->getWorldOrientation(), myAvatar->getWorldPosition());
glm::mat4 worldToSensorMatrix = glm::inverse(myAvatar->getSensorToWorldMatrix());
glm::mat4 avatarToSensorMatrix = worldToSensorMatrix * myAvatarMatrix;
for (auto& action : avatarControllerActions) {
@ -4948,13 +4968,13 @@ void Application::update(float deltaTime) {
// TODO: break these out into distinct perfTimers when they prove interesting
{
PROFILE_RANGE(app, "RayPickManager");
_rayPickManager.update();
PROFILE_RANGE(app, "PickManager");
DependencyManager::get<PickManager>()->update();
}
{
PROFILE_RANGE(app, "LaserPointerManager");
_laserPointerManager.update();
PROFILE_RANGE(app, "PointerManager");
DependencyManager::get<PointerManager>()->update();
}
{
@ -5424,7 +5444,7 @@ std::shared_ptr<MyAvatar> Application::getMyAvatar() const {
}
glm::vec3 Application::getAvatarPosition() const {
return getMyAvatar()->getPosition();
return getMyAvatar()->getWorldPosition();
}
void Application::copyViewFrustum(ViewFrustum& viewOut) const {
@ -5639,7 +5659,7 @@ bool Application::nearbyEntitiesAreReadyForPhysics() {
// whose bounding boxes cannot be computed (it is too loose for our purposes here). Instead we manufacture
// custom filters and use the general-purpose EntityTree::findEntities(filter, ...)
QVector<EntityItemPointer> entities;
AABox avatarBox(getMyAvatar()->getPosition() - glm::vec3(PHYSICS_READY_RANGE), glm::vec3(2 * PHYSICS_READY_RANGE));
AABox avatarBox(getMyAvatar()->getWorldPosition() - glm::vec3(PHYSICS_READY_RANGE), glm::vec3(2 * PHYSICS_READY_RANGE));
// create two functions that use avatarBox (entityScan and elementScan), the second calls the first
std::function<bool (EntityItemPointer&)> entityScan = [=](EntityItemPointer& entity) {
if (entity->shouldBePhysical()) {
@ -5841,6 +5861,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
scriptEngine->registerGlobalObject("RayPick", DependencyManager::get<RayPickScriptingInterface>().data());
scriptEngine->registerGlobalObject("LaserPointers", DependencyManager::get<LaserPointerScriptingInterface>().data());
scriptEngine->registerGlobalObject("Picks", DependencyManager::get<PickScriptingInterface>().data());
scriptEngine->registerGlobalObject("Pointers", DependencyManager::get<PointerScriptingInterface>().data());
// Caches
scriptEngine->registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data());
@ -5898,6 +5920,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue);
DependencyManager::get<PickScriptingInterface>()->registerMetaTypes(scriptEngine.data());
// connect this script engines printedMessage signal to the global ScriptEngines these various messages
connect(scriptEngine.data(), &ScriptEngine::printedMessage,
DependencyManager::get<ScriptEngines>().data(), &ScriptEngines::onPrintedMessage);
@ -6139,46 +6163,60 @@ bool Application::askToReplaceDomainContent(const QString& url) {
"and restoring content, visit the documentation page at: ", MAX_CHARACTERS_PER_LINE) +
"\nhttps://docs.highfidelity.com/create-and-explore/start-working-in-your-sandbox/restoring-sandbox-content";
bool agreeToReplaceContent = false; // assume false
agreeToReplaceContent = QMessageBox::Yes == OffscreenUi::question("Are you sure you want to replace this domain's content set?",
infoText, QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
ModalDialogListener* dig = OffscreenUi::asyncQuestion("Are you sure you want to replace this domain's content set?",
infoText, QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
if (agreeToReplaceContent) {
// Given confirmation, send request to domain server to replace content
qCDebug(interfaceapp) << "Attempting to replace domain content: " << url;
QByteArray urlData(url.toUtf8());
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) {
return node->getType() == NodeType::EntityServer && node->getActiveSocket();
}, [&urlData, limitedNodeList](const SharedNodePointer& octreeNode) {
auto octreeFilePacket = NLPacket::create(PacketType::OctreeFileReplacementFromUrl, urlData.size(), true);
octreeFilePacket->write(urlData);
limitedNodeList->sendPacket(std::move(octreeFilePacket), *octreeNode);
});
auto addressManager = DependencyManager::get<AddressManager>();
addressManager->handleLookupString(DOMAIN_SPAWNING_POINT);
QString newHomeAddress = addressManager->getHost() + DOMAIN_SPAWNING_POINT;
qCDebug(interfaceapp) << "Setting new home bookmark to: " << newHomeAddress;
DependencyManager::get<LocationBookmarks>()->setHomeLocationToAddress(newHomeAddress);
methodDetails = "SuccessfulRequestToReplaceContent";
} else {
methodDetails = "UserDeclinedToReplaceContent";
}
QObject::connect(dig, &ModalDialogListener::response, this, [=] (QVariant answer) {
QString details;
if (static_cast<QMessageBox::StandardButton>(answer.toInt()) == QMessageBox::Yes) {
// Given confirmation, send request to domain server to replace content
qCDebug(interfaceapp) << "Attempting to replace domain content: " << url;
QByteArray urlData(url.toUtf8());
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) {
return node->getType() == NodeType::EntityServer && node->getActiveSocket();
}, [&urlData, limitedNodeList](const SharedNodePointer& octreeNode) {
auto octreeFilePacket = NLPacket::create(PacketType::OctreeFileReplacementFromUrl, urlData.size(), true);
octreeFilePacket->write(urlData);
limitedNodeList->sendPacket(std::move(octreeFilePacket), *octreeNode);
});
auto addressManager = DependencyManager::get<AddressManager>();
addressManager->handleLookupString(DOMAIN_SPAWNING_POINT);
QString newHomeAddress = addressManager->getHost() + DOMAIN_SPAWNING_POINT;
qCDebug(interfaceapp) << "Setting new home bookmark to: " << newHomeAddress;
DependencyManager::get<LocationBookmarks>()->setHomeLocationToAddress(newHomeAddress);
details = "SuccessfulRequestToReplaceContent";
} else {
details = "UserDeclinedToReplaceContent";
}
QJsonObject messageProperties = {
{ "status", details },
{ "content_set_url", url }
};
UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties);
QObject::disconnect(dig, &ModalDialogListener::response, this, nullptr);
});
} else {
methodDetails = "ContentSetDidNotOriginateFromMarketplace";
QJsonObject messageProperties = {
{ "status", methodDetails },
{ "content_set_url", url }
};
UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties);
}
} else {
methodDetails = "UserDoesNotHavePermissionToReplaceContent";
static const QString warningMessage = simpleWordWrap("The domain owner must enable 'Replace Content' "
"permissions for you in this domain's server settings before you can continue.", MAX_CHARACTERS_PER_LINE);
OffscreenUi::warning("You do not have permissions to replace domain content", warningMessage,
OffscreenUi::asyncWarning("You do not have permissions to replace domain content", warningMessage,
QMessageBox::Ok, QMessageBox::Ok);
QJsonObject messageProperties = {
{ "status", methodDetails },
{ "content_set_url", url }
};
UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties);
}
QJsonObject messageProperties = {
{ "status", methodDetails },
{ "content_set_url", url }
};
UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties);
return true;
}
@ -6451,9 +6489,9 @@ void Application::addAssetToWorldAddEntity(QString filePath, QString mapping) {
properties.setShapeType(SHAPE_TYPE_SIMPLE_COMPOUND);
properties.setCollisionless(true); // Temporarily set so that doesn't collide with avatar.
properties.setVisible(false); // Temporarily set so that don't see at large unresized dimensions.
glm::vec3 positionOffset = getMyAvatar()->getOrientation() * (getMyAvatar()->getSensorToWorldScale() * glm::vec3(0.0f, 0.0f, -2.0f));
properties.setPosition(getMyAvatar()->getPosition() + positionOffset);
properties.setRotation(getMyAvatar()->getOrientation());
glm::vec3 positionOffset = getMyAvatar()->getWorldOrientation() * (getMyAvatar()->getSensorToWorldScale() * glm::vec3(0.0f, 0.0f, -2.0f));
properties.setPosition(getMyAvatar()->getWorldPosition() + positionOffset);
properties.setRotation(getMyAvatar()->getWorldOrientation());
properties.setGravity(glm::vec3(0.0f, 0.0f, 0.0f));
auto entityID = DependencyManager::get<EntityScriptingInterface>()->addEntity(properties);
@ -7037,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;
}
@ -7060,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;

View file

@ -70,9 +70,6 @@
#include "ui/overlays/Overlays.h"
#include "UndoStackScriptingInterface.h"
#include "raypick/RayPickManager.h"
#include "raypick/LaserPointerManager.h"
#include <procedural/ProceduralSkybox.h>
#include <model/Skybox.h>
#include <ModelScriptingInterface.h>
@ -161,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);
@ -231,8 +228,6 @@ public:
FileLogger* getLogger() const { return _logger; }
glm::vec2 getViewportDimensions() const;
NodeToJurisdictionMap& getEntityServerJurisdictions() { return _entityServerJurisdictions; }
float getRenderResolutionScale() const;
@ -286,9 +281,6 @@ public:
QUrl getAvatarOverrideUrl() { return _avatarOverrideUrl; }
bool getSaveAvatarOverrideUrl() { return _saveAvatarOverrideUrl; }
LaserPointerManager& getLaserPointerManager() { return _laserPointerManager; }
RayPickManager& getRayPickManager() { return _rayPickManager; }
signals:
void svoImportRequested(const QString& url);
@ -703,9 +695,6 @@ private:
bool _saveAvatarOverrideUrl { false };
QObject* _renderEventHandler{ nullptr };
RayPickManager _rayPickManager;
LaserPointerManager _laserPointerManager;
friend class RenderEventHandler;
std::atomic<bool> _pendingIdleEvent { true };

View file

@ -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);
}

View file

@ -529,7 +529,7 @@ void AvatarActionHold::lateAvatarUpdate(const AnimPose& prePhysicsRoomPose, cons
rigidBody->setWorldTransform(worldTrans);
bool positionSuccess;
ownerEntity->setPosition(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset(), positionSuccess, false);
ownerEntity->setWorldPosition(bulletToGLM(worldTrans.getOrigin()) + ObjectMotionState::getWorldOffset(), positionSuccess, false);
bool orientationSuccess;
ownerEntity->setOrientation(bulletToGLM(worldTrans.getRotation()), orientationSuccess, false);
ownerEntity->setWorldOrientation(bulletToGLM(worldTrans.getRotation()), orientationSuccess, false);
}

View file

@ -28,6 +28,7 @@
#include <shared/QtHelpers.h>
#include <AvatarData.h>
#include <PerfStat.h>
#include <PrioritySortUtil.h>
#include <RegisteredMetaTypes.h>
#include <Rig.h>
#include <SettingHandle.h>
@ -142,32 +143,39 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
PerformanceTimer perfTimer("otherAvatars");
auto avatarMap = getHashCopy();
QList<AvatarSharedPointer> 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>(_avatar)->getBoundingRadius(); }
uint64_t getTimestamp() const override { return std::static_pointer_cast<Avatar>(_avatar)->getLastRenderUpdateTime(); }
const AvatarSharedPointer& getAvatar() const { return _avatar; }
private:
AvatarSharedPointer _avatar;
};
ViewFrustum cameraView;
qApp->copyDisplayViewFrustum(cameraView);
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(cameraView,
AvatarData::_avatarSortCoefficientSize,
AvatarData::_avatarSortCoefficientCenter,
AvatarData::_avatarSortCoefficientAge);
std::priority_queue<AvatarPriority> sortedAvatars;
AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars,
[](AvatarSharedPointer avatar)->uint64_t{
return std::static_pointer_cast<Avatar>(avatar)->getLastRenderUpdateTime();
},
[](AvatarSharedPointer avatar)->float{
return std::static_pointer_cast<Avatar>(avatar)->getBoundingRadius();
},
[this](AvatarSharedPointer avatar)->bool{
const auto& castedAvatar = std::static_pointer_cast<Avatar>(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<Avatar>(*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<Avatar>(sortData.avatar);
const SortableAvatar& sortData = sortedAvatars.top();
const auto& avatar = std::static_pointer_cast<Avatar>(sortData.getAvatar());
bool ignoring = DependencyManager::get<NodeList>()->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<Avatar>(newSortData.avatar);
inView = newSortData.priority > OUT_OF_VIEW_THRESHOLD;
const SortableAvatar& newSortData = sortedAvatars.top();
const auto& newAvatar = std::static_pointer_cast<Avatar>(newSortData.getAvatar());
inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD;
if (inView && newAvatar->hasNewJointData()) {
numAVatarsNotUpdated++;
}
@ -441,7 +449,7 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents
static const int MAX_INJECTOR_COUNT = 3;
if (_collisionInjectors.size() < MAX_INJECTOR_COUNT) {
auto injector = AudioInjector::playSound(collisionSound, energyFactorOfFull, AVATAR_STRETCH_FACTOR,
myAvatar->getPosition());
myAvatar->getWorldPosition());
_collisionInjectors.emplace_back(injector);
}
myAvatar->collisionWithEntity(collision);

View file

@ -41,7 +41,7 @@ public:
void init();
std::shared_ptr<MyAvatar> getMyAvatar() { return _myAvatar; }
glm::vec3 getMyAvatarPosition() const { return _myAvatar->getPosition(); }
glm::vec3 getMyAvatarPosition() const { return _myAvatar->getWorldPosition(); }
// Null/Default-constructed QUuids will return MyAvatar
Q_INVOKABLE virtual ScriptAvatarData* getAvatar(QUuid avatarID) override { return new ScriptAvatar(getAvatarBySessionID(avatarID)); }

View file

@ -106,22 +106,22 @@ float AvatarMotionState::getObjectAngularDamping() const {
// virtual
glm::vec3 AvatarMotionState::getObjectPosition() const {
return _avatar->getPosition();
return _avatar->getWorldPosition();
}
// virtual
glm::quat AvatarMotionState::getObjectRotation() const {
return _avatar->getOrientation();
return _avatar->getWorldOrientation();
}
// virtual
glm::vec3 AvatarMotionState::getObjectLinearVelocity() const {
return _avatar->getVelocity();
return _avatar->getWorldVelocity();
}
// virtual
glm::vec3 AvatarMotionState::getObjectAngularVelocity() const {
return _avatar->getAngularVelocity();
return _avatar->getWorldAngularVelocity();
}
// virtual

View file

@ -196,8 +196,8 @@ MyAvatar::MyAvatar(QThread* thread) :
setDisplayName(dummyAvatar.getDisplayName());
}
setPosition(dummyAvatar.getPosition());
setOrientation(dummyAvatar.getOrientation());
setWorldPosition(dummyAvatar.getWorldPosition());
setWorldOrientation(dummyAvatar.getWorldOrientation());
if (!dummyAvatar.getAttachmentData().isEmpty()) {
setAttachmentData(dummyAvatar.getAttachmentData());
@ -250,11 +250,11 @@ void MyAvatar::registerMetaTypes(ScriptEnginePointer engine) {
}
void MyAvatar::setOrientationVar(const QVariant& newOrientationVar) {
Avatar::setOrientation(quatFromVariant(newOrientationVar));
Avatar::setWorldOrientation(quatFromVariant(newOrientationVar));
}
QVariant MyAvatar::getOrientationVar() const {
return quatToVariant(Avatar::getOrientation());
return quatToVariant(Avatar::getWorldOrientation());
}
glm::quat MyAvatar::getOrientationOutbound() const {
@ -276,7 +276,7 @@ void MyAvatar::simulateAttachments(float deltaTime) {
QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) {
CameraMode mode = qApp->getCamera().getMode();
_globalPosition = getPosition();
_globalPosition = getWorldPosition();
// This might not be right! Isn't the capsule local offset in avatar space, and don't we need to add the radius to the y as well? -HRS 5/26/17
_globalBoundingBoxDimensions.x = _characterController.getCapsuleRadius();
_globalBoundingBoxDimensions.y = _characterController.getCapsuleHalfHeight();
@ -284,11 +284,11 @@ QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropF
_globalBoundingBoxOffset = _characterController.getCapsuleLocalOffset();
if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT) {
// fake the avatar position that is sent up to the AvatarMixer
glm::vec3 oldPosition = getPosition();
setPosition(getSkeletonPosition());
glm::vec3 oldPosition = getWorldPosition();
setWorldPosition(getSkeletonPosition());
QByteArray array = AvatarData::toByteArrayStateful(dataDetail);
// copy the correct position back
setPosition(oldPosition);
setWorldPosition(oldPosition);
return array;
}
return AvatarData::toByteArrayStateful(dataDetail);
@ -321,15 +321,15 @@ void MyAvatar::centerBody() {
if (_characterController.getState() == CharacterController::State::Ground) {
// the avatar's physical aspect thinks it is standing on something
// therefore need to be careful to not "center" the body below the floor
float downStep = glm::dot(worldBodyPos - getPosition(), _worldUpDirection);
float downStep = glm::dot(worldBodyPos - getWorldPosition(), _worldUpDirection);
if (downStep < -0.5f * _characterController.getCapsuleHalfHeight() + _characterController.getCapsuleRadius()) {
worldBodyPos -= downStep * _worldUpDirection;
}
}
// this will become our new position.
setPosition(worldBodyPos);
setOrientation(worldBodyRot);
setWorldPosition(worldBodyPos);
setWorldOrientation(worldBodyRot);
// reset the body in sensor space
_bodySensorMatrix = newBodySensorMatrix;
@ -372,8 +372,8 @@ void MyAvatar::reset(bool andRecenter, bool andReload, bool andHead) {
auto worldBodyRot = glmExtractRotation(worldBodyMatrix);
// this will become our new position.
setPosition(worldBodyPos);
setOrientation(worldBodyRot);
setWorldPosition(worldBodyPos);
setWorldOrientation(worldBodyRot);
// now sample the new hmd orientation AFTER sensor reset, which should be identity.
glm::mat4 identity;
@ -410,8 +410,8 @@ void MyAvatar::update(float deltaTime) {
#endif
if (_goToPending) {
setPosition(_goToPosition);
setOrientation(_goToOrientation);
setWorldPosition(_goToPosition);
setWorldOrientation(_goToOrientation);
_headControllerFacingMovingAverage = _headControllerFacing; // reset moving average
_goToPending = false;
// updateFromHMDSensorMatrix (called from paintGL) expects that the sensorToWorldMatrix is updated for any position changes
@ -444,7 +444,7 @@ void MyAvatar::update(float deltaTime) {
// This might not be right! Isn't the capsule local offset in avatar space? -HRS 5/26/17
halfBoundingBoxDimensions += _characterController.getCapsuleLocalOffset();
QMetaObject::invokeMethod(audio.data(), "setAvatarBoundingBoxParameters",
Q_ARG(glm::vec3, (getPosition() - halfBoundingBoxDimensions)),
Q_ARG(glm::vec3, (getWorldPosition() - halfBoundingBoxDimensions)),
Q_ARG(glm::vec3, (halfBoundingBoxDimensions*2.0f)));
if (getIdentityDataChanged()) {
@ -537,7 +537,7 @@ void MyAvatar::simulate(float deltaTime) {
if (!_skeletonModel->hasSkeleton()) {
// All the simulation that can be done has been done
getHead()->setPosition(getPosition()); // so audio-position isn't 0,0,0
getHead()->setPosition(getWorldPosition()); // so audio-position isn't 0,0,0
return;
}
@ -555,7 +555,7 @@ void MyAvatar::simulate(float deltaTime) {
Head* head = getHead();
glm::vec3 headPosition;
if (!_skeletonModel->getHeadPosition(headPosition)) {
headPosition = getPosition();
headPosition = getWorldPosition();
}
head->setPosition(headPosition);
head->setScale(getModelScale());
@ -666,7 +666,7 @@ void MyAvatar::updateSensorToWorldMatrix() {
// update the sensor mat so that the body position will end up in the desired
// position when driven from the head.
float sensorToWorldScale = getEyeHeight() / getUserEyeHeight();
glm::mat4 desiredMat = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), getOrientation(), getPosition());
glm::mat4 desiredMat = createMatFromScaleQuatAndPos(glm::vec3(sensorToWorldScale), getWorldOrientation(), getWorldPosition());
_sensorToWorldMatrix = desiredMat * glm::inverse(_bodySensorMatrix);
lateUpdatePalms();
@ -783,8 +783,8 @@ controller::Pose MyAvatar::getRightHandTipPose() const {
}
glm::vec3 MyAvatar::worldToJointPoint(const glm::vec3& position, const int jointIndex) const {
glm::vec3 jointPos = getPosition();//default value if no or invalid joint specified
glm::quat jointRot = getRotation();//default value if no or invalid joint specified
glm::vec3 jointPos = getWorldPosition();//default value if no or invalid joint specified
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if (jointIndex != -1) {
if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) {
_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot);
@ -799,7 +799,7 @@ glm::vec3 MyAvatar::worldToJointPoint(const glm::vec3& position, const int joint
}
glm::vec3 MyAvatar::worldToJointDirection(const glm::vec3& worldDir, const int jointIndex) const {
glm::quat jointRot = getRotation();//default value if no or invalid joint specified
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
@ -809,7 +809,7 @@ glm::vec3 MyAvatar::worldToJointDirection(const glm::vec3& worldDir, const int j
}
glm::quat MyAvatar::worldToJointRotation(const glm::quat& worldRot, const int jointIndex) const {
glm::quat jointRot = getRotation();//default value if no or invalid joint specified
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
@ -818,8 +818,8 @@ glm::quat MyAvatar::worldToJointRotation(const glm::quat& worldRot, const int jo
}
glm::vec3 MyAvatar::jointToWorldPoint(const glm::vec3& jointSpacePos, const int jointIndex) const {
glm::vec3 jointPos = getPosition();//default value if no or invalid joint specified
glm::quat jointRot = getRotation();//default value if no or invalid joint specified
glm::vec3 jointPos = getWorldPosition();//default value if no or invalid joint specified
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if (jointIndex != -1) {
if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) {
@ -836,7 +836,7 @@ glm::vec3 MyAvatar::jointToWorldPoint(const glm::vec3& jointSpacePos, const int
}
glm::vec3 MyAvatar::jointToWorldDirection(const glm::vec3& jointSpaceDir, const int jointIndex) const {
glm::quat jointRot = getRotation();//default value if no or invalid joint specified
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
@ -845,7 +845,7 @@ glm::vec3 MyAvatar::jointToWorldDirection(const glm::vec3& jointSpaceDir, const
}
glm::quat MyAvatar::jointToWorldRotation(const glm::quat& jointSpaceRot, const int jointIndex) const {
glm::quat jointRot = getRotation();//default value if no or invalid joint specified
glm::quat jointRot = getWorldOrientation();//default value if no or invalid joint specified
if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) {
qWarning() << "Invalid joint index specified: " << jointIndex;
}
@ -1226,7 +1226,7 @@ void MyAvatar::updateLookAtTargetAvatar() {
float angleTo = coneSphereAngle(getHead()->getEyePosition(), lookForward, avatar->getHead()->getEyePosition(), radius);
if (angleTo < (smallestAngleTo * (isCurrentTarget ? KEEP_LOOKING_AT_CURRENT_ANGLE_FACTOR : 1.0f))) {
_lookAtTargetAvatar = avatarPointer;
_targetAvatarPosition = avatarPointer->getPosition();
_targetAvatarPosition = avatarPointer->getWorldPosition();
}
if (_lookAtSnappingEnabled && avatar->getLookAtSnappingEnabled() && isLookingAtMe(avatar)) {
@ -1297,7 +1297,7 @@ eyeContactTarget MyAvatar::getEyeContactTarget() {
}
glm::vec3 MyAvatar::getDefaultEyePosition() const {
return getPosition() + getOrientation() * Quaternions::Y_180 * _skeletonModel->getDefaultEyeModelPosition();
return getWorldPosition() + getWorldOrientation() * Quaternions::Y_180 * _skeletonModel->getDefaultEyeModelPosition();
}
const float SCRIPT_PRIORITY = 1.0f + 1.0f;
@ -1457,9 +1457,9 @@ glm::vec3 MyAvatar::getSkeletonPosition() const {
// The avatar is rotated PI about the yAxis, so we have to correct for it
// to get the skeleton offset contribution in the world-frame.
const glm::quat FLIP = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
return getPosition() + getOrientation() * FLIP * _skeletonOffset;
return getWorldPosition() + getWorldOrientation() * FLIP * _skeletonOffset;
}
return Avatar::getPosition();
return Avatar::getWorldPosition();
}
void MyAvatar::rebuildCollisionShape() {
@ -1505,7 +1505,7 @@ controller::Pose MyAvatar::getControllerPoseInWorldFrame(controller::Action acti
controller::Pose MyAvatar::getControllerPoseInAvatarFrame(controller::Action action) const {
auto pose = getControllerPoseInWorldFrame(action);
if (pose.valid) {
glm::mat4 invAvatarMatrix = glm::inverse(createMatFromQuatAndPos(getOrientation(), getPosition()));
glm::mat4 invAvatarMatrix = glm::inverse(createMatFromQuatAndPos(getWorldOrientation(), getWorldPosition()));
return pose.transform(invAvatarMatrix);
} else {
return controller::Pose(); // invalid pose
@ -1524,7 +1524,7 @@ void MyAvatar::updateMotors() {
// we decompose camera's rotation and store the twist part in motorRotation
// however, we need to perform the decomposition in the avatar-frame
// using the local UP axis and then transform back into world-frame
glm::quat orientation = getOrientation();
glm::quat orientation = getWorldOrientation();
glm::quat headOrientation = glm::inverse(orientation) * getMyHead()->getHeadOrientation(); // avatar-frame
glm::quat liftRotation;
swingTwistDecomposition(headOrientation, Vectors::UNIT_Y, liftRotation, motorRotation);
@ -1544,7 +1544,7 @@ void MyAvatar::updateMotors() {
if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) {
motorRotation = getMyHead()->getHeadOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y);
} else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) {
motorRotation = getOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y);
motorRotation = getWorldOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y);
} else {
// world-frame
motorRotation = glm::quat();
@ -1572,7 +1572,7 @@ void MyAvatar::prepareForPhysicsSimulation() {
_characterController.setParentVelocity(parentVelocity);
_characterController.setScaleFactor(getSensorToWorldScale());
_characterController.setPositionAndOrientation(getPosition(), getOrientation());
_characterController.setPositionAndOrientation(getWorldPosition(), getWorldOrientation());
auto headPose = getControllerPoseInAvatarFrame(controller::Action::HEAD);
if (headPose.isValid()) {
_follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput());
@ -1606,20 +1606,20 @@ void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) {
if (_characterController.isEnabledAndReady()) {
_characterController.getPositionAndOrientation(position, orientation);
} else {
position = getPosition();
orientation = getOrientation();
position = getWorldPosition();
orientation = getWorldOrientation();
}
nextAttitude(position, orientation);
_bodySensorMatrix = _follow.postPhysicsUpdate(*this, _bodySensorMatrix);
if (_characterController.isEnabledAndReady()) {
setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity());
setWorldVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity());
if (_characterController.isStuck()) {
_physicsSafetyPending = true;
_goToPosition = getPosition();
_goToPosition = getWorldPosition();
}
} else {
setVelocity(getVelocity() + _characterController.getFollowVelocity());
setWorldVelocity(getWorldVelocity() + _characterController.getFollowVelocity());
}
}
@ -1844,15 +1844,15 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) {
}
}
DebugDraw::getInstance().updateMyAvatarPos(getPosition());
DebugDraw::getInstance().updateMyAvatarRot(getOrientation());
DebugDraw::getInstance().updateMyAvatarPos(getWorldPosition());
DebugDraw::getInstance().updateMyAvatarRot(getWorldOrientation());
AnimPose postUpdateRoomPose(_sensorToWorldMatrix);
updateHoldActions(_prePhysicsRoomPose, postUpdateRoomPose);
if (_enableDebugDrawDetailedCollision) {
AnimPose rigToWorldPose(glm::vec3(1.0f), getRotation() * Quaternions::Y_180, getPosition());
AnimPose rigToWorldPose(glm::vec3(1.0f), getWorldOrientation() * Quaternions::Y_180, getWorldPosition());
const int NUM_DEBUG_COLORS = 8;
const glm::vec4 DEBUG_COLORS[NUM_DEBUG_COLORS] = {
glm::vec4(1.0f, 1.0f, 1.0f, 1.0f),
@ -1959,7 +1959,7 @@ void MyAvatar::updateOrientation(float deltaTime) {
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(getVelocity());
float speed = glm::length(getWorldVelocity());
if (speed >= MIN_CONTROL_SPEED) {
// Feather turn when stopping moving.
float speedFactor;
@ -1970,7 +1970,7 @@ void MyAvatar::updateOrientation(float deltaTime) {
speedFactor = glm::min(speed / _lastDrivenSpeed, 1.0f);
}
float direction = glm::dot(getVelocity(), getRotation() * Vectors::UNIT_NEG_Z) > 0.0f ? 1.0f : -1.0f;
float direction = glm::dot(getWorldVelocity(), getWorldOrientation() * Vectors::UNIT_NEG_Z) > 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;
@ -1983,12 +1983,12 @@ void MyAvatar::updateOrientation(float deltaTime) {
// update body orientation by movement inputs
glm::quat initialOrientation = getOrientationOutbound();
setOrientation(getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f))));
setWorldOrientation(getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f))));
if (snapTurn) {
// Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position.
_smoothOrientationInitial = initialOrientation;
_smoothOrientationTarget = getOrientation();
_smoothOrientationTarget = getWorldOrientation();
_smoothOrientationTimer = 0.0f;
}
@ -2081,7 +2081,7 @@ void MyAvatar::updatePosition(float deltaTime) {
updateActionMotor(deltaTime);
}
vec3 velocity = getVelocity();
vec3 velocity = getWorldVelocity();
float sensorToWorldScale = getSensorToWorldScale();
float sensorToWorldScale2 = sensorToWorldScale * sensorToWorldScale;
const float MOVING_SPEED_THRESHOLD_SQUARED = 0.0001f; // 0.01 m/s
@ -2103,9 +2103,9 @@ void MyAvatar::updatePosition(float deltaTime) {
if (_moving) {
// scan for walkability
glm::vec3 position = getPosition();
glm::vec3 position = getWorldPosition();
MyCharacterController::RayShotgunResult result;
glm::vec3 step = deltaTime * (getRotation() * _actionMotorVelocity);
glm::vec3 step = deltaTime * (getWorldOrientation() * _actionMotorVelocity);
_characterController.testRayShotgun(position, step, result);
_characterController.setStepUpEnabled(result.walkable);
}
@ -2289,7 +2289,7 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition,
_goToPending = true;
_goToPosition = newPosition;
_goToOrientation = getOrientation();
_goToOrientation = getWorldOrientation();
if (hasOrientation) {
qCDebug(interfaceapp).nospace() << "MyAvatar goToLocation - new orientation is "
<< newOrientation.x << ", " << newOrientation.y << ", " << newOrientation.z << ", " << newOrientation.w;
@ -2358,7 +2358,7 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette
return false; // no entity tree
}
// More utilities.
const auto offset = getOrientation() *_characterController.getCapsuleLocalOffset();
const auto offset = getWorldOrientation() *_characterController.getCapsuleLocalOffset();
const auto capsuleCenter = positionIn + offset;
const auto up = _worldUpDirection, down = -up;
glm::vec3 upperIntersection, upperNormal, lowerIntersection, lowerNormal;
@ -2859,7 +2859,7 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, co
}
float MyAvatar::getAccelerationEnergy() {
glm::vec3 velocity = getVelocity();
glm::vec3 velocity = getWorldVelocity();
int changeInVelocity = abs(velocity.length() - priorVelocity.length());
float changeInEnergy = priorVelocity.length() * changeInVelocity * AVATAR_MOVEMENT_ENERGY_CONSTANT;
priorVelocity = velocity;
@ -2880,7 +2880,7 @@ float MyAvatar::getAudioEnergy() {
}
bool MyAvatar::didTeleport() {
glm::vec3 pos = getPosition();
glm::vec3 pos = getWorldPosition();
glm::vec3 changeInPosition = pos - lastPosition;
lastPosition = pos;
return (changeInPosition.length() > MAX_AVATAR_MOVEMENT_PER_FRAME);
@ -2924,7 +2924,7 @@ glm::mat4 MyAvatar::computeCameraRelativeHandControllerMatrix(const glm::mat4& c
glm::mat4 controllerWorldMatrix = getSensorToWorldMatrix() * delta * controllerSensorMatrix;
// transform controller into avatar space
glm::mat4 avatarMatrix = createMatFromQuatAndPos(getOrientation(), getPosition());
glm::mat4 avatarMatrix = createMatFromQuatAndPos(getWorldOrientation(), getWorldPosition());
return glm::inverse(avatarMatrix) * controllerWorldMatrix;
}
@ -3128,7 +3128,7 @@ bool MyAvatar::pinJoint(int index, const glm::vec3& position, const glm::quat& o
}
slamPosition(position);
setOrientation(orientation);
setWorldOrientation(orientation);
_skeletonModel->getRig().setMaxHipsOffsetLength(0.05f);

View file

@ -114,7 +114,7 @@ class MyAvatar : public Avatar {
// FIXME: `glm::vec3 position` is not accessible from QML, so this exposes position in a QML-native type
Q_PROPERTY(QVector3D qmlPosition READ getQmlPosition)
QVector3D getQmlPosition() { auto p = getPosition(); return QVector3D(p.x, p.y, p.z); }
QVector3D getQmlPosition() { auto p = getWorldPosition(); return QVector3D(p.x, p.y, p.z); }
Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally)
Q_PROPERTY(glm::vec3 motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity)

View file

@ -64,8 +64,8 @@ void MyCharacterController::updateShapeIfNecessary() {
_rigidBody->setSleepingThresholds(0.0f, 0.0f);
_rigidBody->setAngularFactor(0.0f);
_rigidBody->setWorldTransform(btTransform(glmToBullet(_avatar->getOrientation()),
glmToBullet(_avatar->getPosition())));
_rigidBody->setWorldTransform(btTransform(glmToBullet(_avatar->getWorldOrientation()),
glmToBullet(_avatar->getWorldPosition())));
_rigidBody->setDamping(0.0f, 0.0f);
if (_state == State::Hover) {
_rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f));

View file

@ -39,7 +39,7 @@ glm::quat MyHead::getHeadOrientation() const {
return headPose.rotation * Quaternions::Y_180;
}
return myAvatar->getOrientation() * glm::quat(glm::radians(glm::vec3(_basePitch, 0.0f, 0.0f)));
return myAvatar->getWorldOrientation() * glm::quat(glm::radians(glm::vec3(_basePitch, 0.0f, 0.0f)));
}
void MyHead::simulate(float deltaTime) {

View file

@ -128,6 +128,11 @@ void QmlCommerce::reset() {
wallet->reset();
}
void QmlCommerce::resetLocalWalletOnly() {
auto wallet = DependencyManager::get<Wallet>();
wallet->reset();
}
void QmlCommerce::account() {
auto ledger = DependencyManager::get<Ledger>();
ledger->account();

View file

@ -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);

View file

@ -27,11 +27,11 @@
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/evp.h>
#include <openssl/aes.h>
#include <openssl/ecdsa.h>
// I know, right? But per https://www.openssl.org/docs/faq.html
// this avoids OPENSSL_Uplink(00007FF847238000,08): no OPENSSL_Applink
@ -78,18 +78,19 @@ int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) {
}
}
RSA* readKeys(const char* filename) {
EC_KEY* readKeys(const char* filename) {
FILE* fp;
RSA* key = NULL;
EC_KEY *key = NULL;
if ((fp = fopen(filename, "rt"))) {
// file opened successfully
qCDebug(commerce) << "opened key file" << filename;
if ((key = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL))) {
if ((key = PEM_read_EC_PUBKEY(fp, NULL, NULL, NULL))) {
// now read private key
qCDebug(commerce) << "read public key";
if ((key = PEM_read_RSAPrivateKey(fp, &key, passwordCallback, NULL))) {
if ((key = PEM_read_ECPrivateKey(fp, &key, passwordCallback, NULL))) {
qCDebug(commerce) << "read private key";
fclose(fp);
return key;
@ -137,18 +138,18 @@ bool Wallet::writeBackupInstructions() {
return retval;
}
bool writeKeys(const char* filename, RSA* keys) {
bool writeKeys(const char* filename, EC_KEY* keys) {
FILE* fp;
bool retval = false;
if ((fp = fopen(filename, "wt"))) {
if (!PEM_write_RSAPublicKey(fp, keys)) {
if (!PEM_write_EC_PUBKEY(fp, keys)) {
fclose(fp);
qCDebug(commerce) << "failed to write public key";
QFile(QString(filename)).remove();
return retval;
}
if (!PEM_write_RSAPrivateKey(fp, keys, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) {
if (!PEM_write_ECPrivateKey(fp, keys, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) {
fclose(fp);
qCDebug(commerce) << "failed to write private key";
QFile(QString(filename)).remove();
@ -164,50 +165,29 @@ bool writeKeys(const char* filename, RSA* keys) {
return retval;
}
// copied (without emits for various signals) from libraries/networking/src/RSAKeypairGenerator.cpp.
// We will have a different implementation in practice, but this gives us a start for now
//
// TODO: we don't really use the private keys returned - we can see how this evolves, but probably
// we should just return a list of public keys?
// or perhaps return the RSA* instead?
QPair<QByteArray*, QByteArray*> generateRSAKeypair() {
QPair<QByteArray*, QByteArray*> generateECKeypair() {
RSA* keyPair = RSA_new();
BIGNUM* exponent = BN_new();
EC_KEY* keyPair = EC_KEY_new_by_curve_name(NID_secp256k1);
QPair<QByteArray*, QByteArray*> retval;
const unsigned long RSA_KEY_EXPONENT = 65537;
BN_set_word(exponent, RSA_KEY_EXPONENT);
// seed the random number generator before we call RSA_generate_key_ex
srand(time(NULL));
const int RSA_KEY_BITS = 2048;
if (!RSA_generate_key_ex(keyPair, RSA_KEY_BITS, exponent, NULL)) {
qCDebug(commerce) << "Error generating 2048-bit RSA Keypair -" << ERR_get_error();
// we're going to bust out of here but first we cleanup the BIGNUM
BN_free(exponent);
EC_KEY_set_asn1_flag(keyPair, OPENSSL_EC_NAMED_CURVE);
if (!EC_KEY_generate_key(keyPair)) {
qCDebug(commerce) << "Error generating EC Keypair -" << ERR_get_error();
return retval;
}
// we don't need the BIGNUM anymore so clean that up
BN_free(exponent);
// grab the public key and private key from the file
unsigned char* publicKeyDER = NULL;
int publicKeyLength = i2d_RSAPublicKey(keyPair, &publicKeyDER);
int publicKeyLength = i2d_EC_PUBKEY(keyPair, &publicKeyDER);
unsigned char* privateKeyDER = NULL;
int privateKeyLength = i2d_RSAPrivateKey(keyPair, &privateKeyDER);
int privateKeyLength = i2d_ECPrivateKey(keyPair, &privateKeyDER);
if (publicKeyLength <= 0 || privateKeyLength <= 0) {
qCDebug(commerce) << "Error getting DER public or private key from RSA struct -" << ERR_get_error();
qCDebug(commerce) << "Error getting DER public or private key from EC struct -" << ERR_get_error();
// cleanup the RSA struct
RSA_free(keyPair);
// cleanup the EC struct
EC_KEY_free(keyPair);
// cleanup the public and private key DER data, if required
if (publicKeyLength > 0) {
@ -227,13 +207,13 @@ QPair<QByteArray*, QByteArray*> generateRSAKeypair() {
return retval;
}
RSA_free(keyPair);
EC_KEY_free(keyPair);
// prepare the return values. TODO: Fix this - we probably don't really even want the
// private key at all (better to read it when we need it?). Or maybe we do, when we have
// multiple keys?
retval.first = new QByteArray(reinterpret_cast<char*>(publicKeyDER), publicKeyLength ),
retval.second = new QByteArray(reinterpret_cast<char*>(privateKeyDER), privateKeyLength );
retval.first = new QByteArray(reinterpret_cast<char*>(publicKeyDER), publicKeyLength);
retval.second = new QByteArray(reinterpret_cast<char*>(privateKeyDER), privateKeyLength);
// cleanup the publicKeyDER and publicKeyDER data
OPENSSL_free(publicKeyDER);
@ -245,18 +225,18 @@ QPair<QByteArray*, QByteArray*> generateRSAKeypair() {
// the public key can just go into a byte array
QByteArray readPublicKey(const char* filename) {
FILE* fp;
RSA* key = NULL;
EC_KEY* key = NULL;
if ((fp = fopen(filename, "r"))) {
// file opened successfully
qCDebug(commerce) << "opened key file" << filename;
if ((key = PEM_read_RSAPublicKey(fp, NULL, NULL, NULL))) {
if ((key = PEM_read_EC_PUBKEY(fp, NULL, NULL, NULL))) {
// file read successfully
unsigned char* publicKeyDER = NULL;
int publicKeyLength = i2d_RSAPublicKey(key, &publicKeyDER);
int publicKeyLength = i2d_EC_PUBKEY(key, &publicKeyDER);
// TODO: check for 0 length?
// cleanup
RSA_free(key);
EC_KEY_free(key);
fclose(fp);
qCDebug(commerce) << "parsed public key file successfully";
@ -274,15 +254,15 @@ QByteArray readPublicKey(const char* filename) {
return QByteArray();
}
// the private key should be read/copied into heap memory. For now, we need the RSA struct
// so I'll return that. Note we need to RSA_free(key) later!!!
RSA* readPrivateKey(const char* filename) {
// the private key should be read/copied into heap memory. For now, we need the EC_KEY struct
// so I'll return that.
EC_KEY* readPrivateKey(const char* filename) {
FILE* fp;
RSA* key = NULL;
EC_KEY* key = NULL;
if ((fp = fopen(filename, "r"))) {
// file opened successfully
qCDebug(commerce) << "opened key file" << filename;
if ((key = PEM_read_RSAPrivateKey(fp, &key, passwordCallback, NULL))) {
if ((key = PEM_read_ECPrivateKey(fp, &key, passwordCallback, NULL))) {
qCDebug(commerce) << "parsed private key file successfully";
} else {
@ -341,6 +321,16 @@ Wallet::Wallet() {
auto accountManager = DependencyManager::get<AccountManager>();
connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() {
getWalletStatus();
_publicKeys.clear();
if (_securityImage) {
delete _securityImage;
}
_securityImage = nullptr;
// tell the provider we got nothing
updateImageProvider();
_passphrase->clear();
});
}
@ -509,7 +499,7 @@ bool Wallet::walletIsAuthenticatedWithPassphrase() {
if (publicKey.size() > 0) {
if (auto key = readPrivateKey(keyFilePath().toStdString().c_str())) {
RSA_free(key);
EC_KEY_free(key);
// be sure to add the public key so we don't do this over and over
_publicKeys.push_back(publicKey.toBase64());
@ -525,7 +515,7 @@ bool Wallet::generateKeyPair() {
initialize();
qCInfo(commerce) << "Generating keypair.";
auto keyPair = generateRSAKeypair();
auto keyPair = generateECKeypair();
writeBackupInstructions();
@ -557,25 +547,25 @@ QStringList Wallet::listPublicKeys() {
// the horror of code pages and so on (changing the bytes) by just returning a base64
// encoded string representing the signature (suitable for http, etc...)
QString Wallet::signWithKey(const QByteArray& text, const QString& key) {
qCInfo(commerce) << "Signing text.";
RSA* rsaPrivateKey = NULL;
if ((rsaPrivateKey = readPrivateKey(keyFilePath().toStdString().c_str()))) {
QByteArray signature(RSA_size(rsaPrivateKey), 0);
qCInfo(commerce) << "Signing text" << text << "with key" << key;
EC_KEY* ecPrivateKey = NULL;
if ((ecPrivateKey = readPrivateKey(keyFilePath().toStdString().c_str()))) {
unsigned char* sig = new unsigned char[ECDSA_size(ecPrivateKey)];
unsigned int signatureBytes = 0;
QByteArray hashedPlaintext = QCryptographicHash::hash(text, QCryptographicHash::Sha256);
int encryptReturn = RSA_sign(NID_sha256,
reinterpret_cast<const unsigned char*>(hashedPlaintext.constData()),
hashedPlaintext.size(),
reinterpret_cast<unsigned char*>(signature.data()),
&signatureBytes,
rsaPrivateKey);
// free the private key RSA struct now that we are done with it
RSA_free(rsaPrivateKey);
int retrn = ECDSA_sign(0,
reinterpret_cast<const unsigned char*>(hashedPlaintext.constData()),
hashedPlaintext.size(),
sig,
&signatureBytes, ecPrivateKey);
if (encryptReturn != -1) {
EC_KEY_free(ecPrivateKey);
QByteArray signature(reinterpret_cast<const char*>(sig), signatureBytes);
if (retrn != -1) {
return signature.toBase64();
}
}
@ -674,7 +664,7 @@ void Wallet::reset() {
keyFile.remove();
}
bool Wallet::writeWallet(const QString& newPassphrase) {
RSA* keys = readKeys(keyFilePath().toStdString().c_str());
EC_KEY* keys = readKeys(keyFilePath().toStdString().c_str());
if (keys) {
// we read successfully, so now write to a new temp file
QString tempFileName = QString("%1.%2").arg(keyFilePath(), QString("temp"));
@ -720,82 +710,86 @@ bool Wallet::changePassphrase(const QString& newPassphrase) {
void Wallet::handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
auto nodeList = DependencyManager::get<NodeList>();
// With EC keys, we receive a nonce from the metaverse server, which is signed
// here with the private key and returned. Verification is done at server.
bool challengeOriginatedFromClient = packet->getType() == PacketType::ChallengeOwnershipRequest;
unsigned char decryptedText[64];
int status;
int certIDByteArraySize;
int encryptedTextByteArraySize;
int textByteArraySize;
int challengingNodeUUIDByteArraySize;
packet->readPrimitive(&certIDByteArraySize);
packet->readPrimitive(&encryptedTextByteArraySize);
packet->readPrimitive(&textByteArraySize); // returns a cast char*, size
if (challengeOriginatedFromClient) {
packet->readPrimitive(&challengingNodeUUIDByteArraySize);
}
// "encryptedText" is now a series of random bytes, a nonce
QByteArray certID = packet->read(certIDByteArraySize);
QByteArray encryptedText = packet->read(encryptedTextByteArraySize);
QByteArray text = packet->read(textByteArraySize);
QByteArray challengingNodeUUID;
if (challengeOriginatedFromClient) {
challengingNodeUUID = packet->read(challengingNodeUUIDByteArraySize);
}
RSA* rsa = readKeys(keyFilePath().toStdString().c_str());
int decryptionStatus = -1;
EC_KEY* ec = readKeys(keyFilePath().toStdString().c_str());
QString sig;
if (rsa) {
if (ec) {
ERR_clear_error();
decryptionStatus = RSA_private_decrypt(encryptedTextByteArraySize,
reinterpret_cast<const unsigned char*>(encryptedText.constData()),
decryptedText,
rsa,
RSA_PKCS1_OAEP_PADDING);
RSA_free(rsa);
sig = signWithKey(text, ""); // base64 signature, QByteArray cast (on return) to QString FIXME should pass ec as string so we can tell which key to sign with
status = 1;
} else {
qCDebug(commerce) << "During entity ownership challenge, creating the RSA object failed.";
qCDebug(commerce) << "During entity ownership challenge, creating the EC-signed nonce failed.";
status = -1;
}
QByteArray decryptedTextByteArray;
if (decryptionStatus > -1) {
decryptedTextByteArray = QByteArray(reinterpret_cast<const char*>(decryptedText), decryptionStatus);
EC_KEY_free(ec);
QByteArray ba = sig.toLocal8Bit();
const char *sigChar = ba.data();
QByteArray textByteArray;
if (status > -1) {
textByteArray = QByteArray(sigChar, (int) strlen(sigChar));
}
int decryptedTextByteArraySize = decryptedTextByteArray.size();
textByteArraySize = textByteArray.size();
int certIDSize = certID.size();
// setup the packet
if (challengeOriginatedFromClient) {
auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
certIDSize + decryptedTextByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int),
auto textPacket = NLPacket::create(PacketType::ChallengeOwnershipReply,
certIDSize + textByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int),
true);
decryptedTextPacket->writePrimitive(certIDSize);
decryptedTextPacket->writePrimitive(decryptedTextByteArraySize);
decryptedTextPacket->writePrimitive(challengingNodeUUIDByteArraySize);
decryptedTextPacket->write(certID);
decryptedTextPacket->write(decryptedTextByteArray);
decryptedTextPacket->write(challengingNodeUUID);
textPacket->writePrimitive(certIDSize);
textPacket->writePrimitive(textByteArraySize);
textPacket->writePrimitive(challengingNodeUUIDByteArraySize);
textPacket->write(certID);
textPacket->write(textByteArray);
textPacket->write(challengingNodeUUID);
qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID;
qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing signed text" << textByteArray << "for CertID" << certID;
nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode);
nodeList->sendPacket(std::move(textPacket), *sendingNode);
} else {
auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + decryptedTextByteArraySize + 2 * sizeof(int), true);
auto textPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + textByteArraySize + 2 * sizeof(int), true);
decryptedTextPacket->writePrimitive(certIDSize);
decryptedTextPacket->writePrimitive(decryptedTextByteArraySize);
decryptedTextPacket->write(certID);
decryptedTextPacket->write(decryptedTextByteArray);
textPacket->writePrimitive(certIDSize);
textPacket->writePrimitive(textByteArraySize);
textPacket->write(certID);
textPacket->write(textByteArray);
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID;
qCDebug(commerce) << "Sending ChallengeOwnership Packet containing signed text" << textByteArray << "for CertID" << certID;
nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode);
nodeList->sendPacket(std::move(textPacket), *sendingNode);
}
if (decryptionStatus == -1) {
qCDebug(commerce) << "During entity ownership challenge, decrypting the encrypted text failed.";
if (status == -1) {
qCDebug(commerce) << "During entity ownership challenge, signing the text failed.";
long error = ERR_get_error();
if (error != 0) {
const char* error_str = ERR_error_string(error, NULL);
qCWarning(entities) << "RSA error:" << error_str;
qCWarning(entities) << "EC error:" << error_str;
}
}
}

View file

@ -12,7 +12,7 @@
#include "avatar/AvatarManager.h"
JointRayPick::JointRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const RayPickFilter& filter, const float maxDistance, const bool enabled) :
JointRayPick::JointRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const PickFilter& filter, float maxDistance, bool enabled) :
RayPick(filter, maxDistance, enabled),
_jointName(jointName),
_posOffset(posOffset),
@ -20,7 +20,7 @@ JointRayPick::JointRayPick(const std::string& jointName, const glm::vec3& posOff
{
}
const PickRay JointRayPick::getPickRay(bool& valid) const {
PickRay JointRayPick::getMathematicalPick() const {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
int jointIndex = myAvatar->getJointIndex(QString::fromStdString(_jointName));
bool useAvatarHead = _jointName == "Avatar";
@ -28,8 +28,8 @@ const PickRay JointRayPick::getPickRay(bool& valid) const {
if (jointIndex != INVALID_JOINT || useAvatarHead) {
glm::vec3 jointPos = useAvatarHead ? myAvatar->getHeadPosition() : myAvatar->getAbsoluteJointTranslationInObjectFrame(jointIndex);
glm::quat jointRot = useAvatarHead ? myAvatar->getHeadOrientation() : myAvatar->getAbsoluteJointRotationInObjectFrame(jointIndex);
glm::vec3 avatarPos = myAvatar->getPosition();
glm::quat avatarRot = myAvatar->getOrientation();
glm::vec3 avatarPos = myAvatar->getWorldPosition();
glm::quat avatarRot = myAvatar->getWorldOrientation();
glm::vec3 pos = useAvatarHead ? jointPos : avatarPos + (avatarRot * jointPos);
glm::quat rot = useAvatarHead ? jointRot * glm::angleAxis(-PI / 2.0f, Vectors::RIGHT) : avatarRot * jointRot;
@ -38,10 +38,8 @@ const PickRay JointRayPick::getPickRay(bool& valid) const {
pos = pos + (rot * (myAvatar->getSensorToWorldScale() * _posOffset));
glm::vec3 dir = rot * glm::normalize(_dirOffset);
valid = true;
return PickRay(pos, dir);
}
valid = false;
return PickRay();
}

View file

@ -11,14 +11,17 @@
#ifndef hifi_JointRayPick_h
#define hifi_JointRayPick_h
#include <pointers/rays/RayPick.h>
#include "RayPick.h"
class JointRayPick : public RayPick {
public:
JointRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const RayPickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false);
JointRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false);
const PickRay getPickRay(bool& valid) const override;
PickRay getMathematicalPick() const override;
bool isLeftHand() const override { return (_jointName == "_CONTROLLER_LEFTHAND") || (_jointName == "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); }
bool isRightHand() const override { return (_jointName == "_CONTROLLER_RIGHTHAND") || (_jointName == "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"); }
private:
std::string _jointName;

View file

@ -12,21 +12,24 @@
#include "Application.h"
#include "avatar/AvatarManager.h"
#include "RayPickScriptingInterface.h"
LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates,
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool distanceScaleEnd, const bool scaleWithAvatar, const bool enabled) :
_renderingEnabled(enabled),
#include <DependencyManager.h>
#include <PickManager.h>
#include "PickScriptingInterface.h"
#include "RayPick.h"
LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover,
const PointerTriggers& triggers, bool faceAvatar, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) :
Pointer(DependencyManager::get<PickScriptingInterface>()->createRayPick(rayProps), enabled, hover),
_triggers(triggers),
_renderStates(renderStates),
_defaultRenderStates(defaultRenderStates),
_faceAvatar(faceAvatar),
_centerEndY(centerEndY),
_lockEnd(lockEnd),
_distanceScaleEnd(distanceScaleEnd),
_scaleWithAvatar(scaleWithAvatar),
_rayPickUID(DependencyManager::get<RayPickScriptingInterface>()->createRayPick(rayProps))
_scaleWithAvatar(scaleWithAvatar)
{
for (auto& state : _renderStates) {
if (!enabled || state.first != _currentRenderState) {
disableRenderState(state.second);
@ -40,8 +43,6 @@ LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& rende
}
LaserPointer::~LaserPointer() {
qApp->getRayPickManager().removeRayPick(_rayPickUID);
for (auto& renderState : _renderStates) {
renderState.second.deleteOverlays();
}
@ -50,28 +51,6 @@ LaserPointer::~LaserPointer() {
}
}
void LaserPointer::enable() {
qApp->getRayPickManager().enableRayPick(_rayPickUID);
withWriteLock([&] {
_renderingEnabled = true;
});
}
void LaserPointer::disable() {
qApp->getRayPickManager().disableRayPick(_rayPickUID);
withWriteLock([&] {
_renderingEnabled = false;
if (!_currentRenderState.empty()) {
if (_renderStates.find(_currentRenderState) != _renderStates.end()) {
disableRenderState(_renderStates[_currentRenderState]);
}
if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
disableRenderState(_defaultRenderStates[_currentRenderState].second);
}
}
});
}
void LaserPointer::setRenderState(const std::string& state) {
withWriteLock([&] {
if (!_currentRenderState.empty() && state != _currentRenderState) {
@ -110,11 +89,7 @@ void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant&
}
}
const RayPickResult LaserPointer::getPrevRayPickResult() {
return qApp->getRayPickManager().getPrevRayPickResult(_rayPickUID);
}
void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, const float distance, const QUuid& objectID, const PickRay& pickRay, const bool defaultState) {
void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay, bool defaultState) {
if (!renderState.getStartID().isNull()) {
QVariantMap startProps;
startProps.insert("position", vec3toVariant(pickRay.origin));
@ -172,7 +147,7 @@ void LaserPointer::updateRenderState(const RenderState& renderState, const Inter
}
if (!renderState.getEndID().isNull()) {
QVariantMap endProps;
glm::quat faceAvatarRotation = DependencyManager::get<AvatarManager>()->getMyAvatar()->getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, 180.0f, 0.0f)));
glm::quat faceAvatarRotation = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, 180.0f, 0.0f)));
glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(renderState.getEndID(), "dimensions").value);
if (_distanceScaleEnd) {
dim = renderState.getEndDim() * glm::distance(pickRay.origin, endVec);
@ -214,36 +189,53 @@ void LaserPointer::disableRenderState(const RenderState& renderState) {
}
}
void LaserPointer::update() {
// This only needs to be a read lock because update won't change any of the properties that can be modified from scripts
withReadLock([&] {
RayPickResult prevRayPickResult = qApp->getRayPickManager().getPrevRayPickResult(_rayPickUID);
if (_renderingEnabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() &&
(prevRayPickResult.type != IntersectionType::NONE || _laserLength > 0.0f || !_lockEndObject.id.isNull())) {
float distance = _laserLength > 0.0f ? _laserLength : prevRayPickResult.distance;
updateRenderState(_renderStates[_currentRenderState], prevRayPickResult.type, distance, prevRayPickResult.objectID, prevRayPickResult.searchRay, false);
disableRenderState(_defaultRenderStates[_currentRenderState].second);
} else if (_renderingEnabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
disableRenderState(_renderStates[_currentRenderState]);
updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), prevRayPickResult.searchRay, true);
} else if (!_currentRenderState.empty()) {
disableRenderState(_renderStates[_currentRenderState]);
disableRenderState(_defaultRenderStates[_currentRenderState].second);
void LaserPointer::updateVisuals(const PickResultPointer& pickResult) {
auto rayPickResult = std::static_pointer_cast<const RayPickResult>(pickResult);
IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE;
if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() &&
(type != IntersectionType::NONE || _laserLength > 0.0f || !_lockEndObject.id.isNull())) {
PickRay pickRay(rayPickResult->pickVariant);
QUuid uid = rayPickResult->objectID;
float distance = _laserLength > 0.0f ? _laserLength : rayPickResult->distance;
updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay, false);
disableRenderState(_defaultRenderStates[_currentRenderState].second);
} else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
disableRenderState(_renderStates[_currentRenderState]);
PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay();
updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay, true);
} else if (!_currentRenderState.empty()) {
disableRenderState(_renderStates[_currentRenderState]);
disableRenderState(_defaultRenderStates[_currentRenderState].second);
}
}
Pointer::PickedObject LaserPointer::getHoveredObject(const PickResultPointer& pickResult) {
auto rayPickResult = std::static_pointer_cast<const RayPickResult>(pickResult);
if (!rayPickResult) {
return PickedObject();
}
return PickedObject(rayPickResult->objectID, rayPickResult->type);
}
Pointer::Buttons LaserPointer::getPressedButtons() {
std::unordered_set<std::string> toReturn;
for (const PointerTrigger& trigger : _triggers) {
// TODO: right now, LaserPointers don't support axes, only on/off buttons
if (trigger.getEndpoint()->peek() >= 1.0f) {
toReturn.insert(trigger.getButton());
}
});
}
return toReturn;
}
void LaserPointer::setPrecisionPicking(const bool precisionPicking) {
qApp->getRayPickManager().setPrecisionPicking(_rayPickUID, precisionPicking);
}
void LaserPointer::setLaserLength(const float laserLength) {
void LaserPointer::setLength(float length) {
withWriteLock([&] {
_laserLength = laserLength;
_laserLength = length;
});
}
void LaserPointer::setLockEndUUID(QUuid objectID, const bool isOverlay, const glm::mat4& offsetMat) {
void LaserPointer::setLockEndUUID(const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) {
withWriteLock([&] {
_lockEndObject.id = objectID;
_lockEndObject.isOverlay = isOverlay;
@ -251,14 +243,6 @@ void LaserPointer::setLockEndUUID(QUuid objectID, const bool isOverlay, const gl
});
}
void LaserPointer::setIgnoreItems(const QVector<QUuid>& ignoreItems) const {
qApp->getRayPickManager().setIgnoreItems(_rayPickUID, ignoreItems);
}
void LaserPointer::setIncludeItems(const QVector<QUuid>& includeItems) const {
qApp->getRayPickManager().setIncludeItems(_rayPickUID, includeItems);
}
RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) :
_startID(startID), _pathID(pathID), _endID(endID)
{
@ -286,3 +270,66 @@ void RenderState::deleteOverlays() {
qApp->getOverlays().deleteOverlay(_endID);
}
}
RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) {
QUuid startID;
if (propMap["start"].isValid()) {
QVariantMap startMap = propMap["start"].toMap();
if (startMap["type"].isValid()) {
startMap.remove("visible");
startID = qApp->getOverlays().addOverlay(startMap["type"].toString(), startMap);
}
}
QUuid pathID;
if (propMap["path"].isValid()) {
QVariantMap pathMap = propMap["path"].toMap();
// right now paths must be line3ds
if (pathMap["type"].isValid() && pathMap["type"].toString() == "line3d") {
pathMap.remove("visible");
pathID = qApp->getOverlays().addOverlay(pathMap["type"].toString(), pathMap);
}
}
QUuid endID;
if (propMap["end"].isValid()) {
QVariantMap endMap = propMap["end"].toMap();
if (endMap["type"].isValid()) {
endMap.remove("visible");
endID = qApp->getOverlays().addOverlay(endMap["type"].toString(), endMap);
}
}
return RenderState(startID, pathID, endID);
}
PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover) const {
QUuid pickedID;
glm::vec3 intersection, surfaceNormal, direction, origin;
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
if (rayPickResult) {
intersection = rayPickResult->intersection;
surfaceNormal = rayPickResult->surfaceNormal;
const QVariantMap& searchRay = rayPickResult->pickVariant;
direction = vec3FromVariant(searchRay["direction"]);
origin = vec3FromVariant(searchRay["origin"]);
pickedID = rayPickResult->objectID;
}
glm::vec2 pos2D;
if (pickedID != target.objectID) {
if (target.type == ENTITY) {
intersection = RayPick::intersectRayWithEntityXYPlane(target.objectID, origin, direction);
} else if (target.type == OVERLAY) {
intersection = RayPick::intersectRayWithOverlayXYPlane(target.objectID, origin, direction);
}
}
if (target.type == ENTITY) {
pos2D = RayPick::projectOntoEntityXYPlane(target.objectID, intersection);
} else if (target.type == OVERLAY) {
pos2D = RayPick::projectOntoOverlayXYPlane(target.objectID, intersection);
} else if (target.type == HUD) {
pos2D = DependencyManager::get<PickManager>()->calculatePos2DFromHUD(intersection);
}
return PointerEvent(pos2D, intersection, surfaceNormal, direction);
}

View file

@ -14,12 +14,10 @@
#include <QString>
#include <glm/glm.hpp>
#include <DependencyManager.h>
#include <shared/ReadWriteLockable.h>
#include "ui/overlays/Overlay.h"
class RayPickResult;
#include <Pointer.h>
#include <Pick.h>
struct LockEndObject {
QUuid id { QUuid() };
@ -60,39 +58,38 @@ private:
float _lineWidth;
};
class LaserPointer : public ReadWriteLockable {
class LaserPointer : public Pointer {
using Parent = Pointer;
public:
using Pointer = std::shared_ptr<LaserPointer>;
typedef std::unordered_map<std::string, RenderState> RenderStateMap;
typedef std::unordered_map<std::string, std::pair<float, RenderState>> DefaultRenderStateMap;
LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates,
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool distanceScaleEnd, const bool scaleWithAvatar, const bool enabled);
LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers,
bool faceAvatar, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled);
~LaserPointer();
QUuid getRayUID() { return _rayPickUID; }
void enable();
void disable();
const RayPickResult getPrevRayPickResult();
void setRenderState(const std::string& state);
void setRenderState(const std::string& state) override;
// You cannot use editRenderState to change the overlay type of any part of the laser pointer. You can only edit the properties of the existing overlays.
void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps);
void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override;
void setPrecisionPicking(const bool precisionPicking);
void setLaserLength(const float laserLength);
void setLockEndUUID(QUuid objectID, const bool isOverlay, const glm::mat4& offsetMat = glm::mat4());
void setLength(float length) override;
void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) override;
void setIgnoreItems(const QVector<QUuid>& ignoreItems) const;
void setIncludeItems(const QVector<QUuid>& includeItems) const;
void updateVisuals(const PickResultPointer& prevRayPickResult) override;
void update();
static RenderState buildRenderState(const QVariantMap& propMap);
protected:
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover = true) const override;
PickedObject getHoveredObject(const PickResultPointer& pickResult) override;
Pointer::Buttons getPressedButtons() override;
bool shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
bool shouldTrigger(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
private:
bool _renderingEnabled;
PointerTriggers _triggers;
float _laserLength { 0.0f };
std::string _currentRenderState { "" };
RenderStateMap _renderStates;
@ -104,10 +101,8 @@ private:
bool _scaleWithAvatar;
LockEndObject _lockEndObject;
const QUuid _rayPickUID;
void updateRenderStateOverlay(const OverlayID& id, const QVariant& props);
void updateRenderState(const RenderState& renderState, const IntersectionType type, const float distance, const QUuid& objectID, const PickRay& pickRay, const bool defaultState);
void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay, bool defaultState);
void disableRenderState(const RenderState& renderState);
};

View file

@ -1,121 +0,0 @@
//
// LaserPointerManager.cpp
// interface/src/raypick
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "LaserPointerManager.h"
QUuid LaserPointerManager::createLaserPointer(const QVariant& rayProps, const LaserPointer::RenderStateMap& renderStates, const LaserPointer::DefaultRenderStateMap& defaultRenderStates,
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool distanceScaleEnd, const bool scaleWithAvatar, const bool enabled) {
QUuid result;
std::shared_ptr<LaserPointer> laserPointer = std::make_shared<LaserPointer>(rayProps, renderStates, defaultRenderStates, faceAvatar, centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled);
if (!laserPointer->getRayUID().isNull()) {
result = QUuid::createUuid();
withWriteLock([&] { _laserPointers[result] = laserPointer; });
}
return result;
}
LaserPointer::Pointer LaserPointerManager::find(const QUuid& uid) const {
return resultWithReadLock<LaserPointer::Pointer>([&] {
auto itr = _laserPointers.find(uid);
if (itr != _laserPointers.end()) {
return *itr;
}
return LaserPointer::Pointer();
});
}
void LaserPointerManager::removeLaserPointer(const QUuid& uid) {
withWriteLock([&] {
_laserPointers.remove(uid);
});
}
void LaserPointerManager::enableLaserPointer(const QUuid& uid) const {
auto laserPointer = find(uid);
if (laserPointer) {
laserPointer->enable();
}
}
void LaserPointerManager::disableLaserPointer(const QUuid& uid) const {
auto laserPointer = find(uid);
if (laserPointer) {
laserPointer->disable();
}
}
void LaserPointerManager::setRenderState(const QUuid& uid, const std::string& renderState) const {
auto laserPointer = find(uid);
if (laserPointer) {
laserPointer->setRenderState(renderState);
}
}
void LaserPointerManager::editRenderState(const QUuid& uid, const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) const {
auto laserPointer = find(uid);
if (laserPointer) {
laserPointer->editRenderState(state, startProps, pathProps, endProps);
}
}
const RayPickResult LaserPointerManager::getPrevRayPickResult(const QUuid& uid) const {
auto laserPointer = find(uid);
if (laserPointer) {
return laserPointer->getPrevRayPickResult();
}
return RayPickResult();
}
void LaserPointerManager::update() {
auto cachedLaserPointers = resultWithReadLock<QList<std::shared_ptr<LaserPointer>>>([&] {
return _laserPointers.values();
});
for (const auto& laserPointer : cachedLaserPointers) {
laserPointer->update();
}
}
void LaserPointerManager::setPrecisionPicking(const QUuid& uid, const bool precisionPicking) const {
auto laserPointer = find(uid);
if (laserPointer) {
laserPointer->setPrecisionPicking(precisionPicking);
}
}
void LaserPointerManager::setLaserLength(const QUuid& uid, const float laserLength) const {
auto laserPointer = find(uid);
if (laserPointer) {
laserPointer->setLaserLength(laserLength);
}
}
void LaserPointerManager::setIgnoreItems(const QUuid& uid, const QVector<QUuid>& ignoreEntities) const {
auto laserPointer = find(uid);
if (laserPointer) {
laserPointer->setIgnoreItems(ignoreEntities);
}
}
void LaserPointerManager::setIncludeItems(const QUuid& uid, const QVector<QUuid>& includeEntities) const {
auto laserPointer = find(uid);
if (laserPointer) {
laserPointer->setIncludeItems(includeEntities);
}
}
void LaserPointerManager::setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) const {
auto laserPointer = find(uid);
if (laserPointer) {
laserPointer->setLockEndUUID(objectID, isOverlay, offsetMat);
}
}

View file

@ -1,51 +0,0 @@
//
// LaserPointerManager.h
// interface/src/raypick
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_LaserPointerManager_h
#define hifi_LaserPointerManager_h
#include <memory>
#include <glm/glm.hpp>
#include <shared/ReadWriteLockable.h>
#include "LaserPointer.h"
class RayPickResult;
class LaserPointerManager : protected ReadWriteLockable {
public:
QUuid createLaserPointer(const QVariant& rayProps, const LaserPointer::RenderStateMap& renderStates, const LaserPointer::DefaultRenderStateMap& defaultRenderStates,
const bool faceAvatar, const bool centerEndY, const bool lockEnd, const bool distanceScaleEnd, const bool scaleWithAvatar, const bool enabled);
void removeLaserPointer(const QUuid& uid);
void enableLaserPointer(const QUuid& uid) const;
void disableLaserPointer(const QUuid& uid) const;
void setRenderState(const QUuid& uid, const std::string& renderState) const;
void editRenderState(const QUuid& uid, const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) const;
const RayPickResult getPrevRayPickResult(const QUuid& uid) const;
void setPrecisionPicking(const QUuid& uid, const bool precisionPicking) const;
void setLaserLength(const QUuid& uid, const float laserLength) const;
void setIgnoreItems(const QUuid& uid, const QVector<QUuid>& ignoreEntities) const;
void setIncludeItems(const QUuid& uid, const QVector<QUuid>& includeEntities) const;
void setLockEndUUID(const QUuid& uid, const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const;
void update();
private:
LaserPointer::Pointer find(const QUuid& uid) const;
QHash<QUuid, std::shared_ptr<LaserPointer>> _laserPointers;
};
#endif // hifi_LaserPointerManager_h

View file

@ -11,132 +11,25 @@
#include "LaserPointerScriptingInterface.h"
#include <QtCore/QVariant>
#include "RegisteredMetaTypes.h"
#include "PointerScriptingInterface.h"
#include <GLMHelpers.h>
#include <RegisteredMetaTypes.h>
void LaserPointerScriptingInterface::setIgnoreItems(const QUuid& uid, const QScriptValue& ignoreItems) const {
qApp->getLaserPointerManager().setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems));
}
void LaserPointerScriptingInterface::setIncludeItems(const QUuid& uid, const QScriptValue& includeItems) const {
qApp->getLaserPointerManager().setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems));
void LaserPointerScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) const {
DependencyManager::get<PointerManager>()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems));
}
QUuid LaserPointerScriptingInterface::createLaserPointer(const QVariant& properties) const {
QVariantMap propertyMap = properties.toMap();
bool faceAvatar = false;
if (propertyMap["faceAvatar"].isValid()) {
faceAvatar = propertyMap["faceAvatar"].toBool();
}
bool centerEndY = true;
if (propertyMap["centerEndY"].isValid()) {
centerEndY = propertyMap["centerEndY"].toBool();
}
bool lockEnd = false;
if (propertyMap["lockEnd"].isValid()) {
lockEnd = propertyMap["lockEnd"].toBool();
}
bool distanceScaleEnd = false;
if (propertyMap["distanceScaleEnd"].isValid()) {
distanceScaleEnd = propertyMap["distanceScaleEnd"].toBool();
}
bool enabled = false;
if (propertyMap["enabled"].isValid()) {
enabled = propertyMap["enabled"].toBool();
}
bool scaleWithAvatar = false;
if (propertyMap["scaleWithAvatar"].isValid()) {
scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool();
}
LaserPointer::RenderStateMap renderStates;
if (propertyMap["renderStates"].isValid()) {
QList<QVariant> renderStateVariants = propertyMap["renderStates"].toList();
for (QVariant& renderStateVariant : renderStateVariants) {
if (renderStateVariant.isValid()) {
QVariantMap renderStateMap = renderStateVariant.toMap();
if (renderStateMap["name"].isValid()) {
std::string name = renderStateMap["name"].toString().toStdString();
renderStates[name] = buildRenderState(renderStateMap);
}
}
}
}
LaserPointer::DefaultRenderStateMap defaultRenderStates;
if (propertyMap["defaultRenderStates"].isValid()) {
QList<QVariant> renderStateVariants = propertyMap["defaultRenderStates"].toList();
for (QVariant& renderStateVariant : renderStateVariants) {
if (renderStateVariant.isValid()) {
QVariantMap renderStateMap = renderStateVariant.toMap();
if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) {
std::string name = renderStateMap["name"].toString().toStdString();
float distance = renderStateMap["distance"].toFloat();
defaultRenderStates[name] = std::pair<float, RenderState>(distance, buildRenderState(renderStateMap));
}
}
}
}
return qApp->getLaserPointerManager().createLaserPointer(properties, renderStates, defaultRenderStates, faceAvatar, centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled);
void LaserPointerScriptingInterface::setIncludeItems(unsigned int uid, const QScriptValue& includeItems) const {
DependencyManager::get<PointerManager>()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems));
}
void LaserPointerScriptingInterface::editRenderState(const QUuid& uid, const QString& renderState, const QVariant& properties) const {
QVariantMap propMap = properties.toMap();
QVariant startProps;
if (propMap["start"].isValid()) {
startProps = propMap["start"];
}
QVariant pathProps;
if (propMap["path"].isValid()) {
pathProps = propMap["path"];
}
QVariant endProps;
if (propMap["end"].isValid()) {
endProps = propMap["end"];
}
qApp->getLaserPointerManager().editRenderState(uid, renderState.toStdString(), startProps, pathProps, endProps);
unsigned int LaserPointerScriptingInterface::createLaserPointer(const QVariant& properties) const {
return DependencyManager::get<PointerScriptingInterface>()->createLaserPointer(properties);
}
RenderState LaserPointerScriptingInterface::buildRenderState(const QVariantMap& propMap) {
QUuid startID;
if (propMap["start"].isValid()) {
QVariantMap startMap = propMap["start"].toMap();
if (startMap["type"].isValid()) {
startMap.remove("visible");
startID = qApp->getOverlays().addOverlay(startMap["type"].toString(), startMap);
}
}
void LaserPointerScriptingInterface::editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const {
DependencyManager::get<PointerScriptingInterface>()->editRenderState(uid, renderState, properties);
}
QUuid pathID;
if (propMap["path"].isValid()) {
QVariantMap pathMap = propMap["path"].toMap();
// right now paths must be line3ds
if (pathMap["type"].isValid() && pathMap["type"].toString() == "line3d") {
pathMap.remove("visible");
pathID = qApp->getOverlays().addOverlay(pathMap["type"].toString(), pathMap);
}
}
QUuid endID;
if (propMap["end"].isValid()) {
QVariantMap endMap = propMap["end"].toMap();
if (endMap["type"].isValid()) {
endMap.remove("visible");
endID = qApp->getOverlays().addOverlay(endMap["type"].toString(), endMap);
}
}
return RenderState(startID, pathID, endID);
}
QVariantMap LaserPointerScriptingInterface::getPrevRayPickResult(unsigned int uid) const {
return DependencyManager::get<PointerScriptingInterface>()->getPrevPickResult(uid);
}

View file

@ -13,33 +13,32 @@
#include <QtCore/QObject>
#include "RegisteredMetaTypes.h"
#include "DependencyManager.h"
#include "Application.h"
#include <PointerManager.h>
class LaserPointerScriptingInterface : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
public slots:
Q_INVOKABLE QUuid createLaserPointer(const QVariant& properties) const;
Q_INVOKABLE void enableLaserPointer(const QUuid& uid) const { qApp->getLaserPointerManager().enableLaserPointer(uid); }
Q_INVOKABLE void disableLaserPointer(const QUuid& uid) const { qApp->getLaserPointerManager().disableLaserPointer(uid); }
Q_INVOKABLE void removeLaserPointer(const QUuid& uid) const { qApp->getLaserPointerManager().removeLaserPointer(uid); }
Q_INVOKABLE void editRenderState(const QUuid& uid, const QString& renderState, const QVariant& properties) const;
Q_INVOKABLE void setRenderState(const QUuid& uid, const QString& renderState) const { qApp->getLaserPointerManager().setRenderState(uid, renderState.toStdString()); }
Q_INVOKABLE RayPickResult getPrevRayPickResult(QUuid uid) const { return qApp->getLaserPointerManager().getPrevRayPickResult(uid); }
public:
Q_INVOKABLE unsigned int createLaserPointer(const QVariant& properties) const;
Q_INVOKABLE void enableLaserPointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->enablePointer(uid); }
Q_INVOKABLE void disableLaserPointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->disablePointer(uid); }
Q_INVOKABLE void removeLaserPointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->removePointer(uid); }
Q_INVOKABLE void editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const;
Q_INVOKABLE void setRenderState(unsigned int uid, const QString& renderState) const { DependencyManager::get<PointerManager>()->setRenderState(uid, renderState.toStdString()); }
Q_INVOKABLE QVariantMap getPrevRayPickResult(unsigned int uid) const;
Q_INVOKABLE void setPrecisionPicking(const QUuid& uid, bool precisionPicking) const { qApp->getLaserPointerManager().setPrecisionPicking(uid, precisionPicking); }
Q_INVOKABLE void setLaserLength(const QUuid& uid, float laserLength) const { qApp->getLaserPointerManager().setLaserLength(uid, laserLength); }
Q_INVOKABLE void setIgnoreItems(const QUuid& uid, const QScriptValue& ignoreEntities) const;
Q_INVOKABLE void setIncludeItems(const QUuid& uid, const QScriptValue& includeEntities) const;
Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking) const { DependencyManager::get<PointerManager>()->setPrecisionPicking(uid, precisionPicking); }
Q_INVOKABLE void setLaserLength(unsigned int uid, float laserLength) const { DependencyManager::get<PointerManager>()->setLength(uid, laserLength); }
Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities) const;
Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities) const;
Q_INVOKABLE void setLockEndUUID(const QUuid& uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const { qApp->getLaserPointerManager().setLockEndUUID(uid, objectID, isOverlay, offsetMat); }
private:
static RenderState buildRenderState(const QVariantMap& propMap);
Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get<PointerManager>()->setLockEndUUID(uid, objectID, isOverlay, offsetMat); }
Q_INVOKABLE bool isLeftHand(unsigned int uid) { return DependencyManager::get<PointerManager>()->isLeftHand(uid); }
Q_INVOKABLE bool isRightHand(unsigned int uid) { return DependencyManager::get<PointerManager>()->isRightHand(uid); }
Q_INVOKABLE bool isMouse(unsigned int uid) { return DependencyManager::get<PointerManager>()->isMouse(uid); }
};
#endif // hifi_LaserPointerScriptingInterface_h

View file

@ -10,23 +10,20 @@
//
#include "MouseRayPick.h"
#include "DependencyManager.h"
#include "Application.h"
#include "display-plugins/CompositorHelper.h"
MouseRayPick::MouseRayPick(const RayPickFilter& filter, const float maxDistance, const bool enabled) :
MouseRayPick::MouseRayPick(const PickFilter& filter, float maxDistance, bool enabled) :
RayPick(filter, maxDistance, enabled)
{
}
const PickRay MouseRayPick::getPickRay(bool& valid) const {
PickRay MouseRayPick::getMathematicalPick() const {
QVariant position = qApp->getApplicationCompositor().getReticleInterface()->getPosition();
if (position.isValid()) {
QVariantMap posMap = position.toMap();
valid = true;
return qApp->getCamera().computePickRay(posMap["x"].toFloat(), posMap["y"].toFloat());
}
valid = false;
return PickRay();
}

View file

@ -11,14 +11,16 @@
#ifndef hifi_MouseRayPick_h
#define hifi_MouseRayPick_h
#include <pointers/rays/RayPick.h>
#include "RayPick.h"
class MouseRayPick : public RayPick {
public:
MouseRayPick(const RayPickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false);
MouseRayPick(const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false);
const PickRay getPickRay(bool& valid) const override;
PickRay getMathematicalPick() const override;
bool isMouse() const override { return true; }
};
#endif // hifi_MouseRayPick_h

View file

@ -0,0 +1,177 @@
//
// Created by Sam Gondelman 10/20/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 "PickScriptingInterface.h"
#include <QVariant>
#include "GLMHelpers.h"
#include <PickManager.h>
#include "StaticRayPick.h"
#include "JointRayPick.h"
#include "MouseRayPick.h"
#include "StylusPick.h"
#include <ScriptEngine.h>
unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type, const QVariant& properties) {
switch (type) {
case PickQuery::PickType::Ray:
return createRayPick(properties);
case PickQuery::PickType::Stylus:
return createStylusPick(properties);
default:
return PickManager::INVALID_PICK_ID;
}
}
unsigned int PickScriptingInterface::createRayPick(const QVariant& properties) {
QVariantMap propMap = properties.toMap();
bool enabled = false;
if (propMap["enabled"].isValid()) {
enabled = propMap["enabled"].toBool();
}
PickFilter filter = PickFilter();
if (propMap["filter"].isValid()) {
filter = PickFilter(propMap["filter"].toUInt());
}
float maxDistance = 0.0f;
if (propMap["maxDistance"].isValid()) {
maxDistance = propMap["maxDistance"].toFloat();
}
if (propMap["joint"].isValid()) {
std::string jointName = propMap["joint"].toString().toStdString();
if (jointName != "Mouse") {
// x = upward, y = forward, z = lateral
glm::vec3 posOffset = Vectors::ZERO;
if (propMap["posOffset"].isValid()) {
posOffset = vec3FromVariant(propMap["posOffset"]);
}
glm::vec3 dirOffset = Vectors::UP;
if (propMap["dirOffset"].isValid()) {
dirOffset = vec3FromVariant(propMap["dirOffset"]);
}
return DependencyManager::get<PickManager>()->addPick(PickQuery::Ray, std::make_shared<JointRayPick>(jointName, posOffset, dirOffset, filter, maxDistance, enabled));
} else {
return DependencyManager::get<PickManager>()->addPick(PickQuery::Ray, std::make_shared<MouseRayPick>(filter, maxDistance, enabled));
}
} else if (propMap["position"].isValid()) {
glm::vec3 position = vec3FromVariant(propMap["position"]);
glm::vec3 direction = -Vectors::UP;
if (propMap["direction"].isValid()) {
direction = vec3FromVariant(propMap["direction"]);
}
return DependencyManager::get<PickManager>()->addPick(PickQuery::Ray, std::make_shared<StaticRayPick>(position, direction, filter, maxDistance, enabled));
}
return PickManager::INVALID_PICK_ID;
}
unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties) {
QVariantMap propMap = properties.toMap();
bilateral::Side side = bilateral::Side::Invalid;
{
QVariant handVar = propMap["hand"];
if (handVar.isValid()) {
side = bilateral::side(handVar.toInt());
}
}
bool enabled = false;
if (propMap["enabled"].isValid()) {
enabled = propMap["enabled"].toBool();
}
PickFilter filter = PickFilter();
if (propMap["filter"].isValid()) {
filter = PickFilter(propMap["filter"].toUInt());
}
float maxDistance = 0.0f;
if (propMap["maxDistance"].isValid()) {
maxDistance = propMap["maxDistance"].toFloat();
}
return DependencyManager::get<PickManager>()->addPick(PickQuery::Stylus, std::make_shared<StylusPick>(side, filter, maxDistance, enabled));
}
void PickScriptingInterface::enablePick(unsigned int uid) {
DependencyManager::get<PickManager>()->enablePick(uid);
}
void PickScriptingInterface::disablePick(unsigned int uid) {
DependencyManager::get<PickManager>()->disablePick(uid);
}
void PickScriptingInterface::removePick(unsigned int uid) {
DependencyManager::get<PickManager>()->removePick(uid);
}
QVariantMap PickScriptingInterface::getPrevPickResult(unsigned int uid) {
QVariantMap result;
auto pickResult = DependencyManager::get<PickManager>()->getPrevPickResult(uid);
if (pickResult) {
result = pickResult->toVariantMap();
}
return result;
}
void PickScriptingInterface::setPrecisionPicking(unsigned int uid, bool precisionPicking) {
DependencyManager::get<PickManager>()->setPrecisionPicking(uid, precisionPicking);
}
void PickScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) {
DependencyManager::get<PickManager>()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems));
}
void PickScriptingInterface::setIncludeItems(unsigned int uid, const QScriptValue& includeItems) {
DependencyManager::get<PickManager>()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems));
}
bool PickScriptingInterface::isLeftHand(unsigned int uid) {
return DependencyManager::get<PickManager>()->isLeftHand(uid);
}
bool PickScriptingInterface::isRightHand(unsigned int uid) {
return DependencyManager::get<PickManager>()->isRightHand(uid);
}
bool PickScriptingInterface::isMouse(unsigned int uid) {
return DependencyManager::get<PickManager>()->isMouse(uid);
}
QScriptValue pickTypesToScriptValue(QScriptEngine* engine, const PickQuery::PickType& pickType) {
return pickType;
}
void pickTypesFromScriptValue(const QScriptValue& object, PickQuery::PickType& pickType) {
pickType = static_cast<PickQuery::PickType>(object.toUInt16());
}
void PickScriptingInterface::registerMetaTypes(QScriptEngine* engine) {
QScriptValue pickTypes = engine->newObject();
auto metaEnum = QMetaEnum::fromType<PickQuery::PickType>();
for (int i = 0; i < PickQuery::PickType::NUM_PICK_TYPES; ++i) {
pickTypes.setProperty(metaEnum.key(i), metaEnum.value(i));
}
engine->globalObject().setProperty("PickType", pickTypes);
qScriptRegisterMetaType(engine, pickTypesToScriptValue, pickTypesFromScriptValue);
}

View file

@ -0,0 +1,203 @@
//
// Created by Sam Gondelman 10/20/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_PickScriptingInterface_h
#define hifi_PickScriptingInterface_h
#include <QtCore/QObject>
#include <RegisteredMetaTypes.h>
#include <DependencyManager.h>
#include <Pick.h>
/**jsdoc
* The Picks API lets you create and manage objects for repeatedly calculating intersections in different ways.
*
* @namespace Picks
* @property PICK_NOTHING {number} A filter flag. Don't intersect with anything.
* @property PICK_ENTITIES {number} A filter flag. Include entities when intersecting.
* @property PICK_OVERLAYS {number} A filter flag. Include overlays when intersecting.
* @property PICK_AVATARS {number} A filter flag. Include avatars when intersecting.
* @property PICK_HUD {number} A filter flag. Include the HUD sphere when intersecting in HMD mode.
* @property PICK_COARSE {number} A filter flag. Pick against coarse meshes, instead of exact meshes.
* @property PICK_INCLUDE_INVISIBLE {number} A filter flag. Include invisible objects when intersecting.
* @property PICK_INCLUDE_NONCOLLIDABLE {number} A filter flag. Include non-collidable objects when intersecting.
* @property INTERSECTED_NONE {number} An intersection type. Intersected nothing with the given filter flags.
* @property INTERSECTED_ENTITY {number} An intersection type. Intersected an entity.
* @property INTERSECTED_OVERLAY {number} An intersection type. Intersected an overlay.
* @property INTERSECTED_AVATAR {number} An intersection type. Intersected an avatar.
* @property INTERSECTED_HUD {number} An intersection type. Intersected the HUD sphere.
*/
class PickScriptingInterface : public QObject, public Dependency {
Q_OBJECT
Q_PROPERTY(unsigned int PICK_NOTHING READ PICK_NOTHING CONSTANT)
Q_PROPERTY(unsigned int PICK_ENTITIES READ PICK_ENTITIES CONSTANT)
Q_PROPERTY(unsigned int PICK_OVERLAYS READ PICK_OVERLAYS CONSTANT)
Q_PROPERTY(unsigned int PICK_AVATARS READ PICK_AVATARS CONSTANT)
Q_PROPERTY(unsigned int PICK_HUD READ PICK_HUD CONSTANT)
Q_PROPERTY(unsigned int PICK_COARSE READ PICK_COARSE CONSTANT)
Q_PROPERTY(unsigned int PICK_INCLUDE_INVISIBLE READ PICK_INCLUDE_INVISIBLE CONSTANT)
Q_PROPERTY(unsigned int PICK_INCLUDE_NONCOLLIDABLE READ PICK_INCLUDE_NONCOLLIDABLE CONSTANT)
Q_PROPERTY(unsigned int PICK_ALL_INTERSECTIONS READ PICK_ALL_INTERSECTIONS CONSTANT)
Q_PROPERTY(unsigned int INTERSECTED_NONE READ INTERSECTED_NONE CONSTANT)
Q_PROPERTY(unsigned int INTERSECTED_ENTITY READ INTERSECTED_ENTITY CONSTANT)
Q_PROPERTY(unsigned int INTERSECTED_OVERLAY READ INTERSECTED_OVERLAY CONSTANT)
Q_PROPERTY(unsigned int INTERSECTED_AVATAR READ INTERSECTED_AVATAR CONSTANT)
Q_PROPERTY(unsigned int INTERSECTED_HUD READ INTERSECTED_HUD CONSTANT)
SINGLETON_DEPENDENCY
public:
unsigned int createRayPick(const QVariant& properties);
unsigned int createStylusPick(const QVariant& properties);
void registerMetaTypes(QScriptEngine* engine);
/**jsdoc
* A set of properties that can be passed to {@link Picks.createPick} to create a new Pick.
*
* Different {@link Picks.PickType}s use different properties, and within one PickType, the properties you choose can lead to a wide range of behaviors. For example,
* with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pick, a Mouse Ray Pick, or a Joint Ray Pick.
*
* @typedef {Object} Picks.PickProperties
* @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
* @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
* @property {float} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
* @property {string} [joint] Only for Joint or Mouse Ray Picks. If "Mouse", it will create a Ray Pick that follows the system mouse, in desktop or HMD.
* If "Avatar", it will create a Joint Ray Pick that follows your avatar's head. Otherwise, it will create a Joint Ray Pick that follows the given joint, if it
* exists on your current avatar.
* @property {Vec3} [posOffset=Vec3.ZERO] Only for Joint Ray Picks. A local joint position offset, in meters. x = upward, y = forward, z = lateral
* @property {Vec3} [dirOffset=Vec3.UP] Only for Joint Ray Picks. A local joint direction offset. x = upward, y = forward, z = lateral
* @property {Vec3} [position] Only for Static Ray Picks. The world-space origin of the ray.
* @property {Vec3} [direction=-Vec3.UP] Only for Static Ray Picks. The world-space direction of the ray.
* @property {number} [hand=-1] Only for Stylus Picks. An integer. 0 == left, 1 == right. Invalid otherwise.
*/
/**jsdoc
* Adds a new Pick.
* @function Picks.createPick
* @param {Picks.PickType} type A PickType that specifies the method of picking to use
* @param {Picks.PickProperties} properties A PickProperties object, containing all the properties for initializing this Pick
* @returns {number} The ID of the created Pick. Used for managing the Pick. 0 if invalid.
*/
Q_INVOKABLE unsigned int createPick(const PickQuery::PickType type, const QVariant& properties);
/**jsdoc
* Enables a Pick.
* @function Picks.enablePick
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
*/
Q_INVOKABLE void enablePick(unsigned int uid);
/**jsdoc
* Disables a Pick.
* @function Picks.disablePick
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
*/
Q_INVOKABLE void disablePick(unsigned int uid);
/**jsdoc
* Removes a Pick.
* @function Picks.removePick
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
*/
Q_INVOKABLE void removePick(unsigned int uid);
/**jsdoc
* An intersection result for a Ray Pick.
*
* @typedef {Object} Picks.RayPickResult
* @property {number} type The intersection type.
* @property {bool} intersects If there was a valid intersection (type != INTERSECTED_NONE)
* @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections.
* @property {float} distance The distance to the intersection point from the origin of the ray.
* @property {Vec3} intersection The intersection point in world-space.
* @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD.
* @property {PickRay} searchRay The PickRay that was used. Valid even if there was no intersection.
*/
/**jsdoc
* An intersection result for a Stylus Pick.
*
* @typedef {Object} Picks.StylusPickResult
* @property {number} type The intersection type.
* @property {bool} intersects If there was a valid intersection (type != INTERSECTED_NONE)
* @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections.
* @property {float} distance The distance to the intersection point from the origin of the ray.
* @property {Vec3} intersection The intersection point in world-space.
* @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD.
* @property {StylusTip} stylusTip The StylusTip that was used. Valid even if there was no intersection.
*/
/**jsdoc
* Get the most recent pick result from this Pick. This will be updated as long as the Pick is enabled.
* @function Picks.getPrevPickResult
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
* @returns {PickResult} The most recent intersection result. This will be slightly different for different PickTypes. See {@link Picks.RayPickResult} and {@link Picks.StylusPickResult}.
*/
Q_INVOKABLE QVariantMap getPrevPickResult(unsigned int uid);
/**jsdoc
* Sets whether or not to use precision picking.
* @function Picks.setPrecisionPicking
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
* @param {boolean} precisionPicking Whether or not to use precision picking
*/
Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking);
/**jsdoc
* Sets a list of Entity IDs, Overlay IDs, and/or Avatar IDs to ignore during intersection. Not used by Stylus Picks.
* @function Picks.setIgnoreItems
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
* @param {Uuid[]} ignoreItems A list of IDs to ignore.
*/
Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems);
/**jsdoc
* Sets a list of Entity IDs, Overlay IDs, and/or Avatar IDs to include during intersection, instead of intersecting with everything. Stylus
* Picks <b>only</b> intersect with objects in their include list.
* @function Picks.setIncludeItems
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
* @param {Uuid[]} includeItems A list of IDs to include.
*/
Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeItems);
/**jsdoc
* Check if a Pick is associated with the left hand.
* @function Picks.isLeftHand
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
* @returns {boolean} True if the Pick is a Joint Ray Pick with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pick with hand == 0.
*/
Q_INVOKABLE bool isLeftHand(unsigned int uid);
/**jsdoc
* Check if a Pick is associated with the right hand.
* @function Picks.isRightHand
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
* @returns {boolean} True if the Pick is a Joint Ray Pick with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pick with hand == 1.
*/
Q_INVOKABLE bool isRightHand(unsigned int uid);
/**jsdoc
* Check if a Pick is associated with the system mouse.
* @function Picks.isMouse
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
* @returns {boolean} True if the Pick is a Mouse Ray Pick, false otherwise.
*/
Q_INVOKABLE bool isMouse(unsigned int uid);
public slots:
static constexpr unsigned int PICK_NOTHING() { return 0; }
static constexpr unsigned int PICK_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_ENTITIES); }
static constexpr unsigned int PICK_OVERLAYS() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_OVERLAYS); }
static constexpr unsigned int PICK_AVATARS() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_AVATARS); }
static constexpr unsigned int PICK_HUD() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_HUD); }
static constexpr unsigned int PICK_COARSE() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_COARSE); }
static constexpr unsigned int PICK_INCLUDE_INVISIBLE() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_INCLUDE_INVISIBLE); }
static constexpr unsigned int PICK_INCLUDE_NONCOLLIDABLE() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_INCLUDE_NONCOLLIDABLE); }
static constexpr unsigned int PICK_ALL_INTERSECTIONS() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_ALL_INTERSECTIONS); }
static constexpr unsigned int INTERSECTED_NONE() { return IntersectionType::NONE; }
static constexpr unsigned int INTERSECTED_ENTITY() { return IntersectionType::ENTITY; }
static constexpr unsigned int INTERSECTED_OVERLAY() { return IntersectionType::OVERLAY; }
static constexpr unsigned int INTERSECTED_AVATAR() { return IntersectionType::AVATAR; }
static constexpr unsigned int INTERSECTED_HUD() { return IntersectionType::HUD; }
};
#endif // hifi_PickScriptingInterface_h

View file

@ -0,0 +1,178 @@
//
// Created by Sam Gondelman 10/20/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 "PointerScriptingInterface.h"
#include <QtCore/QVariant>
#include <GLMHelpers.h>
#include <shared/QtHelpers.h>
#include "Application.h"
#include "LaserPointer.h"
#include "StylusPointer.h"
void PointerScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) const {
DependencyManager::get<PointerManager>()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems));
}
void PointerScriptingInterface::setIncludeItems(unsigned int uid, const QScriptValue& includeItems) const {
DependencyManager::get<PointerManager>()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems));
}
unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType& type, const QVariant& properties) {
// Interaction with managers should always happen on the main thread
if (QThread::currentThread() != qApp->thread()) {
unsigned int result;
BLOCKING_INVOKE_METHOD(this, "createPointer", Q_RETURN_ARG(unsigned int, result), Q_ARG(PickQuery::PickType, type), Q_ARG(QVariant, properties));
return result;
}
switch (type) {
case PickQuery::PickType::Ray:
return createLaserPointer(properties);
case PickQuery::PickType::Stylus:
return createStylus(properties);
default:
return PointerEvent::INVALID_POINTER_ID;
}
}
unsigned int PointerScriptingInterface::createStylus(const QVariant& properties) const {
QVariantMap propertyMap = properties.toMap();
bool hover = false;
if (propertyMap["hover"].isValid()) {
hover = propertyMap["hover"].toBool();
}
bool enabled = false;
if (propertyMap["enabled"].isValid()) {
enabled = propertyMap["enabled"].toBool();
}
return DependencyManager::get<PointerManager>()->addPointer(std::make_shared<StylusPointer>(properties, StylusPointer::buildStylusOverlay(propertyMap), hover, enabled));
}
unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& properties) const {
QVariantMap propertyMap = properties.toMap();
bool faceAvatar = false;
if (propertyMap["faceAvatar"].isValid()) {
faceAvatar = propertyMap["faceAvatar"].toBool();
}
bool centerEndY = true;
if (propertyMap["centerEndY"].isValid()) {
centerEndY = propertyMap["centerEndY"].toBool();
}
bool lockEnd = false;
if (propertyMap["lockEnd"].isValid()) {
lockEnd = propertyMap["lockEnd"].toBool();
}
bool distanceScaleEnd = false;
if (propertyMap["distanceScaleEnd"].isValid()) {
distanceScaleEnd = propertyMap["distanceScaleEnd"].toBool();
}
bool scaleWithAvatar = false;
if (propertyMap["scaleWithAvatar"].isValid()) {
scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool();
}
bool enabled = false;
if (propertyMap["enabled"].isValid()) {
enabled = propertyMap["enabled"].toBool();
}
LaserPointer::RenderStateMap renderStates;
if (propertyMap["renderStates"].isValid()) {
QList<QVariant> renderStateVariants = propertyMap["renderStates"].toList();
for (const QVariant& renderStateVariant : renderStateVariants) {
if (renderStateVariant.isValid()) {
QVariantMap renderStateMap = renderStateVariant.toMap();
if (renderStateMap["name"].isValid()) {
std::string name = renderStateMap["name"].toString().toStdString();
renderStates[name] = LaserPointer::buildRenderState(renderStateMap);
}
}
}
}
LaserPointer::DefaultRenderStateMap defaultRenderStates;
if (propertyMap["defaultRenderStates"].isValid()) {
QList<QVariant> renderStateVariants = propertyMap["defaultRenderStates"].toList();
for (const QVariant& renderStateVariant : renderStateVariants) {
if (renderStateVariant.isValid()) {
QVariantMap renderStateMap = renderStateVariant.toMap();
if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) {
std::string name = renderStateMap["name"].toString().toStdString();
float distance = renderStateMap["distance"].toFloat();
defaultRenderStates[name] = std::pair<float, RenderState>(distance, LaserPointer::buildRenderState(renderStateMap));
}
}
}
}
bool hover = false;
if (propertyMap["hover"].isValid()) {
hover = propertyMap["hover"].toBool();
}
PointerTriggers triggers;
auto userInputMapper = DependencyManager::get<UserInputMapper>();
if (propertyMap["triggers"].isValid()) {
QList<QVariant> triggerVariants = propertyMap["triggers"].toList();
for (const QVariant& triggerVariant : triggerVariants) {
if (triggerVariant.isValid()) {
QVariantMap triggerMap = triggerVariant.toMap();
if (triggerMap["action"].isValid() && triggerMap["button"].isValid()) {
controller::Endpoint::Pointer endpoint = userInputMapper->endpointFor(controller::Input(triggerMap["action"].toUInt()));
if (endpoint) {
std::string button = triggerMap["button"].toString().toStdString();
triggers.emplace_back(endpoint, button);
}
}
}
}
}
return DependencyManager::get<PointerManager>()->addPointer(std::make_shared<LaserPointer>(properties, renderStates, defaultRenderStates, hover, triggers,
faceAvatar, centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled));
}
void PointerScriptingInterface::editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const {
QVariantMap propMap = properties.toMap();
QVariant startProps;
if (propMap["start"].isValid()) {
startProps = propMap["start"];
}
QVariant pathProps;
if (propMap["path"].isValid()) {
pathProps = propMap["path"];
}
QVariant endProps;
if (propMap["end"].isValid()) {
endProps = propMap["end"];
}
DependencyManager::get<PointerManager>()->editRenderState(uid, renderState.toStdString(), startProps, pathProps, endProps);
}
QVariantMap PointerScriptingInterface::getPrevPickResult(unsigned int uid) const {
QVariantMap result;
auto pickResult = DependencyManager::get<PointerManager>()->getPrevPickResult(uid);
if (pickResult) {
result = pickResult->toVariantMap();
}
return result;
}

View file

@ -0,0 +1,229 @@
//
// Created by Sam Gondelman 10/20/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_PointerScriptingInterface_h
#define hifi_PointerScriptingInterface_h
#include <QtCore/QObject>
#include "DependencyManager.h"
#include <PointerManager.h>
#include <Pick.h>
/**jsdoc
* The Pointers API lets you create and manage objects for repeatedly calculating intersections in different ways, as well as the visual representation of those objects.
* Pointers can also be configured to automatically generate PointerEvents.
*
* @namespace Pointers
*/
class PointerScriptingInterface : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
public:
unsigned int createLaserPointer(const QVariant& properties) const;
unsigned int createStylus(const QVariant& properties) const;
/**jsdoc
* A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Also contains the relevant {@link Picks.PickProperties} to define the underlying Pick.
*
* Different {@link PickType}s use different properties, and within one PickType, the properties you choose can lead to a wide range of behaviors. For example,
* with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pointer, a Mouse Ray Pointer, or a Joint Ray Pointer.
*
* @typedef {Object} Pointers.PointerProperties
* @property {boolean} [hover=false] If this Pointer should generate hover events.
* @property {boolean} [faceAvatar=false] Ray Pointers only. If true, the end of the Pointer will always rotate to face the avatar.
* @property {boolean} [centerEndY=true] Ray Pointers only. If false, the end of the Pointer will be moved up by half of its height.
* @property {boolean} [lockEnd=false] Ray Pointers only. If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing.
* @property {boolean} [distanceScaleEnd=false] Ray Pointers only. If true, the dimensions of the end of the Pointer will scale linearly with distance.
* @property {boolean} [scaleWithAvatar=false] Ray Pointers only. If true, the width of the Pointer's path will scale linearly with your avatar's scale.
* @property {Pointers.RayPointerRenderState[]} [renderStates] Ray Pointers only. A list of different visual states to switch between.
* @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] Ray Pointers only. A list of different visual states to use if there is no intersection.
* @property {Pointers.Trigger[]} [triggers] Ray Pointers only. A list of different triggers mechanisms that control this Pointer's click event generation.
*/
/**jsdoc
* A set of properties used to define the visual aspect of a Ray Pointer in the case that the Pointer is intersecting something.
*
* @typedef {Object} Pointers.RayPointerRenderState
* @property {string} name The name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState}
* @property {OverlayProperties} [start] All of the properties you would normally pass to {@Overlays.addOverlay}, plus the type (as a <code>type</code> field).
* An overlay to represent the beginning of the Ray Pointer, if desired.
* @property {OverlayProperties} [path] All of the properties you would normally pass to {@Overlays.addOverlay}, plus the type (as a <code>type</code> field), which <b>must</b> be "line3d".
* An overlay to represent the path of the Ray Pointer, if desired.
* @property {OverlayProperties} [end] All of the properties you would normally pass to {@Overlays.addOverlay}, plus the type (as a <code>type</code> field).
* An overlay to represent the end of the Ray Pointer, if desired.
*/
/**jsdoc
* A set of properties used to define the visual aspect of a Ray Pointer in the case that the Pointer is not intersecting something. Same as a {@link Pointers.RayPointerRenderState},
* but with an additional distance field.
*
* @typedef {Object} Pointers.DefaultRayPointerRenderState
* @augments Pointers.RayPointerRenderState
* @property {number} distance The distance at which to render the end of this Ray Pointer, if one is defined.
*/
/**jsdoc
* A trigger mechanism for Ray Pointers.
*
* @typedef {Object} Pointers.Trigger
* @property {Controller.Action} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger <code>button</code>.
* @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered,
* it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None".
*/
/**jsdoc
* Adds a new Pointer
* @function Pointers.createPointer
* @param {Picks.PickType} type A PickType that specifies the method of picking to use
* @param {Pointers.PointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer <b>and</b> the {@link Picks.PickProperties} for the Pick that
* this Pointer will use to do its picking.
* @returns {number} The ID of the created Pointer. Used for managing the Pointer. 0 if invalid.
*
* @example <caption>Create a left hand Ray Pointer that triggers events on left controller trigger click and changes color when it's intersecting something.</caption>
*
* var end = {
* type: "sphere",
* dimensions: {x:0.5, y:0.5, z:0.5},
* solid: true,
* color: {red:0, green:255, blue:0},
* ignoreRayIntersection: true
* };
* var end2 = {
* type: "sphere",
* dimensions: {x:0.5, y:0.5, z:0.5},
* solid: true,
* color: {red:255, green:0, blue:0},
* ignoreRayIntersection: true
* };
*
* var renderStates = [ {name: "test", end: end} ];
* var defaultRenderStates = [ {name: "test", distance: 10.0, end: end2} ];
* var pointer = Pointers.createPointer(PickType.Ray, {
* joint: "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND",
* filter: Picks.PICK_OVERLAYS | Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE,
* renderStates: renderStates,
* defaultRenderStates: defaultRenderStates,
* distanceScaleEnd: true,
* triggers: [ {action: Controller.Standard.LTClick, button: "Focus"}, {action: Controller.Standard.LTClick, button: "Primary"} ],
* hover: true,
* enabled: true
* });
* Pointers.setRenderState(pointer, "test");
*/
Q_INVOKABLE unsigned int createPointer(const PickQuery::PickType& type, const QVariant& properties);
/**jsdoc
* Enables a Pointer.
* @function Pointers.enablePointer
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
*/
Q_INVOKABLE void enablePointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->enablePointer(uid); }
/**jsdoc
* Disables a Pointer.
* @function Pointers.disablePointer
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
*/
Q_INVOKABLE void disablePointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->disablePointer(uid); }
/**jsdoc
* Removes a Pointer.
* @function Pointers.removePointer
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
*/
Q_INVOKABLE void removePointer(unsigned int uid) const { DependencyManager::get<PointerManager>()->removePointer(uid); }
/**jsdoc
* Edit some visual aspect of a Pointer. Currently only supported for Ray Pointers.
* @function Pointers.editRenderState
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
* @param {string} renderState The name of the render state you want to edit.
* @param {RenderState} properties The new properties for <code>renderState</code>. For Ray Pointers, a {@link Pointers.RayPointerRenderState}.
*/
Q_INVOKABLE void editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const;
/**jsdoc
* Set the render state of a Pointer. For Ray Pointers, this means switching between their {@link Pointers.RayPointerRenderState}s, or "" to turn off rendering and hover/trigger events.
* For Stylus Pointers, there are three built-in options: "events on" (render and send events, the default), "events off" (render but don't send events), and "disabled" (don't render, don't send events).
* @function Pointers.setRenderState
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
* @param {string} renderState The name of the render state to which you want to switch.
*/
Q_INVOKABLE void setRenderState(unsigned int uid, const QString& renderState) const { DependencyManager::get<PointerManager>()->setRenderState(uid, renderState.toStdString()); }
/**jsdoc
* Get the most recent pick result from this Pointer. This will be updated as long as the Pointer is enabled, regardless of the render state.
* @function Pointers.getPrevPickResult
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
* @returns {PickResult} The most recent intersection result. This will be slightly different for different PickTypes. See {@link Picks.RayPickResult} and {@link Picks.StylusPickResult}.
*/
Q_INVOKABLE QVariantMap getPrevPickResult(unsigned int uid) const;
/**jsdoc
* Sets whether or not to use precision picking.
* @function Pointers.setPrecisionPicking
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
* @param {boolean} precisionPicking Whether or not to use precision picking
*/
Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking) const { DependencyManager::get<PointerManager>()->setPrecisionPicking(uid, precisionPicking); }
/**jsdoc
* Sets the length of this Pointer. No effect on Stylus Pointers.
* @function Pointers.setLength
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
* @param {float} length The desired length of the Pointer.
*/
Q_INVOKABLE void setLength(unsigned int uid, float length) const { DependencyManager::get<PointerManager>()->setLength(uid, length); }
/**jsdoc
* Sets a list of Entity IDs, Overlay IDs, and/or Avatar IDs to ignore during intersection. Not used by Stylus Pointers.
* @function Pointers.setIgnoreItems
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
* @param {Uuid[]} ignoreItems A list of IDs to ignore.
*/
Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities) const;
/**jsdoc
* Sets a list of Entity IDs, Overlay IDs, and/or Avatar IDs to include during intersection, instead of intersecting with everything. Stylus
* Pointers <b>only</b> intersect with objects in their include list.
* @function Pointers.setIncludeItems
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
* @param {Uuid[]} includeItems A list of IDs to include.
*/
Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities) const;
/**jsdoc
* Lock a Pointer onto a specific object (overlay, entity, or avatar). Optionally, provide an offset in object-space, otherwise the Pointer will lock on to the center of the object.
* Not used by Stylus Pointers.
* @function Pointers.setLockEndUUID
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
* @param {QUuid} objectID The ID of the object to which to lock on.
* @param {boolean} isOverlay False for entities or avatars, true for overlays
* @param {Mat4} [offsetMat] The offset matrix to use if you do not want to lock on to the center of the object.
*/
Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) const { DependencyManager::get<PointerManager>()->setLockEndUUID(uid, objectID, isOverlay, offsetMat); }
/**jsdoc
* Check if a Pointer is associated with the left hand.
* @function Pointers.isLeftHand
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
* @returns {boolean} True if the Pointer is a Joint Ray Pointer with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pointer with hand == 0
*/
Q_INVOKABLE bool isLeftHand(unsigned int uid) { return DependencyManager::get<PointerManager>()->isLeftHand(uid); }
/**jsdoc
* Check if a Pointer is associated with the right hand.
* @function Pointers.isRightHand
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
* @returns {boolean} True if the Pointer is a Joint Ray Pointer with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pointer with hand == 1
*/
Q_INVOKABLE bool isRightHand(unsigned int uid) { return DependencyManager::get<PointerManager>()->isRightHand(uid); }
/**jsdoc
* Check if a Pointer is associated with the system mouse.
* @function Pointers.isMouse
* @param {number} uid The ID of the Pointer, as returned by {@link Pointers.createPointer}.
* @returns {boolean} True if the Pointer is a Mouse Ray Pointer, false otherwise.
*/
Q_INVOKABLE bool isMouse(unsigned int uid) { return DependencyManager::get<PointerManager>()->isMouse(uid); }
};
#endif // hifi_PointerScriptingInterface_h

View file

@ -0,0 +1,106 @@
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "RayPick.h"
#include "Application.h"
#include "EntityScriptingInterface.h"
#include "ui/overlays/Overlays.h"
#include "avatar/AvatarManager.h"
#include "scripting/HMDScriptingInterface.h"
#include "DependencyManager.h"
PickResultPointer RayPick::getEntityIntersection(const PickRay& pick) {
RayToEntityIntersectionResult entityRes =
DependencyManager::get<EntityScriptingInterface>()->findRayIntersectionVector(pick, !getFilter().doesPickCoarse(),
getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
if (entityRes.intersects) {
return std::make_shared<RayPickResult>(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, pick, entityRes.surfaceNormal);
} else {
return std::make_shared<RayPickResult>(pick.toVariantMap());
}
}
PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) {
RayToOverlayIntersectionResult overlayRes =
qApp->getOverlays().findRayIntersectionVector(pick, !getFilter().doesPickCoarse(),
getIncludeItemsAs<OverlayID>(), getIgnoreItemsAs<OverlayID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
if (overlayRes.intersects) {
return std::make_shared<RayPickResult>(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, pick, overlayRes.surfaceNormal);
} else {
return std::make_shared<RayPickResult>(pick.toVariantMap());
}
}
PickResultPointer RayPick::getAvatarIntersection(const PickRay& pick) {
RayToAvatarIntersectionResult avatarRes = DependencyManager::get<AvatarManager>()->findRayIntersectionVector(pick, getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>());
if (avatarRes.intersects) {
return std::make_shared<RayPickResult>(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick);
} else {
return std::make_shared<RayPickResult>(pick.toVariantMap());
}
}
PickResultPointer RayPick::getHUDIntersection(const PickRay& pick) {
glm::vec3 hudRes = DependencyManager::get<HMDScriptingInterface>()->calculateRayUICollisionPoint(pick.origin, pick.direction);
return std::make_shared<RayPickResult>(IntersectionType::HUD, QUuid(), glm::distance(pick.origin, hudRes), hudRes, pick);
}
glm::vec3 RayPick::intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat& rotation, const glm::vec3& registration) {
// TODO: take into account registration
glm::vec3 n = rotation * Vectors::FRONT;
float t = glm::dot(n, point - origin) / glm::dot(n, direction);
return origin + t * direction;
}
glm::vec3 RayPick::intersectRayWithOverlayXYPlane(const QUuid& overlayID, const glm::vec3& origin, const glm::vec3& direction) {
glm::vec3 position = vec3FromVariant(qApp->getOverlays().getProperty(overlayID, "position").value);
glm::quat rotation = quatFromVariant(qApp->getOverlays().getProperty(overlayID, "rotation").value);
return intersectRayWithXYPlane(origin, direction, position, rotation, ENTITY_ITEM_DEFAULT_REGISTRATION_POINT);
}
glm::vec3 RayPick::intersectRayWithEntityXYPlane(const QUuid& entityID, const glm::vec3& origin, const glm::vec3& direction) {
auto props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID);
return intersectRayWithXYPlane(origin, direction, props.getPosition(), props.getRotation(), props.getRegistrationPoint());
}
glm::vec2 RayPick::projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized) {
glm::quat invRot = glm::inverse(rotation);
glm::vec3 localPos = invRot * (worldPos - position);
glm::vec3 normalizedPos = (localPos / dimensions) + registrationPoint;
glm::vec2 pos2D = glm::vec2(normalizedPos.x, (1.0f - normalizedPos.y));
if (unNormalized) {
pos2D *= glm::vec2(dimensions.x, dimensions.y);
}
return pos2D;
}
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);
}
return projectOntoXYPlane(worldPos, position, rotation, dimensions, ENTITY_ITEM_DEFAULT_REGISTRATION_POINT, unNormalized);
}
glm::vec2 RayPick::projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized) {
auto props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID);
return projectOntoXYPlane(worldPos, props.getPosition(), props.getRotation(), props.getDimensions(), props.getRegistrationPoint(), unNormalized);
}

View file

@ -0,0 +1,89 @@
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_RayPick_h
#define hifi_RayPick_h
#include <RegisteredMetaTypes.h>
#include <Pick.h>
class EntityItemID;
class OverlayID;
class RayPickResult : public PickResult {
public:
RayPickResult() {}
RayPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {}
RayPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN)) :
PickResult(searchRay.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal) {
}
RayPickResult(const RayPickResult& rayPickResult) : PickResult(rayPickResult.pickVariant) {
type = rayPickResult.type;
intersects = rayPickResult.intersects;
objectID = rayPickResult.objectID;
distance = rayPickResult.distance;
intersection = rayPickResult.intersection;
surfaceNormal = rayPickResult.surfaceNormal;
}
IntersectionType type { NONE };
bool intersects { false };
QUuid objectID;
float distance { FLT_MAX };
glm::vec3 intersection { NAN };
glm::vec3 surfaceNormal { NAN };
virtual QVariantMap toVariantMap() const override {
QVariantMap toReturn;
toReturn["type"] = type;
toReturn["intersects"] = intersects;
toReturn["objectID"] = objectID;
toReturn["distance"] = distance;
toReturn["intersection"] = vec3toVariant(intersection);
toReturn["surfaceNormal"] = vec3toVariant(surfaceNormal);
toReturn["searchRay"] = PickResult::toVariantMap();
return toReturn;
}
bool doesIntersect() const override { return intersects; }
bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return distance < maxDistance; }
PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override {
auto newRayRes = std::static_pointer_cast<RayPickResult>(newRes);
if (newRayRes->distance < distance) {
return std::make_shared<RayPickResult>(*newRayRes);
} else {
return std::make_shared<RayPickResult>(*this);
}
}
};
class RayPick : public Pick<PickRay> {
public:
RayPick(const PickFilter& filter, float maxDistance, bool enabled) : Pick(filter, maxDistance, enabled) {}
PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { return std::make_shared<RayPickResult>(pickVariant); }
PickResultPointer getEntityIntersection(const PickRay& pick) override;
PickResultPointer getOverlayIntersection(const PickRay& pick) override;
PickResultPointer getAvatarIntersection(const PickRay& pick) override;
PickResultPointer getHUDIntersection(const PickRay& pick) override;
// These are helper functions for projecting and intersecting rays
static glm::vec3 intersectRayWithEntityXYPlane(const QUuid& entityID, const glm::vec3& origin, const glm::vec3& direction);
static glm::vec3 intersectRayWithOverlayXYPlane(const QUuid& overlayID, const glm::vec3& origin, const glm::vec3& direction);
static glm::vec2 projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized = true);
static glm::vec2 projectOntoOverlayXYPlane(const QUuid& overlayID, const glm::vec3& worldPos, bool unNormalized = true);
private:
static glm::vec3 intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat& rotation, const glm::vec3& registration);
static glm::vec2 projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized);
};
#endif // hifi_RayPick_h

View file

@ -1,217 +0,0 @@
//
// RayPickManager.cpp
// interface/src/raypick
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "RayPickManager.h"
#include <pointers/rays/StaticRayPick.h>
#include "Application.h"
#include "EntityScriptingInterface.h"
#include "ui/overlays/Overlays.h"
#include "avatar/AvatarManager.h"
#include "scripting/HMDScriptingInterface.h"
#include "DependencyManager.h"
#include "JointRayPick.h"
#include "MouseRayPick.h"
bool RayPickManager::checkAndCompareCachedResults(QPair<glm::vec3, glm::vec3>& ray, RayPickCache& cache, RayPickResult& res, const RayCacheKey& key) {
if (cache.contains(ray) && cache[ray].find(key) != cache[ray].end()) {
if (cache[ray][key].distance < res.distance) {
res = cache[ray][key];
}
return true;
}
return false;
}
void RayPickManager::cacheResult(const bool intersects, const RayPickResult& resTemp, const RayCacheKey& key, RayPickResult& res, QPair<glm::vec3, glm::vec3>& ray, RayPickCache& cache) {
if (intersects) {
cache[ray][key] = resTemp;
if (resTemp.distance < res.distance) {
res = resTemp;
}
} else {
cache[ray][key] = RayPickResult(res.searchRay);
}
}
void RayPickManager::update() {
RayPickCache results;
QHash<QUuid, RayPick::Pointer> cachedRayPicks;
withReadLock([&] {
cachedRayPicks = _rayPicks;
});
for (const auto& uid : cachedRayPicks.keys()) {
std::shared_ptr<RayPick> rayPick = cachedRayPicks[uid];
if (!rayPick->isEnabled() || rayPick->getFilter().doesPickNothing() || rayPick->getMaxDistance() < 0.0f) {
continue;
}
PickRay ray;
{
bool valid;
ray = rayPick->getPickRay(valid);
if (!valid) {
continue;
}
}
QPair<glm::vec3, glm::vec3> rayKey = QPair<glm::vec3, glm::vec3>(ray.origin, ray.direction);
RayPickResult res = RayPickResult(ray);
if (rayPick->getFilter().doesPickEntities()) {
RayToEntityIntersectionResult entityRes;
bool fromCache = true;
bool invisible = rayPick->getFilter().doesPickInvisible();
bool nonCollidable = rayPick->getFilter().doesPickNonCollidable();
RayCacheKey entityKey = { rayPick->getFilter().getEntityFlags(), rayPick->getIncludeItems(), rayPick->getIgnoreItems() };
if (!checkAndCompareCachedResults(rayKey, results, res, entityKey)) {
entityRes = DependencyManager::get<EntityScriptingInterface>()->findRayIntersectionVector(ray, !rayPick->getFilter().doesPickCoarse(),
rayPick->getIncludeItemsAs<EntityItemID>(), rayPick->getIgnoreItemsAs<EntityItemID>(), !invisible, !nonCollidable);
fromCache = false;
}
if (!fromCache) {
cacheResult(entityRes.intersects, RayPickResult(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, ray, entityRes.surfaceNormal),
entityKey, res, rayKey, results);
}
}
if (rayPick->getFilter().doesPickOverlays()) {
RayToOverlayIntersectionResult overlayRes;
bool fromCache = true;
bool invisible = rayPick->getFilter().doesPickInvisible();
bool nonCollidable = rayPick->getFilter().doesPickNonCollidable();
RayCacheKey overlayKey = { rayPick->getFilter().getOverlayFlags(), rayPick->getIncludeItems(), rayPick->getIgnoreItems() };
if (!checkAndCompareCachedResults(rayKey, results, res, overlayKey)) {
overlayRes = qApp->getOverlays().findRayIntersectionVector(ray, !rayPick->getFilter().doesPickCoarse(),
rayPick->getIncludeItemsAs<OverlayID>(), rayPick->getIgnoreItemsAs<OverlayID>(), !invisible, !nonCollidable);
fromCache = false;
}
if (!fromCache) {
cacheResult(overlayRes.intersects, RayPickResult(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.intersection, ray, overlayRes.surfaceNormal),
overlayKey, res, rayKey, results);
}
}
if (rayPick->getFilter().doesPickAvatars()) {
RayCacheKey avatarKey = { rayPick->getFilter().getAvatarFlags(), rayPick->getIncludeItems(), rayPick->getIgnoreItems() };
if (!checkAndCompareCachedResults(rayKey, results, res, avatarKey)) {
RayToAvatarIntersectionResult avatarRes = DependencyManager::get<AvatarManager>()->findRayIntersectionVector(ray,
rayPick->getIncludeItemsAs<EntityItemID>(), rayPick->getIgnoreItemsAs<EntityItemID>());
cacheResult(avatarRes.intersects, RayPickResult(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, ray), avatarKey, res, rayKey, results);
}
}
// Can't intersect with HUD in desktop mode
if (rayPick->getFilter().doesPickHUD() && DependencyManager::get<HMDScriptingInterface>()->isHMDMode()) {
RayCacheKey hudKey = { rayPick->getFilter().getHUDFlags(), QVector<QUuid>(), QVector<QUuid>() };
if (!checkAndCompareCachedResults(rayKey, results, res, hudKey)) {
glm::vec3 hudRes = DependencyManager::get<HMDScriptingInterface>()->calculateRayUICollisionPoint(ray.origin, ray.direction);
cacheResult(true, RayPickResult(IntersectionType::HUD, 0, glm::distance(ray.origin, hudRes), hudRes, ray), hudKey, res, rayKey, results);
}
}
if (rayPick->getMaxDistance() == 0.0f || (rayPick->getMaxDistance() > 0.0f && res.distance < rayPick->getMaxDistance())) {
rayPick->setRayPickResult(res);
} else {
rayPick->setRayPickResult(RayPickResult(ray));
}
}
}
QUuid RayPickManager::createRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const RayPickFilter& filter, float maxDistance, bool enabled) {
auto newRayPick = std::make_shared<JointRayPick>(jointName, posOffset, dirOffset, filter, maxDistance, enabled);
QUuid id = QUuid::createUuid();
withWriteLock([&] {
_rayPicks[id] = newRayPick;
});
return id;
}
QUuid RayPickManager::createRayPick(const RayPickFilter& filter, float maxDistance, bool enabled) {
QUuid id = QUuid::createUuid();
auto newRayPick = std::make_shared<MouseRayPick>(filter, maxDistance, enabled);
withWriteLock([&] {
_rayPicks[id] = newRayPick;
});
return id;
}
QUuid RayPickManager::createRayPick(const glm::vec3& position, const glm::vec3& direction, const RayPickFilter& filter, float maxDistance, bool enabled) {
QUuid id = QUuid::createUuid();
auto newRayPick = std::make_shared<StaticRayPick>(position, direction, filter, maxDistance, enabled);
withWriteLock([&] {
_rayPicks[id] = newRayPick;
});
return id;
}
void RayPickManager::removeRayPick(const QUuid& uid) {
withWriteLock([&] {
_rayPicks.remove(uid);
});
}
RayPick::Pointer RayPickManager::findRayPick(const QUuid& uid) const {
return resultWithReadLock<RayPick::Pointer>([&] {
if (_rayPicks.contains(uid)) {
return _rayPicks[uid];
}
return RayPick::Pointer();
});
}
void RayPickManager::enableRayPick(const QUuid& uid) const {
auto rayPick = findRayPick(uid);
if (rayPick) {
rayPick->enable();
}
}
void RayPickManager::disableRayPick(const QUuid& uid) const {
auto rayPick = findRayPick(uid);
if (rayPick) {
rayPick->disable();
}
}
RayPickResult RayPickManager::getPrevRayPickResult(const QUuid& uid) const {
auto rayPick = findRayPick(uid);
if (rayPick) {
return rayPick->getPrevRayPickResult();
}
return RayPickResult();
}
void RayPickManager::setPrecisionPicking(const QUuid& uid, bool precisionPicking) const {
auto rayPick = findRayPick(uid);
if (rayPick) {
rayPick->setPrecisionPicking(precisionPicking);
}
}
void RayPickManager::setIgnoreItems(const QUuid& uid, const QVector<QUuid>& ignore) const {
auto rayPick = findRayPick(uid);
if (rayPick) {
rayPick->setIgnoreItems(ignore);
}
}
void RayPickManager::setIncludeItems(const QUuid& uid, const QVector<QUuid>& include) const {
auto rayPick = findRayPick(uid);
if (rayPick) {
rayPick->setIncludeItems(include);
}
}

View file

@ -1,74 +0,0 @@
//
// RayPickManager.h
// interface/src/raypick
//
// Created by Sam Gondelman 7/11/2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_RayPickManager_h
#define hifi_RayPickManager_h
#include <memory>
#include <unordered_map>
#include <queue>
#include <QtCore/QObject>
#include <RegisteredMetaTypes.h>
#include <pointers/rays/RayPick.h>
class RayPickResult;
typedef struct RayCacheKey {
RayPickFilter::Flags mask;
QVector<QUuid> include;
QVector<QUuid> ignore;
bool operator==(const RayCacheKey& other) const {
return (mask == other.mask && include == other.include && ignore == other.ignore);
}
} RayCacheKey;
namespace std {
template <>
struct hash<RayCacheKey> {
size_t operator()(const RayCacheKey& k) const {
return ((hash<RayPickFilter::Flags>()(k.mask) ^ (qHash(k.include) << 1)) >> 1) ^ (qHash(k.ignore) << 1);
}
};
}
class RayPickManager : protected ReadWriteLockable {
public:
void update();
QUuid createRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const RayPickFilter& filter, const float maxDistance, const bool enabled);
QUuid createRayPick(const RayPickFilter& filter, const float maxDistance, const bool enabled);
QUuid createRayPick(const glm::vec3& position, const glm::vec3& direction, const RayPickFilter& filter, const float maxDistance, const bool enabled);
void removeRayPick(const QUuid& uid);
void enableRayPick(const QUuid& uid) const;
void disableRayPick(const QUuid& uid) const;
RayPickResult getPrevRayPickResult(const QUuid& uid) const;
void setPrecisionPicking(const QUuid& uid, bool precisionPicking) const;
void setIgnoreItems(const QUuid& uid, const QVector<QUuid>& ignore) const;
void setIncludeItems(const QUuid& uid, const QVector<QUuid>& include) const;
private:
RayPick::Pointer findRayPick(const QUuid& uid) const;
QHash<QUuid, RayPick::Pointer> _rayPicks;
typedef QHash<QPair<glm::vec3, glm::vec3>, std::unordered_map<RayCacheKey, RayPickResult>> RayPickCache;
// Returns true if this ray exists in the cache, and if it does, update res if the cached result is closer
bool checkAndCompareCachedResults(QPair<glm::vec3, glm::vec3>& ray, RayPickCache& cache, RayPickResult& res, const RayCacheKey& key);
void cacheResult(const bool intersects, const RayPickResult& resTemp, const RayCacheKey& key, RayPickResult& res, QPair<glm::vec3, glm::vec3>& ray, RayPickCache& cache);
};
#endif // hifi_RayPickManager_h

View file

@ -13,83 +13,58 @@
#include <QVariant>
#include "GLMHelpers.h"
#include "Application.h"
QUuid RayPickScriptingInterface::createRayPick(const QVariant& properties) {
QVariantMap propMap = properties.toMap();
#include <PickManager.h>
bool enabled = false;
if (propMap["enabled"].isValid()) {
enabled = propMap["enabled"].toBool();
#include "StaticRayPick.h"
#include "JointRayPick.h"
#include "MouseRayPick.h"
unsigned int RayPickScriptingInterface::createRayPick(const QVariant& properties) {
return DependencyManager::get<PickScriptingInterface>()->createRayPick(properties);
}
void RayPickScriptingInterface::enableRayPick(unsigned int uid) {
DependencyManager::get<PickManager>()->enablePick(uid);
}
void RayPickScriptingInterface::disableRayPick(unsigned int uid) {
DependencyManager::get<PickManager>()->disablePick(uid);
}
void RayPickScriptingInterface::removeRayPick(unsigned int uid) {
DependencyManager::get<PickManager>()->removePick(uid);
}
QVariantMap RayPickScriptingInterface::getPrevRayPickResult(unsigned int uid) {
QVariantMap result;
auto pickResult = DependencyManager::get<PickManager>()->getPrevPickResult(uid);
if (pickResult) {
result = pickResult->toVariantMap();
}
RayPickFilter filter = RayPickFilter();
if (propMap["filter"].isValid()) {
filter = RayPickFilter(propMap["filter"].toUInt());
}
float maxDistance = 0.0f;
if (propMap["maxDistance"].isValid()) {
maxDistance = propMap["maxDistance"].toFloat();
}
if (propMap["joint"].isValid()) {
std::string jointName = propMap["joint"].toString().toStdString();
if (jointName != "Mouse") {
// x = upward, y = forward, z = lateral
glm::vec3 posOffset = Vectors::ZERO;
if (propMap["posOffset"].isValid()) {
posOffset = vec3FromVariant(propMap["posOffset"]);
}
glm::vec3 dirOffset = Vectors::UP;
if (propMap["dirOffset"].isValid()) {
dirOffset = vec3FromVariant(propMap["dirOffset"]);
}
return qApp->getRayPickManager().createRayPick(jointName, posOffset, dirOffset, filter, maxDistance, enabled);
} else {
return qApp->getRayPickManager().createRayPick(filter, maxDistance, enabled);
}
} else if (propMap["position"].isValid()) {
glm::vec3 position = vec3FromVariant(propMap["position"]);
glm::vec3 direction = -Vectors::UP;
if (propMap["direction"].isValid()) {
direction = vec3FromVariant(propMap["direction"]);
}
return qApp->getRayPickManager().createRayPick(position, direction, filter, maxDistance, enabled);
}
return QUuid();
return result;
}
void RayPickScriptingInterface::enableRayPick(const QUuid& uid) {
qApp->getRayPickManager().enableRayPick(uid);
void RayPickScriptingInterface::setPrecisionPicking(unsigned int uid, bool precisionPicking) {
DependencyManager::get<PickManager>()->setPrecisionPicking(uid, precisionPicking);
}
void RayPickScriptingInterface::disableRayPick(const QUuid& uid) {
qApp->getRayPickManager().disableRayPick(uid);
void RayPickScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) {
DependencyManager::get<PickManager>()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems));
}
void RayPickScriptingInterface::removeRayPick(const QUuid& uid) {
qApp->getRayPickManager().removeRayPick(uid);
void RayPickScriptingInterface::setIncludeItems(unsigned int uid, const QScriptValue& includeItems) {
DependencyManager::get<PickManager>()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems));
}
RayPickResult RayPickScriptingInterface::getPrevRayPickResult(const QUuid& uid) {
return qApp->getRayPickManager().getPrevRayPickResult(uid);
bool RayPickScriptingInterface::isLeftHand(unsigned int uid) {
return DependencyManager::get<PickManager>()->isLeftHand(uid);
}
void RayPickScriptingInterface::setPrecisionPicking(const QUuid& uid, const bool precisionPicking) {
qApp->getRayPickManager().setPrecisionPicking(uid, precisionPicking);
bool RayPickScriptingInterface::isRightHand(unsigned int uid) {
return DependencyManager::get<PickManager>()->isRightHand(uid);
}
void RayPickScriptingInterface::setIgnoreItems(const QUuid& uid, const QScriptValue& ignoreItems) {
qApp->getRayPickManager().setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems));
}
void RayPickScriptingInterface::setIncludeItems(const QUuid& uid, const QScriptValue& includeItems) {
qApp->getRayPickManager().setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems));
}
bool RayPickScriptingInterface::isMouse(unsigned int uid) {
return DependencyManager::get<PickManager>()->isMouse(uid);
}

View file

@ -13,9 +13,10 @@
#include <QtCore/QObject>
#include <RegisteredMetaTypes.h>
#include "RegisteredMetaTypes.h"
#include <DependencyManager.h>
#include <pointers/rays/RayPick.h>
#include "PickScriptingInterface.h"
class RayPickScriptingInterface : public QObject, public Dependency {
Q_OBJECT
@ -35,31 +36,36 @@ class RayPickScriptingInterface : public QObject, public Dependency {
Q_PROPERTY(unsigned int INTERSECTED_HUD READ INTERSECTED_HUD CONSTANT)
SINGLETON_DEPENDENCY
public:
Q_INVOKABLE unsigned int createRayPick(const QVariant& properties);
Q_INVOKABLE void enableRayPick(unsigned int uid);
Q_INVOKABLE void disableRayPick(unsigned int uid);
Q_INVOKABLE void removeRayPick(unsigned int uid);
Q_INVOKABLE QVariantMap getPrevRayPickResult(unsigned int uid);
Q_INVOKABLE void setPrecisionPicking(unsigned int uid, bool precisionPicking);
Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities);
Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities);
Q_INVOKABLE bool isLeftHand(unsigned int uid);
Q_INVOKABLE bool isRightHand(unsigned int uid);
Q_INVOKABLE bool isMouse(unsigned int uid);
public slots:
Q_INVOKABLE QUuid createRayPick(const QVariant& properties);
Q_INVOKABLE void enableRayPick(const QUuid& uid);
Q_INVOKABLE void disableRayPick(const QUuid& uid);
Q_INVOKABLE void removeRayPick(const QUuid& uid);
Q_INVOKABLE RayPickResult getPrevRayPickResult(const QUuid& uid);
Q_INVOKABLE void setPrecisionPicking(const QUuid& uid, const bool precisionPicking);
Q_INVOKABLE void setIgnoreItems(const QUuid& uid, const QScriptValue& ignoreEntities);
Q_INVOKABLE void setIncludeItems(const QUuid& uid, const QScriptValue& includeEntities);
unsigned int PICK_NOTHING() { return 0; }
unsigned int PICK_ENTITIES() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_ENTITIES); }
unsigned int PICK_OVERLAYS() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_OVERLAYS); }
unsigned int PICK_AVATARS() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_AVATARS); }
unsigned int PICK_HUD() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_HUD); }
unsigned int PICK_COARSE() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_COARSE); }
unsigned int PICK_INCLUDE_INVISIBLE() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_INCLUDE_INVISIBLE); }
unsigned int PICK_INCLUDE_NONCOLLIDABLE() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_INCLUDE_NONCOLLIDABLE); }
unsigned int PICK_ALL_INTERSECTIONS() { return RayPickFilter::getBitMask(RayPickFilter::FlagBit::PICK_ALL_INTERSECTIONS); }
unsigned int INTERSECTED_NONE() { return IntersectionType::NONE; }
unsigned int INTERSECTED_ENTITY() { return IntersectionType::ENTITY; }
unsigned int INTERSECTED_OVERLAY() { return IntersectionType::OVERLAY; }
unsigned int INTERSECTED_AVATAR() { return IntersectionType::AVATAR; }
unsigned int INTERSECTED_HUD() { return IntersectionType::HUD; }
static unsigned int PICK_NOTHING() { return PickScriptingInterface::PICK_NOTHING(); }
static unsigned int PICK_ENTITIES() { return PickScriptingInterface::PICK_ENTITIES(); }
static unsigned int PICK_OVERLAYS() { return PickScriptingInterface::PICK_OVERLAYS(); }
static unsigned int PICK_AVATARS() { return PickScriptingInterface::PICK_AVATARS(); }
static unsigned int PICK_HUD() { return PickScriptingInterface::PICK_HUD(); }
static unsigned int PICK_COARSE() { return PickScriptingInterface::PICK_COARSE(); }
static unsigned int PICK_INCLUDE_INVISIBLE() { return PickScriptingInterface::PICK_INCLUDE_INVISIBLE(); }
static unsigned int PICK_INCLUDE_NONCOLLIDABLE() { return PickScriptingInterface::PICK_INCLUDE_NONCOLLIDABLE(); }
static unsigned int PICK_ALL_INTERSECTIONS() { return PickScriptingInterface::PICK_ALL_INTERSECTIONS(); }
static unsigned int INTERSECTED_NONE() { return PickScriptingInterface::INTERSECTED_NONE(); }
static unsigned int INTERSECTED_ENTITY() { return PickScriptingInterface::INTERSECTED_ENTITY(); }
static unsigned int INTERSECTED_OVERLAY() { return PickScriptingInterface::INTERSECTED_OVERLAY(); }
static unsigned int INTERSECTED_AVATAR() { return PickScriptingInterface::INTERSECTED_AVATAR(); }
static unsigned int INTERSECTED_HUD() { return PickScriptingInterface::INTERSECTED_HUD(); }
};
#endif // hifi_RayPickScriptingInterface_h

View file

@ -7,13 +7,12 @@
//
#include "StaticRayPick.h"
StaticRayPick::StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const RayPickFilter& filter, const float maxDistance, const bool enabled) :
StaticRayPick::StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const PickFilter& filter, float maxDistance, bool enabled) :
RayPick(filter, maxDistance, enabled),
_pickRay(position, direction)
{
}
const PickRay StaticRayPick::getPickRay(bool& valid) const {
valid = true;
PickRay StaticRayPick::getMathematicalPick() const {
return _pickRay;
}

View file

@ -13,9 +13,9 @@
class StaticRayPick : public RayPick {
public:
StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const RayPickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false);
StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false);
const PickRay getPickRay(bool& valid) const override;
PickRay getMathematicalPick() const override;
private:
PickRay _pickRay;

View file

@ -0,0 +1,228 @@
//
// Created by Bradley Austin Davis on 2017/10/24
// Copyright 2013-2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "StylusPick.h"
#include "RayPick.h"
#include <glm/glm.hpp>
#include "ui/overlays/Base3DOverlay.h"
#include "Application.h"
#include <DependencyManager.h>
#include "avatar/AvatarManager.h"
#include <controllers/StandardControls.h>
#include <controllers/UserInputMapper.h>
using namespace bilateral;
// TODO: make these configurable per pick
static const float WEB_STYLUS_LENGTH = 0.2f;
static const float WEB_TOUCH_Y_OFFSET = 0.105f; // how far forward (or back with a negative number) to slide stylus in hand
static const glm::vec3 TIP_OFFSET = glm::vec3(0.0f, WEB_STYLUS_LENGTH - WEB_TOUCH_Y_OFFSET, 0.0f);
struct SideData {
QString avatarJoint;
QString cameraJoint;
controller::StandardPoseChannel channel;
controller::Hand hand;
glm::vec3 grabPointSphereOffset;
int getJointIndex(bool finger) {
const auto& jointName = finger ? avatarJoint : cameraJoint;
return DependencyManager::get<AvatarManager>()->getMyAvatar()->getJointIndex(jointName);
}
};
static const std::array<SideData, 2> SIDES{ { { "LeftHandIndex4",
"_CAMERA_RELATIVE_CONTROLLER_LEFTHAND",
controller::StandardPoseChannel::LEFT_HAND,
controller::Hand::LEFT,
{ -0.04f, 0.13f, 0.039f } },
{ "RightHandIndex4",
"_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND",
controller::StandardPoseChannel::RIGHT_HAND,
controller::Hand::RIGHT,
{ 0.04f, 0.13f, 0.039f } } } };
std::shared_ptr<PickResult> StylusPickResult::compareAndProcessNewResult(const std::shared_ptr<PickResult>& newRes) {
auto newStylusResult = std::static_pointer_cast<StylusPickResult>(newRes);
if (newStylusResult && newStylusResult->distance < distance) {
return std::make_shared<StylusPickResult>(*newStylusResult);
} else {
return std::make_shared<StylusPickResult>(*this);
}
}
bool StylusPickResult::checkOrFilterAgainstMaxDistance(float maxDistance) {
return distance < maxDistance;
}
StylusPick::StylusPick(Side side, const PickFilter& filter, float maxDistance, bool enabled) :
Pick(filter, maxDistance, enabled),
_side(side)
{
}
static StylusTip getFingerWorldLocation(Side side) {
const auto& sideData = SIDES[index(side)];
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
auto fingerJointIndex = myAvatar->getJointIndex(sideData.avatarJoint);
if (fingerJointIndex == -1) {
return StylusTip();
}
auto fingerPosition = myAvatar->getAbsoluteJointTranslationInObjectFrame(fingerJointIndex);
auto fingerRotation = myAvatar->getAbsoluteJointRotationInObjectFrame(fingerJointIndex);
auto avatarOrientation = myAvatar->getWorldOrientation();
auto avatarPosition = myAvatar->getWorldPosition();
StylusTip result;
result.side = side;
result.orientation = avatarOrientation * fingerRotation;
result.position = avatarPosition + (avatarOrientation * fingerPosition);
return result;
}
// controllerWorldLocation is where the controller would be, in-world, with an added offset
static StylusTip getControllerWorldLocation(Side side) {
static const std::array<controller::Input, 2> INPUTS{ { UserInputMapper::makeStandardInput(SIDES[0].channel),
UserInputMapper::makeStandardInput(SIDES[1].channel) } };
const auto sideIndex = index(side);
const auto& input = INPUTS[sideIndex];
const auto pose = DependencyManager::get<UserInputMapper>()->getPose(input);
const auto& valid = pose.valid;
StylusTip result;
if (valid) {
result.side = side;
const auto& sideData = SIDES[sideIndex];
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
float sensorScaleFactor = myAvatar->getSensorToWorldScale();
auto controllerJointIndex = myAvatar->getJointIndex(sideData.cameraJoint);
const auto avatarOrientation = myAvatar->getWorldOrientation();
const auto avatarPosition = myAvatar->getWorldPosition();
result.orientation = avatarOrientation * myAvatar->getAbsoluteJointRotationInObjectFrame(controllerJointIndex);
result.position = avatarPosition + (avatarOrientation * myAvatar->getAbsoluteJointTranslationInObjectFrame(controllerJointIndex));
// add to the real position so the grab-point is out in front of the hand, a bit
result.position += result.orientation * (sideData.grabPointSphereOffset * sensorScaleFactor);
// move the stylus forward a bit
result.position += result.orientation * (TIP_OFFSET * sensorScaleFactor);
auto worldControllerPos = avatarPosition + avatarOrientation * pose.translation;
// compute tip velocity from hand controller motion, it is more accurate than computing it from previous positions.
auto worldControllerLinearVel = avatarOrientation * pose.velocity;
auto worldControllerAngularVel = avatarOrientation * pose.angularVelocity;
result.velocity = worldControllerLinearVel + glm::cross(worldControllerAngularVel, result.position - worldControllerPos);
}
return result;
}
StylusTip StylusPick::getMathematicalPick() const {
StylusTip result;
if (qApp->getPreferAvatarFingerOverStylus()) {
result = getFingerWorldLocation(_side);
} else {
result = getControllerWorldLocation(_side);
}
return result;
}
PickResultPointer StylusPick::getDefaultResult(const QVariantMap& pickVariant) const {
return std::make_shared<StylusPickResult>(pickVariant);
}
PickResultPointer StylusPick::getEntityIntersection(const StylusTip& pick) {
std::vector<StylusPickResult> results;
for (const auto& target : getIncludeItems()) {
if (target.isNull()) {
continue;
}
auto entity = qApp->getEntities()->getTree()->findEntityByEntityItemID(target);
if (!entity) {
continue;
}
if (!entity->getVisible() && !getFilter().doesPickInvisible()) {
continue;
}
const auto entityRotation = entity->getWorldOrientation();
const auto entityPosition = entity->getWorldPosition();
glm::vec3 normal = entityRotation * Vectors::UNIT_Z;
float distance = glm::dot(pick.position - entityPosition, normal);
glm::vec3 intersection = pick.position - (normal * distance);
glm::vec2 pos2D = RayPick::projectOntoEntityXYPlane(target, intersection, false);
if (pos2D == glm::clamp(pos2D, glm::vec2(0), glm::vec2(1))) {
results.push_back(StylusPickResult(IntersectionType::ENTITY, target, distance, intersection, pick, normal));
}
}
StylusPickResult nearestTarget(pick.toVariantMap());
for (const auto& result : results) {
if (result.distance < nearestTarget.distance) {
nearestTarget = result;
}
}
return std::make_shared<StylusPickResult>(nearestTarget);
}
PickResultPointer StylusPick::getOverlayIntersection(const StylusTip& pick) {
std::vector<StylusPickResult> results;
for (const auto& target : getIncludeItems()) {
if (target.isNull()) {
continue;
}
auto overlay = qApp->getOverlays().getOverlay(target);
// Don't interact with non-3D or invalid overlays
if (!overlay || !overlay->is3D()) {
continue;
}
if (!overlay->getVisible() && !getFilter().doesPickInvisible()) {
continue;
}
auto overlay3D = std::static_pointer_cast<Base3DOverlay>(overlay);
const auto overlayRotation = overlay3D->getWorldOrientation();
const auto overlayPosition = overlay3D->getWorldPosition();
glm::vec3 normal = overlayRotation * Vectors::UNIT_Z;
float distance = glm::dot(pick.position - overlayPosition, normal);
glm::vec3 intersection = pick.position - (normal * distance);
glm::vec2 pos2D = RayPick::projectOntoOverlayXYPlane(target, intersection, false);
if (pos2D == glm::clamp(pos2D, glm::vec2(0), glm::vec2(1))) {
results.push_back(StylusPickResult(IntersectionType::OVERLAY, target, distance, intersection, pick, normal));
}
}
StylusPickResult nearestTarget(pick.toVariantMap());
for (const auto& result : results) {
if (result.distance < nearestTarget.distance) {
nearestTarget = result;
}
}
return std::make_shared<StylusPickResult>(nearestTarget);
}
PickResultPointer StylusPick::getAvatarIntersection(const StylusTip& pick) {
return std::make_shared<StylusPickResult>(pick.toVariantMap());
}
PickResultPointer StylusPick::getHUDIntersection(const StylusTip& pick) {
return std::make_shared<StylusPickResult>(pick.toVariantMap());
}

View file

@ -0,0 +1,77 @@
//
// Created by Bradley Austin Davis on 2017/10/24
// Copyright 2013-2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_StylusPick_h
#define hifi_StylusPick_h
#include <Pick.h>
#include "RegisteredMetaTypes.h"
class StylusPickResult : public PickResult {
using Side = bilateral::Side;
public:
StylusPickResult() {}
StylusPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {}
StylusPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const StylusTip& stylusTip,
const glm::vec3& surfaceNormal = glm::vec3(NAN)) :
PickResult(stylusTip.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal) {
}
StylusPickResult(const StylusPickResult& stylusPickResult) : PickResult(stylusPickResult.pickVariant) {
type = stylusPickResult.type;
intersects = stylusPickResult.intersects;
objectID = stylusPickResult.objectID;
distance = stylusPickResult.distance;
intersection = stylusPickResult.intersection;
surfaceNormal = stylusPickResult.surfaceNormal;
}
IntersectionType type { NONE };
bool intersects { false };
QUuid objectID;
float distance { FLT_MAX };
glm::vec3 intersection { NAN };
glm::vec3 surfaceNormal { NAN };
virtual QVariantMap toVariantMap() const override {
QVariantMap toReturn;
toReturn["type"] = type;
toReturn["intersects"] = intersects;
toReturn["objectID"] = objectID;
toReturn["distance"] = distance;
toReturn["intersection"] = vec3toVariant(intersection);
toReturn["surfaceNormal"] = vec3toVariant(surfaceNormal);
toReturn["stylusTip"] = PickResult::toVariantMap();
return toReturn;
}
bool doesIntersect() const override { return intersects; }
std::shared_ptr<PickResult> compareAndProcessNewResult(const std::shared_ptr<PickResult>& newRes) override;
bool checkOrFilterAgainstMaxDistance(float maxDistance) override;
};
class StylusPick : public Pick<StylusTip> {
using Side = bilateral::Side;
public:
StylusPick(Side side, const PickFilter& filter, float maxDistance, bool enabled);
StylusTip getMathematicalPick() const override;
PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override;
PickResultPointer getEntityIntersection(const StylusTip& pick) override;
PickResultPointer getOverlayIntersection(const StylusTip& pick) override;
PickResultPointer getAvatarIntersection(const StylusTip& pick) override;
PickResultPointer getHUDIntersection(const StylusTip& pick) override;
bool isLeftHand() const override { return _side == Side::Left; }
bool isRightHand() const override { return _side == Side::Right; }
private:
const Side _side;
};
#endif // hifi_StylusPick_h

View file

@ -0,0 +1,228 @@
//
// Created by Bradley Austin Davis on 2017/10/24
// Copyright 2013-2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "StylusPointer.h"
#include "RayPick.h"
#include "Application.h"
#include "avatar/AvatarManager.h"
#include "avatar/MyAvatar.h"
#include <DependencyManager.h>
#include "PickScriptingInterface.h"
#include <PickManager.h>
// TODO: make these configurable per pointer
static const float WEB_STYLUS_LENGTH = 0.2f;
static const float TABLET_MIN_HOVER_DISTANCE = -0.1f;
static const float TABLET_MAX_HOVER_DISTANCE = 0.1f;
static const float TABLET_MIN_TOUCH_DISTANCE = -0.1f;
static const float TABLET_MAX_TOUCH_DISTANCE = 0.01f;
static const float HOVER_HYSTERESIS = 0.01f;
static const float TOUCH_HYSTERESIS = 0.02f;
static const float STYLUS_MOVE_DELAY = 0.33f * USECS_PER_SECOND;
static const float TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0481f;
static const float TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED = TOUCH_PRESS_TO_MOVE_DEADSPOT * TOUCH_PRESS_TO_MOVE_DEADSPOT;
StylusPointer::StylusPointer(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled) :
Pointer(DependencyManager::get<PickScriptingInterface>()->createStylusPick(props), enabled, hover),
_stylusOverlay(stylusOverlay)
{
}
StylusPointer::~StylusPointer() {
if (!_stylusOverlay.isNull()) {
qApp->getOverlays().deleteOverlay(_stylusOverlay);
}
}
OverlayID StylusPointer::buildStylusOverlay(const QVariantMap& properties) {
QVariantMap overlayProperties;
// TODO: make these configurable per pointer
overlayProperties["name"] = "stylus";
overlayProperties["url"] = PathUtils::resourcesPath() + "/meshes/tablet-stylus-fat.fbx";
overlayProperties["loadPriority"] = 10.0f;
overlayProperties["solid"] = true;
overlayProperties["visible"] = false;
overlayProperties["ignoreRayIntersection"] = true;
overlayProperties["drawInFront"] = false;
return qApp->getOverlays().addOverlay("model", overlayProperties);
}
void StylusPointer::updateVisuals(const PickResultPointer& pickResult) {
auto stylusPickResult = std::static_pointer_cast<const StylusPickResult>(pickResult);
if (_enabled && !qApp->getPreferAvatarFingerOverStylus() && _renderState != DISABLED && stylusPickResult) {
StylusTip tip(stylusPickResult->pickVariant);
if (tip.side != bilateral::Side::Invalid) {
show(tip);
return;
}
}
hide();
}
void StylusPointer::show(const StylusTip& tip) {
if (!_stylusOverlay.isNull()) {
QVariantMap props;
static const glm::quat X_ROT_NEG_90{ 0.70710678f, -0.70710678f, 0.0f, 0.0f };
auto modelOrientation = tip.orientation * X_ROT_NEG_90;
auto sensorToWorldScale = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
auto modelPositionOffset = modelOrientation * (vec3(0.0f, 0.0f, -WEB_STYLUS_LENGTH / 2.0f) * sensorToWorldScale);
props["position"] = vec3toVariant(tip.position + modelPositionOffset);
props["rotation"] = quatToVariant(modelOrientation);
props["dimensions"] = vec3toVariant(sensorToWorldScale * vec3(0.01f, 0.01f, WEB_STYLUS_LENGTH));
props["visible"] = true;
qApp->getOverlays().editOverlay(_stylusOverlay, props);
}
}
void StylusPointer::hide() {
if (!_stylusOverlay.isNull()) {
QVariantMap props;
props.insert("visible", false);
qApp->getOverlays().editOverlay(_stylusOverlay, props);
}
}
bool StylusPointer::shouldHover(const PickResultPointer& pickResult) {
auto stylusPickResult = std::static_pointer_cast<const StylusPickResult>(pickResult);
if (_renderState == EVENTS_ON && stylusPickResult && stylusPickResult->intersects) {
auto sensorScaleFactor = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
float hysteresis = _state.hovering ? HOVER_HYSTERESIS * sensorScaleFactor : 0.0f;
bool hovering = isWithinBounds(stylusPickResult->distance, TABLET_MIN_HOVER_DISTANCE * sensorScaleFactor,
TABLET_MAX_HOVER_DISTANCE * sensorScaleFactor, hysteresis);
_state.hovering = hovering;
return hovering;
}
_state.hovering = false;
return false;
}
bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) {
auto stylusPickResult = std::static_pointer_cast<const StylusPickResult>(pickResult);
if (_renderState == EVENTS_ON && stylusPickResult) {
auto sensorScaleFactor = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
float distance = stylusPickResult->distance;
// If we're triggering on an object, recalculate the distance instead of using the pickResult
glm::vec3 origin = vec3FromVariant(stylusPickResult->pickVariant["position"]);
glm::vec3 direction = -_state.surfaceNormal;
if (!_state.triggeredObject.objectID.isNull() && stylusPickResult->objectID != _state.triggeredObject.objectID) {
distance = glm::dot(findIntersection(_state.triggeredObject, origin, direction) - origin, direction);
}
float hysteresis = _state.triggering ? TOUCH_HYSTERESIS * sensorScaleFactor : 0.0f;
if (isWithinBounds(distance, TABLET_MIN_TOUCH_DISTANCE * sensorScaleFactor,
TABLET_MAX_TOUCH_DISTANCE * sensorScaleFactor, hysteresis)) {
if (_state.triggeredObject.objectID.isNull()) {
_state.triggeredObject = PickedObject(stylusPickResult->objectID, stylusPickResult->type);
_state.intersection = findIntersection(_state.triggeredObject, origin, direction);
_state.triggerPos2D = findPos2D(_state.triggeredObject, origin);
_state.triggerStartTime = usecTimestampNow();
_state.surfaceNormal = stylusPickResult->surfaceNormal;
_state.triggering = true;
}
return true;
}
}
_state.triggeredObject = PickedObject();
_state.intersection = glm::vec3(NAN);
_state.triggerPos2D = glm::vec2(NAN);
_state.triggerStartTime = 0;
_state.surfaceNormal = glm::vec3(NAN);
_state.triggering = false;
return false;
}
Pointer::PickedObject StylusPointer::getHoveredObject(const PickResultPointer& pickResult) {
auto stylusPickResult = std::static_pointer_cast<const StylusPickResult>(pickResult);
if (!stylusPickResult) {
return PickedObject();
}
return PickedObject(stylusPickResult->objectID, stylusPickResult->type);
}
Pointer::Buttons StylusPointer::getPressedButtons() {
// TODO: custom buttons for styluses
Pointer::Buttons toReturn({ "Primary", "Focus" });
return toReturn;
}
PointerEvent StylusPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover) const {
QUuid pickedID;
glm::vec2 pos2D;
glm::vec3 intersection, surfaceNormal, direction, origin;
auto stylusPickResult = std::static_pointer_cast<StylusPickResult>(pickResult);
if (stylusPickResult) {
intersection = stylusPickResult->intersection;
surfaceNormal = hover ? stylusPickResult->surfaceNormal : _state.surfaceNormal;
const QVariantMap& stylusTip = stylusPickResult->pickVariant;
origin = vec3FromVariant(stylusTip["position"]);
direction = -surfaceNormal;
pos2D = findPos2D(target, origin);
pickedID = stylusPickResult->objectID;
}
// If we just started triggering and we haven't moved too much, don't update intersection and pos2D
if (!_state.triggeredObject.objectID.isNull() && usecTimestampNow() - _state.triggerStartTime < STYLUS_MOVE_DELAY &&
glm::distance2(pos2D, _state.triggerPos2D) < TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED) {
pos2D = _state.triggerPos2D;
intersection = _state.intersection;
} else if (pickedID != target.objectID) {
intersection = findIntersection(target, origin, direction);
}
return PointerEvent(pos2D, intersection, surfaceNormal, direction);
}
bool StylusPointer::isWithinBounds(float distance, float min, float max, float hysteresis) {
return (distance == glm::clamp(distance, min - hysteresis, max + hysteresis));
}
void StylusPointer::setRenderState(const std::string& state) {
if (state == "events on") {
_renderState = EVENTS_ON;
} else if (state == "events off") {
_renderState = EVENTS_OFF;
} else if (state == "disabled") {
_renderState = DISABLED;
}
}
glm::vec3 StylusPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction) {
switch (pickedObject.type) {
case ENTITY:
return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction);
case OVERLAY:
return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction);
default:
return glm::vec3(NAN);
}
}
glm::vec2 StylusPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) {
switch (pickedObject.type) {
case ENTITY:
return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin);
case OVERLAY:
return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin);
case HUD:
return DependencyManager::get<PickManager>()->calculatePos2DFromHUD(origin);
default:
return glm::vec2(NAN);
}
}

View file

@ -0,0 +1,83 @@
//
// Created by Bradley Austin Davis on 2017/10/24
// Copyright 2013-2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_StylusPointer_h
#define hifi_StylusPointer_h
#include <Pointer.h>
#include <shared/Bilateral.h>
#include <RegisteredMetaTypes.h>
#include "ui/overlays/Overlay.h"
#include "StylusPick.h"
class StylusPointer : public Pointer {
using Parent = Pointer;
using Ptr = std::shared_ptr<StylusPointer>;
public:
StylusPointer(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled);
~StylusPointer();
void updateVisuals(const PickResultPointer& pickResult) override;
// Styluses have three render states:
// default: "events on" -> render and hover/trigger
// "events off" -> render, don't hover/trigger
// "disabled" -> don't render, don't hover/trigger
void setRenderState(const std::string& state) override;
void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override {}
static OverlayID buildStylusOverlay(const QVariantMap& properties);
protected:
PickedObject getHoveredObject(const PickResultPointer& pickResult) override;
Buttons getPressedButtons() override;
bool shouldHover(const PickResultPointer& pickResult) override;
bool shouldTrigger(const PickResultPointer& pickResult) override;
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, bool hover = true) const override;
private:
void show(const StylusTip& tip);
void hide();
struct TriggerState {
PickedObject triggeredObject;
glm::vec3 intersection { NAN };
glm::vec2 triggerPos2D { NAN };
glm::vec3 surfaceNormal { NAN };
quint64 triggerStartTime { 0 };
bool triggering { false };
bool hovering { false };
};
TriggerState _state;
enum RenderState {
EVENTS_ON = 0,
EVENTS_OFF,
DISABLED
};
RenderState _renderState { EVENTS_ON };
const OverlayID _stylusOverlay;
static bool isWithinBounds(float distance, float min, float max, float hysteresis);
static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction);
static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin);
};
#endif // hifi_StylusPointer_h

Some files were not shown because too many files have changed in this diff Show more