Merging with upstream

This commit is contained in:
samcake 2017-12-07 09:23:38 -08:00
commit e0110ff7e6
746 changed files with 25373 additions and 14126 deletions

39
.clang-format Normal file
View file

@ -0,0 +1,39 @@
Language: Cpp
Standard: Cpp11
BasedOnStyle: "Chromium"
ColumnLimit: 128
IndentWidth: 4
UseTab: Never
BreakBeforeBraces: Custom
BraceWrapping:
AfterEnum: true
AfterClass: false
AfterControlStatement: false
AfterFunction: false
AfterNamespace: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
SplitEmptyFunction: false
SplitEmptyNamespace: true
AccessModifierOffset: -4
AllowShortFunctionsOnASingleLine: InlineOnly
BreakConstructorInitializers: BeforeColon
BreakConstructorInitializersBeforeComma: true
IndentCaseLabels: true
ReflowComments: false
Cpp11BracedListStyle: false
ContinuationIndentWidth: 4
ConstructorInitializerAllOnOneLineOrOnePerLine: false
CompactNamespaces: true
SortIncludes: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
PenaltyReturnTypeOnItsOwnLine: 1000
PenaltyBreakBeforeFirstCallParameter: 1000

View file

@ -68,7 +68,7 @@ module.exports = {
"eqeqeq": ["error", "always"],
"indent": ["error", 4, { "SwitchCase": 1 }],
"keyword-spacing": ["error", { "before": true, "after": true }],
"max-len": ["error", 192, 4],
"max-len": ["error", 128, 4],
"new-cap": ["error"],
"no-floating-decimal": ["error"],
//"no-magic-numbers": ["error", { "ignore": [0, 1], "ignoreArrayIndexes": true }],

8
.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
@ -64,6 +66,10 @@ gvr-interface/libs/*
# ignore files for various dev environments
TAGS
*.sw[po]
*.qmlc
# ignore QML compilation output
*.qmlc
# ignore node files for the console
node_modules

View file

@ -25,7 +25,7 @@ The above dependencies will be downloaded, built, linked and included automatica
These are not placed in your normal build tree when doing an out of source build so that they do not need to be re-downloaded and re-compiled every time the CMake build folder is cleared. Should you want to force a re-download and re-compile of a specific external, you can simply remove that directory from the appropriate subfolder in `build/ext`. Should you want to force a re-download and re-compile of all externals, just remove the `build/ext` folder.
If you would like to use a specific install of a dependency instead of the version that would be grabbed as a CMake ExternalProject, you can pass -DUSE_LOCAL_$NAME=0 (where $NAME is the name of the subfolder in [cmake/externals](cmake/externals)) when you run CMake to tell it not to get that dependency as an external project.
If you would like to use a specific install of a dependency instead of the version that would be grabbed as a CMake ExternalProject, you can pass -DUSE\_LOCAL\_$NAME=0 (where $NAME is the name of the subfolder in [cmake/externals](cmake/externals)) when you run CMake to tell it not to get that dependency as an external project.
### OS Specific Build Guides

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

@ -19,6 +19,10 @@ Documentation is available at [docs.highfidelity.com](https://docs.highfidelity.
There is also detailed [documentation on our coding standards](https://wiki.highfidelity.com/wiki/Coding_Standards).
Contributor License Agreement (CLA)
=========
Technology companies frequently receive and use code from contributors outside the company's development team. Outside code can be a tremendous resource, but it also carries responsibility. Best practice for accepting outside contributions consists of an Apache-type Contributor License Agreement (CLA). We have modeled the High Fidelity CLA after the CLA that Google presents to developers for contributions to their projects. This CLA does not transfer ownership of code, instead simply granting a non-exclusive right for High Fidelity to use the code youve contributed. In that regard, you should be sure you have permission if the work relates to or uses the resources of a company that you work for. You will be asked to sign our CLA when you create your first PR or when the CLA is updated. You can also [review it here](https://gist.githubusercontent.com/hifi-gustavo/fef8f06a8233d42a0040d45c3efb97a9/raw/9981827eb94f0b18666083670b6f6a02929fb402/High%2520Fidelity%2520CLA). We sincerely appreciate your contribution and efforts toward the success of the platform.
Build Instructions
=========
All information required to build is found in the [build guide](BUILD.md).

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

@ -28,6 +28,10 @@
const QString ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME = "assignment-client-monitor";
const int WAIT_FOR_CHILD_MSECS = 1000;
#ifdef Q_OS_WIN
HANDLE PROCESS_GROUP = createProcessGroup();
#endif
AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmentClientForks,
const unsigned int minAssignmentClientForks,
const unsigned int maxAssignmentClientForks,
@ -202,6 +206,10 @@ void AssignmentClientMonitor::spawnChildClient() {
assignmentClient->setProcessChannelMode(QProcess::ForwardedChannels);
assignmentClient->start(QCoreApplication::applicationFilePath(), _childArguments);
#ifdef Q_OS_WIN
addProcessToGroup(PROCESS_GROUP, assignmentClient->processId());
#endif
QString stdoutPath, stderrPath;
if (_wantsChildFileLogging) {

View file

@ -30,6 +30,7 @@
#include <ClientServerUtils.h>
#include <FBXBaker.h>
#include <JSBaker.h>
#include <NodeType.h>
#include <SharedUtil.h>
#include <PathUtils.h>
@ -51,8 +52,10 @@ const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
static const QStringList BAKEABLE_MODEL_EXTENSIONS = { "fbx" };
static QStringList BAKEABLE_TEXTURE_EXTENSIONS;
static const QStringList BAKEABLE_SCRIPT_EXTENSIONS = {};
static const QString BAKED_MODEL_SIMPLE_NAME = "asset.fbx";
static const QString BAKED_TEXTURE_SIMPLE_NAME = "texture.ktx";
static const QString BAKED_SCRIPT_SIMPLE_NAME = "asset.js";
void AssetServer::bakeAsset(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) {
qDebug() << "Starting bake for: " << assetPath << assetHash;
@ -99,6 +102,8 @@ std::pair<BakingStatus, QString> AssetServer::getAssetStatus(const AssetPath& pa
bakedFilename = BAKED_MODEL_SIMPLE_NAME;
} else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit()) && hasMetaFile(hash)) {
bakedFilename = BAKED_TEXTURE_SIMPLE_NAME;
} else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extension)) {
bakedFilename = BAKED_SCRIPT_SIMPLE_NAME;
} else {
return { Irrelevant, "" };
}
@ -186,6 +191,8 @@ bool AssetServer::needsToBeBaked(const AssetPath& path, const AssetHash& assetHa
bakedFilename = BAKED_MODEL_SIMPLE_NAME;
} else if (loaded && BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit())) {
bakedFilename = BAKED_TEXTURE_SIMPLE_NAME;
} else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extension)) {
bakedFilename = BAKED_SCRIPT_SIMPLE_NAME;
} else {
return false;
}
@ -228,7 +235,8 @@ void updateConsumedCores() {
AssetServer::AssetServer(ReceivedMessage& message) :
ThreadedAssignment(message),
_transferTaskPool(this),
_bakingTaskPool(this)
_bakingTaskPool(this),
_filesizeLimit(MAX_UPLOAD_SIZE)
{
// store the current state of image compression so we can reset it when this assignment is complete
_wasColorTextureCompressionEnabled = image::isColorTexturesCompressionEnabled();
@ -336,8 +344,8 @@ void AssetServer::completeSetup() {
auto maxBandwidthValue = assetServerObject[MAX_BANDWIDTH_OPTION];
auto maxBandwidthFloat = maxBandwidthValue.toDouble(-1);
const int BITS_PER_MEGABITS = 1000 * 1000;
if (maxBandwidthFloat > 0.0) {
const int BITS_PER_MEGABITS = 1000 * 1000;
int maxBandwidth = maxBandwidthFloat * BITS_PER_MEGABITS;
nodeList->setConnectionMaxBandwidth(maxBandwidth);
qCInfo(asset_server) << "Set maximum bandwith per connection to" << maxBandwidthFloat << "Mb/s."
@ -399,6 +407,15 @@ void AssetServer::completeSetup() {
qCCritical(asset_server) << "Asset Server assignment will not continue because mapping file could not be loaded.";
setFinished(true);
}
// get file size limit for an asset
static const QString ASSETS_FILESIZE_LIMIT_OPTION = "assets_filesize_limit";
auto assetsFilesizeLimitJSONValue = assetServerObject[ASSETS_FILESIZE_LIMIT_OPTION];
auto assetsFilesizeLimit = (uint64_t)assetsFilesizeLimitJSONValue.toInt(MAX_UPLOAD_SIZE);
if (assetsFilesizeLimit != 0 && assetsFilesizeLimit < MAX_UPLOAD_SIZE) {
_filesizeLimit = assetsFilesizeLimit * BITS_PER_MEGABITS;
}
}
void AssetServer::cleanupUnmappedFiles() {
@ -488,6 +505,8 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNode
bakedRootFile = BAKED_MODEL_SIMPLE_NAME;
} else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(assetPathExtension.toLocal8Bit())) {
bakedRootFile = BAKED_TEXTURE_SIMPLE_NAME;
} else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(assetPathExtension)) {
bakedRootFile = BAKED_SCRIPT_SIMPLE_NAME;
}
auto originalAssetHash = it->second;
@ -721,7 +740,7 @@ void AssetServer::handleAssetUpload(QSharedPointer<ReceivedMessage> message, Sha
if (senderNode->getCanWriteToAssetServer()) {
qCDebug(asset_server) << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID());
auto task = new UploadAssetTask(message, senderNode, _filesDirectory);
auto task = new UploadAssetTask(message, senderNode, _filesDirectory, _filesizeLimit);
_transferTaskPool.start(task);
} else {
// this is a node the domain told us is not allowed to rez entities
@ -1141,6 +1160,7 @@ bool AssetServer::renameMapping(AssetPath oldPath, AssetPath newPath) {
static const QString BAKED_ASSET_SIMPLE_FBX_NAME = "asset.fbx";
static const QString BAKED_ASSET_SIMPLE_TEXTURE_NAME = "texture.ktx";
static const QString BAKED_ASSET_SIMPLE_JS_NAME = "asset.js";
QString getBakeMapping(const AssetHash& hash, const QString& relativeFilePath) {
return HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + relativeFilePath;
@ -1204,14 +1224,14 @@ void AssetServer::handleCompletedBake(QString originalAssetHash, QString origina
// setup the mapping for this bake file
auto relativeFilePath = QUrl(filePath).fileName();
qDebug() << "Relative file path is: " << relativeFilePath;
if (relativeFilePath.endsWith(".fbx", Qt::CaseInsensitive)) {
// for an FBX file, we replace the filename with the simple name
// (to handle the case where two mapped assets have the same hash but different names)
relativeFilePath = BAKED_ASSET_SIMPLE_FBX_NAME;
} else if (relativeFilePath.endsWith(".js", Qt::CaseInsensitive)) {
relativeFilePath = BAKED_ASSET_SIMPLE_JS_NAME;
} else if (!originalAssetPath.endsWith(".fbx", Qt::CaseInsensitive)) {
relativeFilePath = BAKED_ASSET_SIMPLE_TEXTURE_NAME;
}
QString bakeMapping = getBakeMapping(originalAssetHash, relativeFilePath);
@ -1364,6 +1384,8 @@ bool AssetServer::setBakingEnabled(const AssetPathList& paths, bool enabled) {
bakedFilename = BAKED_MODEL_SIMPLE_NAME;
} else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit()) && hasMetaFile(hash)) {
bakedFilename = BAKED_TEXTURE_SIMPLE_NAME;
} else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extension)) {
bakedFilename = BAKED_SCRIPT_SIMPLE_NAME;
} else {
continue;
}

View file

@ -127,6 +127,8 @@ private:
bool _wasGrayscaleTextureCompressionEnabled { false };
bool _wasNormalTextureCompressionEnabled { false };
bool _wasCubeTextureCompressionEnabled { false };
uint64_t _filesizeLimit;
};
#endif

View file

@ -15,6 +15,7 @@
#include <FBXBaker.h>
#include <PathUtils.h>
#include <JSBaker.h>
BakeAssetTask::BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) :
_assetHash(assetHash),
@ -52,6 +53,10 @@ void BakeAssetTask::run() {
_baker = std::unique_ptr<FBXBaker> {
new FBXBaker(QUrl("file:///" + _filePath), fn, tempOutputDir)
};
} else if (_assetPath.endsWith(".js", Qt::CaseInsensitive)) {
_baker = std::unique_ptr<JSBaker>{
new JSBaker(QUrl("file:///" + _filePath), PathUtils::generateTemporaryDir())
};
} else {
tempOutputDir = PathUtils::generateTemporaryDir();
_baker = std::unique_ptr<TextureBaker> {

View file

@ -22,10 +22,11 @@
UploadAssetTask::UploadAssetTask(QSharedPointer<ReceivedMessage> receivedMessage, SharedNodePointer senderNode,
const QDir& resourcesDir) :
const QDir& resourcesDir, uint64_t filesizeLimit) :
_receivedMessage(receivedMessage),
_senderNode(senderNode),
_resourcesDir(resourcesDir)
_resourcesDir(resourcesDir),
_filesizeLimit(filesizeLimit)
{
}
@ -48,7 +49,7 @@ void UploadAssetTask::run() {
auto replyPacket = NLPacket::create(PacketType::AssetUploadReply, -1, true);
replyPacket->writePrimitive(messageID);
if (fileSize > MAX_UPLOAD_SIZE) {
if (fileSize > _filesizeLimit) {
replyPacket->writePrimitive(AssetServerError::AssetTooLarge);
} else {
QByteArray fileData = buffer.read(fileSize);

View file

@ -26,7 +26,8 @@ class Node;
class UploadAssetTask : public QRunnable {
public:
UploadAssetTask(QSharedPointer<ReceivedMessage> message, QSharedPointer<Node> senderNode, const QDir& resourcesDir);
UploadAssetTask(QSharedPointer<ReceivedMessage> message, QSharedPointer<Node> senderNode,
const QDir& resourcesDir, uint64_t filesizeLimit);
void run() override;
@ -34,6 +35,7 @@ private:
QSharedPointer<ReceivedMessage> _receivedMessage;
QSharedPointer<Node> _senderNode;
QDir _resourcesDir;
uint64_t _filesizeLimit;
};
#endif // hifi_UploadAssetTask_h

View file

@ -29,6 +29,7 @@
#include <UUID.h>
#include <CPUDetect.h>
#include "AudioLogging.h"
#include "AudioHelpers.h"
#include "AudioRingBuffer.h"
#include "AudioMixerClientData.h"
@ -130,7 +131,7 @@ void AudioMixer::queueReplicatedAudioPacket(QSharedPointer<ReceivedMessage> mess
PacketType rewrittenType = PacketTypeEnum::getReplicatedPacketMapping().key(message->getType());
if (rewrittenType == PacketType::Unknown) {
qDebug() << "Cannot unwrap replicated packet type not present in REPLICATED_PACKET_WRAPPING";
qCDebug(audio) << "Cannot unwrap replicated packet type not present in REPLICATED_PACKET_WRAPPING";
}
auto replicatedMessage = QSharedPointer<ReceivedMessage>::create(audioData, rewrittenType,
@ -345,7 +346,7 @@ void AudioMixer::sendStatsPacket() {
void AudioMixer::run() {
qDebug() << "Waiting for connection to domain to request settings from domain-server.";
qCDebug(audio) << "Waiting for connection to domain to request settings from domain-server.";
// wait until we have the domain-server settings, otherwise we bail
DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
@ -502,14 +503,14 @@ void AudioMixer::throttle(std::chrono::microseconds duration, int frame) {
int proportionalTerm = 1 + (_trailingMixRatio - TARGET) / 0.1f;
_throttlingRatio += THROTTLE_RATE * proportionalTerm;
_throttlingRatio = std::min(_throttlingRatio, 1.0f);
qDebug("audio-mixer is struggling (%f mix/sleep) - throttling %f of streams",
(double)_trailingMixRatio, (double)_throttlingRatio);
qCDebug(audio) << "audio-mixer is struggling (" << _trailingMixRatio << "mix/sleep) - throttling"
<< _throttlingRatio << "of streams";
} else if (_throttlingRatio > 0.0f && _trailingMixRatio <= BACKOFF_TARGET) {
int proportionalTerm = 1 + (TARGET - _trailingMixRatio) / 0.2f;
_throttlingRatio -= BACKOFF_RATE * proportionalTerm;
_throttlingRatio = std::max(_throttlingRatio, 0.0f);
qDebug("audio-mixer is recovering (%f mix/sleep) - throttling %f of streams",
(double)_trailingMixRatio, (double)_throttlingRatio);
qCDebug(audio) << "audio-mixer is recovering (" << _trailingMixRatio << "mix/sleep) - throttling"
<< _throttlingRatio << "of streams";
}
}
}
@ -534,7 +535,7 @@ void AudioMixer::clearDomainSettings() {
}
void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
qDebug() << "AVX2 Support:" << (cpuSupportsAVX2() ? "enabled" : "disabled");
qCDebug(audio) << "AVX2 Support:" << (cpuSupportsAVX2() ? "enabled" : "disabled");
if (settingsObject.contains(AUDIO_THREADING_GROUP_KEY)) {
QJsonObject audioThreadingGroupObject = settingsObject[AUDIO_THREADING_GROUP_KEY].toObject();
@ -557,7 +558,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
const QString DYNAMIC_JITTER_BUFFER_JSON_KEY = "dynamic_jitter_buffer";
bool enableDynamicJitterBuffer = audioBufferGroupObject[DYNAMIC_JITTER_BUFFER_JSON_KEY].toBool();
if (enableDynamicJitterBuffer) {
qDebug() << "Enabling dynamic jitter buffers.";
qCDebug(audio) << "Enabling dynamic jitter buffers.";
bool ok;
const QString DESIRED_JITTER_BUFFER_FRAMES_KEY = "static_desired_jitter_buffer_frames";
@ -565,9 +566,9 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
if (!ok) {
_numStaticJitterFrames = InboundAudioStream::DEFAULT_STATIC_JITTER_FRAMES;
}
qDebug() << "Static desired jitter buffer frames:" << _numStaticJitterFrames;
qCDebug(audio) << "Static desired jitter buffer frames:" << _numStaticJitterFrames;
} else {
qDebug() << "Disabling dynamic jitter buffers.";
qCDebug(audio) << "Disabling dynamic jitter buffers.";
_numStaticJitterFrames = DISABLE_STATIC_JITTER_FRAMES;
}
@ -621,7 +622,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
if (audioEnvGroupObject[CODEC_PREFERENCE_ORDER].isString()) {
QString codecPreferenceOrder = audioEnvGroupObject[CODEC_PREFERENCE_ORDER].toString();
_codecPreferenceOrder = codecPreferenceOrder.split(",");
qDebug() << "Codec preference order changed to" << _codecPreferenceOrder;
qCDebug(audio) << "Codec preference order changed to" << _codecPreferenceOrder;
}
const QString ATTENATION_PER_DOULING_IN_DISTANCE = "attenuation_per_doubling_in_distance";
@ -630,7 +631,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
float attenuation = audioEnvGroupObject[ATTENATION_PER_DOULING_IN_DISTANCE].toString().toFloat(&ok);
if (ok) {
_attenuationPerDoublingInDistance = attenuation;
qDebug() << "Attenuation per doubling in distance changed to" << _attenuationPerDoublingInDistance;
qCDebug(audio) << "Attenuation per doubling in distance changed to" << _attenuationPerDoublingInDistance;
}
}
@ -640,7 +641,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
float noiseMutingThreshold = audioEnvGroupObject[NOISE_MUTING_THRESHOLD].toString().toFloat(&ok);
if (ok) {
_noiseMutingThreshold = noiseMutingThreshold;
qDebug() << "Noise muting threshold changed to" << _noiseMutingThreshold;
qCDebug(audio) << "Noise muting threshold changed to" << _noiseMutingThreshold;
}
}
@ -680,8 +681,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
glm::vec3 dimensions(xMax - xMin, yMax - yMin, zMax - zMin);
AABox zoneAABox(corner, dimensions);
_audioZones.insert(zone, zoneAABox);
qDebug() << "Added zone:" << zone << "(corner:" << corner
<< ", dimensions:" << dimensions << ")";
qCDebug(audio) << "Added zone:" << zone << "(corner:" << corner << ", dimensions:" << dimensions << ")";
}
}
}
@ -712,7 +712,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
_audioZones.contains(settings.source) && _audioZones.contains(settings.listener)) {
_zoneSettings.push_back(settings);
qDebug() << "Added Coefficient:" << settings.source << settings.listener << settings.coefficient;
qCDebug(audio) << "Added Coefficient:" << settings.source << settings.listener << settings.coefficient;
}
}
}
@ -745,7 +745,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
_zoneReverbSettings.push_back(settings);
qDebug() << "Added Reverb:" << zone << reverbTime << wetLevel;
qCDebug(audio) << "Added Reverb:" << zone << reverbTime << wetLevel;
}
}
}

View file

@ -19,6 +19,7 @@
#include "InjectedAudioStream.h"
#include "AudioLogging.h"
#include "AudioHelpers.h"
#include "AudioMixer.h"
#include "AudioMixerClientData.h"
@ -132,7 +133,7 @@ void AudioMixerClientData::optionallyReplicatePacket(ReceivedMessage& message, c
if (PacketTypeEnum::getReplicatedPacketMapping().key(message.getType()) != PacketType::Unknown) {
mirroredType = message.getType();
} else {
qDebug() << "Packet passed to optionallyReplicatePacket was not a replicatable type - returning";
qCDebug(audio) << "Packet passed to optionallyReplicatePacket was not a replicatable type - returning";
return;
}
}
@ -189,8 +190,16 @@ void AudioMixerClientData::parsePerAvatarGainSet(ReceivedMessage& message, const
uint8_t packedGain;
message.readPrimitive(&packedGain);
float gain = unpackFloatGainFromByte(packedGain);
hrtfForStream(avatarUuid, QUuid()).setGainAdjustment(gain);
qDebug() << "Setting gain adjustment for hrtf[" << uuid << "][" << avatarUuid << "] to " << gain;
if (avatarUuid.isNull()) {
// set the MASTER avatar gain
setMasterAvatarGain(gain);
qCDebug(audio) << "Setting MASTER avatar gain for " << uuid << " to " << gain;
} else {
// set the per-source avatar gain
hrtfForStream(avatarUuid, QUuid()).setGainAdjustment(gain);
qCDebug(audio) << "Setting avatar gain adjustment for hrtf[" << uuid << "][" << avatarUuid << "] to " << gain;
}
}
void AudioMixerClientData::parseNodeIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node) {
@ -276,7 +285,7 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
auto avatarAudioStream = new AvatarAudioStream(isStereo, AudioMixer::getStaticJitterFrames());
avatarAudioStream->setupCodec(_codec, _selectedCodecName, AudioConstants::MONO);
qDebug() << "creating new AvatarAudioStream... codec:" << _selectedCodecName;
qCDebug(audio) << "creating new AvatarAudioStream... codec:" << _selectedCodecName;
connect(avatarAudioStream, &InboundAudioStream::mismatchedAudioCodec,
this, &AudioMixerClientData::handleMismatchAudioFormat);
@ -315,7 +324,7 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
#if INJECTORS_SUPPORT_CODECS
injectorStream->setupCodec(_codec, _selectedCodecName, isStereo ? AudioConstants::STEREO : AudioConstants::MONO);
qDebug() << "creating new injectorStream... codec:" << _selectedCodecName;
qCDebug(audio) << "creating new injectorStream... codec:" << _selectedCodecName;
#endif
auto emplaced = _audioStreams.emplace(
@ -339,8 +348,8 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
auto parseResult = matchingStream->parseData(message);
if (matchingStream->getOverflowCount() > overflowBefore) {
qDebug() << "Just overflowed on stream from" << message.getSourceID() << "at" << message.getSenderSockAddr();
qDebug() << "This stream is for" << (isMicStream ? "microphone audio" : "injected audio");
qCDebug(audio) << "Just overflowed on stream from" << message.getSourceID() << "at" << message.getSenderSockAddr();
qCDebug(audio) << "This stream is for" << (isMicStream ? "microphone audio" : "injected audio");
}
return parseResult;
@ -689,7 +698,7 @@ void AudioMixerClientData::setupCodecForReplicatedAgent(QSharedPointer<ReceivedM
auto codecString = message->readString();
if (codecString != _selectedCodecName) {
qDebug() << "Manually setting codec for replicated agent" << uuidStringWithoutCurlyBraces(getNodeID())
qCDebug(audio) << "Manually setting codec for replicated agent" << uuidStringWithoutCurlyBraces(getNodeID())
<< "-" << codecString;
const std::pair<QString, CodecPluginPointer> codec = AudioMixer::negotiateCodec({ codecString });

View file

@ -83,6 +83,9 @@ public:
// uses randomization to have the AudioMixer send a stats packet to this node around every second
bool shouldSendStats(int frameNumber);
float getMasterAvatarGain() const { return _masterAvatarGain; }
void setMasterAvatarGain(float gain) { _masterAvatarGain = gain; }
AudioLimiter audioLimiter;
void setupCodec(CodecPluginPointer codec, const QString& codecName);
@ -175,6 +178,8 @@ private:
int _frameToSendStats { 0 };
float _masterAvatarGain { 1.0f }; // per-listener mixing gain, applied only to avatars
CodecPluginPointer _codec;
QString _selectedCodecName;
Encoder* _encoder{ nullptr }; // for outbound mixed stream

View file

@ -48,8 +48,8 @@ void sendEnvironmentPacket(const SharedNodePointer& node, AudioMixerClientData&
// mix helpers
inline float approximateGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
const glm::vec3& relativePosition);
inline float computeGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
const glm::vec3& relativePosition, bool isEcho);
inline float computeGain(const AudioMixerClientData& listenerNodeData, const AvatarAudioStream& listeningNodeStream,
const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, bool isEcho);
inline float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
const glm::vec3& relativePosition);
@ -266,7 +266,7 @@ void AudioMixerSlave::addStream(AudioMixerClientData& listenerNodeData, const QU
glm::vec3 relativePosition = streamToAdd.getPosition() - listeningNodeStream.getPosition();
float distance = glm::max(glm::length(relativePosition), EPSILON);
float gain = computeGain(listeningNodeStream, streamToAdd, relativePosition, isEcho);
float gain = computeGain(listenerNodeData, listeningNodeStream, streamToAdd, relativePosition, isEcho);
float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition);
const int HRTF_DATASET_INDEX = 1;
@ -484,10 +484,12 @@ float approximateGain(const AvatarAudioStream& listeningNodeStream, const Positi
// when throttling, as close streams are expected to be heard by a user
float distance = glm::length(relativePosition);
return gain / distance;
// avatar: skip master gain - it is constant for all streams
}
float computeGain(const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd,
const glm::vec3& relativePosition, bool isEcho) {
float computeGain(const AudioMixerClientData& listenerNodeData, const AvatarAudioStream& listeningNodeStream,
const PositionalAudioStream& streamToAdd, const glm::vec3& relativePosition, bool isEcho) {
float gain = 1.0f;
// injector: apply attenuation
@ -507,6 +509,9 @@ float computeGain(const AvatarAudioStream& listeningNodeStream, const Positional
float offAxisCoefficient = MAX_OFF_AXIS_ATTENUATION + (angleOfDelivery * (OFF_AXIS_ATTENUATION_STEP / PI_OVER_TWO));
gain *= offAxisCoefficient;
// apply master gain, only to avatars
gain *= listenerNodeData.getMasterAvatarGain();
}
auto& audioZones = AudioMixer::getAudioZones();

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

View file

@ -209,7 +209,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
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());
glm::vec3 nodeBoxHalfScale = (avatar->getWorldPosition() - avatar->getGlobalBoundingBoxCorner() * avatar->getSensorToWorldScale());
return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z));
}, [&](AvatarSharedPointer avatar)->bool {
if (avatar == thisAvatar) {

View file

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

View file

@ -16,6 +16,10 @@
#include <ResourceCache.h>
#include <ScriptCache.h>
#include <EntityEditFilters.h>
#include <NetworkingConstants.h>
#include <QJsonArray>
#include <QJsonDocument>
#include <AddressManager.h>
#include "AssignmentParentFinder.h"
#include "EntityNodeData.h"
@ -29,15 +33,26 @@ const char* LOCAL_MODELS_PERSIST_FILE = "resources/models.svo";
EntityServer::EntityServer(ReceivedMessage& message) :
OctreeServer(message),
_entitySimulation(NULL)
_entitySimulation(NULL),
_dynamicDomainVerificationTimer(this)
{
DependencyManager::set<ResourceManager>();
DependencyManager::set<ResourceCacheSharedItems>();
DependencyManager::set<ScriptCache>();
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityEdit, PacketType::EntityErase, PacketType::EntityPhysics },
this, "handleEntityPacket");
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd,
PacketType::EntityEdit,
PacketType::EntityErase,
PacketType::EntityPhysics,
PacketType::ChallengeOwnership,
PacketType::ChallengeOwnershipRequest,
PacketType::ChallengeOwnershipReply },
this,
"handleEntityPacket");
connect(&_dynamicDomainVerificationTimer, &QTimer::timeout, this, &EntityServer::startDynamicDomainVerification);
_dynamicDomainVerificationTimer.setSingleShot(true);
}
EntityServer::~EntityServer() {
@ -93,6 +108,9 @@ void EntityServer::beforeRun() {
connect(_pruneDeletedEntitiesTimer, SIGNAL(timeout()), this, SLOT(pruneDeletedEntities()));
const int PRUNE_DELETED_MODELS_INTERVAL_MSECS = 1 * 1000; // once every second
_pruneDeletedEntitiesTimer->start(PRUNE_DELETED_MODELS_INTERVAL_MSECS);
DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
connect(&domainHandler, &DomainHandler::settingsReceiveFail, this, &EntityServer::domainSettingsRequestFailed);
}
void EntityServer::entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode) {
@ -296,6 +314,18 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
tree->setEntityMaxTmpLifetime(EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME);
}
int minTime;
if (readOptionInt("dynamicDomainVerificationTimeMin", settingsSectionObject, minTime)) {
_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = minTime * 1000;
}
int maxTime;
if (readOptionInt("dynamicDomainVerificationTimeMax", settingsSectionObject, maxTime)) {
_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = maxTime * 1000;
}
startDynamicDomainVerification();
tree->setWantEditLogging(wantEditLogging);
tree->setWantTerseEditLogging(wantTerseEditLogging);
@ -410,3 +440,79 @@ QString EntityServer::serverSubclassStats() {
return statsString;
}
void EntityServer::domainSettingsRequestFailed() {
auto nodeList = DependencyManager::get<NodeList>();
qCDebug(entities) << "The EntityServer couldn't get the Domain Settings. Starting dynamic domain verification with default values...";
_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS;
_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS;
startDynamicDomainVerification();
}
void EntityServer::startDynamicDomainVerification() {
qCDebug(entities) << "Starting Dynamic Domain Verification...";
QString thisDomainID = DependencyManager::get<AddressManager>()->getDomainId().remove(QRegExp("\\{|\\}"));
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
QHash<QString, EntityItemID> localMap(tree->getEntityCertificateIDMap());
QHashIterator<QString, EntityItemID> i(localMap);
qCDebug(entities) << localMap.size() << "entities in _entityCertificateIDMap";
while (i.hasNext()) {
i.next();
EntityItemPointer entity = tree->findEntityByEntityItemID(i.value());
if (entity) {
if (!entity->getProperties().verifyStaticCertificateProperties()) {
qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << i.value() << "failed"
<< "static certificate verification.";
// Delete the entity if it doesn't pass static certificate verification
tree->deleteEntity(i.value(), true);
} else {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest;
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL;
requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/location");
QJsonObject request;
request["certificate_id"] = i.key();
networkRequest.setUrl(requestURL);
QNetworkReply* networkReply = NULL;
networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
connect(networkReply, &QNetworkReply::finished, [=]() {
QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object();
jsonObject = jsonObject["data"].toObject();
if (networkReply->error() == QNetworkReply::NoError) {
if (jsonObject["domain_id"].toString() != thisDomainID) {
qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString()
<< "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << i.value();
tree->deleteEntity(i.value(), true);
} else {
qCDebug(entities) << "Entity passed dynamic domain verification:" << i.value();
}
} else {
qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << i.value()
<< "More info:" << jsonObject;
tree->deleteEntity(i.value(), true);
}
networkReply->deleteLater();
});
}
} else {
qCWarning(entities) << "During DDV, an entity with ID" << i.value() << "was NOT found in the Entity Tree!";
}
}
int nextInterval = qrand() % ((_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS + 1) - _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS) + _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS;
qCDebug(entities) << "Restarting Dynamic Domain Verification timer for" << nextInterval / 1000 << "seconds";
_dynamicDomainVerificationTimer.start(nextInterval);
}

View file

@ -73,6 +73,7 @@ protected:
private slots:
void handleEntityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void domainSettingsRequestFailed();
private:
SimpleEntitySimulationPointer _entitySimulation;
@ -80,6 +81,13 @@ private:
QReadWriteLock _viewerSendingStatsLock;
QMap<QUuid, QMap<QUuid, ViewerSendingStats>> _viewerSendingStats;
static const int DEFAULT_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = 45 * 60 * 1000; // 45m
static const int DEFAULT_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = 60 * 60 * 1000; // 1h
int _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS; // 45m
int _MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = DEFAULT_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS; // 1h
QTimer _dynamicDomainVerificationTimer;
void startDynamicDomainVerification();
};
#endif // hifi_EntityServer_h

View file

@ -23,6 +23,17 @@ EntityTreeSendThread::EntityTreeSendThread(OctreeServer* myServer, const SharedN
{
connect(std::static_pointer_cast<EntityTree>(myServer->getOctree()).get(), &EntityTree::editingEntityPointer, this, &EntityTreeSendThread::editingEntityPointer, Qt::QueuedConnection);
connect(std::static_pointer_cast<EntityTree>(myServer->getOctree()).get(), &EntityTree::deletingEntityPointer, this, &EntityTreeSendThread::deletingEntityPointer, Qt::QueuedConnection);
// connect to connection ID change on EntityNodeData so we can clear state for this receiver
auto nodeData = static_cast<EntityNodeData*>(node->getLinkedData());
connect(nodeData, &EntityNodeData::incomingConnectionIDChanged, this, &EntityTreeSendThread::resetState);
}
void EntityTreeSendThread::resetState() {
qCDebug(entities) << "Clearing known EntityTreeSendThread state for" << _nodeUuid;
_knownState.clear();
_traversal.reset();
}
void EntityTreeSendThread::preDistributionProcessing() {
@ -175,7 +186,7 @@ bool EntityTreeSendThread::addAncestorsToExtraFlaggedEntities(const QUuid& filte
return parentWasNew || ancestorsWereNew;
}
// since we didn't have a parent niether of our parents or ancestors could be new additions
// since we didn't have a parent, neither of our parents or ancestors could be new additions
return false;
}
@ -204,7 +215,9 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil
return hasNewChild || hasNewDescendants;
}
void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum) {
void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset,
bool usesViewFrustum) {
DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset, usesViewFrustum);
// there are three types of traversal:
//
@ -423,12 +436,19 @@ bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstream
uint64_t sendTime = usecTimestampNow();
auto nodeData = static_cast<OctreeQueryNode*>(params.nodeData);
nodeData->stats.encodeStarted();
auto entityNode = _node.toStrongRef();
auto entityNodeData = static_cast<EntityNodeData*>(entityNode->getLinkedData());
while(!_sendQueue.empty()) {
PrioritizedEntity queuedItem = _sendQueue.top();
EntityItemPointer entity = queuedItem.getEntity();
if (entity) {
// Only send entities that match the jsonFilters, but keep track of everything we've tried to send so we don't try to send it again
if (entity->matchesJSONFilters(jsonFilters)) {
bool entityMatchesFilters = entity->matchesJSONFilters(jsonFilters);
if (entityMatchesFilters || entityNodeData->isEntityFlaggedAsExtra(entity->getID())) {
if (!jsonFilters.isEmpty() && entityMatchesFilters) {
// Record explicitly filtered-in entity so that extra entities can be flagged.
entityNodeData->insertSentFilteredEntity(entity->getID());
}
OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData);
if (appendEntityState != OctreeElement::COMPLETED) {

View file

@ -33,12 +33,16 @@ protected:
void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,
bool viewFrustumChanged, bool isFullScene) override;
private slots:
void resetState(); // clears our known state forcing entities to appear unsent
private:
// the following two methods return booleans to indicate if any extra flagged entities were new additions to set
bool addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
bool addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData);
void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset, bool usesViewFrustum);
void startNewTraversal(const ViewFrustum& viewFrustum, EntityTreeElementPointer root, int32_t lodLevelOffset,
bool usesViewFrustum);
bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) override;
void preDistributionProcessing() override;

View file

@ -92,7 +92,19 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer<ReceivedMessage>
// Ask our tree subclass if it can handle the incoming packet...
PacketType packetType = message->getType();
if (_myServer->getOctree()->handlesEditPacketType(packetType)) {
if (packetType == PacketType::ChallengeOwnership) {
_myServer->getOctree()->withWriteLock([&] {
_myServer->getOctree()->processChallengeOwnershipPacket(*message, sendingNode);
});
} else if (packetType == PacketType::ChallengeOwnershipRequest) {
_myServer->getOctree()->withWriteLock([&] {
_myServer->getOctree()->processChallengeOwnershipRequestPacket(*message, sendingNode);
});
} else if (packetType == PacketType::ChallengeOwnershipReply) {
_myServer->getOctree()->withWriteLock([&] {
_myServer->getOctree()->processChallengeOwnershipReplyPacket(*message, sendingNode);
});
} else if (_myServer->getOctree()->handlesEditPacketType(packetType)) {
PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE", debugProcessPacket);
_receivedPacketCount++;

View file

@ -82,8 +82,12 @@ bool OctreeSendThread::process() {
if (auto node = _node.lock()) {
OctreeQueryNode* nodeData = static_cast<OctreeQueryNode*>(node->getLinkedData());
// Sometimes the node data has not yet been linked, in which case we can't really do anything
if (nodeData && !nodeData->isShuttingDown()) {
// If we don't have the OctreeQueryNode at all
// or it's uninitialized because we haven't received a query yet from the client
// or we don't know where we should send packets for this node
// or we're shutting down
// then we can't send an entity data packet
if (nodeData && nodeData->hasReceivedFirstQuery() && node->getActiveSocket() && !nodeData->isShuttingDown()) {
bool viewFrustumChanged = nodeData->updateCurrentViewFrustum();
packetDistributor(node, nodeData, viewFrustumChanged);
}

View file

@ -59,7 +59,8 @@ protected:
OctreePacketData _packetData;
QWeakPointer<Node> _node;
OctreeServer* _myServer { nullptr };
QUuid _nodeUuid;
private:
/// Called before a packetDistributor pass to allow for pre-distribution processing
virtual void preDistributionProcessing() {};
@ -71,8 +72,6 @@ private:
virtual void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene);
virtual bool shouldTraverseAndSend(OctreeQueryNode* nodeData) { return hasSomethingToSend(nodeData); }
QUuid _nodeUuid;
int _truePacketsSent { 0 }; // available for debug stats
int _trueBytesSent { 0 }; // available for debug stats
int _packetsSentThisInterval { 0 }; // used for bandwidth throttle condition

View file

@ -22,7 +22,6 @@
#include <HTTPConnection.h>
#include <LogHandler.h>
#include <shared/NetworkUtils.h>
#include <NetworkingConstants.h>
#include <NumericalConstants.h>
#include <UUID.h>

View file

@ -30,6 +30,8 @@
#include <UUID.h>
#include <WebSocketServerClass.h>
#include <EntityScriptClient.h> // for EntityScriptServerServices
#include "EntityScriptServerLogging.h"
#include "../entities/AssignmentParentFinder.h"
@ -68,6 +70,9 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig
DependencyManager::set<ScriptCache>();
DependencyManager::set<ScriptEngines>(ScriptEngine::ENTITY_SERVER_SCRIPT);
DependencyManager::set<EntityScriptServerServices>();
// Needed to ensure the creation of the DebugDraw instance on the main thread
DebugDraw::getInstance();
@ -85,6 +90,7 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig
packetReceiver.registerListener(PacketType::ReloadEntityServerScript, this, "handleReloadEntityServerScriptPacket");
packetReceiver.registerListener(PacketType::EntityScriptGetStatus, this, "handleEntityScriptGetStatusPacket");
packetReceiver.registerListener(PacketType::EntityServerScriptLog, this, "handleEntityServerScriptLogPacket");
packetReceiver.registerListener(PacketType::EntityScriptCallMethod, this, "handleEntityScriptCallMethodPacket");
static const int LOG_INTERVAL = MSECS_PER_SECOND / 10;
auto timer = new QTimer(this);
@ -231,6 +237,27 @@ void EntityScriptServer::pushLogs() {
}
}
void EntityScriptServer::handleEntityScriptCallMethodPacket(QSharedPointer<ReceivedMessage> receivedMessage, SharedNodePointer senderNode) {
if (_entitiesScriptEngine && _entityViewer.getTree() && !_shuttingDown) {
auto entityID = QUuid::fromRfc4122(receivedMessage->read(NUM_BYTES_RFC4122_UUID));
auto method = receivedMessage->readString();
quint16 paramCount;
receivedMessage->readPrimitive(&paramCount);
QStringList params;
for (int param = 0; param < paramCount; param++) {
auto paramString = receivedMessage->readString();
params << paramString;
}
_entitiesScriptEngine->callEntityScriptMethod(entityID, method, params, senderNode->getUUID());
}
}
void EntityScriptServer::run() {
// make sure we request our script once the agent connects to the domain
auto nodeList = DependencyManager::get<NodeList>();
@ -561,6 +588,7 @@ void EntityScriptServer::aboutToFinish() {
// cleanup the AudioInjectorManager (and any still running injectors)
DependencyManager::destroy<AudioInjectorManager>();
DependencyManager::destroy<ScriptEngines>();
DependencyManager::destroy<EntityScriptServerServices>();
// cleanup codec & encoder
if (_codec && _encoder) {

View file

@ -54,6 +54,9 @@ private slots:
void pushLogs();
void handleEntityScriptCallMethodPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
private:
void negotiateAudioFormat();
void selectAudioFormat(const QString& selectedCodecName);

View file

@ -13,7 +13,7 @@ ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/draco-1.1.0.zip
URL_MD5 208f8b04c91d5f1c73d731a3ea37c5bb
CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> ${EXTRA_CMAKE_FLAGS}
CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>-$<CONFIG> ${EXTRA_CMAKE_FLAGS}
LOG_DOWNLOAD 1
LOG_CONFIGURE 1
LOG_BUILD 1
@ -23,10 +23,11 @@ ExternalProject_Add(
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
set(SUFFIXED_INSTALL_DIR "${INSTALL_DIR}-$<CONFIG>")
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of Draco include directories")
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SUFFIXED_INSTALL_DIR}/include CACHE PATH "List of Draco include directories")
if (UNIX)
set(LIB_PREFIX "lib")
@ -35,6 +36,6 @@ elseif (WIN32)
set(LIB_EXT "lib")
endif ()
set(${EXTERNAL_NAME_UPPER}_LIBRARY ${INSTALL_DIR}/lib/${LIB_PREFIX}draco.${LIB_EXT} CACHE FILEPATH "Path to Draco release library")
set(${EXTERNAL_NAME_UPPER}_ENCODER_LIBRARY ${INSTALL_DIR}/lib/${LIB_PREFIX}dracoenc.${LIB_EXT} CACHE FILEPATH "Path to Draco encoder release library")
set(${EXTERNAL_NAME_UPPER}_DECODER_LIBRARY ${INSTALL_DIR}/lib/${LIB_PREFIX}dracodec.${LIB_EXT} CACHE FILEPATH "Path to Draco decoder release library")
set(${EXTERNAL_NAME_UPPER}_LIBRARY ${SUFFIXED_INSTALL_DIR}/lib/${LIB_PREFIX}draco.${LIB_EXT} CACHE FILEPATH "Path to Draco release library")
set(${EXTERNAL_NAME_UPPER}_ENCODER_LIBRARY ${SUFFIXED_INSTALL_DIR}/lib/${LIB_PREFIX}dracoenc.${LIB_EXT} CACHE FILEPATH "Path to Draco encoder release library")
set(${EXTERNAL_NAME_UPPER}_DECODER_LIBRARY ${SUFFIXED_INSTALL_DIR}/lib/${LIB_PREFIX}dracodec.${LIB_EXT} CACHE FILEPATH "Path to Draco decoder release library")

View file

@ -5,43 +5,41 @@ set(EXTERNAL_NAME hifiAudioCodec)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
if (NOT ANDROID)
if (WIN32 OR APPLE)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-1.zip
URL_MD5 23ec3fe51eaa155ea159a4971856fc13
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
LOG_DOWNLOAD 1
)
else ()
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-linux.zip
URL_MD5 7d37914a18aa4de971d2f45dd3043bde
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
LOG_DOWNLOAD 1
)
endif()
# Hide this external target (for ide users)
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL)
if (WIN32)
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE TYPE INTERNAL)
elseif(APPLE)
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL)
elseif(NOT ANDROID)
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL)
endif()
if (WIN32)
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-win-2.0.zip)
set(DOWNLOAD_MD5 9199d4dbd6b16bed736b235efe980e67)
elseif (APPLE)
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-mac-2.0.zip)
set(DOWNLOAD_MD5 21649881e7d5dc94f922179be96f76ba)
elseif (ANDROID)
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-android-2.0.zip)
set(DOWNLOAD_MD5 aef2a852600d498d58aa586668191683)
elseif (UNIX)
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-linux-2.0.zip)
set(DOWNLOAD_MD5 67fb7755f9bcafb98a9fceea53bc7481)
else()
return()
endif()
ExternalProject_Add(
${EXTERNAL_NAME}
URL ${DOWNLOAD_URL}
URL_MD5 ${DOWNLOAD_MD5}
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
LOG_DOWNLOAD 1
)
# Hide this external target (for ide users)
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL)
if (WIN32)
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE TYPE INTERNAL)
else()
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL)
endif()

View file

@ -6,8 +6,8 @@ if (WIN32)
include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi9.zip
URL_MD5 94f4765bdbcd53cd099f349ae031e769
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi10.zip
URL_MD5 4f40e49715a420fb67b45b9cee19052c
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""

View file

@ -118,6 +118,7 @@ macro(AUTOSCRIBE_SHADER_LIB)
foreach(SHADER_FILE ${SHADER_SOURCE_FILES})
AUTOSCRIBE_SHADER(${SHADER_FILE} ${SHADER_INCLUDE_FILES})
file(TO_CMAKE_PATH "${AUTOSCRIBE_SHADER_RETURN}" AUTOSCRIBE_GENERATED_FILE)
set_property(SOURCE ${AUTOSCRIBE_GENERATED_FILE} PROPERTY SKIP_AUTOMOC ON)
list(APPEND AUTOSCRIBE_SHADER_SRC ${AUTOSCRIBE_GENERATED_FILE})
endforeach()
#message(${TARGET_NAME} ${AUTOSCRIBE_SHADER_SRC})

View file

@ -0,0 +1,20 @@
function(GENERATE_QRC)
set(oneValueArgs OUTPUT PREFIX PATH)
set(multiValueArgs GLOBS)
cmake_parse_arguments(GENERATE_QRC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
if ("${GENERATE_QRC_PREFIX}" STREQUAL "")
set(QRC_PREFIX_PATH /)
else()
set(QRC_PREFIX_PATH ${GENERATE_QRC_PREFIX})
endif()
foreach(GLOB ${GENERATE_QRC_GLOBS})
file(GLOB_RECURSE FOUND_FILES RELATIVE ${GENERATE_QRC_PATH} ${GLOB})
foreach(FILENAME ${FOUND_FILES})
set(QRC_CONTENTS "${QRC_CONTENTS}<file alias=\"${FILENAME}\">${GENERATE_QRC_PATH}/${FILENAME}</file>\n")
endforeach()
endforeach()
configure_file("${HF_CMAKE_DIR}/templates/resources.qrc.in" ${GENERATE_QRC_OUTPUT})
endfunction()

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,19 +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 ("$ENV{${_ENV_VAR_NAME}}" STREQUAL "")
set (${_RESULT_NAME} ${_DEFAULT_VALUE} PARENT_SCOPE)
else()
set (${_RESULT_NAME} $ENV{${_ENV_VAR_NAME}} PARENT_SCOPE)
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

@ -60,7 +60,7 @@ if (WIN32 AND NOT CYGWIN)
select_library_configurations(LIB_EAY)
select_library_configurations(SSL_EAY)
set(OPENSSL_LIBRARIES ${SSL_EAY_LIBRARY} ${LIB_EAY_LIBRARY})
find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" ${_OPENSSL_ROOT_HINTS_AND_PATHS})
find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} NO_DEFAULT_PATH)
endif()
else()

View file

@ -0,0 +1,5 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="@QRC_PREFIX_PATH@">
@QRC_CONTENTS@
</qresource>
</RCC>

View file

@ -1,6 +1,12 @@
{
"version": 1.8,
"version": 2.0,
"settings": [
{
"name": "label",
"label": "Label",
"settings": [
]
},
{
"name": "metaverse",
"label": "Metaverse / Networking",
@ -14,7 +20,8 @@
{
"name": "id",
"label": "Domain ID",
"help": "This is your High Fidelity domain ID. If you do not want your domain to be registered in the High Fidelity metaverse you can leave this blank."
"help": "This is your High Fidelity domain ID. If you do not want your domain to be registered in the High Fidelity metaverse you can leave this blank.",
"advanced": true
},
{
"name": "automatic_networking",
@ -82,11 +89,13 @@
{
"name": "description",
"label": "Description",
"advanced": true,
"help": "A description of your domain (256 character limit)."
},
{
"name": "maturity",
"label": "Maturity",
"advanced": true,
"help": "A maturity rating, available as a guideline for content on your domain.",
"default": "unrated",
"type": "select",
@ -116,6 +125,7 @@
{
"name": "hosts",
"label": "Hosts",
"advanced": true,
"type": "table",
"can_add_new_rows": true,
"help": "Usernames of hosts who can reliably show your domain to new visitors.",
@ -131,6 +141,7 @@
{
"name": "tags",
"label": "Tags",
"advanced": true,
"type": "table",
"can_add_new_rows": true,
"help": "Common categories under which your domain falls.",
@ -207,7 +218,7 @@
"name": "standard_permissions",
"type": "table",
"label": "Domain-Wide User Permissions",
"help": "Indicate which types of users can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
"help": "Indicate which types of users can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
"caption": "Standard Permissions",
"can_add_new_rows": false,
"groups": [
@ -216,8 +227,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
"span": 8
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let&rsquo;s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
"span": 10
}
],
"columns": [
@ -253,6 +264,20 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_certified",
"label": "Rez Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_rez_tmp_certified",
"label": "Rez Temporary Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_write_to_asset_server",
"label": "Write Assets",
@ -283,7 +308,7 @@
}
],
"non-deletable-row-key": "permissions_id",
"non-deletable-row-values": ["localhost", "anonymous", "logged-in"]
"non-deletable-row-values": [ "localhost", "anonymous", "logged-in" ]
},
{
"name": "group_permissions",
@ -300,8 +325,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in, as well as permissions from the previous section. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 8
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in, as well as permissions from the previous section. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 10
}
],
"columns": [
@ -362,6 +387,20 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_certified",
"label": "Rez Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_rez_tmp_certified",
"label": "Rez Temporary Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_write_to_asset_server",
"label": "Write Assets",
@ -383,7 +422,7 @@
"editable": true,
"default": false
},
{
{
"name": "id_can_replace_content",
"label": "Replace Content",
"type": "checkbox",
@ -407,8 +446,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users in specific groups can replace entire content sets by wiping existing domain content.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 8
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users in specific groups can replace entire content sets by wiping existing domain content.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in. Group permissions are only granted if the user doesn&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 10
}
],
"columns": [
@ -466,6 +505,20 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_certified",
"label": "Rez Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_rez_tmp_certified",
"label": "Rez Temporary Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_write_to_asset_server",
"label": "Write Assets",
@ -487,7 +540,7 @@
"editable": true,
"default": false
},
{
{
"name": "id_can_replace_content",
"label": "Replace Content",
"type": "checkbox",
@ -507,8 +560,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level or group permissions that might otherwise apply to that user.</p>'>?</a>",
"span": 8
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level or group permissions that might otherwise apply to that user.</p>'>?</a>",
"span": 10
}
],
"columns": [
@ -544,6 +597,20 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_certified",
"label": "Rez Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_rez_tmp_certified",
"label": "Rez Temporary Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_write_to_asset_server",
"label": "Write Assets",
@ -565,7 +632,7 @@
"editable": true,
"default": false
},
{
{
"name": "id_can_replace_content",
"label": "Replace Content",
"type": "checkbox",
@ -585,8 +652,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide IP Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users from specific IPs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific IPs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users from specific IPs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users from specific IPs can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users from specific IPs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users from specific IPs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users from specific IPs can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific IP will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). IP address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 8
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide IP Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users from specific IPs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific IPs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users from specific IPs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users from specific IPs can create new entities with a finite lifetime.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users from specific IPs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users from specific IPs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users from specific IPs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users from specific IPs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users from specific IPs can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific IP will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). IP address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 10
}
],
"columns": [
@ -622,6 +689,20 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_certified",
"label": "Rez Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_rez_tmp_certified",
"label": "Rez Temporary Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_write_to_asset_server",
"label": "Write Assets",
@ -643,7 +724,7 @@
"editable": true,
"default": false
},
{
{
"name": "id_can_replace_content",
"label": "Replace Content",
"type": "checkbox",
@ -663,8 +744,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide MAC Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific MACs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific MACs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific MACs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific MACs can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific MACs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific MACs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific MACs can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific MAC will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). MAC address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 8
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide MAC Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific MACs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific MACs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific MACs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific MACs can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific MACs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific MACs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific MACs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific MACs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific MACs can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific MAC will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). MAC address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 10
}
],
"columns": [
@ -700,6 +781,20 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_certified",
"label": "Rez Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_rez_tmp_certified",
"label": "Rez Temporary Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_write_to_asset_server",
"label": "Write Assets",
@ -721,7 +816,7 @@
"editable": true,
"default": false
},
{
{
"name": "id_can_replace_content",
"label": "Replace Content",
"type": "checkbox",
@ -741,8 +836,8 @@
"span": 1
},
{
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide Machine Fingerprint Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific Machine Fingerprints can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific Machine Fingerprints can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific Machine Fingerprints can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific Machine Fingerprints can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific Machine Fingerprints can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific Machine Fingerprints can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific Machine Fingerprints can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific Machine Fingerprint will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). Machine Fingerprint address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 8
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide Machine Fingerprint Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific Machine Fingerprints can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific Machine Fingerprints can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific Machine Fingerprints can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific Machine Fingerprints can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific Machine Fingerprints can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific Machine Fingerprints can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific Machine Fingerprints can replace entire content sets by wiping existing domain content.</li></ul><p>Note that permissions assigned to a specific Machine Fingerprint will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). Machine Fingerprint address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 10
}
],
"columns": [
@ -778,6 +873,20 @@
"editable": true,
"default": false
},
{
"name": "id_can_rez_certified",
"label": "Rez Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_rez_tmp_certified",
"label": "Rez Temporary Certified",
"type": "checkbox",
"editable": true,
"default": false
},
{
"name": "id_can_write_to_asset_server",
"label": "Write Assets",
@ -799,7 +908,7 @@
"editable": true,
"default": false
},
{
{
"name": "id_can_replace_content",
"label": "Replace Content",
"type": "checkbox",
@ -807,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
}
]
},
@ -841,7 +958,7 @@
{
"name": "asset_server",
"label": "Asset Server (ATP)",
"assignment-types": [3],
"assignment-types": [ 3 ],
"settings": [
{
"name": "enabled",
@ -858,13 +975,21 @@
"help": "The path to the directory assets are stored in.<br/>If this path is relative, it will be relative to the application data directory.<br/>If you change this path you will need to manually copy any existing assets from the previous directory.",
"default": "",
"advanced": true
},
{
"name": "assets_filesize_limit",
"type": "int",
"label": "File Size Limit",
"help": "The file size limit of an asset that can be imported into the asset server in MBytes. 0 (default) means no limit on file size.",
"default": 0,
"advanced": true
}
]
},
{
"name": "entity_script_server",
"label": "Entity Script Server (ESS)",
"assignment-types": [5],
"assignment-types": [ 5 ],
"settings": [
{
"name": "entity_pps_per_script",
@ -887,7 +1012,7 @@
{
"name": "avatars",
"label": "Avatars",
"assignment-types": [1, 2],
"assignment-types": [ 1, 2 ],
"settings": [
{
"name": "min_avatar_scale",
@ -926,7 +1051,7 @@
{
"name": "audio_threading",
"label": "Audio Threading",
"assignment-types": [0],
"assignment-types": [ 0 ],
"settings": [
{
"name": "auto_threads",
@ -949,7 +1074,7 @@
{
"name": "audio_env",
"label": "Audio Environment",
"assignment-types": [0],
"assignment-types": [ 0 ],
"settings": [
{
"name": "attenuation_per_doubling_in_distance",
@ -1156,6 +1281,22 @@
"default": "3600",
"advanced": true
},
{
"name": "dynamicDomainVerificationTimeMin",
"label": "Dynamic Domain Verification Time (seconds) - Minimum",
"help": "The lower limit on the amount of time that passes before Dynamic Domain Verification on entities occurs. Units are seconds.",
"placeholder": "2700",
"default": "2700",
"advanced": true
},
{
"name": "dynamicDomainVerificationTimeMax",
"label": "Dynamic Domain Verification Time (seconds) - Maximum",
"help": "The upper limit on the amount of time that passes before Dynamic Domain Verification on entities occurs. Units are seconds.",
"placeholder": "3600",
"default": "3600",
"advanced": true
},
{
"name": "entityScriptSourceWhitelist",
"label": "Entity Scripts Allowed from:",
@ -1503,6 +1644,29 @@
]
}
]
},
{
"name": "wizard",
"label": "Setup Wizard",
"restart": false,
"hidden": true,
"settings": [
{
"name": "cloud_domain",
"type": "checkbox",
"default": false
},
{
"name": "steps_completed",
"type": "int",
"default": 0
},
{
"name": "completed_once",
"type": "checkbox",
"default": false
}
]
}
]
}
}

View file

@ -19,12 +19,13 @@
Upload an entities file (e.g.: models.json.gz) to replace the content of this domain.<br>
Note: <strong>Your domain's content will be replaced by the content you upload</strong>, but the backup files of your domain's content will not immediately be changed.
</p>
<p>
If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:<br>
<pre>C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz</pre>
<pre>/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz</pre>
<pre>/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz</pre>
</p>
<p>If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:</p>
<label class="control-label">Windows</label>
<pre>C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz</pre>
<label class="control-label">OSX</label>
<pre>/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz</pre>
<label class="control-label">Linux</label>
<pre>/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz</pre>
<br>
<input type="file" name="entities-file" class="form-control-file" accept=".json, .gz">
<br>

View file

@ -1,6 +1,21 @@
/* cairo-regular - latin */
@font-face {
font-family: 'Cairo';
font-style: normal;
font-weight: 400;
src: url('/fonts/cairo-v2-latin-regular.eot'); /* IE9 Compat Modes */
src: local('Cairo'), local('Cairo-Regular'),
url('/fonts/cairo-v2-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/cairo-v2-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/cairo-v2-latin-regular.woff') format('woff'), /* Modern Browsers */
url('/fonts/cairo-v2-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/cairo-v2-latin-regular.svg#Cairo') format('svg'); /* Legacy iOS */
}
body {
position: relative;
padding-bottom: 30px;
margin-top: 70px;
}
[hidden] {
@ -27,14 +42,14 @@ body {
.table .value-row td,
.table .value-category td,
.table .inputs td {
vertical-align: middle;
vertical-align: middle;
}
.table .table-checkbox {
/* Fix IE sizing checkboxes to fill table cell */
width: auto;
margin-left: auto;
margin-right: auto;
/* Fix IE sizing checkboxes to fill table cell */
width: auto;
margin-left: auto;
margin-right: auto;
}
.value-category:not(.inputs) {
@ -79,9 +94,23 @@ span.port {
display: none;
}
#setup-sidebar.affix {
position: fixed;
top: 15px;
@media (min-width: 768px) {
#setup-sidebar.affix {
/* This overrides a case where going to the bottom of the page,
* then scrolling up, causes `position: relative` to be added to the style
*/
position: fixed !important;
}
}
@media (max-width: 767px) {
#setup-sidebar.affix {
position: static !important;
}
#setup-sidebar {
margin-bottom: 20px;
}
}
#setup-sidebar button {
@ -145,55 +174,55 @@ table {
}
caption {
color: #333;
font-weight: 700;
padding-top: 0;
color: #333;
font-weight: 700;
padding-top: 0;
}
table > tbody > .headers > td {
vertical-align: middle;
vertical-align: middle;
}
table .headers + .headers td {
font-size: 13px;
color: #222;
font-size: 13px;
color: #222;
}
#security table .headers td + td {
text-align: center;
text-align: center;
}
.tooltip.top .tooltip-arrow {
border-top-color: #fff;
border-width: 10px 10px 0;
margin-bottom: -5px;
border-top-color: #fff;
border-width: 10px 10px 0;
margin-bottom: -5px;
}
.tooltip-inner {
padding: 20px 20px 10px 20px;
font-size: 14px;
text-align: left;
color: #333;
background-color: #fff;
box-shadow: 0 3px 8px 8px #e8e8e8;
padding: 20px 20px 10px 20px;
font-size: 14px;
text-align: left;
color: #333;
background-color: #fff;
box-shadow: 0 3px 8px 8px #e8e8e8;
}
.tooltip.in {
opacity: 1;
opacity: 1;
}
.tooltip-inner ul {
padding-left: 0;
margin-bottom: 15px;
padding-left: 0;
margin-bottom: 15px;
}
.tooltip-inner li {
list-style-type: none;
margin-bottom: 5px;
list-style-type: none;
margin-bottom: 5px;
}
#security .tooltip-inner {
max-width: 520px;
max-width: 520px;
}
#xs-advanced-container {
@ -241,6 +270,20 @@ table .headers + .headers td {
animation-delay: -0.16s;
}
.col-centered {
float: none;
margin: 0 auto;
}
.centered-hack-parent {
text-align: center;
}
.centered-hack {
text-align: left;
display: inline-block;
}
@-webkit-keyframes bouncedelay {
0%, 80%, 100% { -webkit-transform: scale(0.0) }
40% { -webkit-transform: scale(1.0) }
@ -255,3 +298,50 @@ table .headers + .headers td {
-webkit-transform: scale(1.0);
}
}
/* From https://gist.github.com/alexandrevicenzi/680147013e902a4eaa5d */
.glyphicon-refresh-animate {
-animation: spin .7s infinite linear;
-ms-animation: spin .7s infinite linear;
-webkit-animation: spinw .7s infinite linear;
-moz-animation: spinm .7s infinite linear;
}
@keyframes spin {
from { transform: scale(1) rotate(0deg); }
to { transform: scale(1) rotate(360deg); }
}
@-webkit-keyframes spinw {
from { -webkit-transform: rotate(0deg); }
to { -webkit-transform: rotate(360deg); }
}
@-moz-keyframes spinm {
from { -moz-transform: rotate(0deg); }
to { -moz-transform: rotate(360deg); }
}
.warning-text {
padding-top: 10px;
color: #EB5757;
}
.account-connected-header {
vertical-align: middle;
color: #6FCF97;
font-size: 30px;
margin-right: 20px;
}
.blue-link {
font-size: 14px;
text-decoration-line: underline;
font-weight: normal;
color: #00B3F8;
}
#manage-cloud-domains-link {
text-align: center;
margin-top: 20px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -0,0 +1 @@
<!DOCTYPE html><html lang=en><meta charset=utf-8><meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width"><title>Error 500 (Server Error)!!1</title><style>*{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{color:#222;text-align:unset;margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px;}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}pre{white-space:pre-wrap;}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px}</style><div id="af-error-container"><a href=//www.google.com><span id=logo aria-label=Google></span></a><p><b>500.</b> <ins>Thats an error.</ins><p>There was an error. Please try again later. <ins>Thats all we know.</ins></div>

View file

@ -13,7 +13,7 @@
<script src='/js/sweetalert.min.js'></script>
</head>
<body>
<nav class="navbar navbar-default" role="navigation">
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
@ -23,7 +23,6 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">domain-server</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
@ -40,6 +39,7 @@
<li><a href="/settings/">Settings</a></li>
</ul>
<ul class="nav navbar-right navbar-nav">
<li><a id="visit-domain-link" class="blue-link" target="_blank" style="display: none;">Visit domain in VR</a></li>
<li><a href="#" id="restart-server"><span class="glyphicon glyphicon-refresh"></span> Restart</a></li>
</ul>
</div>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 576 576" style="enable-background:new 0 0 576 576;" xml:space="preserve">
<path d="M329.7,410.7H127.8c-42.1,0-76.4-34.3-76.4-76.4V132.4c0-42.1,34.3-76.4,76.4-76.4h201.9c42.1,0,76.4,34.3,76.4,76.4v201.9
C406.1,376.4,371.8,410.7,329.7,410.7z M127.8,98.9c-18.5,0-33.5,15-33.5,33.5v201.9c0,18.5,15,33.5,33.5,33.5h201.9
c18.5,0,33.5-15,33.5-33.5V132.4c0-18.5-15-33.5-33.5-33.5H127.8z"/>
<path d="M449.4,519H407c-11.9,0-21.5-9.6-21.5-21.5s9.6-21.5,21.5-21.5h42.4c11.9,0,21.5,9.6,21.5,21.5S461.3,519,449.4,519z
M305.1,519H263c-11.9,0-21.5-9.6-21.5-21.5s9.6-21.5,21.5-21.5h42.2c11.9,0,21.5,9.6,21.5,21.5S317,519,305.1,519z M192.4,464.1
c-11.9,0-21.5-9.6-21.5-21.4v-42.2c0-11.9,9.6-21.5,21.5-21.5c11.9,0,21.5,9.6,21.5,21.5v42.1C213.8,454.5,204.2,464.1,192.4,464.1z
M504.1,448.2c-11.9,0-21.5-9.6-21.5-21.5v-42.2c0-11.9,9.6-21.5,21.5-21.5c11.9,0,21.5,9.6,21.5,21.5v42.2
C525.6,438.6,516,448.2,504.1,448.2z M192.4,320.1c-11.9,0-21.5-9.6-21.5-21.5v-42.2c0-11.9,9.6-21.5,21.5-21.5
c11.9,0,21.5,9.6,21.5,21.5v42.2C213.8,310.5,204.2,320.1,192.4,320.1z M504.1,304.1c-11.9,0-21.5-9.6-21.5-21.5v-42
c0-11.9,9.6-21.6,21.5-21.6c11.9,0,21.5,9.5,21.5,21.4v42.2C525.6,294.5,516,304.1,504.1,304.1z M433.4,207.2h-42.2
c-11.9,0-21.5-9.6-21.5-21.5s9.6-21.5,21.5-21.5h42.2c11.9,0,21.5,9.6,21.5,21.5S445.3,207.2,433.4,207.2z M289.3,207.2h-42.1
c-11.9,0-21.5-9.6-21.5-21.5s9.6-21.5,21.4-21.5h42.2c11.9,0,21.5,9.6,21.5,21.5S301.2,207.2,289.3,207.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

View file

@ -32,7 +32,6 @@ $(document).ready(function(){
$('ul.nav a').filter(function() {
return this.href == url;
}).parent().addClass('active');
$('body').on('click', '#restart-server', function(e) {
swal( {
title: "Are you sure?",
@ -46,4 +45,4 @@ $(document).ready(function(){
});
return false;
});
});
});

View file

@ -0,0 +1,461 @@
var Settings = {
showAdvanced: false,
ADVANCED_CLASS: 'advanced-setting',
DEPRECATED_CLASS: 'deprecated-setting',
TRIGGER_CHANGE_CLASS: 'trigger-change',
DATA_ROW_CLASS: 'value-row',
DATA_COL_CLASS: 'value-col',
DATA_CATEGORY_CLASS: 'value-category',
ADD_ROW_BUTTON_CLASS: 'add-row',
ADD_ROW_SPAN_CLASSES: 'glyphicon glyphicon-plus add-row',
DEL_ROW_BUTTON_CLASS: 'del-row',
DEL_ROW_SPAN_CLASSES: 'glyphicon glyphicon-remove del-row',
ADD_CATEGORY_BUTTON_CLASS: 'add-category',
ADD_CATEGORY_SPAN_CLASSES: 'glyphicon glyphicon-plus add-category',
TOGGLE_CATEGORY_COLUMN_CLASS: 'toggle-category',
TOGGLE_CATEGORY_SPAN_CLASS: 'toggle-category-icon',
TOGGLE_CATEGORY_SPAN_CLASSES: 'glyphicon toggle-category-icon',
TOGGLE_CATEGORY_EXPANDED_CLASS: 'glyphicon-triangle-bottom',
TOGGLE_CATEGORY_CONTRACTED_CLASS: 'glyphicon-triangle-right',
DEL_CATEGORY_BUTTON_CLASS: 'del-category',
DEL_CATEGORY_SPAN_CLASSES: 'glyphicon glyphicon-remove del-category',
MOVE_UP_BUTTON_CLASS: 'move-up',
MOVE_UP_SPAN_CLASSES: 'glyphicon glyphicon-chevron-up move-up',
MOVE_DOWN_BUTTON_CLASS: 'move-down',
MOVE_DOWN_SPAN_CLASSES: 'glyphicon glyphicon-chevron-down move-down',
TABLE_BUTTONS_CLASS: 'buttons',
ADD_DEL_BUTTONS_CLASS: 'add-del-buttons',
ADD_DEL_BUTTONS_CLASSES: 'buttons add-del-buttons',
REORDER_BUTTONS_CLASS: 'reorder-buttons',
REORDER_BUTTONS_CLASSES: 'buttons reorder-buttons',
NEW_ROW_CLASS: 'new-row',
CONNECT_ACCOUNT_BTN_ID: 'connect-account-btn',
DISCONNECT_ACCOUNT_BTN_ID: 'disconnect-account-btn',
CREATE_DOMAIN_ID_BTN_ID: 'create-domain-btn',
CHOOSE_DOMAIN_ID_BTN_ID: 'choose-domain-btn',
GET_TEMPORARY_NAME_BTN_ID: 'get-temp-name-btn',
DOMAIN_ID_SELECTOR: '[name="metaverse.id"]',
ACCESS_TOKEN_SELECTOR: '[name="metaverse.access_token"]',
PLACES_TABLE_ID: 'places-table',
ADD_PLACE_BTN_ID: 'add-place-btn',
FORM_ID: 'settings-form',
INVALID_ROW_CLASS: 'invalid-input',
DATA_ROW_INDEX: 'data-row-index'
};
var URLs = {
// STABLE METAVERSE_URL: https://metaverse.highfidelity.com
// STAGING METAVERSE_URL: https://staging.highfidelity.com
METAVERSE_URL: 'https://metaverse.highfidelity.com',
PLACE_URL: 'https://hifi.place',
};
var Strings = {
LOADING_SETTINGS_ERROR: "There was a problem loading the domain settings.\nPlease refresh the page to try again.",
CHOOSE_DOMAIN_BUTTON: "Choose from my domains",
CREATE_DOMAIN_BUTTON: "Create new domain ID",
CREATE_DOMAIN_SUCCESS_JUST_CONNECTED: "We connnected your High Fidelity account and created a new domain ID for this machine.",
CREATE_DOMAIN_SUCCESS: "We created a new domain ID for this machine.",
// When a place modification fails, they will be brought back to the previous
// dialog with new path still set, allowing them to retry immediately, and without
// having to type the new path in again.
EDIT_PLACE_TITLE: "Modify Viewpoint or Path",
EDIT_PLACE_ERROR: "Failed to update Viewpoint or Path for this Place Name. Please try again.",
EDIT_PLACE_CONFIRM_BUTTON: "Save",
EDIT_PLACE_CONFIRM_BUTTON_PENDING: "Saving...",
EDIT_PLACE_CANCEL_BUTTON: "Cancel",
REMOVE_PLACE_TITLE: "Are you sure you want to remove <strong>{{place}}</strong> and its path information?",
REMOVE_PLACE_ERROR: "Failed to remove Place Name and its Path information.",
REMOVE_PLACE_DELETE_BUTTON: "This action removes your Place Name",
REMOVE_PLACE_DELETE_BUTTON_PENDING: "Deleting...",
REMOVE_PLACE_CANCEL_BUTTON: "Cancel",
ADD_PLACE_TITLE: "Choose a place",
ADD_PLACE_MESSAGE: "Choose a Place Name that you own or register a new Place Name.",
ADD_PLACE_CONFIRM_BUTTON: "Save",
ADD_PLACE_CONFIRM_BUTTON_PENDING: "Saving...",
ADD_PLACE_CANCEL_BUTTON: "Cancel",
ADD_PLACE_UNKNOWN_ERROR: "There was an error adding this Place Name. Try saving again",
ADD_PLACE_NO_PLACES_MESSAGE: "You don't have any Place Names registered. Once you have a Place Name, reopen this window to select it.",
ADD_PLACE_NO_PLACES_BUTTON: "Create new place",
ADD_PLACE_UNABLE_TO_LOAD_ERROR: "We were unable to load your place names. Please try again later.",
ADD_PLACE_LOADING_DIALOG: "Loading your places...",
ADD_PLACE_NOT_CONNECTED_TITLE: "Access token required",
ADD_PLACE_NOT_CONNECTED_MESSAGE: "You must have an access token to query your High Fidelity places.<br><br>Please follow the instructions on the settings page to add an access token.",
};
var DOMAIN_ID_TYPE_NONE = 0;
var DOMAIN_ID_TYPE_TEMP = 1;
var DOMAIN_ID_TYPE_FULL = 2;
var DOMAIN_ID_TYPE_UNKNOWN = 3;
function domainIDIsSet() {
return Settings.data.values.metaverse.id.length > 0;
}
function getCurrentDomainIDType() {
if (!domainIDIsSet()) {
return DOMAIN_ID_TYPE_NONE;
}
if (typeof DomainInfo === 'undefined') {
return DOMAIN_ID_TYPE_UNKNOWN;
}
if (DomainInfo !== null) {
if (DomainInfo.name !== undefined) {
return DOMAIN_ID_TYPE_TEMP;
}
return DOMAIN_ID_TYPE_FULL;
}
return DOMAIN_ID_TYPE_UNKNOWN;
}
function showLoadingDialog(msg) {
var message = '<div class="text-center">';
message += '<span class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></span> ' + msg;
message += '</div>';
return bootbox.dialog({
message: message,
closeButton: false
});
}
function sendUpdatePlaceRequest(id, path, domainID, clearDomainID, onSuccess, onError) {
var data = {
place_id: id,
path: path
};
if (domainID) {
data.domain_id = domainID;
}
if (clearDomainID) {
data.domain_id = null;
}
$.ajax({
url: '/api/places',
type: 'PUT',
data: data,
success: onSuccess,
error: onError
});
}
var pendingDomainRequest = null;
function getDomainFromAPI(callback) {
if (pendingDomainRequest !== null) {
pendingDomainRequest.success(callback);
pendingDomainRequest.error(function() { callback({ status: 'fail' }) });
return pendingDomainRequest;
}
if (callback === undefined) {
callback = function() {};
}
var domainID = Settings.data.values.metaverse.id;
if (domainID === null || domainID === undefined || domainID === '') {
callback({ status: 'fail' });
return null;
}
pendingDomainRequest = $.ajax({
url: "/api/domains/" + domainID,
dataType: 'json',
success: function(data) {
pendingDomainRequest = null;
if (data.status === 'success') {
DomainInfo = data.domain;
} else {
DomainInfo = null;
}
callback(data);
},
error: function() {
pendingDomainRequest = null;
DomainInfo = null;
callback({ status: 'fail' });
}
});
return pendingDomainRequest;
}
function chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAdded) {
if (accessToken) {
var loadingDialog = showLoadingDialog(Strings.ADD_PLACE_LOADING_DIALOG);
function loadPlaces() {
$.ajax("/api/places", {
dataType: 'json',
jsonp: false,
success: function(data) {
if (data.status == 'success') {
var modal_buttons = {
cancel: {
label: Strings.ADD_PLACE_CANCEL_BUTTON,
className: 'add-place-cancel-button btn-default'
}
};
var dialog;
var modal_body;
if (data.data.places.length) {
var places_by_id = {};
modal_body = $('<div>');
modal_body.append($("<p>Choose a place name that you own or <a href='" + URLs.METAVERSE_URL + "/user/places' target='_blank'>register a new place name</a></p>"));
var currentDomainIDType = getCurrentDomainIDType();
if (currentDomainIDType === DOMAIN_ID_TYPE_TEMP) {
var warning = "<div class='domain-loading-error alert alert-warning'>";
warning += "If you choose a place name it will replace your current temporary place name.";
warning += "</div>";
modal_body.append(warning);
}
// setup a select box for the returned places
modal_body.append($("<label for='place-name-select'>Places</label>"));
place_select = $("<select id='place-name-select' class='form-control'></select>");
_.each(data.data.places, function(place) {
places_by_id[place.id] = place;
place_select.append("<option value='" + place.id + "'>" + place.name + "</option>");
})
modal_body.append(place_select);
modal_body.append($("<p id='place-name-warning' class='warning-text' style='display: none'>This place name already points to a place or path. Saving this would overwrite the previous settings associated with it.</p>"));
if (forcePathTo === undefined || forcePathTo === null) {
var path = "<div class='form-group'>";
path += "<label for='place-path-input' class='control-label'>Path or Viewpoint</label>";
path += "<input type='text' id='place-path-input' class='form-control' value='/'>";
path += "</div>";
modal_body.append($(path));
}
var place_select = modal_body.find("#place-name-select")
place_select.change(function(ev) {
var warning = modal_body.find("#place-name-warning");
var place = places_by_id[$(this).val()];
if (place === undefined || place.pointee === null) {
warning.hide();
} else {
warning.show();
}
});
place_select.trigger('change');
modal_buttons["success"] = {
label: Strings.ADD_PLACE_CONFIRM_BUTTON,
className: 'add-place-confirm-button btn btn-primary',
callback: function() {
var placeID = $('#place-name-select').val();
// set the place ID on the form
$(Settings.place_ID_SELECTOR).val(placeID).change();
if (forcePathTo === undefined || forcePathTo === null) {
var placePath = $('#place-path-input').val();
} else {
var placePath = forcePathTo;
}
$('.add-place-confirm-button').attr('disabled', 'disabled');
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON_PENDING);
$('.add-place-cancel-button').attr('disabled', 'disabled');
function finalizeSaveDomainID(domainID) {
var jsonSettings = {
metaverse: {
id: domainID
}
}
var dialog = showLoadingDialog("Waiting for Domain Server to restart...");
$.ajax('/settings.json', {
data: JSON.stringify(jsonSettings),
contentType: 'application/json',
type: 'POST'
}).done(function(data) {
if (data.status == "success") {
waitForDomainServerRestart(function() {
dialog.modal('hide');
if (onSuccessfullyAdded) {
onSuccessfullyAdded(places_by_id[placeID].name, domainID);
}
});
} else {
bootbox.alert("Failed to add place");
}
}).fail(function() {
bootbox.alert("Failed to add place");
});
}
// If domainID is not specified, the current domain id will be used.
function finishSettingUpPlace(domainID) {
sendUpdatePlaceRequest(
placeID,
placePath,
domainID,
false,
function(data) {
dialog.modal('hide')
if (domainID) {
$(Settings.DOMAIN_ID_SELECTOR).val(domainID).change();
finalizeSaveDomainID(domainID);
} else {
if (onSuccessfullyAdded) {
onSuccessfullyAdded(places_by_id[placeID].name);
}
}
},
function(data) {
$('.add-place-confirm-button').removeAttr('disabled');
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
$('.add-place-cancel-button').removeAttr('disabled');
bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
}
);
}
function maybeCreateNewDomainID() {
console.log("Maybe creating domain id", currentDomainIDType)
if (currentDomainIDType === DOMAIN_ID_TYPE_FULL) {
finishSettingUpPlace();
} else {
sendCreateDomainRequest(function(domainID) {
console.log("Created domain", domainID);
finishSettingUpPlace(domainID);
}, function() {
$('.add-place-confirm-button').removeAttr('disabled');
$('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON);
$('.add-place-cancel-button').removeAttr('disabled');
bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR);
});
}
}
maybeCreateNewDomainID();
return false;
}
}
} else {
modal_buttons["success"] = {
label: Strings.ADD_PLACE_NO_PLACES_BUTTON,
callback: function() {
window.open(URLs.METAVERSE_URL + "/user/places", '_blank');
}
}
modal_body = Strings.ADD_PLACE_NO_PLACES_MESSAGE;
}
dialog = bootbox.dialog({
title: Strings.ADD_PLACE_TITLE,
message: modal_body,
closeButton: false,
buttons: modal_buttons,
onEscape: true
});
} else {
bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR);
}
},
error: function() {
bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR);
},
complete: function() {
loadingDialog.modal('hide');
}
});
}
var domainType = getCurrentDomainIDType();
if (domainType !== DOMAIN_ID_TYPE_UNKNOWN) {
loadPlaces();
} else {
getDomainFromAPI(function(data) {
if (data.status === 'success') {
var domainType = getCurrentDomainIDType();
loadPlaces();
} else {
loadingDialog.modal('hide');
bootbox.confirm("We were not able to load your domain information from the Metaverse. Would you like to retry?", function(response) {
if (response) {
chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAdded);
}
});
}
})
}
} else {
bootbox.alert({
title: Strings.ADD_PLACE_NOT_CONNECTED_TITLE,
message: Strings.ADD_PLACE_NOT_CONNECTED_MESSAGE
})
}
}
function sendCreateDomainRequest(onSuccess, onError) {
$.ajax({
url: '/api/domains',
dataType: 'json',
type: 'POST',
data: { label: "" },
success: function(data) {
onSuccess(data.domain.id);
},
error: onError
});
}
function waitForDomainServerRestart(callback) {
function checkForDomainUp() {
$.ajax('', {
success: function() {
callback();
},
error: function() {
setTimeout(checkForDomainUp, 50);
}
});
}
setTimeout(checkForDomainUp, 10);
}
function prepareAccessTokenPrompt(callback) {
swal({
title: "Connect Account",
type: "input",
text: "Paste your created access token here." +
"</br></br>If you did not successfully create an access token click cancel below and attempt to connect your account again.</br></br>",
showCancelButton: true,
closeOnConfirm: false,
html: true
}, function(inputValue){
if (inputValue === false) {
return false;
}
if (inputValue === "") {
swal.showInputError("Please paste your access token in the input field.")
return false
}
if (callback) {
callback(inputValue);
}
swal.close();
});
}

View file

@ -9,16 +9,18 @@
<div class="row">
<div class="col-md-3 col-sm-3" id="setup-sidebar-col">
<div id="setup-sidebar" class="hidden-xs" data-spy="affix" data-offset-top="55" data-clampedwidth="#setup-sidebar-col">
<div id="setup-sidebar" data-clampedwidth="#setup-sidebar-col">
<script id="list-group-template" type="text/template">
<% _.each(descriptions, function(group){ %>
<% panelID = group.name ? group.name : group.html_id %>
<li>
<a href="#<%- panelID %>" class="list-group-item">
<span class="badge"></span>
<%- group.label %>
</a>
</li>
<% if (!group.hidden) { %>
<% panelID = group.name ? group.name : group.html_id %>
<li>
<a href="#<%- panelID %>" class="list-group-item">
<span class="badge"></span>
<%- group.label %>
</a>
</li>
<% } %>
<% }); %>
</script>
@ -26,49 +28,58 @@
</ul>
<button id="advanced-toggle-button" class="btn btn-info advanced-toggle">Show advanced</button>
<button class="btn btn-success save-button">Save</button>
<button class="btn btn-success save-button" disabled>Save</button>
<div id="manage-cloud-domains-link" style="display: none;">
<a href="https://highfidelity.com/user/cloud_domains" target="_blank" class="blue-link">Manage Cloud Hosted Domains</a>
</div>
</div>
</div>
<div class="col-md-9 col-sm-9 col-xs-12">
<div id="xs-advanced-container" class="col-xs-12 hidden-sm hidden-md hidden-lg">
<button id="advanced-toggle-button-xs" class="btn btn-info advanced-toggle">Show advanced</button>
</div>
<div class="col-xs-12">
<div id="cloud-domains-alert" class="alert alert-info alert-dismissible" role="alert" style="display: none;">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<span class="alert-link">
<a href="https://highfidelity.com/user/cloud_domains" target="_blank" class="blue-link">Visit Cloud Hosted Domains</a> to manage all your cloud domains
</span>
</div>
<form id="settings-form" role="form">
<script id="panels-template" type="text/template">
<% _.each(descriptions, function(group){ %>
<% var settings = _.partition(group.settings, function(value, index) { return !value.deprecated })[0] %>
<% split_settings = _.partition(settings, function(value, index) { return !value.advanced }) %>
<% isAdvanced = _.isEmpty(split_settings[0]) %>
<% if (isAdvanced) { %>
<% $("a[href=#" + group.name + "]").addClass('advanced-setting').hide() %>
<% } %>
<% if (!group.hidden) { %>
<% var settings = _.partition(group.settings, function(value, index) { return !value.deprecated })[0] %>
<% split_settings = _.partition(settings, function(value, index) { return !value.advanced }) %>
<% isAdvanced = _.isEmpty(split_settings[0]) && !_.isEmpty(split_settings[1]) %>
<% if (isAdvanced) { %>
<% $("a[href=#" + group.name + "]").addClass('advanced-setting').hide() %>
<% } %>
<% isGrouped = !!group.name %>
<% panelID = isGrouped ? group.name : group.html_id %>
<% isGrouped = !!group.name %>
<% panelID = isGrouped ? group.name : group.html_id %>
<div class="panel panel-default<%- (isAdvanced) ? ' advanced-setting' : '' %><%- (isGrouped) ? ' grouped' : '' %>"
id="<%- panelID %>">
<div class="panel-heading">
<h3 class="panel-title"><%- group.label %></h3>
</div>
<div class="panel-body">
<% _.each(split_settings[0], function(setting) { %>
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
<%= getFormGroup(keypath, setting, values, false) %>
<% }); %>
<% if (!_.isEmpty(split_settings[1])) { %>
<% $("#advanced-toggle-button").show() %>
<% _.each(split_settings[1], function(setting) { %>
<div class="panel panel-default<%- (isAdvanced) ? ' advanced-setting' : '' %><%- (isGrouped) ? ' grouped' : '' %>"
id="<%- panelID %>">
<div class="panel-heading">
<h3 class="panel-title"><%- group.label %></h3>
</div>
<div class="panel-body">
<% _.each(split_settings[0], function(setting) { %>
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
<%= getFormGroup(keypath, setting, values, true) %>
<%= getFormGroup(keypath, setting, values, false) %>
<% }); %>
<% }%>
<% if (!_.isEmpty(split_settings[1])) { %>
<% $("#advanced-toggle-button").show() %>
<% _.each(split_settings[1], function(setting) { %>
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
<%= getFormGroup(keypath, setting, values, true) %>
<% }); %>
<% }%>
</div>
</div>
</div>
<% } %>
<% }); %>
</script>
<div id="panels"></div>
@ -85,8 +96,9 @@
<script src='/js/underscore-min.js'></script>
<script src='/js/underscore-keypath.min.js'></script>
<script src='/js/bootbox.min.js'></script>
<script src='js/bootstrap-switch.min.js'></script>
<script src='js/settings.js'></script>
<script src='/js/sha256.js'></script>
<script src='js/form2js.min.js'></script>
<script src='js/sha256.js'></script>
<script src='js/bootstrap-switch.min.js'></script>
<script src='/js/shared.js'></script>
<script src='js/settings.js'></script>
<!--#include virtual="page-end.html"-->

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,162 @@
label {
font-style: normal;
font-weight: normal;
line-height: 24px;
font-size: 16px;
color: #373A3C;
}
.step-title {
margin-bottom: 20px;
line-height: 26px;
font-size: 24px;
color: #373A3C;
}
.step-description {
line-height: 24px;
font-size: 16px;
color: #818A91;
}
.step-info {
font-style: normal;
font-weight: normal;
line-height: 24px;
font-size: 16px;
color: #373A3C;
}
.wizard-link {
font-size: 16px;
font-weight: normal;
color: #2F80ED;
}
#admin-row {
margin-top: 20px;
margin-bottom: 20px;
}
#connect-question {
margin-top: 10px;
}
#connect-account-btn {
margin-top: 30px;
margin-bottom: 205px;
}
#place-name-group {
margin-top: 42px;
margin-bottom: 140px;
display: inline-block;
}
#place-name-link {
line-height: 38px;
font-size: 32px;
display: inline-block;
color: #373A3C;
}
#place-name-edit {
text-align: right;
}
#change-place-name {
line-height: 24px;
font-size: 18px;
}
#rez-options-row {
margin-bottom: 60px;
}
#verify-password-row {
margin-bottom: 33px;
}
.btn.btn-square {
border-radius: 0;
}
.header {
position: absolute;
top: -20px;
left: 50%;
margin-left: -720px; /* Half of the width */
min-width: 1440px;
z-index: -1;
}
.title {
font-family: 'Cairo';
color: white;
margin-top: 260px;
}
#main-description {
margin-top: 32px;
margin-bottom: 35px;
}
#share-box {
background-color: rgb(240, 240, 240);
padding: 20px 20px;
margin-top: 15px;
margin-bottom: 70px;
white-space: nowrap;
}
#share-box > span {
vertical-align: middle;
display: table-cell;
height: 100%;
}
#share-box button {
border-color: rgb(225, 225, 225);
font-size: 18px;
}
#share-box img {
padding-right: 10px;
}
#share-text {
font-size: 22px;
padding-right: 15px;
}
#share-field {
background-color: rgb(255, 255, 255);
padding: 0 10px;
width: 100%;
}
#share-link {
font-size: 28px;
}
#congrats-list > div > div {
margin-bottom: 30px;
}
#congrats-list ul {
padding-left: 16px;
}
#congrats-list {
margin-bottom: 60px;
}
#open-settings {
border-color: black;
}

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<title>domain-server</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/css/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="/css/style.css" rel="stylesheet" media="screen">
<link href="/wizard/css/style.css" rel="stylesheet" media="screen">
<link href="/css/sweetalert.css" rel="stylesheet" media="screen">
<link href="/css/bootstrap-switch.min.css" rel="stylesheet" media="screen">
<script src='/js/sweetalert.min.js'></script>
</head>
<body>
<nav class="navbar navbar-default navbar-fixed-top" role="navigation">
<div class="container-fluid">
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<a class="navbar-brand step-info" href="#">Setup Wizard (Domain Server Settings)</a>
<div class="navbar-form navbar-right">
<button id="skip-wizard-button" type="button" class="btn btn-default" style="display:none;">Skip Wizard</button>
</div>
</div>
</div><!-- /.container-fluid -->
</nav>
<div class="container">

View file

@ -0,0 +1,266 @@
<!--#include virtual="wizard/header.html"-->
<div class="wizard-step desktop-only col-md-6 col-centered" style="display: none;">
<h4 class="step-title"></h4>
<dl class="row">
<dd class="col-md-12">
<span class='step-description'>By connecting your High Fidelity Account you will be granting access to your account information.</span>
</dd>
</dl>
<dl class="row">
<dd class="col-md-12">
<a id="connect-account-btn" role="button" class="btn btn-primary btn-md btn-block" target="_blank">Connect your High Fidelity account</a>
</dd>
</dl>
<dl class="row">
<dd class="col-md-3">
<button type="button" class="btn btn-md btn-block btn-default next-button">Skip</button>
</dd>
</dl>
</div>
<div class="wizard-step col-md-8 col-centered" style="display: none;">
<h4 class="step-title"></h4>
<div class="row">
<div class="col-md-12">
<span class='step-description'>
<a target='_blank' href='https://docs.highfidelity.com/create-and-explore/start-working-in-your-sandbox/place-names'>Place names</a> are similar to web addresses. Users who want to visit your domain can
enter its Place Name in High Fidelity's Interface. You can choose a Place Name for your domain.</br>
People can also use your <b>domain's IP address (shown below)</b> to visit your High Fidelity domain.
</span>
</div>
</div>
<div class="row">
<div class="centered-hack-parent">
<div id="place-name-group" class="centered-hack">
<p id="place-name-link"></p>
<div id="place-name-edit">
<span class='glyphicon glyphicon-pencil'></span>
<a href="#" id="change-place-name">Choose a custom Place Name instead</a>
</div>
</div>
</div>
</div>
<dl class="row">
<dd class="col-md-3">
<button type="button" class="btn btn-md btn-block btn-default back-button">Back</button>
</dd>
<dd class="col-md-3 col-md-offset-6">
<button type="button" class="btn btn-md btn-block btn-primary next-button">Next</button>
</dd>
</dl>
</div>
<div class="wizard-step col-md-9 col-centered" style="display: none;">
<h4 class="step-title"></h4>
<div class="row">
<p id="permissions-description" class="col-md-12 step-info"><b>Localhost</b> has been granted administrator privileges to this domain. (Localhost is any</br>user on the same machine as the High Fidelity Server)</p>
</div>
<div id="admin-row" class="row">
<p class="col-md-6">
<span class="step-info"><span id="admin-description"><b>Add your High Fidelity username</b> and any other usernames</span> to grant <span class='wizard-link' data-toggle="tooltip" title="Users who will have all the permissions for this domain.">administrator privileges</span></span>
</p>
<div class="col-md-6">
<input id="admin-usernames" type="text" class="form-control">
<span class="help-block text-right">separate by commas (user1, user2,..)</span>
</div>
</div>
<div class="row">
<div class="col-md-12">
<span class="step-description">Grant basic permissions to other users. You can change these later.</span>
</div>
</div>
<div class="row">
<div class="col-md-12">
<p id="connect-question" class="step-info">
Who can <a href='#' class='wizard-link perms-link'>connect</a> to your domain?
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title='You can set this to allow a user to connect to this domain.'></span>
</p>
</div>
</div>
<div class="row">
<p class="col-md-2">
<label>
<input id="connect-none" name="connect-radio" type="radio" value="none" checked> None
</label>
</p>
<p class="col-md-3">
<label>
<input id="connect-friends" name="connect-radio" type="radio" value="friends"> Friends
</label>
</p>
<p class="col-md-5">
<label>
<input id="connect-logged-in" name="connect-radio" type="radio" value="logged-in"> Users logged into High Fidelity
</label>
</p>
<p class="col-md-2">
<label>
<input id="connect-everyone" name="connect-radio" type="radio" value="everyone"> Everyone
</label>
</p>
</div>
<div class="row">
<div class="col-md-12">
<p class="step-info">
Who can <a href='#' class='wizard-link perms-link'>rez items</a> in your domain?
<span class='glyphicon glyphicon-info-sign' data-toggle='tooltip' title='You can set this to allow a user to create entities in this domain.'></span>
</p>
</div>
</div>
<div id="rez-options-row" class="row">
<p class="col-md-2">
<label>
<input id="rez-none" name="rez-radio" type="radio" value="none" checked> None
</label>
</p>
<p class="col-md-3">
<label>
<input id="rez-friends" name="rez-radio" type="radio" value="friends" disabled> Friends
</label>
</p>
<p class="col-md-5">
<label>
<input id="rez-logged-in" name="rez-radio" type="radio" value="logged-in" disabled> Users logged into High Fidelity
</label>
</p>
<p class="col-md-2">
<label>
<input id="rez-everyone" name="rez-radio" type="radio" value="everyone" disabled> Everyone
</label>
</p>
</div>
<div class="row">
<dd class="col-md-3">
<button type="button" class="btn btn-md btn-block btn-default back-button">Back</button>
</dd>
<dd class="col-md-3 col-md-offset-6">
<button id="save-permissions" type="button" class="btn btn-md btn-block btn-primary">Next</button>
</dd>
</div>
</div>
<div class="wizard-step cloud-only col-md-7 col-centered" style="display: none;">
<h4 class="step-title"></h4>
<dl class="row">
<dd class="col-md-12">
<span class='step-description'>
Your server settings are currently accessible without a username and password.
Adding credentials will ensure that only authorized users have access to modify the settings.
</span>
</dd>
</dl>
<div class="row">
<p class="col-md-12 step-info">Create a username and password to secure the access to your domain server settings.</p>
</div>
<dl class="row">
<dt class="col-md-4 step-info">Username</dt>
<dd class="col-md-8">
<input id="http_username" type="text" class="form-control">
<span class='help-block'>This does not have to be your High Fidelity username</span>
</dd>
</dl>
<dl class="row">
<dt class="col-md-4 step-info">Enter password</dt>
<dd class="col-md-8">
<input id="http_password" type="password" class="form-control">
<span class='help-block'>This should not be the same as your High Fidelity password</span>
</dd>
</dl>
<dl id="verify-password-row" class="row">
<dt class="col-md-4 step-info">Re-enter password</dt>
<dd class="col-md-8">
<input id="verify_http_password" type="password" class="form-control">
</dd>
</dl>
<dl class="row">
<dd class="col-md-3">
<button type="button" class="btn btn-md btn-block btn-default back-button">Back</button>
</dd>
<dd class="col-md-3 col-md-offset-6">
<button id="save-username-password" type="button" class="btn btn-md btn-block btn-primary">Finish</button>
</dd>
</dl>
</div>
<div id="congratulation-step" class="wizard-step cloud-only col-xs-12 col-centered" style="display: none;">
<div class="row">
<div class="col-xs-10 col-xs-offset-1">
<img class="header" src="/images/wizard-congratulation-header.jpg" alt="Header" width="1440" height="339">
<h1 class="title">Congratulations!</h1>
</div>
</div>
<div class="row">
<div class="col-xs-10 col-xs-offset-1">
<p id="main-description" class="step-info">You have successfully setup and configured your cloud hosted domain.</p>
</div>
</div>
<div class="row">
<div class="col-xs-10 col-xs-offset-1">
<div class="step-info">
<b>Invite people in!</b>
</div>
<div id="share-box">
<span id="share-text" class="step-info">Share your domain:</span>
<span id="share-field">
<a id="share-link" class="blue-link" target="_blank"></a>
</span>
<span>
<button type="button" class="btn btn-md btn-default btn-square" onclick="copyToClipboard('#share-link')">
<img src="/images/copy-icon.svg" alt="cpy icon" height="30" width="30">copy
</button>
</span>
</div>
</div>
</div>
<div id="congrats-list" class="row">
<div class="col-xs-4 col-xs-offset-1">
<div class="step-info">
<b>Go to your Domain:</b>
</div>
<ul>
<li class="step-info">Browse environments in the Marketplace to select the perfect content set for your VR world.</li>
<li class="step-info">Invite people to your domain right now.</li>
<li class="step-info">Meet new people and explore other domains.</li>
</ul>
</div>
<div class="col-xs-3 col-xs-offset-1">
<div class="step-info">
<b>Continue to Domain settings:</b>
</div>
<ul>
<li class="step-info">Set additional permissions for who can visit and make changes.</li>
<li class="step-info">Adjust audio settings.</li>
<li class="step-info">Back up your domain's content.</li>
</ul>
</div>
</div>
<div class="row">
<div class="col-xs-4 col-xs-offset-1">
<button id="visit-domain" type="button" class="btn btn-md btn-primary btn-square">Visit Your VR World</button>
</div>
<div class="col-xs-3 col-xs-offset-1">
<button id="open-settings" type="button" class="btn btn-md btn-default btn-square next-button">Open Domain Settings</button>
</div>
</div>
</div>
<!--#include virtual="footer.html"-->
<script src='/js/underscore-min.js'></script>
<script src='/js/bootbox.min.js'></script>
<script src='/js/sha256.js'></script>
<script src='/js/shared.js'></script>
<script src='js/wizard.js'></script>
<!--#include virtual="page-end.html"-->

View file

@ -0,0 +1,519 @@
var Metaverse = {
accessToken: null
}
var currentStepNumber;
$(document).ready(function(){
Strings.ADD_PLACE_NOT_CONNECTED_MESSAGE = "You must have an access token to query your High Fidelity places.<br><br>" +
"Please go back and connect your account.";
$('#connect-account-btn').attr('href', URLs.METAVERSE_URL + "/user/tokens/new?for_domain_server=true");
$('[data-toggle="tooltip"]').tooltip();
$('.perms-link').on('click', function() {
var modal_body = '<div>';
modal_body += '<b>None</b> - No one will have permissions. Only you and the users your have given administrator privileges to will have permissions.</br></br>';
modal_body += '<b>Friends</b> - Users who are your Friends in High Fidelity.</br></br>';
modal_body += '<b>Users logged into High Fidelity</b> - Users who are currently logged into High Fidelity.</br></br>';
modal_body += '<b>Everyone</b> - Anyone who uses High Fidelity.';
modal_body += '</div>';
dialog = bootbox.dialog({
title: "User definition",
message: modal_body,
closeButton: true
});
return false;
});
$('body').on('click', '.next-button', function() {
goToNextStep();
});
$('body').on('click', '.back-button', function() {
goToPreviousStep();
});
$('body').on('click', '#skip-wizard-button', function() {
skipWizard();
})
$('body').on('click', '#connect-account-btn', function() {
$(this).blur();
prepareAccessTokenPrompt(function(accessToken) {
Metaverse.accessToken = accessToken;
saveAccessToken();
});
});
$('body').on('click', '#save-permissions', function() {
savePermissions();
});
function triggerSaveUsernamePassword(event) {
if (event.keyCode === 13) {
$("#save-username-password").click();
}
}
$("#http_username").keyup(triggerSaveUsernamePassword);
$("#http_password").keyup(triggerSaveUsernamePassword);
$("#verify_http_password").keyup(triggerSaveUsernamePassword);
$('body').on('click', '#save-username-password', function() {
saveUsernamePassword();
});
$('body').on('click', '#change-place-name', function() {
chooseFromHighFidelityPlaces(Settings.data.values.metaverse.access_token, "/0,-10,0", function(placeName) {
updatePlaceNameLink(placeName);
});
});
$('body').on('click', '#visit-domain', function() {
$('#share-link')[0].click();
});
$('input[type=radio][name=connect-radio]').change(function() {
var inputs = $('input[type=radio][name=rez-radio]');
var disabled = [];
switch (this.value) {
case 'none':
disabled = inputs.splice(1);
break;
case 'friends':
disabled = inputs.splice(2);
break;
case 'logged-in':
disabled = inputs.splice(3);
break;
case 'everyone':
disabled = inputs.splice(4);
break;
}
$.each(inputs, function() {
$(this).prop('disabled', false);
});
$.each(disabled, function() {
if ($(this).prop('checked')) {
$(inputs.last()).prop('checked', true);
}
$(this).prop('disabled', true);
});
});
reloadSettings(function(success) {
if (success) {
getDomainFromAPI();
setupWizardSteps();
updatePlaceNameDisplay();
updateUsernameDisplay();
} else {
swal({
title: '',
type: 'error',
text: "There was a problem loading the domain settings.\nPlease refresh the page to try again.",
});
}
});
});
function copyToClipboard(element) {
var $temp = $("<input>");
$("body").append($temp);
$temp.val($(element).text()).select();
document.execCommand("copy");
$temp.remove();
}
function setupWizardSteps() {
currentStepNumber = Settings.data.values.wizard.steps_completed;
var steps = null;
if (Settings.data.values.wizard.cloud_domain) {
$('.desktop-only').remove();
$('.wizard-step:first').find('.back-button').hide();
steps = $('.wizard-step');
$(steps).each(function(i) {
$(this).children(".step-title").text("Step " + (i + 1) + " of " + (steps.length - 1));
});
$('#permissions-description').html('You <span id="username-display"></span>have been assigned administrator privileges to this domain.');
$('#admin-description').html('Add more High Fidelity usernames');
} else {
$('.cloud-only').remove();
$('#save-permissions').text("Finish");
steps = $('.wizard-step');
$(steps).each(function(i) {
$(this).children(".step-title").text("Step " + (i + 1) + " of " + steps.length);
});
if (currentStepNumber == 0) {
$('#skip-wizard-button').show();
}
}
var currentStep = steps[currentStepNumber];
$(currentStep).show();
}
function updatePlaceNameLink(address) {
if (address) {
var url = URLs.PLACE_URL + '/' + address;
$('#place-name-link').html('Your domain is reachable at: <a target="_blank" href="' + url + '">' + address + '</a>');
$('#share-field a').attr('href', url).text(url);
}
}
function updatePlaceNameDisplay() {
if (Settings.data.values.metaverse.id) {
$.getJSON(URLs.METAVERSE_URL + '/api/v1/domains/' + Settings.data.values.metaverse.id, function(data) {
if (data.status === 'success') {
if (data.domain.default_place_name) {
// Place name
updatePlaceNameLink(data.domain.default_place_name);
} else if (data.domain.name) {
// Temporary name
updatePlaceNameLink(data.domain.name);
} else if (data.domain.network_address) {
if (data.domain.network_port !== 40102) {
// IP:PORT
updatePlaceNameLink(data.domain.network_address + ':' + data.domain.network_port);
} else {
// IP
updatePlaceNameLink(data.domain.network_address);
}
}
} else {
console.warn('Request Failed');
}
}).fail(function() {
console.warn('Request Failed');
});
} else {
console.warn('No metaverse domain ID!');
}
}
function updateUsernameDisplay() {
var permissions = Settings.data.values.security.permissions;
if (permissions.length > 0) {
$('#username-display').html('<b>(' + permissions[0].permissions_id + ')</b> ');
}
}
function reloadSettings(callback) {
$.getJSON('/settings.json', function(data){
Settings.data = data;
if (callback) {
// call the callback now that settings are loaded
callback(true);
}
}).fail(function() {
if (callback) {
// call the failure object since settings load failed
callback(false)
}
});
}
function postSettings(jsonSettings, callback) {
console.log("----- SAVING ------");
console.log(JSON.stringify(jsonSettings));
// POST the form JSON to the domain-server settings.json endpoint so the settings are saved
$.ajax('/settings.json', {
data: JSON.stringify(jsonSettings),
contentType: 'application/json',
type: 'POST'
}).done(function(data){
if (data.status == "success") {
if (callback) {
callback();
}
reloadSettings();
} else {
swal("Error", Strings.LOADING_SETTINGS_ERROR)
reloadSettings();
}
}).fail(function(){
swal("Error", Strings.LOADING_SETTINGS_ERROR)
reloadSettings();
});
}
function goToNextStep() {
$('#skip-wizard-button').hide();
var currentStep = $('body').find('.wizard-step:visible');
var nextStep = currentStep.next('.wizard-step');
var formJSON = {
"wizard": {}
}
if (nextStep.length > 0) {
currentStep.hide();
nextStep.show();
currentStepNumber += 1;
postSettings({
"wizard": {
"steps_completed": currentStepNumber.toString()
}
});
} else {
postSettings({
"wizard": {
"steps_completed": "0",
"completed_once": true
}
}, redirectToSettings);
}
}
function goToPreviousStep() {
var currentStep = $('body').find('.wizard-step:visible');
var previousStep = currentStep.prev('.wizard-step');
var formJSON = {
"wizard": {}
}
if (previousStep.length > 0) {
currentStep.hide();
previousStep.show();
currentStepNumber -= 1;
postSettings({
"wizard": {
"steps_completed": currentStepNumber.toString()
}
});
}
}
function skipWizard() {
postSettings({
"wizard": {
"steps_completed": "0",
"completed_once": true
}
}, redirectToSettings);
}
function redirectToSettings() {
var redirectURL = "/settings" + location.search;
if (Settings.data.values.wizard.cloud_domain) {
if (location.search.length > 0) {
redirectURL += "&";
} else {
redirectURL += "?";
}
redirectURL += "cloud-wizard-exit";
}
location.href = redirectURL;
}
function saveAccessToken() {
var formJSON = {
"metaverse": {}
}
if (Metaverse.accessToken) {
formJSON.metaverse.access_token = Metaverse.accessToken;
}
// remove focus from the button
$(this).blur();
// POST the form JSON to the domain-server settings.json endpoint so the settings are saved
postSettings(formJSON, goToNextStep);
}
function getSettingDescriptionForKey(groupKey, settingKey) {
for (var i in Settings.data.descriptions) {
var group = Settings.data.descriptions[i];
if (group.name === groupKey) {
for (var j in group.settings) {
var setting = group.settings[j];
if (setting.name === settingKey) {
return setting;
}
}
}
}
}
function savePermissions() {
var localhostPermissions = (Settings.data.values.wizard.cloud_domain !== true)
var anonymousCanConnect = false;
var friendsCanConnect = false;
var loggedInCanConnect = false;
var anonymousCanRez = false;
var friendsCanRez = false;
var loggedInCanRez = false;
var connectValue = $('input[name=connect-radio]:checked').val();
var rezValue = $('input[name=rez-radio]:checked').val();
switch (connectValue) {
case "friends":
friendsCanConnect = true;
break;
case "logged-in":
friendsCanConnect = true;
loggedInCanConnect = true;
break;
case "everyone":
anonymousCanConnect = true;
friendsCanConnect = true;
loggedInCanConnect = true;
break;
}
switch (rezValue) {
case "friends":
friendsCanRez = true;
break;
case "logged-in":
friendsCanRez = true;
loggedInCanRez = true;
break;
case "everyone":
anonymousCanRez = true;
friendsCanRez = true;
loggedInCanRez = true;
break;
}
var admins = $('#admin-usernames').val().split(',');
var existingAdmins = Settings.data.values.security.permissions.map(function(value) {
return value.permissions_id;
});
admins = admins.concat(existingAdmins);
// Filter out unique values
admins = _.uniq(admins.map(function(username) {
return username.trim();
})).filter(function(username) {
return username !== "";
});
var formJSON = {
"security": {
"standard_permissions": [
{
"id_can_connect": anonymousCanConnect,
"id_can_rez": anonymousCanRez,
"id_can_rez_certified": anonymousCanRez,
"id_can_rez_tmp": anonymousCanRez,
"id_can_rez_tmp_certified": anonymousCanRez,
"permissions_id": "anonymous"
},
{
"id_can_connect": friendsCanConnect,
"id_can_rez": friendsCanRez,
"id_can_rez_certified": friendsCanRez,
"id_can_rez_tmp": friendsCanRez,
"id_can_rez_tmp_certified": friendsCanRez,
"permissions_id": "friends"
},
{
"id_can_connect": loggedInCanConnect,
"id_can_rez": loggedInCanRez,
"id_can_rez_certified": loggedInCanRez,
"id_can_rez_tmp": loggedInCanRez,
"id_can_rez_tmp_certified": loggedInCanRez,
"permissions_id": "logged-in"
},
{
"id_can_adjust_locks": localhostPermissions,
"id_can_connect": localhostPermissions,
"id_can_connect_past_max_capacity": localhostPermissions,
"id_can_kick": localhostPermissions,
"id_can_replace_content": localhostPermissions,
"id_can_rez": localhostPermissions,
"id_can_rez_certified": localhostPermissions,
"id_can_rez_tmp": localhostPermissions,
"id_can_rez_tmp_certified": localhostPermissions,
"id_can_write_to_asset_server": localhostPermissions,
"permissions_id": "localhost"
}
]
}
}
if (admins.length > 0) {
formJSON.security.permissions = [];
var permissionsDesc = getSettingDescriptionForKey("security", "permissions");
for (var i in admins) {
var admin = admins[i];
var perms = {};
for (var i in permissionsDesc.columns) {
var name = permissionsDesc.columns[i].name;
if (name === "permissions_id") {
perms[name] = admin;
} else {
perms[name] = true;
}
}
formJSON.security.permissions.push(perms);
}
}
// remove focus from the button
$(this).blur();
postSettings(formJSON, goToNextStep);
}
function saveUsernamePassword() {
var username = $("#http_username").val();
var password = $("#http_password").val();
var verify_password = $("#verify_http_password").val();
if (username.length == 0) {
bootbox.alert({ "message": "You must set a username!", "title": "Username Error" });
return;
}
if (password.length == 0) {
bootbox.alert({ "message": "You must set a password!", "title": "Password Error" });
return;
}
if (password != verify_password) {
bootbox.alert({ "message": "Passwords must match!", "title": "Password Error" });
return;
}
currentStepNumber += 1;
var formJSON = {
"security": {
"http_username": username,
"http_password": sha256_digest(password)
},
"wizard": {
"steps_completed": currentStepNumber.toString()
}
}
// remove focus from the button
$(this).blur();
// POST the form JSON to the domain-server settings.json endpoint so the settings are saved
postSettings(formJSON, function() {
location.reload();
});
}

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
@ -814,9 +819,15 @@ void DomainGatekeeper::processICEPeerInformationPacket(QSharedPointer<ReceivedMe
void DomainGatekeeper::processICEPingPacket(QSharedPointer<ReceivedMessage> message) {
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
auto pingReplyPacket = limitedNodeList->constructICEPingReplyPacket(*message, limitedNodeList->getSessionUUID());
limitedNodeList->sendPacket(std::move(pingReplyPacket), message->getSenderSockAddr());
// before we respond to this ICE ping packet, make sure we have a peer in the list that matches
QUuid icePeerID = QUuid::fromRfc4122({ message->getRawMessage(), NUM_BYTES_RFC4122_UUID });
if (_icePeers.contains(icePeerID)) {
auto pingReplyPacket = limitedNodeList->constructICEPingReplyPacket(*message, limitedNodeList->getSessionUUID());
limitedNodeList->sendPacket(std::move(pingReplyPacket), message->getSenderSockAddr());
}
}
void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer<ReceivedMessage> message) {

View file

@ -46,6 +46,8 @@
#include "DomainServerNodeData.h"
#include "NodeConnectionData.h"
const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";
int const DomainServer::EXIT_CODE_REBOOT = 234923;
#if USE_STABLE_GLOBAL_SERVICES
@ -54,6 +56,82 @@ const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.com";
const QString ICE_SERVER_DEFAULT_HOSTNAME = "dev-ice.highfidelity.com";
#endif
bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection,
const QString& metaversePath,
const QString& requestSubobjectKey,
std::initializer_list<QString> requiredData,
std::initializer_list<QString> optionalData,
bool requireAccessToken) {
auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
if (accessTokenVariant == nullptr && requireAccessToken) {
connection->respond(HTTPConnection::StatusCode400, "User access token has not been set");
return true;
}
QJsonObject subobject;
auto params = connection->parseUrlEncodedForm();
for (auto& key : requiredData) {
auto it = params.find(key);
if (it == params.end()) {
auto error = "Bad request, expected param '" + key + "'";
connection->respond(HTTPConnection::StatusCode400, error.toLatin1());
return true;
}
subobject.insert(key, it.value());
}
for (auto& key : optionalData) {
auto it = params.find(key);
if (it != params.end()) {
subobject.insert(key, it.value());
}
}
QJsonObject root;
root.insert(requestSubobjectKey, subobject);
QJsonDocument doc { root };
QUrl url { NetworkingConstants::METAVERSE_SERVER_URL.toString() + metaversePath };
QNetworkRequest req(url);
req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
if (accessTokenVariant != nullptr) {
auto accessTokenHeader = QString("Bearer ") + accessTokenVariant->toString();
req.setRawHeader("Authorization", accessTokenHeader.toLatin1());
}
QNetworkReply* reply;
auto method = connection->requestOperation();
if (method == QNetworkAccessManager::GetOperation) {
reply = NetworkAccessManager::getInstance().get(req);
} else if (method == QNetworkAccessManager::PostOperation) {
reply = NetworkAccessManager::getInstance().post(req, doc.toJson());
} else if (method == QNetworkAccessManager::PutOperation) {
reply = NetworkAccessManager::getInstance().put(req, doc.toJson());
} else {
connection->respond(HTTPConnection::StatusCode400, "Error forwarding request, unsupported method");
return true;
}
connect(reply, &QNetworkReply::finished, this, [reply, connection]() {
if (reply->error() != QNetworkReply::NoError) {
auto data = reply->readAll();
qDebug() << "Got error response from metaverse server (" << reply->url() << "): " << data << reply->errorString();
connection->respond(HTTPConnection::StatusCode400, data);
return;
}
connection->respond(HTTPConnection::StatusCode200, reply->readAll());
});
return true;
}
DomainServer::DomainServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_gatekeeper(this),
@ -614,8 +692,6 @@ void DomainServer::setupNodeListAndAssignments() {
nodeList->setPacketFilterOperator(&DomainServer::isPacketVerified);
}
const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";
bool DomainServer::resetAccountManagerAccessToken() {
if (!_oauthProviderURL.isEmpty()) {
// check for an access-token in our settings, can optionally be overidden by env value
@ -754,26 +830,6 @@ void DomainServer::setupICEHeartbeatForFullNetworking() {
void DomainServer::updateICEServerAddresses() {
if (_iceAddressLookupID == INVALID_ICE_LOOKUP_ID) {
_iceAddressLookupID = QHostInfo::lookupHost(_iceServerAddr, this, SLOT(handleICEHostInfo(QHostInfo)));
// there seems to be a 5.9 bug where lookupHost never calls our slot
// so we add a single shot manual "timeout" to fire it off again if it hasn't called back yet
static const int ICE_ADDRESS_LOOKUP_TIMEOUT_MS = 5000;
QTimer::singleShot(ICE_ADDRESS_LOOKUP_TIMEOUT_MS, this, &DomainServer::timeoutICEAddressLookup);
}
}
void DomainServer::timeoutICEAddressLookup() {
if (_iceAddressLookupID != INVALID_ICE_LOOKUP_ID) {
// we waited 5s and didn't hear back for our ICE DNS lookup
// so time that one out and kick off another
qDebug() << "IP address lookup timed out for" << _iceServerAddr << "- retrying";
QHostInfo::abortHostLookup(_iceAddressLookupID);
_iceAddressLookupID = INVALID_ICE_LOOKUP_ID;
updateICEServerAddresses();
}
}
@ -1731,6 +1787,8 @@ QString DomainServer::pathForRedirect(QString path) const {
return "http://" + _hostname + ":" + QString::number(_httpManager.serverPort()) + path;
}
const QString URI_OAUTH = "/oauth";
bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) {
const QString JSON_MIME_TYPE = "application/json";
@ -1740,15 +1798,23 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
const QString URI_SETTINGS = "/settings";
const QString URI_ENTITY_FILE_UPLOAD = "/content/upload";
const QString URI_RESTART = "/restart";
const QString URI_API_PLACES = "/api/places";
const QString URI_API_DOMAINS = "/api/domains";
const QString URI_API_DOMAINS_ID = "/api/domains/";
const QString UUID_REGEX_STRING = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
auto nodeList = DependencyManager::get<LimitedNodeList>();
// allow sub-handlers to handle requests that do not require authentication
if (_settingsManager.handlePublicHTTPRequest(connection, url)) {
auto getSetting = [this](QString keyPath, QVariant& value) -> bool {
QVariantMap& settingsMap = _settingsManager.getSettingsMap();
QVariant* var = valueForKeyPath(settingsMap, keyPath);
if (var == nullptr) {
return false;
}
value = *var;
return true;
}
};
// check if this is a request for a scripted assignment (with a temp unique UUID)
const QString ASSIGNMENT_REGEX_STRING = QString("\\%1\\/(%2)\\/?$").arg(URI_ASSIGNMENT).arg(UUID_REGEX_STRING);
@ -1810,6 +1876,31 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
return true;
}
// Check if we should redirect/prevent access to the wizard
if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
const QString URI_WIZARD = "/wizard/";
const QString WIZARD_COMPLETED_ONCE_KEY_PATH = "wizard.completed_once";
const QVariant* wizardCompletedOnce = valueForKeyPath(_settingsManager.getSettingsMap(), WIZARD_COMPLETED_ONCE_KEY_PATH);
const bool completedOnce = wizardCompletedOnce && wizardCompletedOnce->toBool();
if (url.path() != URI_WIZARD && url.path().endsWith('/') && !completedOnce) {
// First visit, redirect to the wizard
QUrl redirectedURL = url;
redirectedURL.setPath(URI_WIZARD);
Headers redirectHeaders;
redirectHeaders.insert("Location", redirectedURL.toEncoded());
connection->respond(HTTPConnection::StatusCode302,
QByteArray(), HTTPConnection::DefaultContentType, redirectHeaders);
return true;
} else if (url.path() == URI_WIZARD && completedOnce) {
// Wizard already completed, return 404
connection->respond(HTTPConnection::StatusCode404, "Resource not found.");
return true;
}
}
if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
if (url.path() == "/assignments.json") {
// user is asking for json list of assignments
@ -1899,6 +1990,13 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
connection->respond(HTTPConnection::StatusCode200);
restart();
return true;
} else if (url.path() == URI_API_DOMAINS) {
return forwardMetaverseAPIRequest(connection, "/api/v1/domains", "");
} else if (url.path().startsWith(URI_API_DOMAINS_ID)) {
auto id = url.path().mid(URI_API_DOMAINS_ID.length());
return forwardMetaverseAPIRequest(connection, "/api/v1/domains/" + id, "", {}, {}, false);
} else if (url.path() == URI_API_PLACES) {
return forwardMetaverseAPIRequest(connection, "/api/v1/user/places", "");
} else {
// check if this is for json stats for a node
const QString NODE_JSON_REGEX_STRING = QString("\\%1\\/(%2).json\\/?$").arg(URI_NODES).arg(UUID_REGEX_STRING);
@ -1978,8 +2076,6 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
// this is an entity file upload, ask the HTTPConnection to parse the data
QList<FormData> formData = connection->parseFormData();
Headers redirectHeaders;
if (formData.size() > 0 && formData[0].second.size() > 0) {
// invoke our method to hand the new octree file off to the octree server
QMetaObject::invokeMethod(this, "handleOctreeFileReplacement",
@ -1993,7 +2089,98 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
}
return true;
} else if (url.path() == "/domain_settings") {
auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
if (!accessTokenVariant) {
connection->respond(HTTPConnection::StatusCode400);
return true;
}
} else if (url.path() == URI_API_DOMAINS) {
return forwardMetaverseAPIRequest(connection, "/api/v1/domains", "domain", { "label" });
}
} else if (connection->requestOperation() == QNetworkAccessManager::PutOperation) {
if (url.path() == URI_API_DOMAINS) {
QVariant domainSetting;
if (!getSetting(METAVERSE_DOMAIN_ID_KEY_PATH, domainSetting)) {
connection->respond(HTTPConnection::StatusCode400, "Domain id has not been set");
return true;
}
auto domainID = domainSetting.toString();
return forwardMetaverseAPIRequest(connection, "/api/v1/domains/" + domainID, "domain",
{ }, { "network_address", "network_port", "label" });
} else if (url.path() == URI_API_PLACES) {
auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
if (!accessTokenVariant->isValid()) {
connection->respond(HTTPConnection::StatusCode400, "User access token has not been set");
return true;
}
auto params = connection->parseUrlEncodedForm();
auto it = params.find("place_id");
if (it == params.end()) {
connection->respond(HTTPConnection::StatusCode400);
return true;
}
QString place_id = it.value();
it = params.find("path");
if (it == params.end()) {
connection->respond(HTTPConnection::StatusCode400);
return true;
}
QString path = it.value();
it = params.find("domain_id");
QString domainID;
if (it == params.end()) {
QVariant domainSetting;
if (!getSetting(METAVERSE_DOMAIN_ID_KEY_PATH, domainSetting)) {
connection->respond(HTTPConnection::StatusCode400);
return true;
}
domainID = domainSetting.toString();
} else {
domainID = it.value();
}
QJsonObject root {
{
"place",
QJsonObject({
{ "pointee_query", domainID },
{ "path", path }
})
}
};
QJsonDocument doc(root);
QUrl url { NetworkingConstants::METAVERSE_SERVER_URL.toString() + "/api/v1/places/" + place_id };
url.setQuery("access_token=" + accessTokenVariant->toString());
QNetworkRequest req(url);
req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QNetworkReply* reply = NetworkAccessManager::getInstance().put(req, doc.toJson());
connect(reply, &QNetworkReply::finished, this, [reply, connection]() {
if (reply->error() != QNetworkReply::NoError) {
qDebug() << "Got error response from metaverse server: " << reply->readAll();
connection->respond(HTTPConnection::StatusCode500,
"Error communicating with Metaverse");
return;
}
connection->respond(HTTPConnection::StatusCode200, reply->readAll());
});
return true;
}
} else if (connection->requestOperation() == QNetworkAccessManager::DeleteOperation) {
const QString ALL_NODE_DELETE_REGEX_STRING = QString("\\%1\\/?$").arg(URI_NODES);
const QString NODE_DELETE_REGEX_STRING = QString("\\%1\\/(%2)\\/$").arg(URI_NODES).arg(UUID_REGEX_STRING);
@ -2037,7 +2224,6 @@ static const QString HIFI_SESSION_COOKIE_KEY = "DS_WEB_SESSION_UUID";
static const QString STATE_QUERY_KEY = "state";
bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &url, bool skipSubHandler) {
qDebug() << "HTTPS request received at" << url.toString();
if (url.path() == URI_OAUTH) {
QUrlQuery codeURLQuery(url);
@ -2298,6 +2484,8 @@ QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenR
profileURL.setPath("/api/v1/user/profile");
profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken));
qDebug() << "Sending profile request to: " << profileURL;
QNetworkRequest profileRequest(profileURL);
profileRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
profileRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
@ -2799,9 +2987,20 @@ void DomainServer::handleKeypairChange() {
void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) {
// clear the ICE address lookup ID so that it can fire again
_iceAddressLookupID = -1;
_iceAddressLookupID = INVALID_ICE_LOOKUP_ID;
if (hostInfo.error() != QHostInfo::NoError) {
// enumerate the returned addresses and collect only valid IPv4 addresses
QList<QHostAddress> sanitizedAddresses = hostInfo.addresses();
auto it = sanitizedAddresses.begin();
while (it != sanitizedAddresses.end()) {
if (!it->isNull() && it->protocol() == QAbstractSocket::IPv4Protocol) {
++it;
} else {
it = sanitizedAddresses.erase(it);
}
}
if (hostInfo.error() != QHostInfo::NoError || sanitizedAddresses.empty()) {
qWarning() << "IP address lookup failed for" << _iceServerAddr << ":" << hostInfo.errorString();
// if we don't have an ICE server to use yet, trigger a retry
@ -2814,7 +3013,7 @@ void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) {
} else {
int countBefore = _iceServerAddresses.count();
_iceServerAddresses = hostInfo.addresses();
_iceServerAddresses = sanitizedAddresses;
if (countBefore == 0) {
qInfo() << "Found" << _iceServerAddresses.count() << "ice-server IP addresses for" << _iceServerAddr;

View file

@ -116,8 +116,6 @@ private slots:
void tokenGrantFinished();
void profileRequestFinished();
void timeoutICEAddressLookup();
signals:
void iceServerChanged();
void userConnected();
@ -187,6 +185,13 @@ private:
HTTPSConnection* connectionFromReplyWithState(QNetworkReply* reply);
bool forwardMetaverseAPIRequest(HTTPConnection* connection,
const QString& metaversePath,
const QString& requestSubobject,
std::initializer_list<QString> requiredData = { },
std::initializer_list<QString> optionalData = { },
bool requireAccessToken = true);
SubnetList _acSubnetWhitelist;
std::vector<QString> _replicatedUsernames;

View file

@ -29,6 +29,7 @@
#include <NLPacketList.h>
#include <NumericalConstants.h>
#include <SettingHandle.h>
#include <SettingHelpers.h>
#include <AvatarData.h> //for KillAvatarReason
#include <FingerprintUtils.h>
#include "DomainServerNodeData.h"
@ -43,12 +44,7 @@ const QString DESCRIPTION_COLUMNS_KEY = "columns";
const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
static Setting::Handle<double> JSON_SETTING_VERSION("json-settings/version", 0.0);
DomainServerSettingsManager::DomainServerSettingsManager() :
_descriptionArray(),
_configMap()
{
DomainServerSettingsManager::DomainServerSettingsManager() {
// load the description object from the settings description
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
descriptionFile.open(QIODevice::ReadOnly);
@ -100,12 +96,34 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer<Re
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
_argumentList = argumentList;
// after 1.7 we no longer use the master or merged configs - this is kept in place for migration
_configMap.loadMasterAndUserConfig(_argumentList);
_configMap.loadConfig(_argumentList);
static const auto VERSION_SETTINGS_KEYPATH = "version";
QVariant* versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH);
if (!versionVariant) {
versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH, true);
*versionVariant = _descriptionVersion;
persistToFile();
qDebug() << "No version in config file, setting to current version" << _descriptionVersion;
}
{
// Backward compatibility migration code
// The config version used to be stored in a different file
// This moves it to the actual config file.
Setting::Handle<double> JSON_SETTING_VERSION("json-settings/version", 0.0);
if (JSON_SETTING_VERSION.isSet()) {
auto version = JSON_SETTING_VERSION.get();
*versionVariant = version;
persistToFile();
QFile::remove(settingsFilename());
}
}
// What settings version were we before and what are we using now?
// Do we need to do any re-mapping?
double oldVersion = JSON_SETTING_VERSION.get();
double oldVersion = versionVariant->toDouble();
if (oldVersion != _descriptionVersion) {
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
@ -137,12 +155,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
QVariant* restrictedAccess = _configMap.valueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH, true);
*restrictedAccess = QVariant(true);
// write the new settings to the json file
persistToFile();
// reload the master and user config so that the merged config is right
_configMap.loadMasterAndUserConfig(_argumentList);
}
}
@ -172,12 +184,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
*entityServerVariant = entityServerMap;
}
// write the new settings to the json file
persistToFile();
// reload the master and user config so that the merged config is right
_configMap.loadMasterAndUserConfig(_argumentList);
}
}
@ -195,12 +201,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
qDebug() << "Migrating plaintext password to SHA256 hash in domain-server settings.";
*passwordVariant = QCryptographicHash::hash(plaintextPassword.toUtf8(), QCryptographicHash::Sha256).toHex();
// write the new settings to file
persistToFile();
// reload the master and user config so the merged config is correct
_configMap.loadMasterAndUserConfig(_argumentList);
}
}
@ -283,19 +283,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
packPermissions();
}
if (oldVersion < 1.7) {
// This was prior to the removal of the master config file
// So we write the merged config to the user config file, and stop reading from the user config file
qDebug() << "Migrating merged config to user config file. The master config file is deprecated.";
// replace the user config by the merged config
_configMap.getConfig() = _configMap.getMergedConfig();
// persist the new config so the user config file has the correctly merged config
persistToFile();
}
if (oldVersion < 1.8) {
unpackPermissions();
// This was prior to addition of domain content replacement, add that to localhost permissions by default
@ -310,12 +297,22 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
_standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canRezTemporaryCertifiedEntities);
packPermissions();
}
if (oldVersion < 2.0) {
const QString WIZARD_COMPLETED_ONCE = "wizard.completed_once";
QVariant* wizardCompletedOnce = _configMap.valueForKeyPath(WIZARD_COMPLETED_ONCE, true);
*wizardCompletedOnce = QVariant(true);
}
// write the current description version to our settings
*versionVariant = _descriptionVersion;
// write the new settings to the json file
persistToFile();
}
unpackPermissions();
// write the current description version to our settings
JSON_SETTING_VERSION.set(_descriptionVersion);
}
QVariantMap& DomainServerSettingsManager::getDescriptorsMap() {
@ -553,6 +550,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
@ -674,7 +672,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);
@ -686,7 +684,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) {
@ -721,8 +726,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;
}
@ -960,29 +965,6 @@ QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QStrin
return QVariant();
}
bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connection, const QUrl &url) {
if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH_JSON) {
// this is a GET operation for our settings
// check if there is a query parameter for settings affecting a particular type of assignment
const QString SETTINGS_TYPE_QUERY_KEY = "type";
QUrlQuery settingsQuery(url);
QString typeValue = settingsQuery.queryItemValue(SETTINGS_TYPE_QUERY_KEY);
if (!typeValue.isEmpty()) {
QJsonObject responseObject = responseObjectForType(typeValue);
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(responseObject).toJson(), "application/json");
return true;
} else {
return false;
}
}
return false;
}
bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection *connection, const QUrl &url) {
if (connection->requestOperation() == QNetworkAccessManager::PostOperation && url.path() == SETTINGS_PATH_JSON) {
// this is a POST operation to change one or more settings
@ -1214,6 +1196,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
static const QString SECURITY_ROOT_KEY = "security";
static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist";
static const QString BROADCASTING_KEY = "broadcasting";
static const QString WIZARD_KEY = "wizard";
static const QString DESCRIPTION_ROOT_KEY = "descriptors";
auto& settingsVariant = _configMap.getConfig();
@ -1266,7 +1249,8 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
if (!matchingDescriptionObject.isEmpty()) {
updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject);
if (rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY && rootKey != SETTINGS_PATHS_KEY ) {
if (rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY &&
rootKey != SETTINGS_PATHS_KEY && rootKey != WIZARD_KEY) {
needRestart = true;
}
} else {
@ -1282,8 +1266,9 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
if (!matchingDescriptionObject.isEmpty()) {
const QJsonValue& settingValue = rootValue.toObject()[settingKey];
updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject);
if ((rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY && rootKey != DESCRIPTION_ROOT_KEY)
|| settingKey == AC_SUBNET_WHITELIST_KEY) {
if ((rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY &&
rootKey != DESCRIPTION_ROOT_KEY && rootKey != WIZARD_KEY) ||
settingKey == AC_SUBNET_WHITELIST_KEY) {
needRestart = true;
}
} else {
@ -1299,9 +1284,6 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ
}
}
// re-merge the user and master configs after a settings change
_configMap.mergeMasterAndUserConfigs();
return needRestart;
}

View file

@ -43,7 +43,6 @@ class DomainServerSettingsManager : public QObject {
Q_OBJECT
public:
DomainServerSettingsManager();
bool handlePublicHTTPRequest(HTTPConnection* connection, const QUrl& url);
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
void setupConfigMap(const QStringList& argumentList);

View file

@ -1,6 +1,20 @@
set(TARGET_NAME interface)
project(${TARGET_NAME})
file(GLOB_RECURSE QML_SRC resources/qml/*.qml resources/qml/*.js)
add_custom_target(qml SOURCES ${QML_SRC})
GroupSources("resources/qml")
function(JOIN VALUES GLUE OUTPUT)
string (REGEX REPLACE "([^\\]|^);" "\\1${GLUE}" _TMP_STR "${VALUES}")
string (REGEX REPLACE "[\\](.)" "\\1" _TMP_STR "${_TMP_STR}") #fixes escaping
set (${OUTPUT} "${_TMP_STR}" PARENT_SCOPE)
endfunction()
set(INTERFACE_QML_QRC ${CMAKE_CURRENT_BINARY_DIR}/qml.qrc)
generate_qrc(OUTPUT ${INTERFACE_QML_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources GLOBS *.qml *.qss *.js *.html *.ttf *.gif *.svg *.png *.jpg)
# set a default root dir for each of our optional externals if it was not passed
set(OPTIONAL_EXTERNALS "LeapMotion")
@ -66,9 +80,7 @@ qt5_wrap_ui(QT_UI_HEADERS "${QT_UI_FILES}")
# add them to the interface source files
set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}")
file(GLOB_RECURSE QML_SRC resources/qml/*.qml resources/qml/*.js)
add_custom_target(qml SOURCES ${QML_SRC})
GroupSources("resources/qml")
list(APPEND INTERFACE_SRCS ${INTERFACE_QML_QRC})
if (UNIX)
install(
@ -131,10 +143,10 @@ if (APPLE)
# append the discovered resources to our list of interface sources
list(APPEND INTERFACE_SRCS ${DISCOVERED_RESOURCES})
set(INTERFACE_SRCS ${INTERFACE_SRCS} "${CMAKE_CURRENT_SOURCE_DIR}/icon/${INTERFACE_ICON_FILENAME}")
list(APPEND INTERFACE_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/icon/${INTERFACE_ICON_FILENAME})
endif()
# create the executable, make it a bundle on OS X
if (APPLE)
add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM})
@ -198,6 +210,7 @@ endif()
# link required hifi libraries
link_hifi_libraries(
shared octree ktx gpu gl gpu-gl procedural model render
pointers
recording fbx networking model-networking entities avatars trackers
audio audio-client animation script-engine physics
render-utils entities-renderer avatars-renderer ui auto-updater midi

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: 312 KiB

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 KiB

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 KiB

After

Width:  |  Height:  |  Size: 254 KiB

View file

@ -12,13 +12,19 @@
var MAX_WARNINGS = 3;
var numWarnings = 0;
var isWindowFocused = true;
var isKeyboardRaised = false;
var isNumericKeyboard = false;
window.isKeyboardRaised = false;
window.isNumericKeyboard = false;
window.isPasswordField = false;
function shouldSetPasswordField() {
var nodeType = document.activeElement.type;
return nodeType === "password";
}
function shouldRaiseKeyboard() {
var nodeName = document.activeElement.nodeName;
var nodeType = document.activeElement.type;
if (nodeName === "INPUT" && ["email", "number", "password", "tel", "text", "url"].indexOf(nodeType) !== -1
if (nodeName === "INPUT" && ["email", "number", "password", "tel", "text", "url", "search"].indexOf(nodeType) !== -1
|| document.activeElement.nodeName === "TEXTAREA") {
return true;
} else {
@ -53,12 +59,14 @@
setInterval(function () {
var keyboardRaised = shouldRaiseKeyboard();
var numericKeyboard = shouldSetNumeric();
var passwordField = shouldSetPasswordField();
if (isWindowFocused && (keyboardRaised !== isKeyboardRaised || numericKeyboard !== isNumericKeyboard)) {
if (isWindowFocused &&
(keyboardRaised !== window.isKeyboardRaised || numericKeyboard !== window.isNumericKeyboard || passwordField !== window.isPasswordField)) {
if (typeof EventBridge !== "undefined" && EventBridge !== null) {
EventBridge.emitWebEvent(
keyboardRaised ? ("_RAISE_KEYBOARD" + (numericKeyboard ? "_NUMERIC" : "")) : "_LOWER_KEYBOARD"
keyboardRaised ? ("_RAISE_KEYBOARD" + (numericKeyboard ? "_NUMERIC" : "") + (passwordField ? "_PASSWORD" : "")) : "_LOWER_KEYBOARD"
);
} else {
if (numWarnings < MAX_WARNINGS) {
@ -67,19 +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;
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);
}
});
@ -90,7 +99,7 @@
window.addEventListener("blur", function () {
isWindowFocused = false;
isKeyboardRaised = false;
isNumericKeyboard = false;
window.isKeyboardRaised = false;
window.isNumericKeyboard = false;
});
})();

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -2,14 +2,14 @@ name = mannequin
type = body+head
scale = 1
filename = mannequin/mannequin.baked.fbx
joint = jointEyeLeft = LeftEye
joint = jointRightHand = RightHand
joint = jointHead = Head
joint = jointEyeRight = RightEye
joint = jointLean = Spine
joint = jointNeck = Neck
joint = jointLeftHand = LeftHand
joint = jointRoot = Hips
joint = jointLean = Spine
joint = jointLeftHand = LeftHand
joint = jointHead = Head
joint = jointEyeLeft = LeftEye
joint = jointEyeRight = RightEye
joint = jointRightHand = RightHand
joint = jointNeck = Neck
freeJoint = LeftArm
freeJoint = LeftForeArm
freeJoint = RightArm
@ -18,72 +18,72 @@ bs = EyeBlink_L = blink = 1
bs = JawOpen = mouth_Open = 1
bs = LipsFunnel = Oo = 1
bs = BrowsU_L = brow_Up = 1
jointIndex = RightHandIndex2 = 27
jointIndex = LeftHandIndex2 = 51
jointIndex = RightUpLeg = 6
jointIndex = RightToe_End = 10
jointIndex = RightEye = 65
jointIndex = LeftHandPinky1 = 42
jointIndex = RightHandRing1 = 22
jointIndex = face = 67
jointIndex = LeftUpLeg = 1
jointIndex = LeftHand = 41
jointIndex = LeftHandMiddle1 = 58
jointIndex = LeftHandIndex1 = 50
jointIndex = LeftEye = 64
jointIndex = RightHandIndex1 = 26
jointIndex = LeftHandPinky4 = 45
jointIndex = RightArm = 15
jointIndex = LeftShoulder = 38
jointIndex = RightHandPinky2 = 19
jointIndex = RightHandThumb1 = 30
jointIndex = RightForeArm = 16
jointIndex = LeftHandMiddle3 = 60
jointIndex = Neck = 62
jointIndex = LeftHandThumb1 = 54
jointIndex = RightHandMiddle2 = 35
jointIndex = LeftHandMiddle4 = 61
jointIndex = mannequin = 68
jointIndex = Spine1 = 12
jointIndex = LeftHand = 41
jointIndex = LeftHandRing4 = 49
jointIndex = RightHandMiddle3 = 36
jointIndex = LeftHandThumb4 = 57
jointIndex = RightToe_End = 10
jointIndex = LeftHandRing1 = 46
jointIndex = LeftForeArm = 40
jointIndex = RightHandIndex4 = 29
jointIndex = LeftShoulder = 38
jointIndex = RightHandMiddle4 = 37
jointIndex = RightShoulder = 14
jointIndex = LeftLeg = 2
jointIndex = LeftToe_End = 5
jointIndex = Hips = 0
jointIndex = RightFoot = 8
jointIndex = RightHandThumb2 = 31
jointIndex = LeftHandMiddle3 = 60
jointIndex = RightHandThumb1 = 30
jointIndex = Neck = 62
jointIndex = Spine = 11
jointIndex = RightHandThumb4 = 33
jointIndex = RightHandMiddle1 = 34
jointIndex = LeftHandIndex4 = 53
jointIndex = face = 68
jointIndex = RightHandRing3 = 24
jointIndex = LeftHandPinky4 = 45
jointIndex = LeftHandMiddle2 = 59
jointIndex = RightHandThumb3 = 32
jointIndex = LeftHandPinky3 = 44
jointIndex = HeadTop_End = 66
jointIndex = Spine1 = 12
jointIndex = LeftHandRing3 = 48
jointIndex = mannequin1 = 67
jointIndex = RightEye = 65
jointIndex = RightHandRing4 = 25
jointIndex = RightHandPinky4 = 21
jointIndex = LeftHandRing2 = 47
jointIndex = RightHandIndex3 = 28
jointIndex = RightUpLeg = 6
jointIndex = LeftArm = 39
jointIndex = LeftHandThumb3 = 56
jointIndex = RightHandIndex2 = 27
jointIndex = RightForeArm = 16
jointIndex = RightArm = 15
jointIndex = RightHandRing2 = 23
jointIndex = LeftHandMiddle1 = 58
jointIndex = Spine2 = 13
jointIndex = LeftHandThumb2 = 55
jointIndex = RightHandMiddle2 = 35
jointIndex = RightHandPinky1 = 18
jointIndex = LeftUpLeg = 1
jointIndex = RightLeg = 7
jointIndex = LeftHandIndex2 = 51
jointIndex = RightHand = 17
jointIndex = LeftHandIndex3 = 52
jointIndex = RightHandIndex3 = 28
jointIndex = RightHandMiddle4 = 37
jointIndex = LeftLeg = 2
jointIndex = RightHandMiddle1 = 34
jointIndex = Spine2 = 13
jointIndex = LeftHandMiddle2 = 59
jointIndex = LeftHandPinky3 = 44
jointIndex = LeftHandThumb3 = 56
jointIndex = LeftHandRing4 = 49
jointIndex = RightHandThumb2 = 31
jointIndex = LeftHandRing3 = 48
jointIndex = HeadTop_End = 66
jointIndex = LeftHandThumb4 = 57
jointIndex = RightHandThumb3 = 32
jointIndex = RightHandPinky1 = 18
jointIndex = RightLeg = 7
jointIndex = RightHandMiddle3 = 36
jointIndex = RightHandPinky3 = 20
jointIndex = LeftToeBase = 4
jointIndex = LeftForeArm = 40
jointIndex = RightShoulder = 14
jointIndex = LeftHandRing2 = 47
jointIndex = LeftHandThumb2 = 55
jointIndex = Head = 63
jointIndex = RightHandRing4 = 25
jointIndex = LeftHandRing1 = 46
jointIndex = LeftFoot = 3
jointIndex = RightHandRing3 = 24
jointIndex = RightHandThumb4 = 33
jointIndex = LeftArm = 39
jointIndex = LeftToe_End = 5
jointIndex = RightHandPinky3 = 20
jointIndex = RightHandIndex1 = 26
jointIndex = LeftHandPinky1 = 42
jointIndex = RightToeBase = 9
jointIndex = RightHandPinky4 = 21
jointIndex = Spine = 11
jointIndex = LeftHandIndex4 = 53
jointIndex = LeftHandIndex1 = 50
jointIndex = LeftToeBase = 4
jointIndex = LeftHandPinky2 = 43
jointIndex = RightHandIndex4 = 29
jointIndex = Hips = 0
jointIndex = RightHandRing2 = 23
jointIndex = RightHandRing1 = 22
jointIndex = LeftHandThumb1 = 54
jointIndex = LeftEye = 64
jointIndex = Head = 63

BIN
interface/resources/meshes/mannequin/Eyes.ktx Executable file → Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

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