diff --git a/.eslintrc.js b/.eslintrc.js index b4d88777f2..54ff0a1268 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -54,7 +54,11 @@ module.exports = { "Window": false, "XMLHttpRequest": false, "location": false, - "print": false + "print": false, + "RayPick": false, + "LaserPointers": false, + "ContextOverlay": false, + "module": false }, "rules": { "brace-style": ["error", "1tbs", { "allowSingleLine": false }], diff --git a/.gitattributes b/.gitattributes index 406780d20a..4a06c4288a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,6 +10,7 @@ *.json text *.js text *.qml text +*.qrc text *.slf text *.slh text *.slv text diff --git a/.gitignore b/.gitignore index d6227f1f30..8aa82865a4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,11 @@ ext/ Makefile *.user +# Android Studio +*.iml +local.properties +android/libraries + # Xcode *.xcodeproj *.xcworkspace diff --git a/BUILD.md b/BUILD.md index 30302d611b..4d321146c3 100644 --- a/BUILD.md +++ b/BUILD.md @@ -2,7 +2,7 @@ - [cmake](https://cmake.org/download/): 3.9 - [Qt](https://www.qt.io/download-open-source): 5.9.1 -- [OpenSSL](https://www.openssl.org/): Use the latest available version of OpenSSL to avoid security vulnerabilities. +- [OpenSSL](https://www.openssl.org/): Use the latest available 1.0 version (**NOT** 1.1) of OpenSSL to avoid security vulnerabilities. - [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional) ### CMake External Project Dependencies diff --git a/BUILD_ANDROID.md b/BUILD_ANDROID.md index d69d20ee8a..cc51e58b1d 100644 --- a/BUILD_ANDROID.md +++ b/BUILD_ANDROID.md @@ -1,19 +1,56 @@ 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. -### Android Dependencies +# Android Dependencies You will need the following tools to build our Android targets. -* [cmake](http://www.cmake.org/download/) ~> 3.5.1 -* [Qt](http://www.qt.io/download-open-source/#) ~> 5.6.2 -* [ant](http://ant.apache.org/bindownload.cgi) ~> 1.9.4 -* [Android NDK](https://developer.android.com/tools/sdk/ndk/index.html) ~> r10d -* [Android SDK](http://developer.android.com/sdk/installing/index.html) ~> 24.4.1.1 - * Install the latest Platform-tools - * Install the latest Build-tools - * Install the SDK Platform for API Level 19 - * Install Sources for Android SDK for API Level 19 - * Install the ARM EABI v7a System Image if you want to run an emulator. +* [Qt](http://www.qt.io/download-open-source/#) ~> 5.9.1 +* [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 + +Download the Qt online installer. Run the installer and select the android_armv7 binaries. Installing to the default path 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 Tools tab select the following + +* Android SDK Build-Tools +* GPU Debugging Tools +* CMake (even if you have a separate CMake installation) +* LLDB +* Android SDK Platform-Tools +* Android SDK Tools +* Android SDK Tools +* NDK (even if you have the NDK installed separately) + +### Google VR SDK + +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 + +#### Set up machine specific Gradle properties + +Create a `gradle.properties` file in ~/.gradle. Edit the file to contain the following + + QT5_ROOT=C\:\\Qt\\5.9.1\\android_armv7 + GVR_ROOT=C\:\\Android\\gvr-android-sdk + +Replace the paths with your local installations of Qt5 and the Google VR SDK + + +# TODO fix the rest 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. diff --git a/BUILD_OSX.md b/BUILD_OSX.md index 3365627b8c..6b66863534 100644 --- a/BUILD_OSX.md +++ b/BUILD_OSX.md @@ -1,29 +1,28 @@ -Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only OS X specific instructions are found in this file. +Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only macOS specific instructions are found in this file. ### Homebrew -[Homebrew](https://brew.sh/) is an excellent package manager for OS X. It makes install of some High Fidelity dependencies very simple. +[Homebrew](https://brew.sh/) is an excellent package manager for macOS. It makes install of some High Fidelity dependencies very simple. - brew tap homebrew/versions - brew install cmake openssl + brew install cmake openssl qt ### OpenSSL Assuming you've installed OpenSSL using the homebrew instructions above, you'll need to set OPENSSL_ROOT_DIR so CMake can find your installations. For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR: - export OPENSSL_ROOT_DIR=/usr/local/Cellar/openssl/1.0.2h_1/ + export OPENSSL_ROOT_DIR=/usr/local/Cellar/openssl/1.0.2l Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change. ### Qt -Download and install the [Qt 5.6.2 for macOS](http://download.qt.io/official_releases/qt/5.6/5.6.2/qt-opensource-mac-x64-clang-5.6.2.dmg). +Assuming you've installed Qt using the homebrew instructions above, you'll need to set QT_CMAKE_PREFIX_PATH so CMake can find your installations. +For Qt installed via homebrew, set QT_CMAKE_PREFIX_PATH: -Keep the default components checked when going through the installer. + export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt/5.9.1/lib/cmake -Once Qt is installed, you need to manually configure the following: -* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt5.6.2/5.6/clang_64/lib/cmake/` directory. +Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change. ### Xcode diff --git a/BUILD_WIN.md b/BUILD_WIN.md index 3e93656d45..eea1f85e5b 100644 --- a/BUILD_WIN.md +++ b/BUILD_WIN.md @@ -3,7 +3,7 @@ This is a stand-alone guide for creating your first High Fidelity build for Wind ## Building High Fidelity Note: We are now using Visual Studio 2017 and Qt 5.9.1. If you are upgrading from Visual Studio 2013 and Qt 5.6.2, do a clean uninstall of those versions before going through this guide. -Note: The prerequisites will require about 10 GB of space on your drive. +Note: The prerequisites will require about 10 GB of space on your drive. You will also need a system with at least 8GB of main memory. ### Step 1. Visual Studio 2017 @@ -27,11 +27,20 @@ Go to `Control Panel > System > Advanced System Settings > Environment Variables * Set "Variable name": `QT_CMAKE_PREFIX_PATH` * Set "Variable value": `C:\Qt\5.9.1\msvc2017_64\lib\cmake` -### Step 5. Installing OpenSSL +### Step 5. Installing [vcpkg](https://github.com/Microsoft/vcpkg) -Download and install the Win64 OpenSSL v1.0.2 Installer[https://slproweb.com/products/Win32OpenSSL.html]. + * Clone the VCPKG [repository](https://github.com/Microsoft/vcpkg) + * Follow the instructions in the [readme](https://github.com/Microsoft/vcpkg/blob/master/README.md) to bootstrap vcpkg + * Note, you may need to do these in a _Developer Command Prompt_ + * Set an environment variable VCPKG_ROOT to the location of the cloned repository + * Close and re-open any command prompts after setting the environment variable so that they will pick up the change -### Step 6. Running CMake to Generate Build Files +### Step 6. Installing OpenSSL via vcpkg + + * In the vcpkg directory, install the 64 bit OpenSSL package with the command `vcpkg install openssl:x64-windows` + * Once the build completes you should have a file `ssl.h` in `${VCPKG_ROOT}/installed/x64-windows/include/openssl` + +### Step 7. Running CMake to Generate Build Files Run Command Prompt from Start and run the following commands: ``` @@ -43,7 +52,7 @@ cmake .. -G "Visual Studio 15 Win64" Where `%HIFI_DIR%` is the directory for the highfidelity repository. -### Step 7. Making a Build +### Step 8. Making a Build Open `%HIFI_DIR%\build\hifi.sln` using Visual Studio. @@ -51,7 +60,7 @@ Change the Solution Configuration (next to the green play button) from "Debug" t Run `Build > Build Solution`. -### Step 8. Testing Interface +### Step 9. Testing Interface Create another environment variable (see Step #4) * Set "Variable name": `_NO_DEBUG_HEAP` @@ -65,16 +74,20 @@ Note: You can also run Interface by launching it from command line or File Explo ## Troubleshooting -For any problems after Step #6, first try this: +For any problems after Step #7, first try this: * Delete your locally cloned copy of the highfidelity repository * Restart your computer * Redownload the [repository](https://github.com/highfidelity/hifi) -* Restart directions from Step #6 +* Restart directions from Step #7 #### CMake gives you the same error message repeatedly after the build fails Remove `CMakeCache.txt` found in the `%HIFI_DIR%\build` directory. +#### CMake can't find OpenSSL + +Remove `CMakeCache.txt` found in the `%HIFI_DIR%\build` directory. Verify that your VCPKG_ROOT environment variable is set and pointing to the correct location. Verify that the file `${VCPKG_ROOT}/installed/x64-windows/include/openssl/ssl.h` exists. + #### Qt is throwing an error Make sure you have the correct version (5.9.1) installed and `QT_CMAKE_PREFIX_PATH` environment variable is set correctly. diff --git a/CMakeLists.txt b/CMakeLists.txt index 9712b2d32e..9d3296a168 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,258 +1,95 @@ -if (WIN32) +# If we're running under the gradle build, HIFI_ANDROID will be set here, but +# ANDROID will not be set until after the `project` statement. This is the *ONLY* +# place you need to use `HIFI_ANDROID` instead of `ANDROID` +if (WIN32 AND NOT HIFI_ANDROID) cmake_minimum_required(VERSION 3.7) else() cmake_minimum_required(VERSION 3.2) endif() -if (USE_ANDROID_TOOLCHAIN) - set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/android/android.toolchain.cmake") - set(ANDROID_NATIVE_API_LEVEL 19) - set(ANDROID_TOOLCHAIN_NAME arm-linux-androideabi-clang3.5) - set(ANDROID_STL c++_shared) -endif () - -if (WIN32) - cmake_policy(SET CMP0020 NEW) -endif (WIN32) - -if (POLICY CMP0028) - cmake_policy(SET CMP0028 OLD) -endif () - -if (POLICY CMP0043) - cmake_policy(SET CMP0043 OLD) -endif () - -if (POLICY CMP0042) - cmake_policy(SET CMP0042 OLD) -endif () - -set_property(GLOBAL PROPERTY USE_FOLDERS ON) -set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMakeTargets") - project(hifi) -add_definitions(-DGLM_FORCE_RADIANS) -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") -find_package( Threads ) +include("cmake/init.cmake") -if (WIN32) - if (NOT "${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - message( FATAL_ERROR "Only 64 bit builds supported." ) - endif() +include("cmake/compiler.cmake") - add_definitions(-DNOMINMAX -D_CRT_SECURE_NO_WARNINGS) - - if (NOT WINDOW_SDK_PATH) - set(DEBUG_DISCOVERED_SDK_PATH TRUE) - endif() - - # sets path for Microsoft SDKs - # if you get build error about missing 'glu32' this path is likely wrong - if (MSVC_VERSION GREATER_EQUAL 1910) # VS 2017 - set(WINDOW_SDK_PATH "C:/Program Files (x86)/Windows Kits/10/Lib/${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}/x64" CACHE PATH "Windows SDK PATH") - elseif (MSVC_VERSION GREATER_EQUAL 1800) # VS 2013 - set(WINDOW_SDK_PATH "C:\\Program Files (x86)\\Windows Kits\\8.1\\Lib\\winv6.3\\um\\${WINDOW_SDK_FOLDER}" CACHE PATH "Windows SDK PATH") - else() - message( FATAL_ERROR "Visual Studio 2013 or higher required." ) - endif() - - if (DEBUG_DISCOVERED_SDK_PATH) - message(STATUS "The discovered Windows SDK path is ${WINDOW_SDK_PATH}") - endif () - - set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${WINDOW_SDK_PATH}) - # /wd4351 disables warning C4351: new behavior: elements of array will be default initialized - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4351") - # /LARGEADDRESSAWARE enables 32-bit apps to use more than 2GB of memory. - # Caveats: http://stackoverflow.com/questions/2288728/drawbacks-of-using-largeaddressaware-for-32-bit-windows-executables - # TODO: Remove when building 64-bit. - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /LARGEADDRESSAWARE") - # always produce symbols as PDB files - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG /OPT:REF /OPT:ICF") -else () - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -fno-strict-aliasing -Wno-unused-parameter") - if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb -Woverloaded-virtual -Wdouble-promotion") - if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "5.1") # gcc 5.1 and on have suggest-override - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override") - endif () - endif () -endif(WIN32) - -if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.3") - # GLM 0.9.8 on Ubuntu 14 (gcc 4.4) has issues with the simd declarations - add_definitions(-DGLM_FORCE_PURE) - endif() +if (NOT DEFINED SERVER_ONLY) + set(SERVER_ONLY 0) endif() -if (NOT ANDROID) - if ((NOT MSVC12) AND (NOT MSVC14)) - include(CheckCXXCompilerFlag) - CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) - CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) - - if (COMPILER_SUPPORTS_CXX11) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") - elseif(COMPILER_SUPPORTS_CXX0X) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") - else() - message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") - endif() - endif () -else () - # assume that the toolchain selected for android has C++11 support - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") -endif () - -if (APPLE) - set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++11") - set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --stdlib=libc++") -endif () - -if (NOT ANDROID_LIB_DIR) - set(ANDROID_LIB_DIR $ENV{ANDROID_LIB_DIR}) -endif () - -if (ANDROID) - if (NOT ANDROID_QT_CMAKE_PREFIX_PATH) - set(QT_CMAKE_PREFIX_PATH ${ANDROID_LIB_DIR}/Qt/5.5/android_armv7/lib/cmake) - else () - set(QT_CMAKE_PREFIX_PATH ${ANDROID_QT_CMAKE_PREFIX_PATH}) - endif () - - set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) - set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/lib) - - if (ANDROID_LIB_DIR) - list(APPEND CMAKE_FIND_ROOT_PATH ${ANDROID_LIB_DIR}) - endif () -else () - if (NOT QT_CMAKE_PREFIX_PATH) - set(QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH}) - endif () - if (NOT QT_CMAKE_PREFIX_PATH) - get_filename_component(QT_CMAKE_PREFIX_PATH "${Qt5_DIR}/.." REALPATH) - endif () -endif () - -set(QT_DIR $ENV{QT_DIR}) - -if (WIN32) - if (NOT EXISTS ${QT_CMAKE_PREFIX_PATH}) - message(FATAL_ERROR "Could not determine QT_CMAKE_PREFIX_PATH.") - endif () +if (ANDROID OR UWP) + set(MOBILE 1) +else() + set(MOBILE 0) endif() -# figure out where the qt dir is -get_filename_component(QT_DIR "${QT_CMAKE_PREFIX_PATH}/../../" ABSOLUTE) +if (ANDROID OR UWP) + option(BUILD_SERVER "Build server components" OFF) + option(BUILD_TOOLS "Build tools" OFF) +else() + option(BUILD_SERVER "Build server components" ON) + option(BUILD_TOOLS "Build tools" ON) +endif() -set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${QT_CMAKE_PREFIX_PATH}) +if (SERVER_ONLY) + option(BUILD_CLIENT "Build client components" OFF) + option(BUILD_TESTS "Build tests" OFF) +else() + option(BUILD_CLIENT "Build client components" ON) + option(BUILD_TESTS "Build tests" ON) +endif() -if (APPLE) +option(BUILD_INSTALLER "Build installer" ON) - exec_program(sw_vers ARGS -productVersion OUTPUT_VARIABLE OSX_VERSION) - string(REGEX MATCH "^[0-9]+\\.[0-9]+" OSX_VERSION ${OSX_VERSION}) - message(STATUS "Detected OS X version = ${OSX_VERSION}") +MESSAGE(STATUS "Build server: " ${BUILD_SERVER}) +MESSAGE(STATUS "Build client: " ${BUILD_CLIENT}) +MESSAGE(STATUS "Build tests: " ${BUILD_TESTS}) +MESSAGE(STATUS "Build tools: " ${BUILD_TOOLS}) +MESSAGE(STATUS "Build installer: " ${BUILD_INSTALLER}) - set(OSX_SDK "${OSX_VERSION}" CACHE String "OS X SDK version to look for inside Xcode bundle or at OSX_SDK_PATH") +if (UNIX AND DEFINED ENV{HIFI_MEMORY_DEBUGGING}) + MESSAGE(STATUS "Memory debugging is enabled") +endif() - # set our OS X deployment target - set(CMAKE_OSX_DEPLOYMENT_TARGET 10.8) - - # find the SDK path for the desired SDK - find_path( - _OSX_DESIRED_SDK_PATH - NAME MacOSX${OSX_SDK}.sdk - HINTS ${OSX_SDK_PATH} - PATHS /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/ - /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/ - ) - - if (NOT _OSX_DESIRED_SDK_PATH) - message(STATUS "Could not find OS X ${OSX_SDK} SDK. Will fall back to default. If you want a specific SDK, please pass OSX_SDK and optionally OSX_SDK_PATH to CMake.") - else () - message(STATUS "Found OS X ${OSX_SDK} SDK at ${_OSX_DESIRED_SDK_PATH}/MacOSX${OSX_SDK}.sdk") - - # set that as the SDK to use - set(CMAKE_OSX_SYSROOT ${_OSX_DESIRED_SDK_PATH}/MacOSX${OSX_SDK}.sdk) - endif () - -endif () - -# Hide automoc folders (for IDEs) -set(AUTOGEN_TARGETS_FOLDER "hidden/generated") - -# Find includes in corresponding build directories -set(CMAKE_INCLUDE_CURRENT_DIR ON) -# Instruct CMake to run moc automatically when needed. -set(CMAKE_AUTOMOC ON) -# Instruct CMake to run rcc automatically when needed -set(CMAKE_AUTORCC ON) - -set(HIFI_LIBRARY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries") - -# setup for find modules -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/") - -if (CMAKE_BUILD_TYPE) - string(TOUPPER ${CMAKE_BUILD_TYPE} UPPER_CMAKE_BUILD_TYPE) -else () - set(UPPER_CMAKE_BUILD_TYPE DEBUG) -endif () - -set(HF_CMAKE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -set(MACRO_DIR "${HF_CMAKE_DIR}/macros") -set(EXTERNAL_PROJECT_DIR "${HF_CMAKE_DIR}/externals") - -file(GLOB HIFI_CUSTOM_MACROS "cmake/macros/*.cmake") -foreach(CUSTOM_MACRO ${HIFI_CUSTOM_MACROS}) - include(${CUSTOM_MACRO}) -endforeach() +# +# Helper projects +# +file(GLOB_RECURSE CMAKE_SRC cmake/*.cmake cmake/CMakeLists.txt) +add_custom_target(cmake SOURCES ${CMAKE_SRC}) +GroupSources("cmake") +unset(CMAKE_SRC) file(GLOB_RECURSE JS_SRC scripts/*.js unpublishedScripts/*.js) add_custom_target(js SOURCES ${JS_SRC}) GroupSources("scripts") GroupSources("unpublishedScripts") +unset(JS_SRC) -if (UNIX) - install( - DIRECTORY "${CMAKE_SOURCE_DIR}/scripts" - DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/interface - COMPONENT ${CLIENT_COMPONENT} - ) -endif() +# Locate the required Qt build on the filesystem +setup_qt() +list(APPEND CMAKE_PREFIX_PATH "${QT_CMAKE_PREFIX_PATH}") -if (ANDROID) - file(GLOB ANDROID_CUSTOM_MACROS "cmake/android/*.cmake") - foreach(CUSTOM_MACRO ${ANDROID_CUSTOM_MACROS}) - include(${CUSTOM_MACRO}) - endforeach() -endif () +find_package( Threads ) + +add_definitions(-DGLM_FORCE_RADIANS) +set(HIFI_LIBRARY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libraries") set(EXTERNAL_PROJECT_PREFIX "project") set_property(DIRECTORY PROPERTY EP_PREFIX ${EXTERNAL_PROJECT_PREFIX}) setup_externals_binary_dir() option(USE_NSIGHT "Attempt to find the nSight libraries" 1) -option(GET_QUAZIP "Get QuaZip library automatically as external project" 1) - - -if (WIN32) - add_paths_to_fixup_libs("${QT_DIR}/bin") -endif () - -if (NOT DEFINED SERVER_ONLY) - set(SERVER_ONLY 0) -endif() set_packaging_parameters() +# FIXME hack to work on the proper Android toolchain +if (ANDROID) + add_subdirectory(android/app) + return() +endif() + # add subdirectories for all targets -if (NOT ANDROID) +if (BUILD_SERVER) add_subdirectory(assignment-client) set_target_properties(assignment-client PROPERTIES FOLDER "Apps") add_subdirectory(domain-server) @@ -260,28 +97,36 @@ if (NOT ANDROID) add_subdirectory(ice-server) set_target_properties(ice-server PROPERTIES FOLDER "Apps") add_subdirectory(server-console) - if (NOT SERVER_ONLY) +endif() + +if (BUILD_CLIENT) add_subdirectory(interface) set_target_properties(interface PROPERTIES FOLDER "Apps") - add_subdirectory(tests) - endif() - add_subdirectory(plugins) + if (ANDROID) + add_subdirectory(gvr-interface) + set_target_properties(gvr-interface PROPERTIES FOLDER "Apps") + endif() +endif() + +if (BUILD_CLIENT OR BUILD_SERVER) + add_subdirectory(plugins) +endif() + +if (BUILD_TOOLS) add_subdirectory(tools) endif() -if (ANDROID OR DESKTOP_GVR) - add_subdirectory(interface) - add_subdirectory(gvr-interface) - add_subdirectory(plugins) -endif () +if (BUILD_TESTS) + add_subdirectory(tests) +endif() -if (DEFINED ENV{HIFI_MEMORY_DEBUGGING}) - SET( HIFI_MEMORY_DEBUGGING true ) -endif () -if (HIFI_MEMORY_DEBUGGING) - if (UNIX) - MESSAGE("-- Memory debugging is enabled") - endif (UNIX) -endif () - -generate_installers() +if (BUILD_INSTALLER) + if (UNIX) + install( + DIRECTORY "${CMAKE_SOURCE_DIR}/scripts" + DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/interface + COMPONENT ${CLIENT_COMPONENT} + ) + endif() + generate_installers() +endif() diff --git a/Test Plan 2.docx b/Test Plan 2.docx deleted file mode 100644 index da60821b53..0000000000 Binary files a/Test Plan 2.docx and /dev/null differ diff --git a/android/app/CMakeLists.txt b/android/app/CMakeLists.txt new file mode 100644 index 0000000000..2d6df925e9 --- /dev/null +++ b/android/app/CMakeLists.txt @@ -0,0 +1,8 @@ +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() +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") diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000000..bd1c596bf3 --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,57 @@ +apply plugin: 'com.android.application' + +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' } + 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" + } + } + jackOptions { enabled true } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + sourceSets { + main { + jniLibs.srcDirs += '../libraries/jni'; + } + } + externalNativeBuild { + cmake { + path '../../CMakeLists.txt' + } + } +} + +dependencies { + compile fileTree(dir: "${project.rootDir}/libraries/jar", include: 'QtAndroid-bundled.jar') + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'com.google.vr:sdk-audio:1.80.0' + compile 'com.google.vr:sdk-base:1.80.0' +} + +build.dependsOn(':extractQt5') diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000000..b3c0078513 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\Android\SDK/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..05547bd5ae --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/cpp/GoogleVRHelpers.h b/android/app/src/main/cpp/GoogleVRHelpers.h new file mode 100644 index 0000000000..10c46b036f --- /dev/null +++ b/android/app/src/main/cpp/GoogleVRHelpers.h @@ -0,0 +1,50 @@ +#include +#include +#include + +namespace googlevr { + + // Convert a GVR matrix to GLM matrix + glm::mat4 toGlm(const gvr::Mat4f &matrix) { + glm::mat4 result; + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + result[j][i] = matrix.m[i][j]; + } + } + return result; + } + + // Given a field of view in degrees, compute the corresponding projection +// matrix. + glm::mat4 perspectiveMatrixFromView(const gvr::Rectf& fov, float z_near, float z_far) { + const float x_left = -std::tan(fov.left * M_PI / 180.0f) * z_near; + const float x_right = std::tan(fov.right * M_PI / 180.0f) * z_near; + const float y_bottom = -std::tan(fov.bottom * M_PI / 180.0f) * z_near; + const float y_top = std::tan(fov.top * M_PI / 180.0f) * z_near; + const float Y = (2 * z_near) / (y_top - y_bottom); + const float A = (x_right + x_left) / (x_right - x_left); + const float B = (y_top + y_bottom) / (y_top - y_bottom); + const float C = (z_near + z_far) / (z_near - z_far); + const float D = (2 * z_near * z_far) / (z_near - z_far); + + glm::mat4 result { 0 }; + result[2][0] = A; + result[1][1] = Y; + result[2][1] = B; + result[2][2] = C; + result[3][2] = D; + result[2][3] = -1; + return result; + } + + glm::quat toGlm(const gvr::ControllerQuat& q) { + glm::quat result; + result.w = q.qw; + result.x = q.qx; + result.y = q.qy; + result.z = q.qz; + return result; + } + +} diff --git a/android/app/src/main/cpp/native-lib.cpp b/android/app/src/main/cpp/native-lib.cpp new file mode 100644 index 0000000000..156d43d849 --- /dev/null +++ b/android/app/src/main/cpp/native-lib.cpp @@ -0,0 +1,78 @@ +#include + +#include +#include + +#include "renderer.h" + +int QtMsgTypeToAndroidPriority(QtMsgType type) { + int priority = ANDROID_LOG_UNKNOWN; + switch (type) { + case QtDebugMsg: priority = ANDROID_LOG_DEBUG; break; + case QtWarningMsg: priority = ANDROID_LOG_WARN; break; + case QtCriticalMsg: priority = ANDROID_LOG_ERROR; break; + case QtFatalMsg: priority = ANDROID_LOG_FATAL; break; + case QtInfoMsg: priority = ANDROID_LOG_INFO; break; + default: break; + } + return priority; +} + +void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { + __android_log_write(QtMsgTypeToAndroidPriority(type), "Interface", message.toStdString().c_str()); +} + +static jlong toJni(NativeRenderer *renderer) { + return reinterpret_cast(renderer); +} + +static NativeRenderer *fromJni(jlong renderer) { + return reinterpret_cast(renderer); +} + +#define JNI_METHOD(r, name) JNIEXPORT r JNICALL Java_org_saintandreas_testapp_MainActivity_##name + +extern "C" { + +JNI_METHOD(jlong, nativeCreateRenderer) +(JNIEnv *env, jclass clazz, jobject class_loader, jobject android_context, jlong native_gvr_api) { + qInstallMessageHandler(messageHandler); +#if defined(GVR) + auto gvrContext = reinterpret_cast(native_gvr_api); + return toJni(new NativeRenderer(gvrContext)); +#else + return toJni(new NativeRenderer(nullptr)); +#endif +} + +JNI_METHOD(void, nativeDestroyRenderer) +(JNIEnv *env, jclass clazz, jlong renderer) { + delete fromJni(renderer); +} + +JNI_METHOD(void, nativeInitializeGl) +(JNIEnv *env, jobject obj, jlong renderer) { + fromJni(renderer)->InitializeGl(); +} + +JNI_METHOD(void, nativeDrawFrame) +(JNIEnv *env, jobject obj, jlong renderer) { + fromJni(renderer)->DrawFrame(); +} + +JNI_METHOD(void, nativeOnTriggerEvent) +(JNIEnv *env, jobject obj, jlong renderer) { + fromJni(renderer)->OnTriggerEvent(); +} + +JNI_METHOD(void, nativeOnPause) +(JNIEnv *env, jobject obj, jlong renderer) { + fromJni(renderer)->OnPause(); +} + +JNI_METHOD(void, nativeOnResume) +(JNIEnv *env, jobject obj, jlong renderer) { + fromJni(renderer)->OnResume(); +} + +} // extern "C" diff --git a/android/app/src/main/cpp/renderer.cpp b/android/app/src/main/cpp/renderer.cpp new file mode 100644 index 0000000000..a877ebd777 --- /dev/null +++ b/android/app/src/main/cpp/renderer.cpp @@ -0,0 +1,636 @@ +#include "renderer.h" + +#include + +#include +#include + +#include "GoogleVRHelpers.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if 0 +#include +#include +#include +#include +#include +#include +#include +#include +#endif + + +template +void withFrameBuffer(gvr::Frame& frame, int32_t index, F f) { + frame.BindBuffer(index); + f(); + frame.Unbind(); +} + + +static const uint64_t kPredictionTimeWithoutVsyncNanos = 50000000; + +// Each shader has two variants: a single-eye ES 2.0 variant, and a multiview +// ES 3.0 variant. The multiview vertex shaders use transforms defined by +// arrays of mat4 uniforms, using gl_ViewID_OVR to determine the array index. + +#define UNIFORM_LIGHT_POS 20 +#define UNIFORM_M 16 +#define UNIFORM_MV 8 +#define UNIFORM_MVP 0 + +#if 0 +uniform Transform { // API uses “Transform[2]” to refer to instance 2 + mat4 u_MVP[2]; + mat4 u_MVMatrix[2]; + mat4 u_Model; + vec3 u_LightPos[2]; +}; +static const char *kDiffuseLightingVertexShader = R"glsl( +#version 300 es +#extension GL_OVR_multiview2 : enable + +layout(num_views=2) in; + +layout(location = 0) uniform mat4 u_MVP[2]; +layout(location = 8) uniform mat4 u_MVMatrix[2]; +layout(location = 16) uniform mat4 u_Model; +layout(location = 20) uniform vec3 u_LightPos[2]; + +layout(location = 0) in vec4 a_Position; +layout(location = 1) in vec4 a_Color; +layout(location = 2) in vec3 a_Normal; + +out vec4 v_Color; +out vec3 v_Grid; + +void main() { + mat4 mvp = u_MVP[gl_ViewID_OVR]; + mat4 modelview = u_MVMatrix[gl_ViewID_OVR]; + vec3 lightpos = u_LightPos[gl_ViewID_OVR]; + v_Grid = vec3(u_Model * a_Position); + vec3 modelViewVertex = vec3(modelview * a_Position); + vec3 modelViewNormal = vec3(modelview * vec4(a_Normal, 0.0)); + float distance = length(lightpos - modelViewVertex); + vec3 lightVector = normalize(lightpos - modelViewVertex); + float diffuse = max(dot(modelViewNormal, lightVector), 0.5); + diffuse = diffuse * (1.0 / (1.0 + (0.00001 * distance * distance))); + v_Color = vec4(a_Color.rgb * diffuse, a_Color.a); + gl_Position = mvp * a_Position; +} +)glsl"; +#endif + + +static const char *kSimepleVertexShader = R"glsl( +#version 300 es +#extension GL_OVR_multiview2 : enable + +layout(num_views=2) in; + +layout(location = 0) in vec4 a_Position; + +out vec4 v_Color; + +void main() { + v_Color = vec4(a_Position.xyz, 1.0); + gl_Position = vec4(a_Position.xyz, 1.0); +} +)glsl"; + + +static const char *kPassthroughFragmentShader = R"glsl( +#version 300 es +precision mediump float; +in vec4 v_Color; +out vec4 FragColor; + +void main() { FragColor = v_Color; } +)glsl"; + +static void CheckGLError(const char* label) { + int gl_error = glGetError(); + if (gl_error != GL_NO_ERROR) { + qWarning("GL error @ %s: %d", label, gl_error); + // Crash immediately to make OpenGL errors obvious. + abort(); + } +} + +// Contains vertex, normal and other data. +namespace cube { + const std::array CUBE_COORDS{{ + // Front face + -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + + // Right face + 1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, + 1.0f, 1.0f, -1.0f, + 1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + + // Back face + 1.0f, 1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + + // Left face + -1.0f, 1.0f, -1.0f, + -1.0f, -1.0f, -1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + + // Top face + -1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, -1.0f, + + // Bottom face + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + -1.0f, -1.0f, -1.0f + }}; + + const std::array CUBE_COLORS{{ + // front, green + 0.0f, 0.5273f, 0.2656f, + 0.0f, 0.5273f, 0.2656f, + 0.0f, 0.5273f, 0.2656f, + 0.0f, 0.5273f, 0.2656f, + 0.0f, 0.5273f, 0.2656f, + 0.0f, 0.5273f, 0.2656f, + + // right, blue + 0.0f, 0.3398f, 0.9023f, + 0.0f, 0.3398f, 0.9023f, + 0.0f, 0.3398f, 0.9023f, + 0.0f, 0.3398f, 0.9023f, + 0.0f, 0.3398f, 0.9023f, + 0.0f, 0.3398f, 0.9023f, + + // back, also green + 0.0f, 0.5273f, 0.2656f, + 0.0f, 0.5273f, 0.2656f, + 0.0f, 0.5273f, 0.2656f, + 0.0f, 0.5273f, 0.2656f, + 0.0f, 0.5273f, 0.2656f, + 0.0f, 0.5273f, 0.2656f, + + // left, also blue + 0.0f, 0.3398f, 0.9023f, + 0.0f, 0.3398f, 0.9023f, + 0.0f, 0.3398f, 0.9023f, + 0.0f, 0.3398f, 0.9023f, + 0.0f, 0.3398f, 0.9023f, + 0.0f, 0.3398f, 0.9023f, + + // top, red + 0.8359375f, 0.17578125f, 0.125f, + 0.8359375f, 0.17578125f, 0.125f, + 0.8359375f, 0.17578125f, 0.125f, + 0.8359375f, 0.17578125f, 0.125f, + 0.8359375f, 0.17578125f, 0.125f, + 0.8359375f, 0.17578125f, 0.125f, + + // bottom, also red + 0.8359375f, 0.17578125f, 0.125f, + 0.8359375f, 0.17578125f, 0.125f, + 0.8359375f, 0.17578125f, 0.125f, + 0.8359375f, 0.17578125f, 0.125f, + 0.8359375f, 0.17578125f, 0.125f, + 0.8359375f, 0.17578125f, 0.125f + }}; + + const std::array CUBE_NORMALS{{ + // Front face + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + + // Right face + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + 1.0f, 0.0f, 0.0f, + + // Back face + 0.0f, 0.0f, -1.0f, + 0.0f, 0.0f, -1.0f, + 0.0f, 0.0f, -1.0f, + 0.0f, 0.0f, -1.0f, + 0.0f, 0.0f, -1.0f, + 0.0f, 0.0f, -1.0f, + + // Left face + -1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + -1.0f, 0.0f, 0.0f, + + // Top face + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + + // Bottom face + 0.0f, -1.0f, 0.0f, + 0.0f, -1.0f, 0.0f, + 0.0f, -1.0f, 0.0f, + 0.0f, -1.0f, 0.0f, + 0.0f, -1.0f, 0.0f, + 0.0f, -1.0f, 0.0f + }}; +} + +namespace triangle { + static std::array TRIANGLE_VERTS {{ + -0.5f, -0.5f, 0.0f, + 0.5f, -0.5f, 0.0f, + 0.0f, 0.5f, 0.0f + }}; +} + +std::array buildViewports(const std::unique_ptr &gvrapi) { + return { {gvrapi->CreateBufferViewport(), gvrapi->CreateBufferViewport()} }; +}; + +const std::string VERTEX_SHADER_DEFINES{ R"GLSL( +#version 300 es +#extension GL_EXT_clip_cull_distance : enable +#define GPU_VERTEX_SHADER +#define GPU_SSBO_TRANSFORM_OBJECT 1 +#define GPU_TRANSFORM_IS_STEREO +#define GPU_TRANSFORM_STEREO_CAMERA +#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED +#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN +)GLSL" }; + +const std::string PIXEL_SHADER_DEFINES{ R"GLSL( +#version 300 es +precision mediump float; +#define GPU_PIXEL_SHADER +#define GPU_TRANSFORM_IS_STEREO +#define GPU_TRANSFORM_STEREO_CAMERA +#define GPU_TRANSFORM_STEREO_CAMERA_INSTANCED +#define GPU_TRANSFORM_STEREO_SPLIT_SCREEN +)GLSL" }; + + +#if defined(GVR) +NativeRenderer::NativeRenderer(gvr_context *vrContext) : + _gvrapi(new gvr::GvrApi(vrContext, false)), + _viewports(buildViewports(_gvrapi)), + _gvr_viewer_type(_gvrapi->GetViewerType()) +#else +NativeRenderer::NativeRenderer(void *vrContext) +#endif +{ + start = std::chrono::system_clock::now(); + qDebug() << "QQQ" << __FUNCTION__; +} + + +/** + * Converts a raw text file, saved as a resource, into an OpenGL ES shader. + * + * @param type The type of shader we will be creating. + * @param resId The resource ID of the raw text file. + * @return The shader object handler. + */ +int LoadGLShader(int type, const char *shadercode) { + GLuint result = 0; + std::string shaderError; + static const std::string SHADER_DEFINES; + if (!gl::compileShader(type, shadercode, SHADER_DEFINES, result, shaderError)) { + qWarning() << "QQQ" << __FUNCTION__ << "Shader compile failure" << shaderError.c_str(); + } + return result; +} + +// Computes a texture size that has approximately half as many pixels. This is +// equivalent to scaling each dimension by approximately sqrt(2)/2. +static gvr::Sizei HalfPixelCount(const gvr::Sizei &in) { + // Scale each dimension by sqrt(2)/2 ~= 7/10ths. + gvr::Sizei out; + out.width = (7 * in.width) / 10; + out.height = (7 * in.height) / 10; + return out; +} + + +#if defined(GVR) +void NativeRenderer::InitializeVR() { + _gvrapi->InitializeGl(); + bool multiviewEnabled = _gvrapi->IsFeatureSupported(GVR_FEATURE_MULTIVIEW); + qWarning() << "QQQ" << __FUNCTION__ << "Multiview enabled " << multiviewEnabled; + // Because we are using 2X MSAA, we can render to half as many pixels and + // achieve similar quality. + _renderSize = HalfPixelCount(_gvrapi->GetMaximumEffectiveRenderTargetSize()); + + std::vector specs; + specs.push_back(_gvrapi->CreateBufferSpec()); + specs[0].SetColorFormat(GVR_COLOR_FORMAT_RGBA_8888); + specs[0].SetDepthStencilFormat(GVR_DEPTH_STENCIL_FORMAT_DEPTH_16); + specs[0].SetSamples(2); + gvr::Sizei half_size = {_renderSize.width / 2, _renderSize.height}; + specs[0].SetMultiviewLayers(2); + specs[0].SetSize(half_size); + + _swapchain.reset(new gvr::SwapChain(_gvrapi->CreateSwapChain(specs))); + _viewportlist.reset(new gvr::BufferViewportList(_gvrapi->CreateEmptyBufferViewportList())); +} +void NativeRenderer::PrepareFramebuffer() { + const gvr::Sizei recommended_size = HalfPixelCount( + _gvrapi->GetMaximumEffectiveRenderTargetSize()); + if (_renderSize.width != recommended_size.width || + _renderSize.height != recommended_size.height) { + // We need to resize the framebuffer. Note that multiview uses two texture + // layers, each with half the render width. + gvr::Sizei framebuffer_size = recommended_size; + framebuffer_size.width /= 2; + _swapchain->ResizeBuffer(0, framebuffer_size); + _renderSize = recommended_size; + } +} +#endif + +void testShaderBuild(const char* vs_src, const char * fs_src) { + std::string error; + GLuint vs, fs; + if (!gl::compileShader(GL_VERTEX_SHADER, vs_src, VERTEX_SHADER_DEFINES, vs, error) || + !gl::compileShader(GL_FRAGMENT_SHADER, fs_src, PIXEL_SHADER_DEFINES, fs, error)) { + throw std::runtime_error("Failed to compile shader"); + } + auto pr = gl::compileProgram({ vs, fs }, error); + if (!pr) { + throw std::runtime_error("Failed to link shader"); + } +} + +void NativeRenderer::InitializeGl() { + qDebug() << "QQQ" << __FUNCTION__; + //gl::initModuleGl(); +#if defined(GVR) + InitializeVR(); +#endif + + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + glDisable(GL_SCISSOR_TEST); + glDisable(GL_BLEND); + + + + const uint32_t vertShader = LoadGLShader(GL_VERTEX_SHADER, kSimepleVertexShader); + //const uint32_t vertShader = LoadGLShader(GL_VERTEX_SHADER, kDiffuseLightingVertexShader); + const uint32_t fragShader = LoadGLShader(GL_FRAGMENT_SHADER, kPassthroughFragmentShader); + std::string error; + _cubeProgram = gl::compileProgram({ vertShader, fragShader }, error); + CheckGLError("build program"); + + glGenBuffers(1, &_cubeBuffer); + glBindBuffer(GL_ARRAY_BUFFER, _cubeBuffer); + 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); + + 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::system_clock::now() - start); + glm::vec3 v; + v.r = (float) (now.count() % 1000) / 1000.0f; + 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"); + +} + +void NativeRenderer::OnTriggerEvent() { + qDebug() << "QQQ" << __FUNCTION__; +} + +void NativeRenderer::OnPause() { + qDebug() << "QQQ" << __FUNCTION__; + _gvrapi->PauseTracking(); +} + +void NativeRenderer::OnResume() { + qDebug() << "QQQ" << __FUNCTION__; + _gvrapi->ResumeTracking(); + _gvrapi->RefreshViewerProfile(); +} diff --git a/android/app/src/main/cpp/renderer.h b/android/app/src/main/cpp/renderer.h new file mode 100644 index 0000000000..df7c51cab4 --- /dev/null +++ b/android/app/src/main/cpp/renderer.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include + +#define GVR + +#if defined(GVR) +#include +#endif + +class NativeRenderer { +public: + +#if defined(GVR) + NativeRenderer(gvr_context* vrContext); +#else + NativeRenderer(void* vrContext); +#endif + + void InitializeGl(); + void DrawFrame(); + void OnTriggerEvent(); + void OnPause(); + void OnResume(); + +private: + + + std::chrono::time_point start; +#if defined(GVR) + void InitializeVR(); + void PrepareFramebuffer(); + + std::unique_ptr _gvrapi; + gvr::ViewerType _gvr_viewer_type; + std::unique_ptr _viewportlist; + std::unique_ptr _swapchain; + std::array _viewports; + gvr::Sizei _renderSize; +#endif + + uint32_t _cubeBuffer { 0 }; + uint32_t _cubeVao { 0 }; + uint32_t _cubeProgram { 0 }; + + glm::mat4 _head_view; + glm::mat4 _model_cube; + glm::mat4 _camera; + glm::mat4 _view; + glm::mat4 _model_floor; + + std::array _modelview_cube; + std::array _modelview_floor; + std::array _modelview_projection_cube; + std::array _modelview_projection_floor; + std::array _light_pos_eye_space; + const glm::vec4 _light_pos_world_space{ 0, 2, 0, 1}; +}; diff --git a/android/app/src/main/java/org/saintandreas/testapp/MainActivity.java b/android/app/src/main/java/org/saintandreas/testapp/MainActivity.java new file mode 100644 index 0000000000..7eea14dce9 --- /dev/null +++ b/android/app/src/main/java/org/saintandreas/testapp/MainActivity.java @@ -0,0 +1,105 @@ +package org.saintandreas.testapp; + +import android.app.Activity; +import android.content.Context; +import android.opengl.GLSurfaceView; +import android.os.Bundle; +import android.view.View; + +import com.google.vr.ndk.base.AndroidCompat; +import com.google.vr.ndk.base.GvrLayout; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +public class MainActivity extends Activity { + private final static int IMMERSIVE_STICKY_VIEW_FLAGS = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + View.SYSTEM_UI_FLAG_FULLSCREEN | + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + + static { + System.loadLibrary("gvr"); + System.loadLibrary("native-lib"); + } + + private long nativeRenderer; + private GvrLayout gvrLayout; + private GLSurfaceView surfaceView; + + private native long nativeCreateRenderer(ClassLoader appClassLoader, Context context, long nativeGvrContext); + private native void nativeDestroyRenderer(long renderer); + private native void nativeInitializeGl(long renderer); + private native void nativeDrawFrame(long renderer); + private native void nativeOnTriggerEvent(long renderer); + private native void nativeOnPause(long renderer); + private native void nativeOnResume(long renderer); + + class NativeRenderer implements GLSurfaceView.Renderer { + @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { nativeInitializeGl(nativeRenderer); } + @Override public void onSurfaceChanged(GL10 gl, int width, int height) { } + @Override public void onDrawFrame(GL10 gl) { + nativeDrawFrame(nativeRenderer); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setImmersiveSticky(); + getWindow() + .getDecorView() + .setOnSystemUiVisibilityChangeListener((int visibility)->{ + if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { setImmersiveSticky(); } + }); + + gvrLayout = new GvrLayout(this); + nativeRenderer = nativeCreateRenderer( + getClass().getClassLoader(), + getApplicationContext(), + gvrLayout.getGvrApi().getNativeGvrContext()); + + 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); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + gvrLayout.shutdown(); + nativeDestroyRenderer(nativeRenderer); + nativeRenderer = 0; + } + + @Override + 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)); + } + + private void setImmersiveSticky() { + getWindow().getDecorView().setSystemUiVisibility(IMMERSIVE_STICKY_VIEW_FLAGS); + } +} diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..cde69bccce Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000..9a078e3e1a Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..c133a0cbd3 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000..efc028a636 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..bfa42f0e7b Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..3af2608a44 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..324e72cdd7 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..9bec2e6231 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..aee44e1384 Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..34947cd6bb Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000000..344907f039 --- /dev/null +++ b/android/app/src/main/res/values/colors.xml @@ -0,0 +1,4 @@ + + + #ffffff + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000000..5d6a4c1b99 --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + TestApp + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000000..033324ac58 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000000..77c3dd498c --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,91 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + jcenter() + } + 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 + } +} + +allprojects { + repositories { + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} + +task extractQt5jars(type: Copy) { + from fileTree(QT5_ROOT + "/jar") + into("${project.rootDir}/libraries/jar") + include("*.jar") +} + +task extractQt5so(type: Copy) { + from fileTree(QT5_ROOT + "/lib") + into("${project.rootDir}/libraries/jni/armeabi-v7a/") + include("libQt5AndroidExtras.so") + include("libQt5Concurrent.so") + include("libQt5Core.so") + include("libQt5Gamepad.so") + include("libQt5Gui.so") + include("libQt5Location.so") + include("libQt5Multimedia.so") + include("libQt5MultimediaQuick_p.so") + include("libQt5Network.so") + include("libQt5NetworkAuth.so") + include("libQt5OpenGL.so") + include("libQt5Positioning.so") + include("libQt5Qml.so") + include("libQt5Quick.so") + include("libQt5QuickControls2.so") + include("libQt5QuickParticles.so") + include("libQt5QuickTemplates2.so") + include("libQt5QuickWidgets.so") + include("libQt5Script.so") + include("libQt5ScriptTools.so") + include("libQt5Sensors.so") + include("libQt5Svg.so") + include("libQt5WebChannel.so") + include("libQt5WebSockets.so") + include("libQt5WebView.so") + include("libQt5Widgets.so") + include("libQt5Xml.so") + include("libQt5XmlPatterns.so") +} + +task extractAudioSo(type: Copy) { + from zipTree(GVR_ROOT + "/libraries/sdk-audio-1.80.0.aar") + into "${project.rootDir}/libraries/" + include "jni/armeabi-v7a/libgvr_audio.so" +} + +task extractGvrSo(type: Copy) { + from zipTree(GVR_ROOT + "/libraries/sdk-base-1.80.0.aar") + into "${project.rootDir}/libraries/" + include "jni/armeabi-v7a/libgvr.so" +} + +task extractNdk { } +extractNdk.dependsOn extractAudioSo +extractNdk.dependsOn extractGvrSo + +task extractQt5 { } +extractQt5.dependsOn extractQt5so +extractQt5.dependsOn extractQt5jars + +task extractBinaries { } +extractBinaries.dependsOn extractQt5 +extractBinaries.dependsOn extractNdk + +task deleteBinaries(type: Delete) { + delete "${project.rootDir}/libraries/jni" +} + +//clean.dependsOn(deleteBinaries) diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000000..aac7c9b461 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,17 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000000..e7b4def49c --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 1a27ddd479..0421195612 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -13,7 +13,7 @@ setup_memory_debugger() link_hifi_libraries( audio avatars octree gpu model fbx entities networking animation recording shared script-engine embedded-webserver - controllers physics plugins midi + controllers physics plugins midi baking image ) if (WIN32) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index c44fdf74ff..4efc3343d1 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -184,6 +184,9 @@ void Agent::run() { // make sure we hear about connected nodes so we can grab an ATP script if a request is pending connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &Agent::nodeActivated); + // make sure we hear about dissappearing nodes so we can clear the entity tree if an entity server goes away + connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &Agent::nodeKilled); + nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::EntityServer, NodeType::MessagesMixer, NodeType::AssetServer }); @@ -259,6 +262,13 @@ void Agent::nodeActivated(SharedNodePointer activatedNode) { } } +void Agent::nodeKilled(SharedNodePointer killedNode) { + if (killedNode->getType() == NodeType::EntityServer) { + // an entity server has gone away, ask the headless viewer to clear its tree + _entityViewer.clear(); + } +} + void Agent::negotiateAudioFormat() { auto nodeList = DependencyManager::get(); auto negotiateFormatPacket = NLPacket::create(PacketType::NegotiateAudioFormat); @@ -344,15 +354,16 @@ void Agent::scriptRequestFinished() { void Agent::executeScript() { - _scriptEngine = std::unique_ptr(new ScriptEngine(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload)); + _scriptEngine = scriptEngineFactory(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload); _scriptEngine->setParent(this); // be the parent of the script engine so it gets moved when we do - DependencyManager::get()->setScriptEngine(_scriptEngine.get()); + DependencyManager::get()->setScriptEngine(_scriptEngine); // setup an Avatar for the script to use auto scriptedAvatar = DependencyManager::get(); - connect(_scriptEngine.get(), SIGNAL(update(float)), scriptedAvatar.data(), SLOT(update(float)), Qt::ConnectionType::QueuedConnection); + connect(_scriptEngine.data(), SIGNAL(update(float)), + scriptedAvatar.data(), SLOT(update(float)), Qt::ConnectionType::QueuedConnection); scriptedAvatar->setForceFaceTrackerConnected(true); // call model URL setters with empty URLs so our avatar, if user, will have the default models diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index da60a07367..168da185b6 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -77,6 +77,7 @@ private slots: void handleSelectedAudioFormat(QSharedPointer message); void nodeActivated(SharedNodePointer activatedNode); + void nodeKilled(SharedNodePointer killedNode); void processAgentAvatar(); void processAgentAvatarAudio(); @@ -87,7 +88,7 @@ private: void encodeFrameOfZeros(QByteArray& encodedZeros); void computeLoudness(const QByteArray* decodedBuffer, QSharedPointer); - std::unique_ptr _scriptEngine; + ScriptEnginePointer _scriptEngine; EntityEditPacketSender _entityEditSender; EntityTreeHeadlessViewer _entityViewer; diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index 3886ff8d92..c03721d097 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -13,24 +13,32 @@ #include "AssetServer.h" #include +#include #include #include #include #include +#include #include #include #include #include +#include +#include +#include +#include +#include +#include #include #include -#include "NetworkLogging.h" -#include "NodeType.h" +#include "AssetServerLogging.h" +#include "BakeAssetTask.h" #include "SendAssetTask.h" #include "UploadAssetTask.h" -#include + static const uint8_t MIN_CORES_FOR_MULTICORE = 4; static const uint8_t CPU_AFFINITY_COUNT_HIGH = 2; @@ -41,6 +49,151 @@ static const int INTERFACE_RUNNING_CHECK_FREQUENCY_MS = 1000; const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server"; +static const QStringList BAKEABLE_MODEL_EXTENSIONS = { "fbx" }; +static QStringList BAKEABLE_TEXTURE_EXTENSIONS; +static const QString BAKED_MODEL_SIMPLE_NAME = "asset.fbx"; +static const QString BAKED_TEXTURE_SIMPLE_NAME = "texture.ktx"; + +void AssetServer::bakeAsset(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) { + qDebug() << "Starting bake for: " << assetPath << assetHash; + auto it = _pendingBakes.find(assetHash); + if (it == _pendingBakes.end()) { + auto task = std::make_shared(assetHash, assetPath, filePath); + task->setAutoDelete(false); + _pendingBakes[assetHash] = task; + + connect(task.get(), &BakeAssetTask::bakeComplete, this, &AssetServer::handleCompletedBake); + connect(task.get(), &BakeAssetTask::bakeFailed, this, &AssetServer::handleFailedBake); + connect(task.get(), &BakeAssetTask::bakeAborted, this, &AssetServer::handleAbortedBake); + + _bakingTaskPool.start(task.get()); + } else { + qDebug() << "Already in queue"; + } +} + +QString AssetServer::getPathToAssetHash(const AssetHash& assetHash) { + return _filesDirectory.absoluteFilePath(assetHash); +} + +std::pair AssetServer::getAssetStatus(const AssetPath& path, const AssetHash& hash) { + auto it = _pendingBakes.find(hash); + if (it != _pendingBakes.end()) { + return { (*it)->isBaking() ? Baking : Pending, "" }; + } + + if (path.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) { + return { Baked, "" }; + } + + auto dotIndex = path.lastIndexOf("."); + if (dotIndex == -1) { + return { Irrelevant, "" }; + } + + auto extension = path.mid(dotIndex + 1); + + QString bakedFilename; + + if (BAKEABLE_MODEL_EXTENSIONS.contains(extension)) { + bakedFilename = BAKED_MODEL_SIMPLE_NAME; + } else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit()) && hasMetaFile(hash)) { + bakedFilename = BAKED_TEXTURE_SIMPLE_NAME; + } else { + return { Irrelevant, "" }; + } + + auto bakedPath = HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + bakedFilename; + auto jt = _fileMappings.find(bakedPath); + if (jt != _fileMappings.end()) { + if (jt->second == hash) { + return { NotBaked, "" }; + } else { + return { Baked, "" }; + } + } else { + bool loaded; + AssetMeta meta; + + std::tie(loaded, meta) = readMetaFile(hash); + if (loaded && meta.failedLastBake) { + return { Error, meta.lastBakeErrors }; + } + } + + return { Pending, "" }; +} + +void AssetServer::bakeAssets() { + auto it = _fileMappings.cbegin(); + for (; it != _fileMappings.cend(); ++it) { + auto path = it->first; + auto hash = it->second; + maybeBake(path, hash); + } +} + +void AssetServer::maybeBake(const AssetPath& path, const AssetHash& hash) { + if (needsToBeBaked(path, hash)) { + qDebug() << "Queuing bake of: " << path; + bakeAsset(hash, path, getPathToAssetHash(hash)); + } +} + +void AssetServer::createEmptyMetaFile(const AssetHash& hash) { + QString metaFilePath = "atp:/" + hash + "/meta.json"; + QFile metaFile { metaFilePath }; + + if (!metaFile.exists()) { + qDebug() << "Creating metafile for " << hash; + if (metaFile.open(QFile::WriteOnly)) { + qDebug() << "Created metafile for " << hash; + metaFile.write("{}"); + } + } +} + +bool AssetServer::hasMetaFile(const AssetHash& hash) { + QString metaFilePath = HIDDEN_BAKED_CONTENT_FOLDER + hash + "/meta.json"; + + return _fileMappings.find(metaFilePath) != _fileMappings.end(); +} + +bool AssetServer::needsToBeBaked(const AssetPath& path, const AssetHash& assetHash) { + if (path.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) { + return false; + } + + auto dotIndex = path.lastIndexOf("."); + if (dotIndex == -1) { + return false; + } + + auto extension = path.mid(dotIndex + 1); + + QString bakedFilename; + + bool loaded; + AssetMeta meta; + std::tie(loaded, meta) = readMetaFile(assetHash); + + // TODO: Allow failed bakes that happened on old versions to be re-baked + if (loaded && meta.failedLastBake) { + return false; + } + + if (BAKEABLE_MODEL_EXTENSIONS.contains(extension)) { + bakedFilename = BAKED_MODEL_SIMPLE_NAME; + } else if (loaded && BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit())) { + bakedFilename = BAKED_TEXTURE_SIMPLE_NAME; + } else { + return false; + } + + auto bakedPath = HIDDEN_BAKED_CONTENT_FOLDER + assetHash + "/" + bakedFilename; + return _fileMappings.find(bakedPath) == _fileMappings.end(); +} + bool interfaceRunning() { bool result = false; @@ -67,20 +220,36 @@ void updateConsumedCores() { if (isInterfaceRunning) { coreCount = coreCount > MIN_CORES_FOR_MULTICORE ? CPU_AFFINITY_COUNT_HIGH : CPU_AFFINITY_COUNT_LOW; } - qDebug() << "Setting max consumed cores to " << coreCount; + qCDebug(asset_server) << "Setting max consumed cores to " << coreCount; setMaxCores(coreCount); } AssetServer::AssetServer(ReceivedMessage& message) : ThreadedAssignment(message), - _taskPool(this) + _transferTaskPool(this), + _bakingTaskPool(this) { + // store the current state of image compression so we can reset it when this assignment is complete + _wasColorTextureCompressionEnabled = image::isColorTexturesCompressionEnabled(); + _wasGrayscaleTextureCompressionEnabled = image::isGrayscaleTexturesCompressionEnabled(); + _wasNormalTextureCompressionEnabled = image::isNormalTexturesCompressionEnabled(); + _wasCubeTextureCompressionEnabled = image::isCubeTexturesCompressionEnabled(); + + // enable compression in image library + image::setColorTexturesCompressionEnabled(true); + image::setGrayscaleTexturesCompressionEnabled(true); + image::setNormalTexturesCompressionEnabled(true); + image::setCubeTexturesCompressionEnabled(true); + + BAKEABLE_TEXTURE_EXTENSIONS = TextureBaker::getSupportedFormats(); + qDebug() << "Supported baking texture formats:" << BAKEABLE_MODEL_EXTENSIONS; // Most of the work will be I/O bound, reading from disk and constructing packet objects, // so the ideal is greater than the number of cores on the system. static const int TASK_POOL_THREAD_COUNT = 50; - _taskPool.setMaxThreadCount(TASK_POOL_THREAD_COUNT); + _transferTaskPool.setMaxThreadCount(TASK_POOL_THREAD_COUNT); + _bakingTaskPool.setMaxThreadCount(1); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListener(PacketType::AssetGet, this, "handleAssetGet"); @@ -103,9 +272,39 @@ AssetServer::AssetServer(ReceivedMessage& message) : #endif } +void AssetServer::aboutToFinish() { + + // remove pending transfer tasks + _transferTaskPool.clear(); + + // abort each of our still running bake tasks, remove pending bakes that were never put on the thread pool + auto it = _pendingBakes.begin(); + while (it != _pendingBakes.end()) { + auto pendingRunnable = _bakingTaskPool.tryTake(it->get()); + + if (pendingRunnable) { + it = _pendingBakes.erase(it); + } else { + it.value()->abort(); + ++it; + } + } + + // make sure all bakers are finished or aborted + while (_pendingBakes.size() > 0) { + QCoreApplication::processEvents(); + } + + // re-set defaults in image library + image::setColorTexturesCompressionEnabled(_wasCubeTextureCompressionEnabled); + image::setGrayscaleTexturesCompressionEnabled(_wasGrayscaleTextureCompressionEnabled); + image::setNormalTexturesCompressionEnabled(_wasNormalTextureCompressionEnabled); + image::setCubeTexturesCompressionEnabled(_wasCubeTextureCompressionEnabled); +} + void AssetServer::run() { - qDebug() << "Waiting for connection to domain to request settings from domain-server."; + qCDebug(asset_server) << "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()->getDomainHandler(); @@ -126,7 +325,7 @@ void AssetServer::completeSetup() { static const QString ASSET_SERVER_SETTINGS_KEY = "asset_server"; if (!settingsObject.contains(ASSET_SERVER_SETTINGS_KEY)) { - qCritical() << "Received settings from the domain-server with no asset-server section. Stopping assignment."; + qCCritical(asset_server) << "Received settings from the domain-server with no asset-server section. Stopping assignment."; setFinished(true); return; } @@ -141,7 +340,7 @@ void AssetServer::completeSetup() { const int BITS_PER_MEGABITS = 1000 * 1000; int maxBandwidth = maxBandwidthFloat * BITS_PER_MEGABITS; nodeList->setConnectionMaxBandwidth(maxBandwidth); - qInfo() << "Set maximum bandwith per connection to" << maxBandwidthFloat << "Mb/s." + qCInfo(asset_server) << "Set maximum bandwith per connection to" << maxBandwidthFloat << "Mb/s." " (" << maxBandwidth << "bits/s)"; } @@ -150,7 +349,7 @@ void AssetServer::completeSetup() { auto assetsJSONValue = assetServerObject[ASSETS_PATH_OPTION]; if (!assetsJSONValue.isString()) { - qCritical() << "Received an assets path from the domain-server that could not be parsed. Stopping assignment."; + qCCritical(asset_server) << "Received an assets path from the domain-server that could not be parsed. Stopping assignment."; setFinished(true); return; } @@ -167,19 +366,19 @@ void AssetServer::completeSetup() { _resourcesDirectory = QDir(absoluteFilePath); - qDebug() << "Creating resources directory"; + qCDebug(asset_server) << "Creating resources directory"; _resourcesDirectory.mkpath("."); _filesDirectory = _resourcesDirectory; if (!_resourcesDirectory.mkpath(ASSET_FILES_SUBDIR) || !_filesDirectory.cd(ASSET_FILES_SUBDIR)) { - qCritical() << "Unable to create file directory for asset-server files. Stopping assignment."; + qCCritical(asset_server) << "Unable to create file directory for asset-server files. Stopping assignment."; setFinished(true); return; } // load whatever mappings we currently have from the local file if (loadMappingsFromFile()) { - qInfo() << "Serving files from: " << _filesDirectory.path(); + qCInfo(asset_server) << "Serving files from: " << _filesDirectory.path(); // Check the asset directory to output some information about what we have auto files = _filesDirectory.entryList(QDir::Files); @@ -187,18 +386,19 @@ void AssetServer::completeSetup() { QRegExp hashFileRegex { ASSET_HASH_REGEX_STRING }; auto hashedFiles = files.filter(hashFileRegex); - qInfo() << "There are" << hashedFiles.size() << "asset files in the asset directory."; + qCInfo(asset_server) << "There are" << hashedFiles.size() << "asset files in the asset directory."; - if (_fileMappings.count() > 0) { + if (_fileMappings.size() > 0) { cleanupUnmappedFiles(); } nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer }); + + bakeAssets(); } else { - qCritical() << "Asset Server assignment will not continue because mapping file could not be loaded."; + qCCritical(asset_server) << "Asset Server assignment will not continue because mapping file could not be loaded."; setFinished(true); } - } void AssetServer::cleanupUnmappedFiles() { @@ -206,21 +406,28 @@ void AssetServer::cleanupUnmappedFiles() { auto files = _filesDirectory.entryInfoList(QDir::Files); - // grab the currently mapped hashes - auto mappedHashes = _fileMappings.values(); - - qInfo() << "Performing unmapped asset cleanup."; + qCInfo(asset_server) << "Performing unmapped asset cleanup."; for (const auto& fileInfo : files) { - if (hashFileRegex.exactMatch(fileInfo.fileName())) { - if (!mappedHashes.contains(fileInfo.fileName())) { + auto filename = fileInfo.fileName(); + if (hashFileRegex.exactMatch(filename)) { + bool matched { false }; + for (auto& pair : _fileMappings) { + if (pair.second == filename) { + matched = true; + break; + } + } + if (!matched) { // remove the unmapped file QFile removeableFile { fileInfo.absoluteFilePath() }; if (removeableFile.remove()) { - qDebug() << "\tDeleted" << fileInfo.fileName() << "from asset files directory since it is unmapped."; + qCDebug(asset_server) << "\tDeleted" << filename << "from asset files directory since it is unmapped."; + + removeBakedPathsForDeletedAsset(filename); } else { - qDebug() << "\tAttempt to delete unmapped file" << fileInfo.fileName() << "failed"; + qCDebug(asset_server) << "\tAttempt to delete unmapped file" << filename << "failed"; } } } @@ -238,26 +445,24 @@ void AssetServer::handleAssetMappingOperation(QSharedPointer me replyPacket->writePrimitive(messageID); switch (operationType) { - case AssetMappingOperationType::Get: { + case AssetMappingOperationType::Get: handleGetMappingOperation(*message, senderNode, *replyPacket); break; - } - case AssetMappingOperationType::GetAll: { + case AssetMappingOperationType::GetAll: handleGetAllMappingOperation(*message, senderNode, *replyPacket); break; - } - case AssetMappingOperationType::Set: { + case AssetMappingOperationType::Set: handleSetMappingOperation(*message, senderNode, *replyPacket); break; - } - case AssetMappingOperationType::Delete: { + case AssetMappingOperationType::Delete: handleDeleteMappingsOperation(*message, senderNode, *replyPacket); break; - } - case AssetMappingOperationType::Rename: { + case AssetMappingOperationType::Rename: handleRenameMappingOperation(*message, senderNode, *replyPacket); break; - } + case AssetMappingOperationType::SetBakingEnabled: + handleSetBakingEnabledOperation(*message, senderNode, *replyPacket); + break; } auto nodeList = DependencyManager::get(); @@ -267,11 +472,75 @@ void AssetServer::handleAssetMappingOperation(QSharedPointer me void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { QString assetPath = message.readString(); + QUrl url { assetPath }; + assetPath = url.path(); + auto it = _fileMappings.find(assetPath); if (it != _fileMappings.end()) { - auto assetHash = it->toString(); + + // check if we should re-direct to a baked asset + + // first, figure out from the mapping extension what type of file this is + auto assetPathExtension = assetPath.mid(assetPath.lastIndexOf('.') + 1).toLower(); + QString bakedRootFile; + + if (BAKEABLE_MODEL_EXTENSIONS.contains(assetPathExtension)) { + bakedRootFile = BAKED_MODEL_SIMPLE_NAME; + } else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(assetPathExtension.toLocal8Bit())) { + bakedRootFile = BAKED_TEXTURE_SIMPLE_NAME; + } + + auto originalAssetHash = it->second; + QString redirectedAssetHash; + QString bakedAssetPath; + quint8 wasRedirected = false; + bool bakingDisabled = false; + + if (!bakedRootFile.isEmpty()) { + // we ran into an asset for which we could have a baked version, let's check if it's ready + bakedAssetPath = HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + bakedRootFile; + auto bakedIt = _fileMappings.find(bakedAssetPath); + + if (bakedIt != _fileMappings.end()) { + if (bakedIt->second != originalAssetHash) { + qDebug() << "Did find baked version for: " << originalAssetHash << assetPath; + // we found a baked version of the requested asset to serve, redirect to that + redirectedAssetHash = bakedIt->second; + wasRedirected = true; + } else { + qDebug() << "Did not find baked version for: " << originalAssetHash << assetPath << " (disabled)"; + bakingDisabled = true; + } + } else { + qDebug() << "Did not find baked version for: " << originalAssetHash << assetPath; + } + } + replyPacket.writePrimitive(AssetServerError::NoError); - replyPacket.write(QByteArray::fromHex(assetHash.toUtf8())); + + if (wasRedirected) { + qDebug() << "Writing re-directed hash for" << originalAssetHash << "to" << redirectedAssetHash; + replyPacket.write(QByteArray::fromHex(redirectedAssetHash.toUtf8())); + + // add a flag saying that this mapping request was redirect + replyPacket.writePrimitive(wasRedirected); + + // include the re-directed path in case the caller needs to make relative path requests for the baked asset + replyPacket.writeString(bakedAssetPath); + + } else { + replyPacket.write(QByteArray::fromHex(originalAssetHash.toUtf8())); + replyPacket.writePrimitive(wasRedirected); + + auto query = QUrlQuery(url.query()); + bool isSkybox = query.hasQueryItem("skybox"); + if (isSkybox) { + writeMetaFile(originalAssetHash); + if (!bakingDisabled) { + maybeBake(assetPath, originalAssetHash); + } + } + } } else { replyPacket.writePrimitive(AssetServerError::AssetNotFound); } @@ -280,13 +549,23 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNode void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { replyPacket.writePrimitive(AssetServerError::NoError); - auto count = _fileMappings.size(); + uint32_t count = (uint32_t)_fileMappings.size(); replyPacket.writePrimitive(count); for (auto it = _fileMappings.cbegin(); it != _fileMappings.cend(); ++ it) { - replyPacket.writeString(it.key()); - replyPacket.write(QByteArray::fromHex(it.value().toString().toUtf8())); + auto mapping = it->first; + auto hash = it->second; + replyPacket.writeString(mapping); + replyPacket.write(QByteArray::fromHex(hash.toUtf8())); + + BakingStatus status; + QString lastBakeErrors; + std::tie(status, lastBakeErrors) = getAssetStatus(mapping, hash); + replyPacket.writePrimitive(status); + if (status == Error) { + replyPacket.writeString(lastBakeErrors); + } } } @@ -296,11 +575,18 @@ void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNode auto assetHash = message.read(SHA256_HASH_LENGTH).toHex(); - if (setMapping(assetPath, assetHash)) { - replyPacket.writePrimitive(AssetServerError::NoError); + // don't process a set mapping operation that is inside the hidden baked folder + if (assetPath.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) { + qCDebug(asset_server) << "Refusing to process a set mapping operation inside" << HIDDEN_BAKED_CONTENT_FOLDER; + replyPacket.writePrimitive(AssetServerError::PermissionDenied); } else { - replyPacket.writePrimitive(AssetServerError::MappingOperationFailed); + if (setMapping(assetPath, assetHash)) { + replyPacket.writePrimitive(AssetServerError::NoError); + } else { + replyPacket.writePrimitive(AssetServerError::MappingOperationFailed); + } } + } else { replyPacket.writePrimitive(AssetServerError::PermissionDenied); } @@ -314,7 +600,14 @@ void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, Shared QStringList mappingsToDelete; for (int i = 0; i < numberOfDeletedMappings; ++i) { - mappingsToDelete << message.readString(); + auto mapping = message.readString(); + + if (!mapping.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) { + mappingsToDelete << mapping; + } else { + qCDebug(asset_server) << "Refusing to delete mapping" << mapping + << "that is inside" << HIDDEN_BAKED_CONTENT_FOLDER; + } } if (deleteMappings(mappingsToDelete)) { @@ -332,7 +625,38 @@ void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, SharedN QString oldPath = message.readString(); QString newPath = message.readString(); - if (renameMapping(oldPath, newPath)) { + if (oldPath.startsWith(HIDDEN_BAKED_CONTENT_FOLDER) || newPath.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) { + qCDebug(asset_server) << "Cannot rename" << oldPath << "to" << newPath + << "since one of the paths is inside" << HIDDEN_BAKED_CONTENT_FOLDER; + replyPacket.writePrimitive(AssetServerError::PermissionDenied); + } else { + if (renameMapping(oldPath, newPath)) { + replyPacket.writePrimitive(AssetServerError::NoError); + } else { + replyPacket.writePrimitive(AssetServerError::MappingOperationFailed); + } + } + + } else { + replyPacket.writePrimitive(AssetServerError::PermissionDenied); + } +} + +void AssetServer::handleSetBakingEnabledOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) { + if (senderNode->getCanWriteToAssetServer()) { + bool enabled { true }; + message.readPrimitive(&enabled); + + int numberOfMappings{ 0 }; + message.readPrimitive(&numberOfMappings); + + QStringList mappings; + + for (int i = 0; i < numberOfMappings; ++i) { + mappings << message.readString(); + } + + if (setBakingEnabled(mappings, enabled)) { replyPacket.writePrimitive(AssetServerError::NoError); } else { replyPacket.writePrimitive(AssetServerError::MappingOperationFailed); @@ -347,7 +671,7 @@ void AssetServer::handleAssetGetInfo(QSharedPointer message, Sh MessageID messageID; if (message->getSize() < qint64(SHA256_HASH_LENGTH + sizeof(messageID))) { - qDebug() << "ERROR bad file request"; + qCDebug(asset_server) << "ERROR bad file request"; return; } @@ -366,11 +690,11 @@ void AssetServer::handleAssetGetInfo(QSharedPointer message, Sh QFileInfo fileInfo { _filesDirectory.filePath(fileName) }; if (fileInfo.exists() && fileInfo.isReadable()) { - qDebug() << "Opening file: " << fileInfo.filePath(); + qCDebug(asset_server) << "Opening file: " << fileInfo.filePath(); replyPacket->writePrimitive(AssetServerError::NoError); replyPacket->writePrimitive(fileInfo.size()); } else { - qDebug() << "Asset not found: " << QString(hexHash); + qCDebug(asset_server) << "Asset not found: " << QString(hexHash); replyPacket->writePrimitive(AssetServerError::AssetNotFound); } @@ -383,22 +707,22 @@ void AssetServer::handleAssetGet(QSharedPointer message, Shared auto minSize = qint64(sizeof(MessageID) + SHA256_HASH_LENGTH + sizeof(DataOffset) + sizeof(DataOffset)); if (message->getSize() < minSize) { - qDebug() << "ERROR bad file request"; + qCDebug(asset_server) << "ERROR bad file request"; return; } // Queue task auto task = new SendAssetTask(message, senderNode, _filesDirectory); - _taskPool.start(task); + _transferTaskPool.start(task); } void AssetServer::handleAssetUpload(QSharedPointer message, SharedNodePointer senderNode) { if (senderNode->getCanWriteToAssetServer()) { - qDebug() << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID()); + qCDebug(asset_server) << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID()); auto task = new UploadAssetTask(message, senderNode, _filesDirectory); - _taskPool.start(task); + _transferTaskPool.start(task); } else { // this is a node the domain told us is not allowed to rez entities // for now this also means it isn't allowed to add assets @@ -502,39 +826,46 @@ bool AssetServer::loadMappingsFromFile() { auto jsonDocument = QJsonDocument::fromJson(mapFile.readAll(), &error); if (error.error == QJsonParseError::NoError) { - _fileMappings = jsonDocument.object().toVariantHash(); - - // remove any mappings that don't match the expected format - auto it = _fileMappings.begin(); - while (it != _fileMappings.end()) { - bool shouldDrop = false; - - if (!isValidFilePath(it.key())) { - qWarning() << "Will not keep mapping for" << it.key() << "since it is not a valid path."; - shouldDrop = true; - } - - if (!isValidHash(it.value().toString())) { - qWarning() << "Will not keep mapping for" << it.key() << "since it does not have a valid hash."; - shouldDrop = true; - } - - if (shouldDrop) { - it = _fileMappings.erase(it); - } else { - ++it; - } + if (!jsonDocument.isObject()) { + qCWarning(asset_server) << "Failed to read mapping file, root value in" << mapFilePath << "is not an object"; + return false; } - qInfo() << "Loaded" << _fileMappings.count() << "mappings from map file at" << mapFilePath; + //_fileMappings = jsonDocument.object().toVariantHash(); + auto root = jsonDocument.object(); + for (auto it = root.begin(); it != root.end(); ++it) { + auto key = it.key(); + auto value = it.value(); + + if (!value.isString()) { + qCWarning(asset_server) << "Skipping" << key << ":" << value << "because it is not a string"; + continue; + } + + if (!isValidFilePath(key)) { + qCWarning(asset_server) << "Will not keep mapping for" << key << "since it is not a valid path."; + continue; + } + + if (!isValidHash(value.toString())) { + qCWarning(asset_server) << "Will not keep mapping for" << key << "since it does not have a valid hash."; + continue; + } + + + qDebug() << "Added " << key << value.toString(); + _fileMappings[key] = value.toString(); + } + + qCInfo(asset_server) << "Loaded" << _fileMappings.size() << "mappings from map file at" << mapFilePath; return true; } } - qCritical() << "Failed to read mapping file at" << mapFilePath; + qCCritical(asset_server) << "Failed to read mapping file at" << mapFilePath; return false; } else { - qInfo() << "No existing mappings loaded from file since no file was found at" << mapFilePath; + qCInfo(asset_server) << "No existing mappings loaded from file since no file was found at" << mapFilePath; } return true; @@ -545,17 +876,22 @@ bool AssetServer::writeMappingsToFile() { QFile mapFile { mapFilePath }; if (mapFile.open(QIODevice::WriteOnly)) { - auto jsonObject = QJsonObject::fromVariantHash(_fileMappings); - QJsonDocument jsonDocument { jsonObject }; + QJsonObject root; + + for (auto it : _fileMappings) { + root[it.first] = it.second; + } + + QJsonDocument jsonDocument { root }; if (mapFile.write(jsonDocument.toJson()) != -1) { - qDebug() << "Wrote JSON mappings to file at" << mapFilePath; + qCDebug(asset_server) << "Wrote JSON mappings to file at" << mapFilePath; return true; } else { - qWarning() << "Failed to write JSON mappings to file at" << mapFilePath; + qCWarning(asset_server) << "Failed to write JSON mappings to file at" << mapFilePath; } } else { - qWarning() << "Failed to open map file at" << mapFilePath; + qCWarning(asset_server) << "Failed to open map file at" << mapFilePath; } return false; @@ -565,17 +901,18 @@ bool AssetServer::setMapping(AssetPath path, AssetHash hash) { path = path.trimmed(); if (!isValidFilePath(path)) { - qWarning() << "Cannot set a mapping for invalid path:" << path << "=>" << hash; + qCWarning(asset_server) << "Cannot set a mapping for invalid path:" << path << "=>" << hash; return false; } if (!isValidHash(hash)) { - qWarning() << "Cannot set a mapping for invalid hash" << path << "=>" << hash; + qCWarning(asset_server) << "Cannot set a mapping for invalid hash" << path << "=>" << hash; return false; } // remember what the old mapping was in case persistence fails - auto oldMapping = _fileMappings.value(path).toString(); + auto it = _fileMappings.find(path); + auto oldMapping = it != _fileMappings.end() ? it->second : ""; // update the in memory QHash _fileMappings[path] = hash; @@ -583,17 +920,18 @@ bool AssetServer::setMapping(AssetPath path, AssetHash hash) { // attempt to write to file if (writeMappingsToFile()) { // persistence succeeded, we are good to go - qDebug() << "Set mapping:" << path << "=>" << hash; + qCDebug(asset_server) << "Set mapping:" << path << "=>" << hash; + maybeBake(path, hash); return true; } else { // failed to persist this mapping to file - put back the old one in our in-memory representation if (oldMapping.isEmpty()) { - _fileMappings.remove(path); + _fileMappings.erase(_fileMappings.find(path)); } else { _fileMappings[path] = oldMapping; } - qWarning() << "Failed to persist mapping:" << path << "=>" << hash; + qCWarning(asset_server) << "Failed to persist mapping:" << path << "=>" << hash; return false; } @@ -603,16 +941,27 @@ bool pathIsFolder(const AssetPath& path) { return path.endsWith('/'); } -bool AssetServer::deleteMappings(AssetPathList& paths) { +void AssetServer::removeBakedPathsForDeletedAsset(AssetHash hash) { + // we deleted the file with this hash + + // check if we had baked content for that file that should also now be removed + // by calling deleteMappings for the hidden baked content folder for this hash + AssetPathList hiddenBakedFolder { HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" }; + + qCDebug(asset_server) << "Deleting baked content below" << hiddenBakedFolder << "since" << hash << "was deleted"; + + deleteMappings(hiddenBakedFolder); +} + +bool AssetServer::deleteMappings(const AssetPathList& paths) { // take a copy of the current mappings in case persistence of these deletes fails auto oldMappings = _fileMappings; QSet hashesToCheckForDeletion; // enumerate the paths to delete and remove them all - for (auto& path : paths) { - - path = path.trimmed(); + for (const auto& rawPath : paths) { + auto path = rawPath.trimmed(); // figure out if this path will delete a file or folder if (pathIsFolder(path)) { @@ -621,9 +970,9 @@ bool AssetServer::deleteMappings(AssetPathList& paths) { auto sizeBefore = _fileMappings.size(); while (it != _fileMappings.end()) { - if (it.key().startsWith(path)) { + if (it->first.startsWith(path)) { // add this hash to the list we need to check for asset removal from the server - hashesToCheckForDeletion << it.value().toString(); + hashesToCheckForDeletion << it->second; it = _fileMappings.erase(it); } else { @@ -633,20 +982,22 @@ bool AssetServer::deleteMappings(AssetPathList& paths) { auto sizeNow = _fileMappings.size(); if (sizeBefore != sizeNow) { - qDebug() << "Deleted" << sizeBefore - sizeNow << "mappings in folder: " << path; + qCDebug(asset_server) << "Deleted" << sizeBefore - sizeNow << "mappings in folder: " << path; } else { - qDebug() << "Did not find any mappings to delete in folder:" << path; + qCDebug(asset_server) << "Did not find any mappings to delete in folder:" << path; } } else { - auto oldMapping = _fileMappings.take(path); - if (!oldMapping.isNull()) { + auto it = _fileMappings.find(path); + if (it != _fileMappings.end()) { // add this hash to the list we need to check for asset removal from server - hashesToCheckForDeletion << oldMapping.toString(); + hashesToCheckForDeletion << it->second; - qDebug() << "Deleted a mapping:" << path << "=>" << oldMapping.toString(); + qCDebug(asset_server) << "Deleted a mapping:" << path << "=>" << it->second; + + _fileMappings.erase(it); } else { - qDebug() << "Unable to delete a mapping that was not found:" << path; + qCDebug(asset_server) << "Unable to delete a mapping that was not found:" << path; } } } @@ -655,12 +1006,9 @@ bool AssetServer::deleteMappings(AssetPathList& paths) { if (writeMappingsToFile()) { // persistence succeeded we are good to go - // grab the current mapped hashes - auto mappedHashes = _fileMappings.values(); - - // enumerate the mapped hashes and clear the list of hashes to check for anything that's present - for (auto& hashVariant : mappedHashes) { - auto it = hashesToCheckForDeletion.find(hashVariant.toString()); + // TODO iterate through hashesToCheckForDeletion instead + for (auto& pair : _fileMappings) { + auto it = hashesToCheckForDeletion.find(pair.second); if (it != hashesToCheckForDeletion.end()) { hashesToCheckForDeletion.erase(it); } @@ -672,15 +1020,17 @@ bool AssetServer::deleteMappings(AssetPathList& paths) { QFile removeableFile { _filesDirectory.absoluteFilePath(hash) }; if (removeableFile.remove()) { - qDebug() << "\tDeleted" << hash << "from asset files directory since it is now unmapped."; + qCDebug(asset_server) << "\tDeleted" << hash << "from asset files directory since it is now unmapped."; + + removeBakedPathsForDeletedAsset(hash); } else { - qDebug() << "\tAttempt to delete unmapped file" << hash << "failed"; + qCDebug(asset_server) << "\tAttempt to delete unmapped file" << hash << "failed"; } } return true; } else { - qWarning() << "Failed to persist deleted mappings, rolling back"; + qCWarning(asset_server) << "Failed to persist deleted mappings, rolling back"; // we didn't delete the previous mapping, put it back in our in-memory representation _fileMappings = oldMappings; @@ -694,7 +1044,7 @@ bool AssetServer::renameMapping(AssetPath oldPath, AssetPath newPath) { newPath = newPath.trimmed(); if (!isValidFilePath(oldPath) || !isValidFilePath(newPath)) { - qWarning() << "Cannot perform rename with invalid paths - both should have leading forward and no ending slashes:" + qCWarning(asset_server) << "Cannot perform rename with invalid paths - both should have leading forward and no ending slashes:" << oldPath << "=>" << newPath; return false; @@ -704,7 +1054,7 @@ bool AssetServer::renameMapping(AssetPath oldPath, AssetPath newPath) { if (pathIsFolder(oldPath)) { if (!pathIsFolder(newPath)) { // we were asked to rename a path to a folder to a path that isn't a folder, this is a fail - qWarning() << "Cannot rename mapping from folder path" << oldPath << "to file path" << newPath; + qCWarning(asset_server) << "Cannot rename mapping from folder path" << oldPath << "to file path" << newPath; return false; } @@ -716,13 +1066,14 @@ bool AssetServer::renameMapping(AssetPath oldPath, AssetPath newPath) { auto it = oldMappings.begin(); while (it != oldMappings.end()) { - if (it.key().startsWith(oldPath)) { - auto newKey = it.key(); + auto& oldKey = it->first; + if (oldKey.startsWith(oldPath)) { + auto newKey = oldKey; newKey.replace(0, oldPath.size(), newPath); // remove the old version from the in memory file mappings - _fileMappings.remove(it.key()); - _fileMappings.insert(newKey, it.value()); + _fileMappings.erase(_fileMappings.find(oldKey)); + _fileMappings[newKey] = it->second; } ++it; @@ -730,52 +1081,54 @@ bool AssetServer::renameMapping(AssetPath oldPath, AssetPath newPath) { if (writeMappingsToFile()) { // persisted the changed mappings, return success - qDebug() << "Renamed folder mapping:" << oldPath << "=>" << newPath; + qCDebug(asset_server) << "Renamed folder mapping:" << oldPath << "=>" << newPath; return true; } else { // couldn't persist the renamed paths, rollback and return failure _fileMappings = oldMappings; - qWarning() << "Failed to persist renamed folder mapping:" << oldPath << "=>" << newPath; + qCWarning(asset_server) << "Failed to persist renamed folder mapping:" << oldPath << "=>" << newPath; return false; } } else { if (pathIsFolder(newPath)) { // we were asked to rename a path to a file to a path that is a folder, this is a fail - qWarning() << "Cannot rename mapping from file path" << oldPath << "to folder path" << newPath; + qCWarning(asset_server) << "Cannot rename mapping from file path" << oldPath << "to folder path" << newPath; return false; } // take the old hash to remove the old mapping - auto oldSourceMapping = _fileMappings.take(oldPath).toString(); + auto it = _fileMappings.find(oldPath); + auto oldSourceMapping = it->second; + _fileMappings.erase(it); // in case we're overwriting, keep the current destination mapping for potential rollback - auto oldDestinationMapping = _fileMappings.value(newPath); + auto oldDestinationIt = _fileMappings.find(newPath); if (!oldSourceMapping.isEmpty()) { _fileMappings[newPath] = oldSourceMapping; if (writeMappingsToFile()) { // persisted the renamed mapping, return success - qDebug() << "Renamed mapping:" << oldPath << "=>" << newPath; + qCDebug(asset_server) << "Renamed mapping:" << oldPath << "=>" << newPath; return true; } else { // we couldn't persist the renamed mapping, rollback and return failure _fileMappings[oldPath] = oldSourceMapping; - if (!oldDestinationMapping.isNull()) { + if (oldDestinationIt != _fileMappings.end()) { // put back the overwritten mapping for the destination path - _fileMappings[newPath] = oldDestinationMapping.toString(); + _fileMappings[newPath] = oldDestinationIt->second; } else { // clear the new mapping - _fileMappings.remove(newPath); + _fileMappings.erase(_fileMappings.find(newPath)); } - qDebug() << "Failed to persist renamed mapping:" << oldPath << "=>" << newPath; + qCDebug(asset_server) << "Failed to persist renamed mapping:" << oldPath << "=>" << newPath; return false; } @@ -785,3 +1138,252 @@ 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"; + +QString getBakeMapping(const AssetHash& hash, const QString& relativeFilePath) { + return HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + relativeFilePath; +} + +void AssetServer::handleFailedBake(QString originalAssetHash, QString assetPath, QString errors) { + qDebug() << "Failed: " << originalAssetHash << assetPath << errors; + + bool loaded; + AssetMeta meta; + + std::tie(loaded, meta) = readMetaFile(originalAssetHash); + + meta.failedLastBake = true; + meta.lastBakeErrors = errors; + + writeMetaFile(originalAssetHash, meta); + + _pendingBakes.remove(originalAssetHash); +} + +void AssetServer::handleCompletedBake(QString originalAssetHash, QString originalAssetPath, + QString bakedTempOutputDir, QVector bakedFilePaths) { + bool errorCompletingBake { false }; + QString errorReason; + + qDebug() << "Completing bake for " << originalAssetHash; + + for (auto& filePath : bakedFilePaths) { + // figure out the hash for the contents of this file + QFile file(filePath); + + qDebug() << "File path: " << filePath; + + AssetHash bakedFileHash; + + if (file.open(QIODevice::ReadOnly)) { + QCryptographicHash hasher(QCryptographicHash::Sha256); + + if (hasher.addData(&file)) { + bakedFileHash = hasher.result().toHex(); + } else { + // stop handling this bake, couldn't hash the contents of the file + errorCompletingBake = true; + errorReason = "Failed to finalize bake"; + break; + } + + // first check that we don't already have this bake file in our list + auto bakeFileDestination = _filesDirectory.absoluteFilePath(bakedFileHash); + if (!QFile::exists(bakeFileDestination)) { + // copy each to our files folder (with the hash as their filename) + if (!file.copy(_filesDirectory.absoluteFilePath(bakedFileHash))) { + // stop handling this bake, couldn't copy the bake file into our files directory + errorCompletingBake = true; + errorReason = "Failed to copy baked assets to asset server"; + break; + } + } + + // 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 (!originalAssetPath.endsWith(".fbx", Qt::CaseInsensitive)) { + relativeFilePath = BAKED_ASSET_SIMPLE_TEXTURE_NAME; + + } + + QString bakeMapping = getBakeMapping(originalAssetHash, relativeFilePath); + + // add a mapping (under the hidden baked folder) for this file resulting from the bake + if (setMapping(bakeMapping, bakedFileHash)) { + qDebug() << "Added" << bakeMapping << "for bake file" << bakedFileHash << "from bake of" << originalAssetHash; + } else { + qDebug() << "Failed to set mapping"; + // stop handling this bake, couldn't add a mapping for this bake file + errorCompletingBake = true; + errorReason = "Failed to finalize bake"; + break; + } + } else { + qDebug() << "Failed to open baked file: " << filePath; + // stop handling this bake, we couldn't open one of the files for reading + errorCompletingBake = true; + errorReason = "Failed to finalize bake"; + break; + } + } + + for (auto& filePath : bakedFilePaths) { + QFile file(filePath); + if (!file.remove()) { + qWarning() << "Failed to remove temporary file:" << filePath; + } + } + if (!QDir(bakedTempOutputDir).rmdir(".")) { + qWarning() << "Failed to remove temporary directory:" << bakedTempOutputDir; + } + + if (!errorCompletingBake) { + // create the meta file to store which version of the baking process we just completed + writeMetaFile(originalAssetHash); + } else { + qWarning() << "Could not complete bake for" << originalAssetHash; + AssetMeta meta; + meta.failedLastBake = true; + meta.lastBakeErrors = errorReason; + writeMetaFile(originalAssetHash, meta); + } + + _pendingBakes.remove(originalAssetHash); +} + +void AssetServer::handleAbortedBake(QString originalAssetHash, QString assetPath) { + // for an aborted bake we don't do anything but remove the BakeAssetTask from our pending bakes + _pendingBakes.remove(originalAssetHash); +} + +static const QString BAKE_VERSION_KEY = "bake_version"; +static const QString FAILED_LAST_BAKE_KEY = "failed_last_bake"; +static const QString LAST_BAKE_ERRORS_KEY = "last_bake_errors"; + +std::pair AssetServer::readMetaFile(AssetHash hash) { + auto metaFilePath = HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + "meta.json"; + + auto it = _fileMappings.find(metaFilePath); + if (it == _fileMappings.end()) { + return { false, {} }; + } + + auto metaFileHash = it->second; + + QFile metaFile(_filesDirectory.absoluteFilePath(metaFileHash)); + + if (metaFile.open(QIODevice::ReadOnly)) { + auto data = metaFile.readAll(); + metaFile.close(); + + QJsonParseError error; + auto doc = QJsonDocument::fromJson(data, &error); + + if (error.error == QJsonParseError::NoError && doc.isObject()) { + auto root = doc.object(); + + auto bakeVersion = root[BAKE_VERSION_KEY].toInt(-1); + auto failedLastBake = root[FAILED_LAST_BAKE_KEY]; + auto lastBakeErrors = root[LAST_BAKE_ERRORS_KEY]; + + if (bakeVersion != -1 + && failedLastBake.isBool() + && lastBakeErrors.isString()) { + + AssetMeta meta; + meta.bakeVersion = bakeVersion; + meta.failedLastBake = failedLastBake.toBool(); + meta.lastBakeErrors = lastBakeErrors.toString(); + + return { true, meta }; + } else { + qCWarning(asset_server) << "Metafile for" << hash << "has either missing or malformed data."; + } + } + } + + return { false, {} }; +} + +bool AssetServer::writeMetaFile(AssetHash originalAssetHash, const AssetMeta& meta) { + // construct the JSON that will be in the meta file + QJsonObject metaFileObject; + + metaFileObject[BAKE_VERSION_KEY] = meta.bakeVersion; + metaFileObject[FAILED_LAST_BAKE_KEY] = meta.failedLastBake; + metaFileObject[LAST_BAKE_ERRORS_KEY] = meta.lastBakeErrors; + + QJsonDocument metaFileDoc; + metaFileDoc.setObject(metaFileObject); + + auto metaFileJSON = metaFileDoc.toJson(); + + // get a hash for the contents of the meta-file + AssetHash metaFileHash = QCryptographicHash::hash(metaFileJSON, QCryptographicHash::Sha256).toHex(); + + // create the meta file in our files folder, named by the hash of its contents + QFile metaFile(_filesDirectory.absoluteFilePath(metaFileHash)); + + if (metaFile.open(QIODevice::WriteOnly)) { + metaFile.write(metaFileJSON); + metaFile.close(); + + // add a mapping to the meta file so it doesn't get deleted because it is unmapped + auto metaFileMapping = HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + "meta.json"; + + return setMapping(metaFileMapping, metaFileHash); + } else { + return false; + } +} + +bool AssetServer::setBakingEnabled(const AssetPathList& paths, bool enabled) { + for (const auto& path : paths) { + auto it = _fileMappings.find(path); + if (it != _fileMappings.end()) { + auto hash = it->second; + + auto dotIndex = path.lastIndexOf("."); + if (dotIndex == -1) { + continue; + } + + auto extension = path.mid(dotIndex + 1); + + QString bakedFilename; + + if (BAKEABLE_MODEL_EXTENSIONS.contains(extension)) { + bakedFilename = BAKED_MODEL_SIMPLE_NAME; + } else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(extension.toLocal8Bit()) && hasMetaFile(hash)) { + bakedFilename = BAKED_TEXTURE_SIMPLE_NAME; + } else { + continue; + } + + auto bakedMapping = getBakeMapping(hash, bakedFilename); + + auto it = _fileMappings.find(bakedMapping); + bool currentlyDisabled = (it != _fileMappings.end() && it->second == hash); + + if (enabled && currentlyDisabled) { + QStringList bakedMappings{ bakedMapping }; + deleteMappings(bakedMappings); + maybeBake(path, hash); + qDebug() << "Enabled baking for" << path; + } else if (!enabled && !currentlyDisabled) { + removeBakedPathsForDeletedAsset(hash); + setMapping(bakedMapping, hash); + qDebug() << "Disabled baking for" << path; + } + } + } + return true; +} diff --git a/assignment-client/src/assets/AssetServer.h b/assignment-client/src/assets/AssetServer.h index 132fb51433..aeb40a416f 100644 --- a/assignment-client/src/assets/AssetServer.h +++ b/assignment-client/src/assets/AssetServer.h @@ -14,17 +14,39 @@ #include #include +#include #include #include "AssetUtils.h" #include "ReceivedMessage.h" + +namespace std { + template <> + struct hash { + size_t operator()(const QString& v) const { return qHash(v); } + }; +} + +struct AssetMeta { + AssetMeta() { + } + + int bakeVersion { 0 }; + bool failedLastBake { false }; + QString lastBakeErrors; +}; + +class BakeAssetTask; + class AssetServer : public ThreadedAssignment { Q_OBJECT public: AssetServer(ReceivedMessage& message); + void aboutToFinish() override; + public slots: void run() override; @@ -39,13 +61,14 @@ private slots: void sendStatsPacket() override; private: - using Mappings = QVariantHash; + using Mappings = std::unordered_map; void handleGetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket); void handleGetAllMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket); void handleSetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket); void handleDeleteMappingsOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket); void handleRenameMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket); + void handleSetBakingEnabledOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket); // Mapping file operations must be called from main assignment thread only bool loadMappingsFromFile(); @@ -55,19 +78,55 @@ private: bool setMapping(AssetPath path, AssetHash hash); /// Delete mapping `path`. Returns `true` if deletion of mappings succeeds, else `false`. - bool deleteMappings(AssetPathList& paths); + bool deleteMappings(const AssetPathList& paths); /// Rename mapping from `oldPath` to `newPath`. Returns true if successful bool renameMapping(AssetPath oldPath, AssetPath newPath); - // deletes any unmapped files from the local asset directory + bool setBakingEnabled(const AssetPathList& paths, bool enabled); + + /// Delete any unmapped files from the local asset directory void cleanupUnmappedFiles(); + QString getPathToAssetHash(const AssetHash& assetHash); + + std::pair getAssetStatus(const AssetPath& path, const AssetHash& hash); + + void bakeAssets(); + void maybeBake(const AssetPath& path, const AssetHash& hash); + void createEmptyMetaFile(const AssetHash& hash); + bool hasMetaFile(const AssetHash& hash); + bool needsToBeBaked(const AssetPath& path, const AssetHash& assetHash); + void bakeAsset(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath); + + /// Move baked content for asset to baked directory and update baked status + void handleCompletedBake(QString originalAssetHash, QString assetPath, QString bakedTempOutputDir, + QVector bakedFilePaths); + void handleFailedBake(QString originalAssetHash, QString assetPath, QString errors); + void handleAbortedBake(QString originalAssetHash, QString assetPath); + + /// Create meta file to describe baked content for original asset + std::pair readMetaFile(AssetHash hash); + bool writeMetaFile(AssetHash originalAssetHash, const AssetMeta& meta = AssetMeta()); + + /// Remove baked paths when the original asset is deleteds + void removeBakedPathsForDeletedAsset(AssetHash originalAssetHash); + Mappings _fileMappings; QDir _resourcesDirectory; QDir _filesDirectory; - QThreadPool _taskPool; + + /// Task pool for handling uploads and downloads of assets + QThreadPool _transferTaskPool; + + QHash> _pendingBakes; + QThreadPool _bakingTaskPool; + + bool _wasColorTextureCompressionEnabled { false }; + bool _wasGrayscaleTextureCompressionEnabled { false }; + bool _wasNormalTextureCompressionEnabled { false }; + bool _wasCubeTextureCompressionEnabled { false }; }; #endif diff --git a/assignment-client/src/assets/AssetServerLogging.cpp b/assignment-client/src/assets/AssetServerLogging.cpp new file mode 100644 index 0000000000..39a02107ea --- /dev/null +++ b/assignment-client/src/assets/AssetServerLogging.cpp @@ -0,0 +1,14 @@ +// +// AssetServerLogging.cpp +// assignment-client/src/assets +// +// Created by Clement Brisset on 8/9/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AssetServerLogging.h" + +Q_LOGGING_CATEGORY(asset_server, "hifi.asset-server") diff --git a/assignment-client/src/assets/AssetServerLogging.h b/assignment-client/src/assets/AssetServerLogging.h new file mode 100644 index 0000000000..986e01ecc5 --- /dev/null +++ b/assignment-client/src/assets/AssetServerLogging.h @@ -0,0 +1,19 @@ +// +// AssetServerLogging.h +// assignment-client/src/assets +// +// Created by Clement Brisset on 8/9/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AssetServerLogging_h +#define hifi_AssetServerLogging_h + +#include + +Q_DECLARE_LOGGING_CATEGORY(asset_server) + +#endif // hifi_AssetServerLogging_h diff --git a/assignment-client/src/assets/BakeAssetTask.cpp b/assignment-client/src/assets/BakeAssetTask.cpp new file mode 100644 index 0000000000..94a0739612 --- /dev/null +++ b/assignment-client/src/assets/BakeAssetTask.cpp @@ -0,0 +1,102 @@ +// +// BakeAssetTask.cpp +// assignment-client/src/assets +// +// Created by Stephen Birarda on 9/18/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "BakeAssetTask.h" + +#include + +#include +#include + +BakeAssetTask::BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) : + _assetHash(assetHash), + _assetPath(assetPath), + _filePath(filePath) +{ + +} + +void cleanupTempFiles(QString tempOutputDir, std::vector files) { + for (const auto& filename : files) { + QFile f { filename }; + if (!f.remove()) { + qDebug() << "Failed to remove:" << filename; + } + } + if (!tempOutputDir.isEmpty()) { + QDir dir { tempOutputDir }; + if (!dir.rmdir(".")) { + qDebug() << "Failed to remove temporary directory:" << tempOutputDir; + } + } +}; + +void BakeAssetTask::run() { + _isBaking.store(true); + + qRegisterMetaType >("QVector"); + TextureBakerThreadGetter fn = []() -> QThread* { return QThread::currentThread(); }; + + QString tempOutputDir; + + if (_assetPath.endsWith(".fbx")) { + tempOutputDir = PathUtils::generateTemporaryDir(); + _baker = std::unique_ptr { + new FBXBaker(QUrl("file:///" + _filePath), fn, tempOutputDir) + }; + } else { + tempOutputDir = PathUtils::generateTemporaryDir(); + _baker = std::unique_ptr { + new TextureBaker(QUrl("file:///" + _filePath), image::TextureUsage::CUBE_TEXTURE, + tempOutputDir) + }; + } + + QEventLoop loop; + connect(_baker.get(), &Baker::finished, &loop, &QEventLoop::quit); + connect(_baker.get(), &Baker::aborted, &loop, &QEventLoop::quit); + QMetaObject::invokeMethod(_baker.get(), "bake", Qt::QueuedConnection); + loop.exec(); + + if (_baker->wasAborted()) { + qDebug() << "Aborted baking: " << _assetHash << _assetPath; + + _wasAborted.store(true); + + cleanupTempFiles(tempOutputDir, _baker->getOutputFiles()); + + emit bakeAborted(_assetHash, _assetPath); + } else if (_baker->hasErrors()) { + qDebug() << "Failed to bake: " << _assetHash << _assetPath << _baker->getErrors(); + + auto errors = _baker->getErrors().join('\n'); // Join error list into a single string for convenience + + _didFinish.store(true); + + cleanupTempFiles(tempOutputDir, _baker->getOutputFiles()); + + emit bakeFailed(_assetHash, _assetPath, errors); + } else { + auto vectorOutputFiles = QVector::fromStdVector(_baker->getOutputFiles()); + + qDebug() << "Finished baking: " << _assetHash << _assetPath << vectorOutputFiles; + + _didFinish.store(true); + + emit bakeComplete(_assetHash, _assetPath, tempOutputDir, vectorOutputFiles); + } +} + +void BakeAssetTask::abort() { + if (_baker) { + _baker->abort(); + } +} diff --git a/assignment-client/src/assets/BakeAssetTask.h b/assignment-client/src/assets/BakeAssetTask.h new file mode 100644 index 0000000000..90458ac223 --- /dev/null +++ b/assignment-client/src/assets/BakeAssetTask.h @@ -0,0 +1,52 @@ +// +// BakeAssetTask.h +// assignment-client/src/assets +// +// Created by Stephen Birarda on 9/18/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_BakeAssetTask_h +#define hifi_BakeAssetTask_h + +#include + +#include +#include +#include + +#include +#include + +class BakeAssetTask : public QObject, public QRunnable { + Q_OBJECT +public: + BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath); + + bool isBaking() { return _isBaking.load(); } + + void run() override; + + void abort(); + bool wasAborted() const { return _wasAborted.load(); } + bool didFinish() const { return _didFinish.load(); } + +signals: + void bakeComplete(QString assetHash, QString assetPath, QString tempOutputDir, QVector outputFiles); + void bakeFailed(QString assetHash, QString assetPath, QString errors); + void bakeAborted(QString assetHash, QString assetPath); + +private: + std::atomic _isBaking { false }; + AssetHash _assetHash; + AssetPath _assetPath; + QString _filePath; + std::unique_ptr _baker; + std::atomic _wasAborted { false }; + std::atomic _didFinish { false }; +}; + +#endif // hifi_BakeAssetTask_h diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index ed63bbc298..a131e266d2 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -558,7 +558,7 @@ float computeAzimuth(const AvatarAudioStream& listeningNodeStream, const Positio // produce an oriented angle about the y-axis glm::vec3 direction = rotatedSourcePosition * (1.0f / fastSqrtf(rotatedSourcePositionLength2)); - float angle = fastAcosf(glm::clamp(-direction.z, -1.0f, 1.0f)); // UNIT_NEG_Z is "forward" + float angle = fastAcosf(glm::clamp(-direction.z, -1.0f, 1.0f)); // UNIT_NEG_Z is "forward" return (direction.x < 0.0f) ? -angle : angle; } else { diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 34feafbd4d..5d36a6d261 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -170,9 +170,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData); // Define the minimum bubble size - static const glm::vec3 minBubbleSize = glm::vec3(0.3f, 1.3f, 0.3f); + static const glm::vec3 minBubbleSize = avatar.getSensorToWorldScale() * glm::vec3(0.3f, 1.3f, 0.3f); // Define the scale of the box for the current node - glm::vec3 nodeBoxScale = (nodeData->getPosition() - nodeData->getGlobalBoundingBoxCorner()) * 2.0f; + glm::vec3 nodeBoxScale = (nodeData->getPosition() - nodeData->getGlobalBoundingBoxCorner()) * 2.0f * avatar.getSensorToWorldScale(); // Set up the bounding box for the current node AABox nodeBox(nodeData->getGlobalBoundingBoxCorner(), nodeBoxScale); // Clamp the size of the bounding box to a minimum scale @@ -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()); + glm::vec3 nodeBoxHalfScale = (avatar->getPosition() - avatar->getGlobalBoundingBoxCorner() * avatar->getSensorToWorldScale()); return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z)); }, [&](AvatarSharedPointer avatar)->bool { if (avatar == thisAvatar) { @@ -244,9 +244,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) // Check to see if the space bubble is enabled // Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored if (node->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { - + float sensorToWorldScale = avatarNodeData->getAvatarSharedPointer()->getSensorToWorldScale(); // Define the scale of the box for the current other node - glm::vec3 otherNodeBoxScale = (avatarNodeData->getPosition() - avatarNodeData->getGlobalBoundingBoxCorner()) * 2.0f; + glm::vec3 otherNodeBoxScale = (avatarNodeData->getPosition() - avatarNodeData->getGlobalBoundingBoxCorner()) * 2.0f * sensorToWorldScale; // Set up the bounding box for the current other node AABox otherNodeBox(avatarNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); // Clamp the size of the bounding box to a minimum scale @@ -334,8 +334,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) glm::vec3 otherPosition = otherAvatar->getClientGlobalPosition(); + // determine if avatar is in view, to determine how much data to include... - glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f; + glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f * otherAvatar->getSensorToWorldScale(); AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); bool isInView = nodeData->otherAvatarInView(otherNodeBox); diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index c7715d4014..5060891284 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -35,7 +35,7 @@ void ScriptableAvatar::startAnimation(const QString& url, float fps, float prior return; } _animation = DependencyManager::get()->getAnimation(url); - _animationDetails = AnimationDetails("", QUrl(url), fps, 0, loop, hold, false, firstFrame, lastFrame, true, firstFrame); + _animationDetails = AnimationDetails("", QUrl(url), fps, 0, loop, hold, false, firstFrame, lastFrame, true, firstFrame, false); _maskedJoints = maskedJoints; } diff --git a/assignment-client/src/entities/EntityPriorityQueue.cpp b/assignment-client/src/entities/EntityPriorityQueue.cpp new file mode 100644 index 0000000000..999a05f2e2 --- /dev/null +++ b/assignment-client/src/entities/EntityPriorityQueue.cpp @@ -0,0 +1,53 @@ +// +// EntityPriorityQueue.cpp +// assignment-client/src/entities +// +// Created by Andrew Meadows 2017.08.08 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "EntityPriorityQueue.h" + +const float PrioritizedEntity::DO_NOT_SEND = -1.0e-6f; +const float PrioritizedEntity::FORCE_REMOVE = -1.0e-5f; +const float PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY = 1.0f; + +void ConicalView::set(const ViewFrustum& viewFrustum) { + // The ConicalView has two parts: a central sphere (same as ViewFrustum) and a circular cone that bounds the frustum part. + // Why? Because approximate intersection tests are much faster to compute for a cone than for a frustum. + _position = viewFrustum.getPosition(); + _direction = viewFrustum.getDirection(); + + // We cache the sin and cos of the half angle of the cone that bounds the frustum. + // (the math here is left as an exercise for the reader) + float A = viewFrustum.getAspectRatio(); + float t = tanf(0.5f * viewFrustum.getFieldOfView()); + _cosAngle = 1.0f / sqrtf(1.0f + (A * A + 1.0f) * (t * t)); + _sinAngle = sqrtf(1.0f - _cosAngle * _cosAngle); + + _radius = viewFrustum.getCenterRadius(); +} + +float ConicalView::computePriority(const AACube& cube) const { + glm::vec3 p = cube.calcCenter() - _position; // position of bounding sphere in view-frame + float d = glm::length(p); // distance to center of bounding sphere + float r = 0.5f * cube.getScale(); // radius of bounding sphere + if (d < _radius + r) { + return r; + } + // We check the angle between the center of the cube and the _direction of the view. + // If it is less than the sum of the half-angle from center of cone to outer edge plus + // the half apparent angle of the bounding sphere then it is in view. + // + // The math here is left as an exercise for the reader with the following hints: + // (1) We actually check the dot product of the cube's local position rather than the angle and + // (2) we take advantage of this trig identity: cos(A+B) = cos(A)*cos(B) - sin(A)*sin(B) + if (glm::dot(p, _direction) > sqrtf(d * d - r * r) * _cosAngle - r * _sinAngle) { + const float AVOID_DIVIDE_BY_ZERO = 0.001f; + return r / (d + AVOID_DIVIDE_BY_ZERO); + } + return PrioritizedEntity::DO_NOT_SEND; +} diff --git a/assignment-client/src/entities/EntityPriorityQueue.h b/assignment-client/src/entities/EntityPriorityQueue.h new file mode 100644 index 0000000000..e308d9b549 --- /dev/null +++ b/assignment-client/src/entities/EntityPriorityQueue.h @@ -0,0 +1,66 @@ +// +// EntityPriorityQueue.h +// assignment-client/src/entities +// +// Created by Andrew Meadows 2017.08.08 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_EntityPriorityQueue_h +#define hifi_EntityPriorityQueue_h + +#include + +#include +#include + +const float SQRT_TWO_OVER_TWO = 0.7071067811865f; +const float DEFAULT_VIEW_RADIUS = 10.0f; + +// ConicalView is an approximation of a ViewFrustum for fast calculation of sort priority. +class ConicalView { +public: + ConicalView() {} + ConicalView(const ViewFrustum& viewFrustum) { set(viewFrustum); } + void set(const ViewFrustum& viewFrustum); + float computePriority(const AACube& cube) const; +private: + glm::vec3 _position { 0.0f, 0.0f, 0.0f }; + glm::vec3 _direction { 0.0f, 0.0f, 1.0f }; + float _sinAngle { SQRT_TWO_OVER_TWO }; + float _cosAngle { SQRT_TWO_OVER_TWO }; + float _radius { DEFAULT_VIEW_RADIUS }; +}; + +// PrioritizedEntity is a placeholder in a sorted queue. +class PrioritizedEntity { +public: + static const float DO_NOT_SEND; + static const float FORCE_REMOVE; + static const float WHEN_IN_DOUBT_PRIORITY; + + PrioritizedEntity(EntityItemPointer entity, float priority, bool forceRemove = false) : _weakEntity(entity), _rawEntityPointer(entity.get()), _priority(priority), _forceRemove(forceRemove) {} + EntityItemPointer getEntity() const { return _weakEntity.lock(); } + EntityItem* getRawEntityPointer() const { return _rawEntityPointer; } + float getPriority() const { return _priority; } + bool shouldForceRemove() const { return _forceRemove; } + + class Compare { + public: + bool operator() (const PrioritizedEntity& A, const PrioritizedEntity& B) { return A._priority < B._priority; } + }; + friend class Compare; + +private: + EntityItemWeakPointer _weakEntity; + EntityItem* _rawEntityPointer; + float _priority; + bool _forceRemove; +}; + +using EntityPriorityQueue = std::priority_queue< PrioritizedEntity, std::vector, PrioritizedEntity::Compare >; + +#endif // hifi_EntityPriorityQueue_h diff --git a/assignment-client/src/entities/EntityTreeSendThread.cpp b/assignment-client/src/entities/EntityTreeSendThread.cpp index 7febdc67e1..03014bae6a 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.cpp +++ b/assignment-client/src/entities/EntityTreeSendThread.cpp @@ -13,9 +13,18 @@ #include #include +#include #include "EntityServer.h" + +EntityTreeSendThread::EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : + OctreeSendThread(myServer, node) +{ + connect(std::static_pointer_cast(myServer->getOctree()).get(), &EntityTree::editingEntityPointer, this, &EntityTreeSendThread::editingEntityPointer, Qt::QueuedConnection); + connect(std::static_pointer_cast(myServer->getOctree()).get(), &EntityTree::deletingEntityPointer, this, &EntityTreeSendThread::deletingEntityPointer, Qt::QueuedConnection); +} + void EntityTreeSendThread::preDistributionProcessing() { auto node = _node.toStrongRef(); auto nodeData = static_cast(node->getLinkedData()); @@ -80,6 +89,72 @@ void EntityTreeSendThread::preDistributionProcessing() { } } +void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, + bool viewFrustumChanged, bool isFullScene) { + if (viewFrustumChanged || _traversal.finished()) { + ViewFrustum viewFrustum; + nodeData->copyCurrentViewFrustum(viewFrustum); + EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); + int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); + startNewTraversal(viewFrustum, root, lodLevelOffset, nodeData->getUsesFrustum()); + + // When the viewFrustum changed the sort order may be incorrect, so we re-sort + // and also use the opportunity to cull anything no longer in view + if (viewFrustumChanged && !_sendQueue.empty()) { + EntityPriorityQueue prevSendQueue; + _sendQueue.swap(prevSendQueue); + _entitiesInQueue.clear(); + // Re-add elements from previous traversal if they still need to be sent + float lodScaleFactor = _traversal.getCurrentLODScaleFactor(); + glm::vec3 viewPosition = _traversal.getCurrentView().getPosition(); + while (!prevSendQueue.empty()) { + EntityItemPointer entity = prevSendQueue.top().getEntity(); + bool forceRemove = prevSendQueue.top().shouldForceRemove(); + prevSendQueue.pop(); + if (entity) { + if (!forceRemove) { + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { + float priority = _conicalView.computePriority(cube); + if (priority != PrioritizedEntity::DO_NOT_SEND) { + float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; + float angularDiameter = cube.getScale() / distance; + if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) { + _sendQueue.push(PrioritizedEntity(entity, priority)); + _entitiesInQueue.insert(entity.get()); + } + } + } + } else { + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); + } + } else { + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::FORCE_REMOVE, true)); + _entitiesInQueue.insert(entity.get()); + } + } + } + } + } + + if (!_traversal.finished()) { + quint64 startTime = usecTimestampNow(); + + #ifdef DEBUG + const uint64_t TIME_BUDGET = 400; // usec + #else + const uint64_t TIME_BUDGET = 200; // usec + #endif + _traversal.traverse(TIME_BUDGET); + OctreeServer::trackTreeTraverseTime((float)(usecTimestampNow() - startTime)); + } + + OctreeSendThread::traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); +} + bool EntityTreeSendThread::addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID, EntityItem& entityItem, EntityNodeData& nodeData) { // check if this entity has a parent that is also an entity @@ -129,4 +204,288 @@ bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& fil return hasNewChild || hasNewDescendants; } +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: + // + // (1) FirstTime = at login --> find everything in view + // (2) Repeat = view hasn't changed --> find what has changed since last complete traversal + // (3) Differential = view has changed --> find what has changed or in new view but not old + // + // The "scanCallback" we provide to the traversal depends on the type: + // + // The _conicalView is updated here as a cached view approximation used by the lambdas for efficient + // computation of entity sorting priorities. + // + _conicalView.set(_traversal.getCurrentView()); + switch (type) { + case DiffTraversal::First: + // When we get to a First traversal, clear the _knownState + _knownState.clear(); + if (usesViewFrustum) { + float lodScaleFactor = _traversal.getCurrentLODScaleFactor(); + glm::vec3 viewPosition = _traversal.getCurrentView().getPosition(); + _traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) { + next.element->forEachEntity([=](EntityItemPointer entity) { + // Bail early if we've already checked this entity this frame + if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { + return; + } + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { + // Check the size of the entity, it's possible that a "too small to see" entity is included in a + // larger octree cell because of its position (for example if it crosses the boundary of a cell it + // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen + // before we consider including it. + float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; + float angularDiameter = cube.getScale() / distance; + if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) { + float priority = _conicalView.computePriority(cube); + _sendQueue.push(PrioritizedEntity(entity, priority)); + _entitiesInQueue.insert(entity.get()); + } + } + } else { + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); + } + }); + }); + } else { + _traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) { + next.element->forEachEntity([this](EntityItemPointer entity) { + // Bail early if we've already checked this entity this frame + if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { + return; + } + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); + }); + }); + } + break; + case DiffTraversal::Repeat: + if (usesViewFrustum) { + float lodScaleFactor = _traversal.getCurrentLODScaleFactor(); + glm::vec3 viewPosition = _traversal.getCurrentView().getPosition(); + _traversal.setScanCallback([=](DiffTraversal::VisibleElement& next) { + uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal(); + if (next.element->getLastChangedContent() > startOfCompletedTraversal) { + next.element->forEachEntity([=](EntityItemPointer entity) { + // Bail early if we've already checked this entity this frame + if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { + return; + } + auto knownTimestamp = _knownState.find(entity.get()); + if (knownTimestamp == _knownState.end()) { + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + if (next.intersection == ViewFrustum::INSIDE || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { + // See the DiffTraversal::First case for an explanation of the "entity is too small" check + float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; + float angularDiameter = cube.getScale() / distance; + if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) { + float priority = _conicalView.computePriority(cube); + _sendQueue.push(PrioritizedEntity(entity, priority)); + _entitiesInQueue.insert(entity.get()); + } + } + } else { + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); + } + } else if (entity->getLastEdited() > knownTimestamp->second) { + // it is known and it changed --> put it on the queue with any priority + // TODO: sort these correctly + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); + } + }); + } + }); + } else { + _traversal.setScanCallback([this](DiffTraversal::VisibleElement& next) { + uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal(); + if (next.element->getLastChangedContent() > startOfCompletedTraversal) { + next.element->forEachEntity([this](EntityItemPointer entity) { + // Bail early if we've already checked this entity this frame + if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { + return; + } + auto knownTimestamp = _knownState.find(entity.get()); + if (knownTimestamp == _knownState.end() || entity->getLastEdited() > knownTimestamp->second) { + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); + } + }); + } + }); + } + break; + case DiffTraversal::Differential: + assert(usesViewFrustum); + float lodScaleFactor = _traversal.getCurrentLODScaleFactor(); + glm::vec3 viewPosition = _traversal.getCurrentView().getPosition(); + float completedLODScaleFactor = _traversal.getCompletedLODScaleFactor(); + glm::vec3 completedViewPosition = _traversal.getCompletedView().getPosition(); + _traversal.setScanCallback([=] (DiffTraversal::VisibleElement& next) { + next.element->forEachEntity([=](EntityItemPointer entity) { + // Bail early if we've already checked this entity this frame + if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) { + return; + } + auto knownTimestamp = _knownState.find(entity.get()); + if (knownTimestamp == _knownState.end()) { + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { + // See the DiffTraversal::First case for an explanation of the "entity is too small" check + float distance = glm::distance(cube.calcCenter(), viewPosition) + MIN_VISIBLE_DISTANCE; + float angularDiameter = cube.getScale() / distance; + if (angularDiameter > MIN_ENTITY_ANGULAR_DIAMETER * lodScaleFactor) { + if (!_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) { + float priority = _conicalView.computePriority(cube); + _sendQueue.push(PrioritizedEntity(entity, priority)); + _entitiesInQueue.insert(entity.get()); + } else { + // If this entity was skipped last time because it was too small, we still need to send it + distance = glm::distance(cube.calcCenter(), completedViewPosition) + MIN_VISIBLE_DISTANCE; + angularDiameter = cube.getScale() / distance; + if (angularDiameter <= MIN_ENTITY_ANGULAR_DIAMETER * completedLODScaleFactor) { + // this object was skipped in last completed traversal + float priority = _conicalView.computePriority(cube); + _sendQueue.push(PrioritizedEntity(entity, priority)); + _entitiesInQueue.insert(entity.get()); + } + } + } + } + } else { + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); + } + } else if (entity->getLastEdited() > knownTimestamp->second) { + // it is known and it changed --> put it on the queue with any priority + // TODO: sort these correctly + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY)); + _entitiesInQueue.insert(entity.get()); + } + }); + }); + break; + } +} + +bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) { + if (_sendQueue.empty()) { + OctreeServer::trackEncodeTime(OctreeServer::SKIP_TIME); + return false; + } + quint64 encodeStart = usecTimestampNow(); + if (!_packetData.hasContent()) { + // This is the beginning of a new packet. + // We pack minimal data for this to be accepted as an OctreeElement payload for the root element. + // The Octree header bytes look like this: + // + // 0x00 octalcode for root + // 0x00 colors (1 bit where recipient should call: child->readElementDataFromBuffer()) + // 0xXX childrenInTreeMask (when params.includeExistsBits is true: 1 bit where child is existant) + // 0x00 childrenInBufferMask (1 bit where recipient should call: child->readElementData() recursively) + const uint8_t zeroByte = 0; + _packetData.appendValue(zeroByte); // octalcode + _packetData.appendValue(zeroByte); // colors + if (params.includeExistsBits) { + uint8_t childrenExistBits = 0; + EntityTreeElementPointer root = std::dynamic_pointer_cast(_myServer->getOctree()->getRoot()); + for (int32_t i = 0; i < NUMBER_OF_CHILDREN; ++i) { + if (root->getChildAtIndex(i)) { + childrenExistBits += (1 << i); + } + } + _packetData.appendValue(childrenExistBits); // childrenInTreeMask + } + _packetData.appendValue(zeroByte); // childrenInBufferMask + + // Pack zero for numEntities. + // But before we do: grab current byteOffset so we can come back later + // and update this with the real number. + _numEntities = 0; + _numEntitiesOffset = _packetData.getUncompressedByteOffset(); + _packetData.appendValue(_numEntities); + } + + LevelDetails entitiesLevel = _packetData.startLevel(); + uint64_t sendTime = usecTimestampNow(); + auto nodeData = static_cast(params.nodeData); + nodeData->stats.encodeStarted(); + 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)) { + OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData); + + if (appendEntityState != OctreeElement::COMPLETED) { + if (appendEntityState == OctreeElement::PARTIAL) { + ++_numEntities; + } + params.stopReason = EncodeBitstreamParams::DIDNT_FIT; + break; + } + ++_numEntities; + } + if (queuedItem.shouldForceRemove()) { + _knownState.erase(entity.get()); + } else { + _knownState[entity.get()] = sendTime; + } + } + _sendQueue.pop(); + _entitiesInQueue.erase(entity.get()); + } + nodeData->stats.encodeStopped(); + if (_sendQueue.empty()) { + assert(_entitiesInQueue.empty()); + params.stopReason = EncodeBitstreamParams::FINISHED; + _extraEncodeData->entities.clear(); + } + + if (_numEntities == 0) { + _packetData.discardLevel(entitiesLevel); + OctreeServer::trackEncodeTime((float)(usecTimestampNow() - encodeStart)); + return false; + } + _packetData.endLevel(entitiesLevel); + _packetData.updatePriorBytes(_numEntitiesOffset, (const unsigned char*)&_numEntities, sizeof(_numEntities)); + OctreeServer::trackEncodeTime((float)(usecTimestampNow() - encodeStart)); + return true; +} + +void EntityTreeSendThread::editingEntityPointer(const EntityItemPointer& entity) { + if (entity) { + if (_entitiesInQueue.find(entity.get()) == _entitiesInQueue.end() && _knownState.find(entity.get()) != _knownState.end()) { + bool success = false; + AACube cube = entity->getQueryAACube(success); + if (success) { + // We can force a removal from _knownState if the current view is used and entity is out of view + if (_traversal.doesCurrentUseViewFrustum() && !_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) { + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::FORCE_REMOVE, true)); + _entitiesInQueue.insert(entity.get()); + } + } else { + _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY, true)); + _entitiesInQueue.insert(entity.get()); + } + } + } +} + +void EntityTreeSendThread::deletingEntityPointer(EntityItem* entity) { + _knownState.erase(entity); +} diff --git a/assignment-client/src/entities/EntityTreeSendThread.h b/assignment-client/src/entities/EntityTreeSendThread.h index bfb4c743f1..49901491ff 100644 --- a/assignment-client/src/entities/EntityTreeSendThread.h +++ b/assignment-client/src/entities/EntityTreeSendThread.h @@ -12,24 +12,55 @@ #ifndef hifi_EntityTreeSendThread_h #define hifi_EntityTreeSendThread_h +#include + #include "../octree/OctreeSendThread.h" +#include + +#include "EntityPriorityQueue.h" + class EntityNodeData; class EntityItem; class EntityTreeSendThread : public OctreeSendThread { + Q_OBJECT public: - EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : OctreeSendThread(myServer, node) {}; + EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node); protected: - virtual void preDistributionProcessing() override; + void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, + bool viewFrustumChanged, bool isFullScene) override; 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); + bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) override; + + void preDistributionProcessing() override; + bool hasSomethingToSend(OctreeQueryNode* nodeData) override { return !_sendQueue.empty(); } + bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) override { return viewFrustumChanged || _traversal.finished(); } + void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) override {}; + bool shouldTraverseAndSend(OctreeQueryNode* nodeData) override { return true; } + + DiffTraversal _traversal; + EntityPriorityQueue _sendQueue; + std::unordered_set _entitiesInQueue; + std::unordered_map _knownState; + ConicalView _conicalView; // cached optimized view for fast priority calculations + + // packet construction stuff + EntityTreeElementExtraEncodeDataPointer _extraEncodeData { new EntityTreeElementExtraEncodeData() }; + int32_t _numEntitiesOffset { 0 }; + uint16_t _numEntities { 0 }; + +private slots: + void editingEntityPointer(const EntityItemPointer& entity); + void deletingEntityPointer(EntityItem* entity); }; #endif // hifi_EntityTreeSendThread_h diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 868b377ced..89e3d403fc 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -17,7 +17,6 @@ #include #include -#include "OctreeQueryNode.h" #include "OctreeSendThread.h" #include "OctreeServer.h" #include "OctreeServerConsts.h" @@ -27,8 +26,8 @@ quint64 startSceneSleepTime = 0; quint64 endSceneSleepTime = 0; OctreeSendThread::OctreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) : - _myServer(myServer), _node(node), + _myServer(myServer), _nodeUuid(node->getUUID()) { QString safeServerName("Octree"); @@ -48,7 +47,7 @@ OctreeSendThread::OctreeSendThread(OctreeServer* myServer, const SharedNodePoint OctreeSendThread::~OctreeSendThread() { setIsShuttingDown(); - + QString safeServerName("Octree"); if (_myServer) { safeServerName = _myServer->getMyServerName(); @@ -301,9 +300,25 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode* return numPackets; } +void OctreeSendThread::preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene) { + // If we're starting a full scene, then definitely we want to empty the elementBag + if (isFullScene) { + nodeData->elementBag.deleteAll(); + } + + // This is the start of "resending" the scene. + bool dontRestartSceneOnMove = false; // this is experimental + if (dontRestartSceneOnMove) { + if (nodeData->elementBag.isEmpty()) { + nodeData->elementBag.insert(_myServer->getOctree()->getRoot()); + } + } else { + nodeData->elementBag.insert(_myServer->getOctree()->getRoot()); + } +} + /// Version of octree element distributor that sends the deepest LOD level at once int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged) { - OctreeServer::didPacketDistributor(this); // if shutting down, exit early @@ -311,7 +326,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* return 0; } - if (nodeData->elementBag.isEmpty()) { + if (shouldStartNewTraversal(nodeData, viewFrustumChanged)) { // if we're about to do a fresh pass, // give our pre-distribution processing a chance to do what it needs preDistributionProcessing(); @@ -345,7 +360,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* // If the current view frustum has changed OR we have nothing to send, then search against // the current view frustum for things to send. - if (viewFrustumChanged || nodeData->elementBag.isEmpty()) { + if (shouldStartNewTraversal(nodeData, viewFrustumChanged)) { // if our view has changed, we need to reset these things... if (viewFrustumChanged) { @@ -367,11 +382,6 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* _packetsSentThisInterval += handlePacketSend(node, nodeData, isFullScene); - // If we're starting a full scene, then definitely we want to empty the elementBag - if (isFullScene) { - nodeData->elementBag.deleteAll(); - } - // TODO: add these to stats page //::startSceneSleepTime = _usleepTime; @@ -380,19 +390,11 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, _myServer->getOctree()->getRoot(), _myServer->getJurisdiction()); - // This is the start of "resending" the scene. - bool dontRestartSceneOnMove = false; // this is experimental - if (dontRestartSceneOnMove) { - if (nodeData->elementBag.isEmpty()) { - nodeData->elementBag.insert(_myServer->getOctree()->getRoot()); - } - } else { - nodeData->elementBag.insert(_myServer->getOctree()->getRoot()); - } + preStartNewScene(nodeData, isFullScene); } // If we have something in our elementBag, then turn them into packets and send them out... - if (!nodeData->elementBag.isEmpty()) { + if (shouldTraverseAndSend(nodeData)) { quint64 start = usecTimestampNow(); traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); @@ -441,7 +443,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* // if after sending packets we've emptied our bag, then we want to remember that we've sent all // the octree elements from the current view frustum - if (nodeData->elementBag.isEmpty()) { + if (!hasSomethingToSend(nodeData)) { nodeData->updateLastKnownViewFrustum(); nodeData->setViewSent(true); @@ -458,84 +460,79 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* return _truePacketsSent; } +bool OctreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters) { + bool somethingToSend = false; + OctreeQueryNode* nodeData = static_cast(params.nodeData); + if (!nodeData->elementBag.isEmpty()) { + quint64 encodeStart = usecTimestampNow(); + quint64 lockWaitStart = encodeStart; + + _myServer->getOctree()->withReadLock([&]{ + OctreeServer::trackTreeWaitTime((float)(usecTimestampNow() - lockWaitStart)); + + OctreeElementPointer subTree = nodeData->elementBag.extract(); + if (subTree) { + // NOTE: this is where the tree "contents" are actually packed + nodeData->stats.encodeStarted(); + _myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->elementBag, params); + nodeData->stats.encodeStopped(); + + somethingToSend = true; + } + }); + + OctreeServer::trackEncodeTime((float)(usecTimestampNow() - encodeStart)); + } else { + OctreeServer::trackTreeWaitTime(OctreeServer::SKIP_TIME); + OctreeServer::trackEncodeTime(OctreeServer::SKIP_TIME); + } + return somethingToSend; +} + void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene) { // calculate max number of packets that can be sent during this interval int clientMaxPacketsPerInterval = std::max(1, (nodeData->getMaxQueryPacketsPerSecond() / INTERVALS_PER_SECOND)); int maxPacketsPerInterval = std::min(clientMaxPacketsPerInterval, _myServer->getPacketsPerClientPerInterval()); int extraPackingAttempts = 0; - bool completedScene = false; + + // init params once outside the while loop + int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust(); + int boundaryLevelAdjust = boundaryLevelAdjustClient + + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); + float octreeSizeScale = nodeData->getOctreeSizeScale(); + EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP, + viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale, + isFullScene, _myServer->getJurisdiction(), nodeData); + // Our trackSend() function is implemented by the server subclass, and will be called back as new entities/data elements are sent + params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) { + _myServer->trackSend(dataID, dataEdited, _nodeUuid); + }; + nodeData->copyCurrentViewFrustum(params.viewFrustum); + if (viewFrustumChanged) { + nodeData->copyLastKnownViewFrustum(params.lastViewFrustum); + } bool somethingToSend = true; // assume we have something + bool hadSomething = hasSomethingToSend(nodeData); while (somethingToSend && _packetsSentThisInterval < maxPacketsPerInterval && !nodeData->isShuttingDown()) { - float lockWaitElapsedUsec = OctreeServer::SKIP_TIME; - float encodeElapsedUsec = OctreeServer::SKIP_TIME; float compressAndWriteElapsedUsec = OctreeServer::SKIP_TIME; float packetSendingElapsedUsec = OctreeServer::SKIP_TIME; quint64 startInside = usecTimestampNow(); bool lastNodeDidntFit = false; // assume each node fits - if (!nodeData->elementBag.isEmpty()) { + params.stopReason = EncodeBitstreamParams::UNKNOWN; // reset params.stopReason before traversal - quint64 lockWaitStart = usecTimestampNow(); - _myServer->getOctree()->withReadLock([&]{ - quint64 lockWaitEnd = usecTimestampNow(); - lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart); - quint64 encodeStart = usecTimestampNow(); + somethingToSend = traverseTreeAndBuildNextPacketPayload(params, nodeData->getJSONParameters()); - OctreeElementPointer subTree = nodeData->elementBag.extract(); - if (!subTree) { - return; - } - - float octreeSizeScale = nodeData->getOctreeSizeScale(); - int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust(); - - int boundaryLevelAdjust = boundaryLevelAdjustClient + - (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST); - - EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP, - viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale, - isFullScene, _myServer->getJurisdiction(), nodeData); - nodeData->copyCurrentViewFrustum(params.viewFrustum); - if (viewFrustumChanged) { - nodeData->copyLastKnownViewFrustum(params.lastViewFrustum); - } - - // Our trackSend() function is implemented by the server subclass, and will be called back - // during the encodeTreeBitstream() as new entities/data elements are sent - params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) { - _myServer->trackSend(dataID, dataEdited, _nodeUuid); - }; - - // TODO: should this include the lock time or not? This stat is sent down to the client, - // it seems like it may be a good idea to include the lock time as part of the encode time - // are reported to client. Since you can encode without the lock - nodeData->stats.encodeStarted(); - - // NOTE: this is where the tree "contents" are actaully packed - _myServer->getOctree()->encodeTreeBitstream(subTree, &_packetData, nodeData->elementBag, params); - - quint64 encodeEnd = usecTimestampNow(); - encodeElapsedUsec = (float)(encodeEnd - encodeStart); - - // If after calling encodeTreeBitstream() there are no nodes left to send, then we know we've - // sent the entire scene. We want to know this below so we'll actually write this content into - // the packet and send it - completedScene = nodeData->elementBag.isEmpty(); - - if (params.stopReason == EncodeBitstreamParams::DIDNT_FIT) { - lastNodeDidntFit = true; - extraPackingAttempts++; - } - - nodeData->stats.encodeStopped(); - }); - } else { - somethingToSend = false; // this will cause us to drop out of the loop... + if (params.stopReason == EncodeBitstreamParams::DIDNT_FIT) { + lastNodeDidntFit = true; + extraPackingAttempts++; } + // If the bag had contents but is now empty then we know we've sent the entire scene. + bool completedScene = hadSomething && nodeData->elementBag.isEmpty(); if (completedScene || lastNodeDidntFit) { // we probably want to flush what has accumulated in nodeData but: // do we have more data to send? and is there room? @@ -562,8 +559,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre if (sendNow) { quint64 packetSendingStart = usecTimestampNow(); _packetsSentThisInterval += handlePacketSend(node, nodeData); - quint64 packetSendingEnd = usecTimestampNow(); - packetSendingElapsedUsec = (float)(packetSendingEnd - packetSendingStart); + packetSendingElapsedUsec = (float)(usecTimestampNow() - packetSendingStart); targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE); extraPackingAttempts = 0; @@ -576,14 +572,9 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre } _packetData.changeSettings(true, targetSize); // will do reset - NOTE: Always compressed } - OctreeServer::trackTreeWaitTime(lockWaitElapsedUsec); - OctreeServer::trackEncodeTime(encodeElapsedUsec); OctreeServer::trackCompressAndWriteTime(compressAndWriteElapsedUsec); OctreeServer::trackPacketSendingTime(packetSendingElapsedUsec); - - quint64 endInside = usecTimestampNow(); - quint64 elapsedInsideUsecs = endInside - startInside; - OctreeServer::trackInsideTime((float)elapsedInsideUsecs); + OctreeServer::trackInsideTime((float)(usecTimestampNow() - startInside)); } if (somethingToSend && _myServer->wantsVerboseDebug()) { diff --git a/assignment-client/src/octree/OctreeSendThread.h b/assignment-client/src/octree/OctreeSendThread.h index d158539f57..bc7d2c2588 100644 --- a/assignment-client/src/octree/OctreeSendThread.h +++ b/assignment-client/src/octree/OctreeSendThread.h @@ -19,6 +19,7 @@ #include #include #include +#include "OctreeQueryNode.h" class OctreeQueryNode; class OctreeServer; @@ -51,22 +52,27 @@ protected: /// Implements generic processing behavior for this thread. virtual bool process() override; - /// Called before a packetDistributor pass to allow for pre-distribution processing - virtual void preDistributionProcessing() {}; - virtual void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged, bool isFullScene); + virtual void traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData, + bool viewFrustumChanged, bool isFullScene); + virtual bool traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params, const QJsonObject& jsonFilters); - OctreeServer* _myServer { nullptr }; + OctreePacketData _packetData; QWeakPointer _node; + OctreeServer* _myServer { nullptr }; private: + /// Called before a packetDistributor pass to allow for pre-distribution processing + virtual void preDistributionProcessing() {}; int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, bool dontSuppressDuplicate = false); int packetDistributor(SharedNodePointer node, OctreeQueryNode* nodeData, bool viewFrustumChanged); + virtual bool hasSomethingToSend(OctreeQueryNode* nodeData) { return !nodeData->elementBag.isEmpty(); } + virtual bool shouldStartNewTraversal(OctreeQueryNode* nodeData, bool viewFrustumChanged) { return viewFrustumChanged || !hasSomethingToSend(nodeData); } + virtual void preStartNewScene(OctreeQueryNode* nodeData, bool isFullScene); + virtual bool shouldTraverseAndSend(OctreeQueryNode* nodeData) { return hasSomethingToSend(nodeData); } QUuid _nodeUuid; - OctreePacketData _packetData; - int _truePacketsSent { 0 }; // available for debug stats int _trueBytesSent { 0 }; // available for debug stats int _packetsSentThisInterval { 0 }; // used for bandwidth throttle condition diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index af5f2c904e..4a1aade59d 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -35,7 +35,7 @@ #include int OctreeServer::_clientCount = 0; -const int MOVING_AVERAGE_SAMPLE_COUNTS = 1000000; +const int MOVING_AVERAGE_SAMPLE_COUNTS = 1000; float OctreeServer::SKIP_TIME = -1.0f; // use this for trackXXXTime() calls for non-times @@ -60,6 +60,8 @@ int OctreeServer::_longTreeWait = 0; int OctreeServer::_shortTreeWait = 0; int OctreeServer::_noTreeWait = 0; +SimpleMovingAverage OctreeServer::_averageTreeTraverseTime(MOVING_AVERAGE_SAMPLE_COUNTS); + SimpleMovingAverage OctreeServer::_averageNodeWaitTime(MOVING_AVERAGE_SAMPLE_COUNTS); SimpleMovingAverage OctreeServer::_averageCompressAndWriteTime(MOVING_AVERAGE_SAMPLE_COUNTS); @@ -106,6 +108,8 @@ void OctreeServer::resetSendingStats() { _shortTreeWait = 0; _noTreeWait = 0; + _averageTreeTraverseTime.reset(); + _averageNodeWaitTime.reset(); _averageCompressAndWriteTime.reset(); @@ -136,18 +140,19 @@ void OctreeServer::trackEncodeTime(float time) { if (time == SKIP_TIME) { _noEncode++; - time = 0.0f; - } else if (time <= MAX_SHORT_TIME) { - _shortEncode++; - _averageShortEncodeTime.updateAverage(time); - } else if (time <= MAX_LONG_TIME) { - _longEncode++; - _averageLongEncodeTime.updateAverage(time); } else { - _extraLongEncode++; - _averageExtraLongEncodeTime.updateAverage(time); + if (time <= MAX_SHORT_TIME) { + _shortEncode++; + _averageShortEncodeTime.updateAverage(time); + } else if (time <= MAX_LONG_TIME) { + _longEncode++; + _averageLongEncodeTime.updateAverage(time); + } else { + _extraLongEncode++; + _averageExtraLongEncodeTime.updateAverage(time); + } + _averageEncodeTime.updateAverage(time); } - _averageEncodeTime.updateAverage(time); } void OctreeServer::trackTreeWaitTime(float time) { @@ -155,18 +160,19 @@ void OctreeServer::trackTreeWaitTime(float time) { const float MAX_LONG_TIME = 100.0f; if (time == SKIP_TIME) { _noTreeWait++; - time = 0.0f; - } else if (time <= MAX_SHORT_TIME) { - _shortTreeWait++; - _averageTreeShortWaitTime.updateAverage(time); - } else if (time <= MAX_LONG_TIME) { - _longTreeWait++; - _averageTreeLongWaitTime.updateAverage(time); } else { - _extraLongTreeWait++; - _averageTreeExtraLongWaitTime.updateAverage(time); + if (time <= MAX_SHORT_TIME) { + _shortTreeWait++; + _averageTreeShortWaitTime.updateAverage(time); + } else if (time <= MAX_LONG_TIME) { + _longTreeWait++; + _averageTreeLongWaitTime.updateAverage(time); + } else { + _extraLongTreeWait++; + _averageTreeExtraLongWaitTime.updateAverage(time); + } + _averageTreeWaitTime.updateAverage(time); } - _averageTreeWaitTime.updateAverage(time); } void OctreeServer::trackCompressAndWriteTime(float time) { @@ -174,26 +180,27 @@ void OctreeServer::trackCompressAndWriteTime(float time) { const float MAX_LONG_TIME = 100.0f; if (time == SKIP_TIME) { _noCompress++; - time = 0.0f; - } else if (time <= MAX_SHORT_TIME) { - _shortCompress++; - _averageShortCompressTime.updateAverage(time); - } else if (time <= MAX_LONG_TIME) { - _longCompress++; - _averageLongCompressTime.updateAverage(time); } else { - _extraLongCompress++; - _averageExtraLongCompressTime.updateAverage(time); + if (time <= MAX_SHORT_TIME) { + _shortCompress++; + _averageShortCompressTime.updateAverage(time); + } else if (time <= MAX_LONG_TIME) { + _longCompress++; + _averageLongCompressTime.updateAverage(time); + } else { + _extraLongCompress++; + _averageExtraLongCompressTime.updateAverage(time); + } + _averageCompressAndWriteTime.updateAverage(time); } - _averageCompressAndWriteTime.updateAverage(time); } void OctreeServer::trackPacketSendingTime(float time) { if (time == SKIP_TIME) { _noSend++; - time = 0.0f; + } else { + _averagePacketSendingTime.updateAverage(time); } - _averagePacketSendingTime.updateAverage(time); } @@ -202,18 +209,19 @@ void OctreeServer::trackProcessWaitTime(float time) { const float MAX_LONG_TIME = 100.0f; if (time == SKIP_TIME) { _noProcessWait++; - time = 0.0f; - } else if (time <= MAX_SHORT_TIME) { - _shortProcessWait++; - _averageProcessShortWaitTime.updateAverage(time); - } else if (time <= MAX_LONG_TIME) { - _longProcessWait++; - _averageProcessLongWaitTime.updateAverage(time); } else { - _extraLongProcessWait++; - _averageProcessExtraLongWaitTime.updateAverage(time); + if (time <= MAX_SHORT_TIME) { + _shortProcessWait++; + _averageProcessShortWaitTime.updateAverage(time); + } else if (time <= MAX_LONG_TIME) { + _longProcessWait++; + _averageProcessLongWaitTime.updateAverage(time); + } else { + _extraLongProcessWait++; + _averageProcessExtraLongWaitTime.updateAverage(time); + } + _averageProcessWaitTime.updateAverage(time); } - _averageProcessWaitTime.updateAverage(time); } OctreeServer::OctreeServer(ReceivedMessage& message) : @@ -518,6 +526,10 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url (double)_averageTreeExtraLongWaitTime.getAverage(), (double)(extraLongVsTotal * AS_PERCENT), _extraLongTreeWait); + // traverse + float averageTreeTraverseTime = getAverageTreeTraverseTime(); + statsString += QString().sprintf(" Average tree traverse time: %9.2f usecs\r\n\r\n", (double)averageTreeTraverseTime); + // encode float averageEncodeTime = getAverageEncodeTime(); statsString += QString().sprintf(" Average encode time: %9.2f usecs\r\n", (double)averageEncodeTime); @@ -879,7 +891,7 @@ OctreeServer::UniqueSendThread OctreeServer::newSendThread(const SharedNodePoint OctreeServer::UniqueSendThread OctreeServer::createSendThread(const SharedNodePointer& node) { auto sendThread = newSendThread(node); - + // we want to be notified when the thread finishes connect(sendThread.get(), &GenericThread::finished, this, &OctreeServer::removeSendThread); sendThread->initialize(true); @@ -901,13 +913,13 @@ void OctreeServer::handleOctreeQueryPacket(QSharedPointer messa // need to make sure we have it in our nodeList. auto nodeList = DependencyManager::get(); nodeList->updateNodeWithDataFromPacket(message, senderNode); - + auto it = _sendThreads.find(senderNode->getUUID()); if (it == _sendThreads.end()) { _sendThreads.emplace(senderNode->getUUID(), createSendThread(senderNode)); } else if (it->second->isShuttingDown()) { _sendThreads.erase(it); // Remove right away and wait on thread to be - + _sendThreads.emplace(senderNode->getUUID(), createSendThread(senderNode)); } } @@ -935,39 +947,7 @@ void OctreeServer::handleOctreeFileReplacement(QSharedPointer m // so here we just store a special file at our persist path // and then force a stop of the server so that it can pick it up when it relaunches if (!_persistAbsoluteFilePath.isEmpty()) { - - // before we restart the server and make it try and load this data, let's make sure it is valid - auto compressedOctree = message->getMessage(); - QByteArray jsonOctree; - - // assume we have GZipped content - bool wasCompressed = gunzip(compressedOctree, jsonOctree); - if (!wasCompressed) { - // the source was not compressed, assume we were sent regular JSON data - jsonOctree = compressedOctree; - } - - // check the JSON data to verify it is an object - if (QJsonDocument::fromJson(jsonOctree).isObject()) { - if (!wasCompressed) { - // source was not compressed, we compress it before we write it locally - gzip(jsonOctree, compressedOctree); - } - - // write the compressed octree data to a special file - auto replacementFilePath = _persistAbsoluteFilePath.append(OctreePersistThread::REPLACEMENT_FILE_EXTENSION); - QFile replacementFile(replacementFilePath); - if (replacementFile.open(QIODevice::WriteOnly) && replacementFile.write(compressedOctree) != -1) { - // we've now written our replacement file, time to take the server down so it can - // process it when it comes back up - qInfo() << "Wrote octree replacement file to" << replacementFilePath << "- stopping server"; - setFinished(true); - } else { - qWarning() << "Could not write replacement octree data to file - refusing to process"; - } - } else { - qDebug() << "Received replacement octree file that is invalid - refusing to process"; - } + replaceContentFromMessageData(message->getMessage()); } else { qDebug() << "Cannot perform octree file replacement since current persist file path is not yet known"; } @@ -977,6 +957,68 @@ void OctreeServer::handleOctreeFileReplacement(QSharedPointer m } } +// Message->getMessage() contains a QByteArray representation of the URL to download from +void OctreeServer::handleOctreeFileReplacementFromURL(QSharedPointer message) { + qInfo() << "Received request to replace content from a url"; + if (!_isFinished && !_isShuttingDown) { + // This call comes from Interface, so we skip our domain server check + // but confirm that we have permissions to replace content sets + if (DependencyManager::get()->getThisNodeCanReplaceContent()) { + if (!_persistAbsoluteFilePath.isEmpty()) { + // Convert message data into our URL + QString url(message->getMessage()); + QUrl modelsURL = QUrl(url, QUrl::StrictMode); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest request(modelsURL); + QNetworkReply* reply = networkAccessManager.get(request); + connect(reply, &QNetworkReply::finished, [this, reply, modelsURL]() { + QNetworkReply::NetworkError networkError = reply->error(); + if (networkError == QNetworkReply::NoError) { + QByteArray contents = reply->readAll(); + replaceContentFromMessageData(contents); + } else { + qDebug() << "Error downloading JSON from specified file"; + } + }); + } else { + qDebug() << "Cannot perform octree file replacement since current persist file path is not yet known"; + } + } + } +} + +void OctreeServer::replaceContentFromMessageData(QByteArray content) { + //Assume we have compressed data + auto compressedOctree = content; + QByteArray jsonOctree; + + bool wasCompressed = gunzip(compressedOctree, jsonOctree); + if (!wasCompressed) { + // the source was not compressed, assume we were sent regular JSON data + jsonOctree = compressedOctree; + } + // check the JSON data to verify it is an object + if (QJsonDocument::fromJson(jsonOctree).isObject()) { + if (!wasCompressed) { + // source was not compressed, we compress it before we write it locally + gzip(jsonOctree, compressedOctree); + } + // write the compressed octree data to a special file + auto replacementFilePath = _persistAbsoluteFilePath.append(OctreePersistThread::REPLACEMENT_FILE_EXTENSION); + QFile replacementFile(replacementFilePath); + if (replacementFile.open(QIODevice::WriteOnly) && replacementFile.write(compressedOctree) != -1) { + // we've now written our replacement file, time to take the server down so it can + // process it when it comes back up + qInfo() << "Wrote octree replacement file to" << replacementFilePath << "- stopping server"; + setFinished(true); + } else { + qWarning() << "Could not write replacement octree data to file - refusing to process"; + } + } else { + qDebug() << "Received replacement octree file that is invalid - refusing to process"; + } +} + bool OctreeServer::readOptionBool(const QString& optionName, const QJsonObject& settingsSectionObject, bool& result) { result = false; // assume it doesn't exist bool optionAvailable = false; @@ -1051,7 +1093,7 @@ void OctreeServer::readConfiguration() { if (getPayload().size() > 0) { parsePayload(); } - + const QJsonObject& settingsObject = DependencyManager::get()->getDomainHandler().getSettingsObject(); QString settingsKey = getMyDomainSettingsKey(); @@ -1178,9 +1220,9 @@ void OctreeServer::run() { OctreeElement::resetPopulationStatistics(); _tree = createTree(); _tree->setIsServer(true); - + qDebug() << "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()->getDomainHandler(); connect(&domainHandler, &DomainHandler::settingsReceived, this, &OctreeServer::domainSettingsRequestComplete); @@ -1191,9 +1233,9 @@ void OctreeServer::run() { } void OctreeServer::domainSettingsRequestComplete() { - + auto nodeList = DependencyManager::get(); - + // we need to ask the DS about agents so we can ping/reply with them nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer }); @@ -1202,26 +1244,27 @@ void OctreeServer::domainSettingsRequestComplete() { packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket"); packetReceiver.registerListener(PacketType::JurisdictionRequest, this, "handleJurisdictionRequestPacket"); packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacement"); - + packetReceiver.registerListener(PacketType::OctreeFileReplacementFromUrl, this, "handleOctreeFileReplacementFromURL"); + readConfiguration(); - + beforeRun(); // after payload has been processed - + connect(nodeList.data(), SIGNAL(nodeAdded(SharedNodePointer)), SLOT(nodeAdded(SharedNodePointer))); connect(nodeList.data(), SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer))); #ifndef WIN32 setvbuf(stdout, NULL, _IOLBF, 0); #endif - + nodeList->linkedDataCreateCallback = [this](Node* node) { auto queryNodeData = createOctreeQueryNode(); queryNodeData->init(); node->setLinkedData(std::move(queryNodeData)); }; - + srand((unsigned)time(0)); - + // if we want Persistence, set up the local file and persist thread if (_wantPersist) { // If persist filename does not exist, let's see if there is one beside the application binary @@ -1316,24 +1359,24 @@ void OctreeServer::domainSettingsRequestComplete() { } } qDebug() << "Backups will be stored in: " << _backupDirectoryPath; - + // now set up PersistThread _persistThread = new OctreePersistThread(_tree, _persistAbsoluteFilePath, _backupDirectoryPath, _persistInterval, _wantBackup, _settings, _debugTimestampNow, _persistAsFileType); _persistThread->initialize(true); } - + // set up our jurisdiction broadcaster... if (_jurisdiction) { _jurisdiction->setNodeType(getMyNodeType()); } _jurisdictionSender = new JurisdictionSender(_jurisdiction, getMyNodeType()); _jurisdictionSender->initialize(true); - + // set up our OctreeServerPacketProcessor _octreeInboundPacketProcessor = new OctreeInboundPacketProcessor(this); _octreeInboundPacketProcessor->initialize(true); - + // Convert now to tm struct for local timezone tm* localtm = localtime(&_started); const int MAX_TIME_LENGTH = 128; @@ -1345,7 +1388,7 @@ void OctreeServer::domainSettingsRequestComplete() { if (gmtm) { strftime(utcBuffer, MAX_TIME_LENGTH, " [%m/%d/%Y %X UTC]", gmtm); } - + qDebug() << "Now running... started at: " << localBuffer << utcBuffer; } @@ -1356,7 +1399,7 @@ void OctreeServer::nodeAdded(SharedNodePointer node) { void OctreeServer::nodeKilled(SharedNodePointer node) { quint64 start = usecTimestampNow(); - + // Shutdown send thread auto it = _sendThreads.find(node->getUUID()); if (it != _sendThreads.end()) { @@ -1402,13 +1445,13 @@ void OctreeServer::aboutToFinish() { if (_jurisdictionSender) { _jurisdictionSender->terminating(); } - + // Shut down all the send threads for (auto& it : _sendThreads) { auto& sendThread = *it.second; sendThread.setIsShuttingDown(); } - + // Clear will destruct all the unique_ptr to OctreeSendThreads which will call the GenericThread's dtor // which waits on the thread to be done before returning _sendThreads.clear(); // Cleans up all the send threads. @@ -1528,7 +1571,7 @@ void OctreeServer::sendStatsPacket() { threadsStats["2. packetDistributor"] = (double)howManyThreadsDidPacketDistributor(oneSecondAgo); threadsStats["3. handlePacektSend"] = (double)howManyThreadsDidHandlePacketSend(oneSecondAgo); threadsStats["4. writeDatagram"] = (double)howManyThreadsDidCallWriteDatagram(oneSecondAgo); - + QJsonObject statsArray1; statsArray1["1. configuration"] = getConfiguration(); statsArray1["2. detailed_stats_url"] = getStatusLink(); @@ -1536,13 +1579,13 @@ void OctreeServer::sendStatsPacket() { statsArray1["4. persistFileLoadTime"] = getFileLoadTime(); statsArray1["5. clients"] = getCurrentClientCount(); statsArray1["6. threads"] = threadsStats; - + // Octree Stats QJsonObject octreeStats; octreeStats["1. elementCount"] = (double)OctreeElement::getNodeCount(); octreeStats["2. internalElementCount"] = (double)OctreeElement::getInternalNodeCount(); octreeStats["3. leafElementCount"] = (double)OctreeElement::getLeafNodeCount(); - + // Stats Object 2 QJsonObject dataObject1; dataObject1["1. totalPackets"] = (double)OctreeSendThread::_totalPackets; @@ -1555,12 +1598,12 @@ void OctreeServer::sendStatsPacket() { QJsonObject timingArray1; timingArray1["1. avgLoopTime"] = getAverageLoopTime(); timingArray1["2. avgInsideTime"] = getAverageInsideTime(); - timingArray1["3. avgTreeLockTime"] = getAverageTreeWaitTime(); + timingArray1["3. avgTreeTraverseTime"] = getAverageTreeTraverseTime(); timingArray1["4. avgEncodeTime"] = getAverageEncodeTime(); timingArray1["5. avgCompressAndWriteTime"] = getAverageCompressAndWriteTime(); timingArray1["6. avgSendTime"] = getAveragePacketSendingTime(); timingArray1["7. nodeWaitTime"] = getAverageNodeWaitTime(); - + QJsonObject statsObject2; statsObject2["data"] = dataObject1; statsObject2["timing"] = timingArray1; @@ -1580,18 +1623,18 @@ void OctreeServer::sendStatsPacket() { timingArray2["4. avgProcessTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageProcessTimePerElement(); timingArray2["5. avgLockWaitTimePerElement"] = (double)_octreeInboundPacketProcessor->getAverageLockWaitTimePerElement(); } - + QJsonObject statsObject3; statsObject3["data"] = dataArray2; statsObject3["timing"] = timingArray2; - + // Merge everything QJsonObject jsonArray; jsonArray["1. misc"] = statsArray1; jsonArray["2. octree"] = octreeStats; jsonArray["3. outbound"] = statsObject2; jsonArray["4. inbound"] = statsObject3; - + QJsonObject statsObject; statsObject[QString(getMyServerName()) + "Server"] = jsonArray; addPacketStatsAndSendStatsPacket(statsObject); diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index 8db8d845de..f930f299f3 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -96,6 +96,9 @@ public: static void trackTreeWaitTime(float time); static float getAverageTreeWaitTime() { return _averageTreeWaitTime.getAverage(); } + static void trackTreeTraverseTime(float time) { _averageTreeTraverseTime.updateAverage(time); } + static float getAverageTreeTraverseTime() { return _averageTreeTraverseTime.getAverage(); } + static void trackNodeWaitTime(float time) { _averageNodeWaitTime.updateAverage(time); } static float getAverageNodeWaitTime() { return _averageNodeWaitTime.getAverage(); } @@ -137,6 +140,7 @@ private slots: void handleOctreeDataNackPacket(QSharedPointer message, SharedNodePointer senderNode); void handleJurisdictionRequestPacket(QSharedPointer message, SharedNodePointer senderNode); void handleOctreeFileReplacement(QSharedPointer message); + void handleOctreeFileReplacementFromURL(QSharedPointer message); void removeSendThread(); protected: @@ -161,6 +165,8 @@ protected: UniqueSendThread createSendThread(const SharedNodePointer& node); virtual UniqueSendThread newSendThread(const SharedNodePointer& node); + void replaceContentFromMessageData(QByteArray content); + int _argc; const char** _argv; char** _parsedArgV; @@ -225,6 +231,8 @@ protected: static int _shortTreeWait; static int _noTreeWait; + static SimpleMovingAverage _averageTreeTraverseTime; + static SimpleMovingAverage _averageNodeWaitTime; static SimpleMovingAverage _averageCompressAndWriteTime; diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index e7433e7c05..2bfcdcdf8c 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -102,7 +102,7 @@ static const QString ENTITY_SCRIPT_SERVER_LOGGING_NAME = "entity-script-server"; void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer message, SharedNodePointer senderNode) { // These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them // about each other. - if (senderNode->getCanRez() || senderNode->getCanRezTmp()) { + if (senderNode->getCanRez() || senderNode->getCanRezTmp() || senderNode->getCanRezCertified() || senderNode->getCanRezTmpCertified()) { auto entityID = QUuid::fromRfc4122(message->read(NUM_BYTES_RFC4122_UUID)); if (_entityViewer.getTree() && !_shuttingDown) { @@ -116,7 +116,7 @@ void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer message, SharedNodePointer senderNode) { // These are temporary checks until we can ensure that nodes eventually disconnect if the Domain Server stops telling them // about each other. - if (senderNode->getCanRez() || senderNode->getCanRezTmp()) { + if (senderNode->getCanRez() || senderNode->getCanRezTmp() || senderNode->getCanRezCertified() || senderNode->getCanRezTmpCertified()) { MessageID messageID; message->readPrimitive(&messageID); auto entityID = QUuid::fromRfc4122(message->read(NUM_BYTES_RFC4122_UUID)); @@ -415,8 +415,7 @@ void EntityScriptServer::selectAudioFormat(const QString& selectedCodecName) { void EntityScriptServer::resetEntitiesScriptEngine() { auto engineName = QString("about:Entities %1").arg(++_entitiesScriptEngineCount); - auto newEngine = QSharedPointer(new ScriptEngine(ScriptEngine::ENTITY_SERVER_SCRIPT, NO_SCRIPT, engineName), - &ScriptEngine::deleteLater); + auto newEngine = scriptEngineFactory(ScriptEngine::ENTITY_SERVER_SCRIPT, NO_SCRIPT, engineName); auto webSocketServerConstructorValue = newEngine->newFunction(WebSocketServerClass::constructor); newEngine->globalObject().setProperty("WebSocketServer", webSocketServerConstructorValue); @@ -437,11 +436,14 @@ void EntityScriptServer::resetEntitiesScriptEngine() { newEngine->runInThread(); - DependencyManager::get()->setEntitiesScriptEngine(newEngine.data()); + auto newEngineSP = qSharedPointerCast(newEngine); + DependencyManager::get()->setEntitiesScriptEngine(newEngineSP); - disconnect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, this, &EntityScriptServer::updateEntityPPS); + disconnect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, + this, &EntityScriptServer::updateEntityPPS); _entitiesScriptEngine.swap(newEngine); - connect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, this, &EntityScriptServer::updateEntityPPS); + connect(_entitiesScriptEngine.data(), &ScriptEngine::entityScriptDetailsUpdated, + this, &EntityScriptServer::updateEntityPPS); } diff --git a/assignment-client/src/scripts/EntityScriptServer.h b/assignment-client/src/scripts/EntityScriptServer.h index 84454375e5..e6bd12460b 100644 --- a/assignment-client/src/scripts/EntityScriptServer.h +++ b/assignment-client/src/scripts/EntityScriptServer.h @@ -72,7 +72,7 @@ private: bool _shuttingDown { false }; static int _entitiesScriptEngineCount; - QSharedPointer _entitiesScriptEngine; + ScriptEnginePointer _entitiesScriptEngine; EntityEditPacketSender _entityEditSender; EntityTreeHeadlessViewer _entityViewer; diff --git a/cmake/android/AndroidManifest.xml.in b/cmake/android/AndroidManifest.xml.in deleted file mode 100755 index aa834f3384..0000000000 --- a/cmake/android/AndroidManifest.xml.in +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ${ANDROID_EXTRA_ACTIVITY_XML} - - - - - - ${ANDROID_EXTRA_APPLICATION_XML} - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/cmake/android/QtCreateAPK.cmake b/cmake/android/QtCreateAPK.cmake deleted file mode 100644 index 30ee2f57bd..0000000000 --- a/cmake/android/QtCreateAPK.cmake +++ /dev/null @@ -1,159 +0,0 @@ -# -# QtCreateAPK.cmake -# -# Created by Stephen Birarda on 11/18/14. -# Copyright 2013 High Fidelity, Inc. -# -# Distributed under the Apache License, Version 2.0. -# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -# - -# -# OPTIONS -# These options will modify how QtCreateAPK behaves. May be useful if somebody wants to fork. -# For High Fidelity purposes these should not need to be changed. -# -set(ANDROID_THIS_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}) # Directory this CMake file is in - -if (POLICY CMP0026) - cmake_policy(SET CMP0026 OLD) -endif () - -macro(qt_create_apk) - if(ANDROID_APK_FULLSCREEN) - set(ANDROID_APK_THEME "android:theme=\"@android:style/Theme.NoTitleBar.Fullscreen\"") - else() - set(ANDROID_APK_THEME "") - endif() - - if (UPPER_CMAKE_BUILD_TYPE MATCHES RELEASE) - set(ANDROID_APK_DEBUGGABLE "false") - set(ANDROID_APK_RELEASE_LOCAL ${ANDROID_APK_RELEASE}) - else () - set(ANDROID_APK_DEBUGGABLE "true") - set(ANDROID_APK_RELEASE_LOCAL "0") - endif () - - # Create "AndroidManifest.xml" - configure_file("${ANDROID_THIS_DIRECTORY}/AndroidManifest.xml.in" "${ANDROID_APK_BUILD_DIR}/AndroidManifest.xml") - - # create "strings.xml" - configure_file("${ANDROID_THIS_DIRECTORY}/strings.xml.in" "${ANDROID_APK_BUILD_DIR}/res/values/strings.xml") - - # find androiddeployqt - find_program(ANDROID_DEPLOY_QT androiddeployqt HINTS "${QT_DIR}/bin") - - # set the path to our app shared library - set(EXECUTABLE_DESTINATION_PATH "${ANDROID_APK_OUTPUT_DIR}/libs/${ANDROID_ABI}/lib${TARGET_NAME}.so") - - # add our dependencies to the deployment file - get_property(_DEPENDENCIES TARGET ${TARGET_NAME} PROPERTY INTERFACE_LINK_LIBRARIES) - - foreach(_IGNORE_COPY IN LISTS IGNORE_COPY_LIBS) - list(REMOVE_ITEM _DEPENDENCIES ${_IGNORE_COPY}) - endforeach() - - foreach(_DEP IN LISTS _DEPENDENCIES) - if (NOT TARGET ${_DEP}) - list(APPEND _DEPS_LIST ${_DEP}) - else () - if(NOT _DEP MATCHES "Qt5::.*") - get_property(_DEP_LOCATION TARGET ${_DEP} PROPERTY "LOCATION_${CMAKE_BUILD_TYPE}") - - # recurisvely add libraries which are dependencies of this target - get_property(_DEP_DEPENDENCIES TARGET ${_DEP} PROPERTY INTERFACE_LINK_LIBRARIES) - - foreach(_SUB_DEP IN LISTS _DEP_DEPENDENCIES) - if (NOT TARGET ${_SUB_DEP} AND NOT _SUB_DEP MATCHES "Qt5::.*") - list(APPEND _DEPS_LIST ${_SUB_DEP}) - endif() - endforeach() - - list(APPEND _DEPS_LIST ${_DEP_LOCATION}) - endif() - endif () - endforeach() - - list(REMOVE_DUPLICATES _DEPS_LIST) - - # just copy static libs to apk libs folder - don't add to deps list - foreach(_LOCATED_DEP IN LISTS _DEPS_LIST) - if (_LOCATED_DEP MATCHES "\\.a$") - add_custom_command( - TARGET ${TARGET_NAME} - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${_LOCATED_DEP} "${ANDROID_APK_OUTPUT_DIR}/libs/${ANDROID_ABI}" - ) - list(REMOVE_ITEM _DEPS_LIST ${_LOCATED_DEP}) - endif () - endforeach() - - string(REPLACE ";" "," _DEPS "${_DEPS_LIST}") - - configure_file("${ANDROID_THIS_DIRECTORY}/deployment-file.json.in" "${TARGET_NAME}-deployment.json") - - # copy the res folder from the target to the apk build dir - add_custom_target( - ${TARGET_NAME}-copy-res - COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/res" "${ANDROID_APK_BUILD_DIR}/res" - ) - - # copy the assets folder from the target to the apk build dir - add_custom_target( - ${TARGET_NAME}-copy-assets - COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/assets" "${ANDROID_APK_BUILD_DIR}/assets" - ) - - # copy the java folder from src to the apk build dir - add_custom_target( - ${TARGET_NAME}-copy-java - COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/src/java" "${ANDROID_APK_BUILD_DIR}/src" - ) - - # copy the libs folder from src to the apk build dir - add_custom_target( - ${TARGET_NAME}-copy-libs - COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/libs" "${ANDROID_APK_BUILD_DIR}/libs" - ) - - # handle setup for ndk-gdb - add_custom_target(${TARGET_NAME}-gdb DEPENDS ${TARGET_NAME}) - - if (ANDROID_APK_DEBUGGABLE) - get_property(TARGET_LOCATION TARGET ${TARGET_NAME} PROPERTY LOCATION) - - set(GDB_SOLIB_PATH ${ANDROID_APK_BUILD_DIR}/obj/local/${ANDROID_NDK_ABI_NAME}/) - - # generate essential Android Makefiles - file(WRITE ${ANDROID_APK_BUILD_DIR}/jni/Android.mk "APP_ABI := ${ANDROID_NDK_ABI_NAME}\n") - file(WRITE ${ANDROID_APK_BUILD_DIR}/jni/Application.mk "APP_ABI := ${ANDROID_NDK_ABI_NAME}\n") - - # create gdb.setup - get_directory_property(PROJECT_INCLUDES DIRECTORY ${PROJECT_SOURCE_DIR} INCLUDE_DIRECTORIES) - string(REGEX REPLACE ";" " " PROJECT_INCLUDES "${PROJECT_INCLUDES}") - file(WRITE ${ANDROID_APK_BUILD_DIR}/libs/${ANDROID_NDK_ABI_NAME}/gdb.setup "set solib-search-path ${GDB_SOLIB_PATH}\n") - file(APPEND ${ANDROID_APK_BUILD_DIR}/libs/${ANDROID_NDK_ABI_NAME}/gdb.setup "directory ${PROJECT_INCLUDES}\n") - - # copy lib to obj - add_custom_command(TARGET ${TARGET_NAME}-gdb PRE_BUILD COMMAND ${CMAKE_COMMAND} -E make_directory ${GDB_SOLIB_PATH}) - add_custom_command(TARGET ${TARGET_NAME}-gdb PRE_BUILD COMMAND cp ${TARGET_LOCATION} ${GDB_SOLIB_PATH}) - - # strip symbols - add_custom_command(TARGET ${TARGET_NAME}-gdb PRE_BUILD COMMAND ${CMAKE_STRIP} ${TARGET_LOCATION}) - endif () - - # use androiddeployqt to create the apk - add_custom_target(${TARGET_NAME}-apk - COMMAND ${ANDROID_DEPLOY_QT} --input "${TARGET_NAME}-deployment.json" --output "${ANDROID_APK_OUTPUT_DIR}" --android-platform android-${ANDROID_API_LEVEL} ${ANDROID_DEPLOY_QT_INSTALL} --verbose --deployment bundled "\\$(ARGS)" - DEPENDS ${TARGET_NAME} ${TARGET_NAME}-copy-res ${TARGET_NAME}-copy-assets ${TARGET_NAME}-copy-java ${TARGET_NAME}-copy-libs ${TARGET_NAME}-gdb - ) - - # rename the APK if the caller asked us to - if (ANDROID_APK_CUSTOM_NAME) - add_custom_command( - TARGET ${TARGET_NAME}-apk - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E rename "${ANDROID_APK_OUTPUT_DIR}/bin/QtApp-debug.apk" "${ANDROID_APK_OUTPUT_DIR}/bin/${ANDROID_APK_CUSTOM_NAME}" - ) - endif () -endmacro() \ No newline at end of file diff --git a/cmake/android/android.toolchain.cmake b/cmake/android/android.toolchain.cmake deleted file mode 100755 index 806cef6b18..0000000000 --- a/cmake/android/android.toolchain.cmake +++ /dev/null @@ -1,1725 +0,0 @@ -# Copyright (c) 2010-2011, Ethan Rublee -# Copyright (c) 2011-2014, Andrey Kamaev -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from this -# software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. - -# ------------------------------------------------------------------------------ -# Android CMake toolchain file, for use with the Android NDK r5-r10d -# Requires cmake 2.6.3 or newer (2.8.9 or newer is recommended). -# See home page: https://github.com/taka-no-me/android-cmake -# -# Usage Linux: -# $ export ANDROID_NDK=/absolute/path/to/the/android-ndk -# $ mkdir build && cd build -# $ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/the/android.toolchain.cmake .. -# $ make -j8 -# -# Usage Windows: -# You need native port of make to build your project. -# Android NDK r7 (and newer) already has make.exe on board. -# For older NDK you have to install it separately. -# For example, this one: http://gnuwin32.sourceforge.net/packages/make.htm -# -# $ SET ANDROID_NDK=C:\absolute\path\to\the\android-ndk -# $ mkdir build && cd build -# $ cmake.exe -G"MinGW Makefiles" -# -DCMAKE_TOOLCHAIN_FILE=path\to\the\android.toolchain.cmake -# -DCMAKE_MAKE_PROGRAM="%ANDROID_NDK%\prebuilt\windows\bin\make.exe" .. -# $ cmake.exe --build . -# -# -# Options (can be set as cmake parameters: -D=): -# ANDROID_NDK=/opt/android-ndk - path to the NDK root. -# Can be set as environment variable. Can be set only at first cmake run. -# -# ANDROID_ABI=armeabi-v7a - specifies the target Application Binary -# Interface (ABI). This option nearly matches to the APP_ABI variable -# used by ndk-build tool from Android NDK. -# -# Possible targets are: -# "armeabi" - ARMv5TE based CPU with software floating point operations -# "armeabi-v7a" - ARMv7 based devices with hardware FPU instructions -# this ABI target is used by default -# "armeabi-v7a with NEON" - same as armeabi-v7a, but -# sets NEON as floating-point unit -# "armeabi-v7a with VFPV3" - same as armeabi-v7a, but -# sets VFPV3 as floating-point unit (has 32 registers instead of 16) -# "armeabi-v6 with VFP" - tuned for ARMv6 processors having VFP -# "x86" - IA-32 instruction set -# "mips" - MIPS32 instruction set -# -# 64-bit ABIs for NDK r10 and newer: -# "arm64-v8a" - ARMv8 AArch64 instruction set -# "x86_64" - Intel64 instruction set (r1) -# "mips64" - MIPS64 instruction set (r6) -# -# ANDROID_NATIVE_API_LEVEL=android-8 - level of Android API compile for. -# Option is read-only when standalone toolchain is used. -# Note: building for "android-L" requires explicit configuration. -# -# ANDROID_TOOLCHAIN_NAME=arm-linux-androideabi-4.9 - the name of compiler -# toolchain to be used. The list of possible values depends on the NDK -# version. For NDK r10c the possible values are: -# -# * aarch64-linux-android-4.9 -# * aarch64-linux-android-clang3.4 -# * aarch64-linux-android-clang3.5 -# * arm-linux-androideabi-4.6 -# * arm-linux-androideabi-4.8 -# * arm-linux-androideabi-4.9 (default) -# * arm-linux-androideabi-clang3.4 -# * arm-linux-androideabi-clang3.5 -# * mips64el-linux-android-4.9 -# * mips64el-linux-android-clang3.4 -# * mips64el-linux-android-clang3.5 -# * mipsel-linux-android-4.6 -# * mipsel-linux-android-4.8 -# * mipsel-linux-android-4.9 -# * mipsel-linux-android-clang3.4 -# * mipsel-linux-android-clang3.5 -# * x86-4.6 -# * x86-4.8 -# * x86-4.9 -# * x86-clang3.4 -# * x86-clang3.5 -# * x86_64-4.9 -# * x86_64-clang3.4 -# * x86_64-clang3.5 -# -# ANDROID_FORCE_ARM_BUILD=OFF - set ON to generate 32-bit ARM instructions -# instead of Thumb. Is not available for "armeabi-v6 with VFP" -# (is forced to be ON) ABI. -# -# ANDROID_NO_UNDEFINED=ON - set ON to show all undefined symbols as linker -# errors even if they are not used. -# -# ANDROID_SO_UNDEFINED=OFF - set ON to allow undefined symbols in shared -# libraries. Automatically turned for NDK r5x and r6x due to GLESv2 -# problems. -# -# ANDROID_STL=gnustl_static - specify the runtime to use. -# -# Possible values are: -# none -> Do not configure the runtime. -# system -> Use the default minimal system C++ runtime library. -# Implies -fno-rtti -fno-exceptions. -# Is not available for standalone toolchain. -# system_re -> Use the default minimal system C++ runtime library. -# Implies -frtti -fexceptions. -# Is not available for standalone toolchain. -# gabi++_static -> Use the GAbi++ runtime as a static library. -# Implies -frtti -fno-exceptions. -# Available for NDK r7 and newer. -# Is not available for standalone toolchain. -# gabi++_shared -> Use the GAbi++ runtime as a shared library. -# Implies -frtti -fno-exceptions. -# Available for NDK r7 and newer. -# Is not available for standalone toolchain. -# stlport_static -> Use the STLport runtime as a static library. -# Implies -fno-rtti -fno-exceptions for NDK before r7. -# Implies -frtti -fno-exceptions for NDK r7 and newer. -# Is not available for standalone toolchain. -# stlport_shared -> Use the STLport runtime as a shared library. -# Implies -fno-rtti -fno-exceptions for NDK before r7. -# Implies -frtti -fno-exceptions for NDK r7 and newer. -# Is not available for standalone toolchain. -# gnustl_static -> Use the GNU STL as a static library. -# Implies -frtti -fexceptions. -# gnustl_shared -> Use the GNU STL as a shared library. -# Implies -frtti -fno-exceptions. -# Available for NDK r7b and newer. -# Silently degrades to gnustl_static if not available. -# c++_static -> Use the LLVM libc++ runtime as a static library. -# c++_shared -> Use the LLVM libc++ runtime as a shared library. -# -# ANDROID_STL_FORCE_FEATURES=ON - turn rtti and exceptions support based on -# chosen runtime. If disabled, then the user is responsible for settings -# these options. -# -# What?: -# android-cmake toolchain searches for NDK/toolchain in the following order: -# ANDROID_NDK - cmake parameter -# ANDROID_NDK - environment variable -# ANDROID_STANDALONE_TOOLCHAIN - cmake parameter -# ANDROID_STANDALONE_TOOLCHAIN - environment variable -# ANDROID_NDK - default locations -# ANDROID_STANDALONE_TOOLCHAIN - default locations -# -# Make sure to do the following in your scripts: -# SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${my_cxx_flags}" ) -# SET( CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${my_cxx_flags}" ) -# The flags will be prepopulated with critical flags, so don't loose them. -# Also be aware that toolchain also sets configuration-specific compiler -# flags and linker flags. -# -# ANDROID and BUILD_ANDROID will be set to true, you may test any of these -# variables to make necessary Android-specific configuration changes. -# -# Also ARMEABI or ARMEABI_V7A or X86 or MIPS or ARM64_V8A or X86_64 or MIPS64 -# will be set true, mutually exclusive. NEON option will be set true -# if VFP is set to NEON. -# -# ------------------------------------------------------------------------------ - -cmake_minimum_required( VERSION 2.6.3 ) - -if( DEFINED CMAKE_CROSSCOMPILING ) - # subsequent toolchain loading is not really needed - return() -endif() - -if( CMAKE_TOOLCHAIN_FILE ) - # touch toolchain variable to suppress "unused variable" warning -endif() - -# inherit settings in recursive loads -get_property( _CMAKE_IN_TRY_COMPILE GLOBAL PROPERTY IN_TRY_COMPILE ) -if( _CMAKE_IN_TRY_COMPILE ) - include( "${CMAKE_CURRENT_SOURCE_DIR}/../android.toolchain.config.cmake" OPTIONAL ) -endif() - -# this one is important -if( CMAKE_VERSION VERSION_GREATER "3.0.99" ) - set( CMAKE_SYSTEM_NAME Android ) -else() - set( CMAKE_SYSTEM_NAME Linux ) -endif() - -# this one not so much -set( CMAKE_SYSTEM_VERSION 1 ) - -# rpath makes low sense for Android -set( CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG "" ) -set( CMAKE_SKIP_RPATH TRUE CACHE BOOL "If set, runtime paths are not added when using shared libraries." ) - -# NDK search paths -set( ANDROID_SUPPORTED_NDK_VERSIONS ${ANDROID_EXTRA_NDK_VERSIONS} -r10d -r10c -r10b -r10 -r9d -r9c -r9b -r9 -r8e -r8d -r8c -r8b -r8 -r7c -r7b -r7 -r6b -r6 -r5c -r5b -r5 "" ) -if( NOT DEFINED ANDROID_NDK_SEARCH_PATHS ) - if( CMAKE_HOST_WIN32 ) - file( TO_CMAKE_PATH "$ENV{PROGRAMFILES}" ANDROID_NDK_SEARCH_PATHS ) - set( ANDROID_NDK_SEARCH_PATHS "${ANDROID_NDK_SEARCH_PATHS}" "$ENV{SystemDrive}/NVPACK" ) - else() - file( TO_CMAKE_PATH "$ENV{HOME}" ANDROID_NDK_SEARCH_PATHS ) - set( ANDROID_NDK_SEARCH_PATHS /opt "${ANDROID_NDK_SEARCH_PATHS}/NVPACK" ) - endif() -endif() -if( NOT DEFINED ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH ) - set( ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH /opt/android-toolchain ) -endif() - -# known ABIs -set( ANDROID_SUPPORTED_ABIS_arm "armeabi-v7a;armeabi;armeabi-v7a with NEON;armeabi-v7a with VFPV3;armeabi-v6 with VFP" ) -set( ANDROID_SUPPORTED_ABIS_arm64 "arm64-v8a" ) -set( ANDROID_SUPPORTED_ABIS_x86 "x86" ) -set( ANDROID_SUPPORTED_ABIS_x86_64 "x86_64" ) -set( ANDROID_SUPPORTED_ABIS_mips "mips" ) -set( ANDROID_SUPPORTED_ABIS_mips64 "mips64" ) - -# API level defaults -set( ANDROID_DEFAULT_NDK_API_LEVEL 8 ) -set( ANDROID_DEFAULT_NDK_API_LEVEL_arm64 21 ) -set( ANDROID_DEFAULT_NDK_API_LEVEL_x86 9 ) -set( ANDROID_DEFAULT_NDK_API_LEVEL_x86_64 21 ) -set( ANDROID_DEFAULT_NDK_API_LEVEL_mips 9 ) -set( ANDROID_DEFAULT_NDK_API_LEVEL_mips64 21 ) - - -macro( __LIST_FILTER listvar regex ) - if( ${listvar} ) - foreach( __val ${${listvar}} ) - if( __val MATCHES "${regex}" ) - list( REMOVE_ITEM ${listvar} "${__val}" ) - endif() - endforeach() - endif() -endmacro() - -macro( __INIT_VARIABLE var_name ) - set( __test_path 0 ) - foreach( __var ${ARGN} ) - if( __var STREQUAL "PATH" ) - set( __test_path 1 ) - break() - endif() - endforeach() - - if( __test_path AND NOT EXISTS "${${var_name}}" ) - unset( ${var_name} CACHE ) - endif() - - if( " ${${var_name}}" STREQUAL " " ) - set( __values 0 ) - foreach( __var ${ARGN} ) - if( __var STREQUAL "VALUES" ) - set( __values 1 ) - elseif( NOT __var STREQUAL "PATH" ) - if( __var MATCHES "^ENV_.*$" ) - string( REPLACE "ENV_" "" __var "${__var}" ) - set( __value "$ENV{${__var}}" ) - elseif( DEFINED ${__var} ) - set( __value "${${__var}}" ) - elseif( __values ) - set( __value "${__var}" ) - else() - set( __value "" ) - endif() - - if( NOT " ${__value}" STREQUAL " " AND (NOT __test_path OR EXISTS "${__value}") ) - set( ${var_name} "${__value}" ) - break() - endif() - endif() - endforeach() - unset( __value ) - unset( __values ) - endif() - - if( __test_path ) - file( TO_CMAKE_PATH "${${var_name}}" ${var_name} ) - endif() - unset( __test_path ) -endmacro() - -macro( __DETECT_NATIVE_API_LEVEL _var _path ) - set( __ndkApiLevelRegex "^[\t ]*#define[\t ]+__ANDROID_API__[\t ]+([0-9]+)[\t ]*.*$" ) - file( STRINGS ${_path} __apiFileContent REGEX "${__ndkApiLevelRegex}" ) - if( NOT __apiFileContent ) - message( SEND_ERROR "Could not get Android native API level. Probably you have specified invalid level value, or your copy of NDK/toolchain is broken." ) - endif() - string( REGEX REPLACE "${__ndkApiLevelRegex}" "\\1" ${_var} "${__apiFileContent}" ) - unset( __apiFileContent ) - unset( __ndkApiLevelRegex ) -endmacro() - -macro( __DETECT_TOOLCHAIN_MACHINE_NAME _var _root ) - if( EXISTS "${_root}" ) - file( GLOB __gccExePath RELATIVE "${_root}/bin/" "${_root}/bin/*-gcc${TOOL_OS_SUFFIX}" ) - __LIST_FILTER( __gccExePath "^[.].*" ) - list( LENGTH __gccExePath __gccExePathsCount ) - if( NOT __gccExePathsCount EQUAL 1 AND NOT _CMAKE_IN_TRY_COMPILE ) - message( WARNING "Could not determine machine name for compiler from ${_root}" ) - set( ${_var} "" ) - else() - get_filename_component( __gccExeName "${__gccExePath}" NAME_WE ) - string( REPLACE "-gcc" "" ${_var} "${__gccExeName}" ) - endif() - unset( __gccExePath ) - unset( __gccExePathsCount ) - unset( __gccExeName ) - else() - set( ${_var} "" ) - endif() -endmacro() - - -# fight against cygwin -set( ANDROID_FORBID_SYGWIN TRUE CACHE BOOL "Prevent cmake from working under cygwin and using cygwin tools") -mark_as_advanced( ANDROID_FORBID_SYGWIN ) -if( ANDROID_FORBID_SYGWIN ) - if( CYGWIN ) - message( FATAL_ERROR "Android NDK and android-cmake toolchain are not welcome Cygwin. It is unlikely that this cmake toolchain will work under cygwin. But if you want to try then you can set cmake variable ANDROID_FORBID_SYGWIN to FALSE and rerun cmake." ) - endif() - - if( CMAKE_HOST_WIN32 ) - # remove cygwin from PATH - set( __new_path "$ENV{PATH}") - __LIST_FILTER( __new_path "cygwin" ) - set(ENV{PATH} "${__new_path}") - unset(__new_path) - endif() -endif() - - -# detect current host platform -if( NOT DEFINED ANDROID_NDK_HOST_X64 AND (CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "amd64|x86_64|AMD64" OR CMAKE_HOST_APPLE) ) - set( ANDROID_NDK_HOST_X64 1 CACHE BOOL "Try to use 64-bit compiler toolchain" ) - mark_as_advanced( ANDROID_NDK_HOST_X64 ) -endif() - -set( TOOL_OS_SUFFIX "" ) -if( CMAKE_HOST_APPLE ) - set( ANDROID_NDK_HOST_SYSTEM_NAME "darwin-x86_64" ) - set( ANDROID_NDK_HOST_SYSTEM_NAME2 "darwin-x86" ) -elseif( CMAKE_HOST_WIN32 ) - set( ANDROID_NDK_HOST_SYSTEM_NAME "windows-x86_64" ) - set( ANDROID_NDK_HOST_SYSTEM_NAME2 "windows" ) - set( TOOL_OS_SUFFIX ".exe" ) -elseif( CMAKE_HOST_UNIX ) - set( ANDROID_NDK_HOST_SYSTEM_NAME "linux-x86_64" ) - set( ANDROID_NDK_HOST_SYSTEM_NAME2 "linux-x86" ) -else() - message( FATAL_ERROR "Cross-compilation on your platform is not supported by this cmake toolchain" ) -endif() - -if( NOT ANDROID_NDK_HOST_X64 ) - set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) -endif() - -# see if we have path to Android NDK -if( NOT ANDROID_NDK AND NOT ANDROID_STANDALONE_TOOLCHAIN ) - __INIT_VARIABLE( ANDROID_NDK PATH ENV_ANDROID_NDK ) -endif() -if( NOT ANDROID_NDK ) - # see if we have path to Android standalone toolchain - __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ENV_ANDROID_STANDALONE_TOOLCHAIN ) - - if( NOT ANDROID_STANDALONE_TOOLCHAIN ) - #try to find Android NDK in one of the the default locations - set( __ndkSearchPaths ) - foreach( __ndkSearchPath ${ANDROID_NDK_SEARCH_PATHS} ) - foreach( suffix ${ANDROID_SUPPORTED_NDK_VERSIONS} ) - list( APPEND __ndkSearchPaths "${__ndkSearchPath}/android-ndk${suffix}" ) - endforeach() - endforeach() - __INIT_VARIABLE( ANDROID_NDK PATH VALUES ${__ndkSearchPaths} ) - unset( __ndkSearchPaths ) - - if( ANDROID_NDK ) - message( STATUS "Using default path for Android NDK: ${ANDROID_NDK}" ) - message( STATUS " If you prefer to use a different location, please define a cmake or environment variable: ANDROID_NDK" ) - else() - #try to find Android standalone toolchain in one of the the default locations - __INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH ) - - if( ANDROID_STANDALONE_TOOLCHAIN ) - message( STATUS "Using default path for standalone toolchain ${ANDROID_STANDALONE_TOOLCHAIN}" ) - message( STATUS " If you prefer to use a different location, please define the variable: ANDROID_STANDALONE_TOOLCHAIN" ) - endif( ANDROID_STANDALONE_TOOLCHAIN ) - endif( ANDROID_NDK ) - endif( NOT ANDROID_STANDALONE_TOOLCHAIN ) -endif( NOT ANDROID_NDK ) - -# remember found paths -if( ANDROID_NDK ) - get_filename_component( ANDROID_NDK "${ANDROID_NDK}" ABSOLUTE ) - set( ANDROID_NDK "${ANDROID_NDK}" CACHE INTERNAL "Path of the Android NDK" FORCE ) - set( BUILD_WITH_ANDROID_NDK True ) - if( EXISTS "${ANDROID_NDK}/RELEASE.TXT" ) - file( STRINGS "${ANDROID_NDK}/RELEASE.TXT" ANDROID_NDK_RELEASE_FULL LIMIT_COUNT 1 REGEX "r[0-9]+[a-z]?" ) - string( REGEX MATCH "r([0-9]+)([a-z]?)" ANDROID_NDK_RELEASE "${ANDROID_NDK_RELEASE_FULL}" ) - else() - set( ANDROID_NDK_RELEASE "r1x" ) - set( ANDROID_NDK_RELEASE_FULL "unreleased" ) - endif() - string( REGEX REPLACE "r([0-9]+)([a-z]?)" "\\1*1000" ANDROID_NDK_RELEASE_NUM "${ANDROID_NDK_RELEASE}" ) - string( FIND " abcdefghijklmnopqastuvwxyz" "${CMAKE_MATCH_2}" __ndkReleaseLetterNum ) - math( EXPR ANDROID_NDK_RELEASE_NUM "${ANDROID_NDK_RELEASE_NUM}+${__ndkReleaseLetterNum}" ) -elseif( ANDROID_STANDALONE_TOOLCHAIN ) - get_filename_component( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" ABSOLUTE ) - # try to detect change - if( CMAKE_AR ) - string( LENGTH "${ANDROID_STANDALONE_TOOLCHAIN}" __length ) - string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidStandaloneToolchainPreviousPath ) - if( NOT __androidStandaloneToolchainPreviousPath STREQUAL ANDROID_STANDALONE_TOOLCHAIN ) - message( FATAL_ERROR "It is not possible to change path to the Android standalone toolchain on subsequent run." ) - endif() - unset( __androidStandaloneToolchainPreviousPath ) - unset( __length ) - endif() - set( ANDROID_STANDALONE_TOOLCHAIN "${ANDROID_STANDALONE_TOOLCHAIN}" CACHE INTERNAL "Path of the Android standalone toolchain" FORCE ) - set( BUILD_WITH_STANDALONE_TOOLCHAIN True ) -else() - list(GET ANDROID_NDK_SEARCH_PATHS 0 ANDROID_NDK_SEARCH_PATH) - message( FATAL_ERROR "Could not find neither Android NDK nor Android standalone toolchain. - You should either set an environment variable: - export ANDROID_NDK=~/my-android-ndk - or - export ANDROID_STANDALONE_TOOLCHAIN=~/my-android-toolchain - or put the toolchain or NDK in the default path: - sudo ln -s ~/my-android-ndk ${ANDROID_NDK_SEARCH_PATH}/android-ndk - sudo ln -s ~/my-android-toolchain ${ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH}" ) -endif() - -# android NDK layout -if( BUILD_WITH_ANDROID_NDK ) - if( NOT DEFINED ANDROID_NDK_LAYOUT ) - # try to automatically detect the layout - if( EXISTS "${ANDROID_NDK}/RELEASE.TXT") - set( ANDROID_NDK_LAYOUT "RELEASE" ) - elseif( EXISTS "${ANDROID_NDK}/../../linux-x86/toolchain/" ) - set( ANDROID_NDK_LAYOUT "LINARO" ) - elseif( EXISTS "${ANDROID_NDK}/../../gcc/" ) - set( ANDROID_NDK_LAYOUT "ANDROID" ) - endif() - endif() - set( ANDROID_NDK_LAYOUT "${ANDROID_NDK_LAYOUT}" CACHE STRING "The inner layout of NDK" ) - mark_as_advanced( ANDROID_NDK_LAYOUT ) - if( ANDROID_NDK_LAYOUT STREQUAL "LINARO" ) - set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) # only 32-bit at the moment - set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/../../${ANDROID_NDK_HOST_SYSTEM_NAME}/toolchain" ) - set( ANDROID_NDK_TOOLCHAINS_SUBPATH "" ) - set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "" ) - elseif( ANDROID_NDK_LAYOUT STREQUAL "ANDROID" ) - set( ANDROID_NDK_HOST_SYSTEM_NAME ${ANDROID_NDK_HOST_SYSTEM_NAME2} ) # only 32-bit at the moment - set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/../../gcc/${ANDROID_NDK_HOST_SYSTEM_NAME}/arm" ) - set( ANDROID_NDK_TOOLCHAINS_SUBPATH "" ) - set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "" ) - else() # ANDROID_NDK_LAYOUT STREQUAL "RELEASE" - set( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK}/toolchains" ) - set( ANDROID_NDK_TOOLCHAINS_SUBPATH "/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME}" ) - set( ANDROID_NDK_TOOLCHAINS_SUBPATH2 "/prebuilt/${ANDROID_NDK_HOST_SYSTEM_NAME2}" ) - endif() - get_filename_component( ANDROID_NDK_TOOLCHAINS_PATH "${ANDROID_NDK_TOOLCHAINS_PATH}" ABSOLUTE ) - - # try to detect change of NDK - if( CMAKE_AR ) - string( LENGTH "${ANDROID_NDK_TOOLCHAINS_PATH}" __length ) - string( SUBSTRING "${CMAKE_AR}" 0 ${__length} __androidNdkPreviousPath ) - if( NOT __androidNdkPreviousPath STREQUAL ANDROID_NDK_TOOLCHAINS_PATH ) - message( FATAL_ERROR "It is not possible to change the path to the NDK on subsequent CMake run. You must remove all generated files from your build folder first. - " ) - endif() - unset( __androidNdkPreviousPath ) - unset( __length ) - endif() -endif() - - -# get all the details about standalone toolchain -if( BUILD_WITH_STANDALONE_TOOLCHAIN ) - __DETECT_NATIVE_API_LEVEL( ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot/usr/include/android/api-level.h" ) - set( ANDROID_STANDALONE_TOOLCHAIN_API_LEVEL ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} ) - set( __availableToolchains "standalone" ) - __DETECT_TOOLCHAIN_MACHINE_NAME( __availableToolchainMachines "${ANDROID_STANDALONE_TOOLCHAIN}" ) - if( NOT __availableToolchainMachines ) - message( FATAL_ERROR "Could not determine machine name of your toolchain. Probably your Android standalone toolchain is broken." ) - endif() - if( __availableToolchainMachines MATCHES x86_64 ) - set( __availableToolchainArchs "x86_64" ) - elseif( __availableToolchainMachines MATCHES i686 ) - set( __availableToolchainArchs "x86" ) - elseif( __availableToolchainMachines MATCHES aarch64 ) - set( __availableToolchainArchs "arm64" ) - elseif( __availableToolchainMachines MATCHES arm ) - set( __availableToolchainArchs "arm" ) - elseif( __availableToolchainMachines MATCHES mips64el ) - set( __availableToolchainArchs "mips64" ) - elseif( __availableToolchainMachines MATCHES mipsel ) - set( __availableToolchainArchs "mips" ) - endif() - execute_process( COMMAND "${ANDROID_STANDALONE_TOOLCHAIN}/bin/${__availableToolchainMachines}-gcc${TOOL_OS_SUFFIX}" -dumpversion - OUTPUT_VARIABLE __availableToolchainCompilerVersions OUTPUT_STRIP_TRAILING_WHITESPACE ) - string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9]+)?" __availableToolchainCompilerVersions "${__availableToolchainCompilerVersions}" ) - if( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/bin/clang${TOOL_OS_SUFFIX}" ) - list( APPEND __availableToolchains "standalone-clang" ) - list( APPEND __availableToolchainMachines ${__availableToolchainMachines} ) - list( APPEND __availableToolchainArchs ${__availableToolchainArchs} ) - list( APPEND __availableToolchainCompilerVersions ${__availableToolchainCompilerVersions} ) - endif() -endif() - -macro( __GLOB_NDK_TOOLCHAINS __availableToolchainsVar __availableToolchainsLst __toolchain_subpath ) - foreach( __toolchain ${${__availableToolchainsLst}} ) - if( "${__toolchain}" MATCHES "-clang3[.][0-9]$" AND NOT EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/${__toolchain}${__toolchain_subpath}" ) - SET( __toolchainVersionRegex "^TOOLCHAIN_VERSION[\t ]+:=[\t ]+(.*)$" ) - FILE( STRINGS "${ANDROID_NDK_TOOLCHAINS_PATH}/${__toolchain}/setup.mk" __toolchainVersionStr REGEX "${__toolchainVersionRegex}" ) - if( __toolchainVersionStr ) - string( REGEX REPLACE "${__toolchainVersionRegex}" "\\1" __toolchainVersionStr "${__toolchainVersionStr}" ) - string( REGEX REPLACE "-clang3[.][0-9]$" "-${__toolchainVersionStr}" __gcc_toolchain "${__toolchain}" ) - else() - string( REGEX REPLACE "-clang3[.][0-9]$" "-4.6" __gcc_toolchain "${__toolchain}" ) - endif() - unset( __toolchainVersionStr ) - unset( __toolchainVersionRegex ) - else() - set( __gcc_toolchain "${__toolchain}" ) - endif() - __DETECT_TOOLCHAIN_MACHINE_NAME( __machine "${ANDROID_NDK_TOOLCHAINS_PATH}/${__gcc_toolchain}${__toolchain_subpath}" ) - if( __machine ) - string( REGEX MATCH "[0-9]+[.][0-9]+([.][0-9x]+)?$" __version "${__gcc_toolchain}" ) - if( __machine MATCHES x86_64 ) - set( __arch "x86_64" ) - elseif( __machine MATCHES i686 ) - set( __arch "x86" ) - elseif( __machine MATCHES aarch64 ) - set( __arch "arm64" ) - elseif( __machine MATCHES arm ) - set( __arch "arm" ) - elseif( __machine MATCHES mips64el ) - set( __arch "mips64" ) - elseif( __machine MATCHES mipsel ) - set( __arch "mips" ) - else() - set( __arch "" ) - endif() - #message("machine: !${__machine}!\narch: !${__arch}!\nversion: !${__version}!\ntoolchain: !${__toolchain}!\n") - if (__arch) - list( APPEND __availableToolchainMachines "${__machine}" ) - list( APPEND __availableToolchainArchs "${__arch}" ) - list( APPEND __availableToolchainCompilerVersions "${__version}" ) - list( APPEND ${__availableToolchainsVar} "${__toolchain}" ) - endif() - endif() - unset( __gcc_toolchain ) - endforeach() -endmacro() - -# get all the details about NDK -if( BUILD_WITH_ANDROID_NDK ) - file( GLOB ANDROID_SUPPORTED_NATIVE_API_LEVELS RELATIVE "${ANDROID_NDK}/platforms" "${ANDROID_NDK}/platforms/android-*" ) - string( REPLACE "android-" "" ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_SUPPORTED_NATIVE_API_LEVELS}" ) - set( __availableToolchains "" ) - set( __availableToolchainMachines "" ) - set( __availableToolchainArchs "" ) - set( __availableToolchainCompilerVersions "" ) - if( ANDROID_TOOLCHAIN_NAME AND EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_TOOLCHAIN_NAME}/" ) - # do not go through all toolchains if we know the name - set( __availableToolchainsLst "${ANDROID_TOOLCHAIN_NAME}" ) - __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) - if( NOT __availableToolchains AND NOT ANDROID_NDK_TOOLCHAINS_SUBPATH STREQUAL ANDROID_NDK_TOOLCHAINS_SUBPATH2 ) - __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH2}" ) - if( __availableToolchains ) - set( ANDROID_NDK_TOOLCHAINS_SUBPATH ${ANDROID_NDK_TOOLCHAINS_SUBPATH2} ) - endif() - endif() - endif() - if( NOT __availableToolchains ) - file( GLOB __availableToolchainsLst RELATIVE "${ANDROID_NDK_TOOLCHAINS_PATH}" "${ANDROID_NDK_TOOLCHAINS_PATH}/*" ) - if( __availableToolchainsLst ) - list(SORT __availableToolchainsLst) # we need clang to go after gcc - endif() - __LIST_FILTER( __availableToolchainsLst "^[.]" ) - __LIST_FILTER( __availableToolchainsLst "llvm" ) - __LIST_FILTER( __availableToolchainsLst "renderscript" ) - __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) - if( NOT __availableToolchains AND NOT ANDROID_NDK_TOOLCHAINS_SUBPATH STREQUAL ANDROID_NDK_TOOLCHAINS_SUBPATH2 ) - __GLOB_NDK_TOOLCHAINS( __availableToolchains __availableToolchainsLst "${ANDROID_NDK_TOOLCHAINS_SUBPATH2}" ) - if( __availableToolchains ) - set( ANDROID_NDK_TOOLCHAINS_SUBPATH ${ANDROID_NDK_TOOLCHAINS_SUBPATH2} ) - endif() - endif() - endif() - if( NOT __availableToolchains ) - message( FATAL_ERROR "Could not find any working toolchain in the NDK. Probably your Android NDK is broken." ) - endif() -endif() - -# build list of available ABIs -set( ANDROID_SUPPORTED_ABIS "" ) -set( __uniqToolchainArchNames ${__availableToolchainArchs} ) -list( REMOVE_DUPLICATES __uniqToolchainArchNames ) -list( SORT __uniqToolchainArchNames ) -foreach( __arch ${__uniqToolchainArchNames} ) - list( APPEND ANDROID_SUPPORTED_ABIS ${ANDROID_SUPPORTED_ABIS_${__arch}} ) -endforeach() -unset( __uniqToolchainArchNames ) -if( NOT ANDROID_SUPPORTED_ABIS ) - message( FATAL_ERROR "No one of known Android ABIs is supported by this cmake toolchain." ) -endif() - -# choose target ABI -__INIT_VARIABLE( ANDROID_ABI VALUES ${ANDROID_SUPPORTED_ABIS} ) -# verify that target ABI is supported -list( FIND ANDROID_SUPPORTED_ABIS "${ANDROID_ABI}" __androidAbiIdx ) -if( __androidAbiIdx EQUAL -1 ) - string( REPLACE ";" "\", \"" PRINTABLE_ANDROID_SUPPORTED_ABIS "${ANDROID_SUPPORTED_ABIS}" ) - message( FATAL_ERROR "Specified ANDROID_ABI = \"${ANDROID_ABI}\" is not supported by this cmake toolchain or your NDK/toolchain. - Supported values are: \"${PRINTABLE_ANDROID_SUPPORTED_ABIS}\" - " ) -endif() -unset( __androidAbiIdx ) - -# set target ABI options -if( ANDROID_ABI STREQUAL "x86" ) - set( X86 true ) - set( ANDROID_NDK_ABI_NAME "x86" ) - set( ANDROID_ARCH_NAME "x86" ) - set( ANDROID_LLVM_TRIPLE "i686-none-linux-android" ) - set( CMAKE_SYSTEM_PROCESSOR "i686" ) -elseif( ANDROID_ABI STREQUAL "x86_64" ) - set( X86 true ) - set( X86_64 true ) - set( ANDROID_NDK_ABI_NAME "x86_64" ) - set( ANDROID_ARCH_NAME "x86_64" ) - set( CMAKE_SYSTEM_PROCESSOR "x86_64" ) - set( ANDROID_LLVM_TRIPLE "x86_64-none-linux-android" ) -elseif( ANDROID_ABI STREQUAL "mips64" ) - set( MIPS64 true ) - set( ANDROID_NDK_ABI_NAME "mips64" ) - set( ANDROID_ARCH_NAME "mips64" ) - set( ANDROID_LLVM_TRIPLE "mips64el-none-linux-android" ) - set( CMAKE_SYSTEM_PROCESSOR "mips64" ) -elseif( ANDROID_ABI STREQUAL "mips" ) - set( MIPS true ) - set( ANDROID_NDK_ABI_NAME "mips" ) - set( ANDROID_ARCH_NAME "mips" ) - set( ANDROID_LLVM_TRIPLE "mipsel-none-linux-android" ) - set( CMAKE_SYSTEM_PROCESSOR "mips" ) -elseif( ANDROID_ABI STREQUAL "arm64-v8a" ) - set( ARM64_V8A true ) - set( ANDROID_NDK_ABI_NAME "arm64-v8a" ) - set( ANDROID_ARCH_NAME "arm64" ) - set( ANDROID_LLVM_TRIPLE "aarch64-none-linux-android" ) - set( CMAKE_SYSTEM_PROCESSOR "aarch64" ) - set( VFPV3 true ) - set( NEON true ) -elseif( ANDROID_ABI STREQUAL "armeabi" ) - set( ARMEABI true ) - set( ANDROID_NDK_ABI_NAME "armeabi" ) - set( ANDROID_ARCH_NAME "arm" ) - set( ANDROID_LLVM_TRIPLE "armv5te-none-linux-androideabi" ) - set( CMAKE_SYSTEM_PROCESSOR "armv5te" ) -elseif( ANDROID_ABI STREQUAL "armeabi-v6 with VFP" ) - set( ARMEABI_V6 true ) - set( ANDROID_NDK_ABI_NAME "armeabi" ) - set( ANDROID_ARCH_NAME "arm" ) - set( ANDROID_LLVM_TRIPLE "armv5te-none-linux-androideabi" ) - set( CMAKE_SYSTEM_PROCESSOR "armv6" ) - # need always fallback to older platform - set( ARMEABI true ) -elseif( ANDROID_ABI STREQUAL "armeabi-v7a") - set( ARMEABI_V7A true ) - set( ANDROID_NDK_ABI_NAME "armeabi-v7a" ) - set( ANDROID_ARCH_NAME "arm" ) - set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" ) - set( CMAKE_SYSTEM_PROCESSOR "armv7-a" ) -elseif( ANDROID_ABI STREQUAL "armeabi-v7a with VFPV3" ) - set( ARMEABI_V7A true ) - set( ANDROID_NDK_ABI_NAME "armeabi-v7a" ) - set( ANDROID_ARCH_NAME "arm" ) - set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" ) - set( CMAKE_SYSTEM_PROCESSOR "armv7-a" ) - set( VFPV3 true ) -elseif( ANDROID_ABI STREQUAL "armeabi-v7a with NEON" ) - set( ARMEABI_V7A true ) - set( ANDROID_NDK_ABI_NAME "armeabi-v7a" ) - set( ANDROID_ARCH_NAME "arm" ) - set( ANDROID_LLVM_TRIPLE "armv7-none-linux-androideabi" ) - set( CMAKE_SYSTEM_PROCESSOR "armv7-a" ) - set( VFPV3 true ) - set( NEON true ) -else() - message( SEND_ERROR "Unknown ANDROID_ABI=\"${ANDROID_ABI}\" is specified." ) -endif() - -if( CMAKE_BINARY_DIR AND EXISTS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeSystem.cmake" ) - # really dirty hack - # it is not possible to change CMAKE_SYSTEM_PROCESSOR after the first run... - file( APPEND "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMakeSystem.cmake" "SET(CMAKE_SYSTEM_PROCESSOR \"${CMAKE_SYSTEM_PROCESSOR}\")\n" ) -endif() - -if( ANDROID_ARCH_NAME STREQUAL "arm" AND NOT ARMEABI_V6 ) - __INIT_VARIABLE( ANDROID_FORCE_ARM_BUILD VALUES OFF ) - set( ANDROID_FORCE_ARM_BUILD ${ANDROID_FORCE_ARM_BUILD} CACHE BOOL "Use 32-bit ARM instructions instead of Thumb-1" FORCE ) - mark_as_advanced( ANDROID_FORCE_ARM_BUILD ) -else() - unset( ANDROID_FORCE_ARM_BUILD CACHE ) -endif() - -# choose toolchain -if( ANDROID_TOOLCHAIN_NAME ) - list( FIND __availableToolchains "${ANDROID_TOOLCHAIN_NAME}" __toolchainIdx ) - if( __toolchainIdx EQUAL -1 ) - list( SORT __availableToolchains ) - string( REPLACE ";" "\n * " toolchains_list "${__availableToolchains}" ) - set( toolchains_list " * ${toolchains_list}") - message( FATAL_ERROR "Specified toolchain \"${ANDROID_TOOLCHAIN_NAME}\" is missing in your NDK or broken. Please verify that your NDK is working or select another compiler toolchain. -To configure the toolchain set CMake variable ANDROID_TOOLCHAIN_NAME to one of the following values:\n${toolchains_list}\n" ) - endif() - list( GET __availableToolchainArchs ${__toolchainIdx} __toolchainArch ) - if( NOT __toolchainArch STREQUAL ANDROID_ARCH_NAME ) - message( SEND_ERROR "Selected toolchain \"${ANDROID_TOOLCHAIN_NAME}\" is not able to compile binaries for the \"${ANDROID_ARCH_NAME}\" platform." ) - endif() -else() - set( __toolchainIdx -1 ) - set( __applicableToolchains "" ) - set( __toolchainMaxVersion "0.0.0" ) - list( LENGTH __availableToolchains __availableToolchainsCount ) - math( EXPR __availableToolchainsCount "${__availableToolchainsCount}-1" ) - foreach( __idx RANGE ${__availableToolchainsCount} ) - list( GET __availableToolchainArchs ${__idx} __toolchainArch ) - if( __toolchainArch STREQUAL ANDROID_ARCH_NAME ) - list( GET __availableToolchainCompilerVersions ${__idx} __toolchainVersion ) - string( REPLACE "x" "99" __toolchainVersion "${__toolchainVersion}") - if( __toolchainVersion VERSION_GREATER __toolchainMaxVersion ) - set( __toolchainMaxVersion "${__toolchainVersion}" ) - set( __toolchainIdx ${__idx} ) - endif() - endif() - endforeach() - unset( __availableToolchainsCount ) - unset( __toolchainMaxVersion ) - unset( __toolchainVersion ) -endif() -unset( __toolchainArch ) -if( __toolchainIdx EQUAL -1 ) - message( FATAL_ERROR "No one of available compiler toolchains is able to compile for ${ANDROID_ARCH_NAME} platform." ) -endif() -list( GET __availableToolchains ${__toolchainIdx} ANDROID_TOOLCHAIN_NAME ) -list( GET __availableToolchainMachines ${__toolchainIdx} ANDROID_TOOLCHAIN_MACHINE_NAME ) -list( GET __availableToolchainCompilerVersions ${__toolchainIdx} ANDROID_COMPILER_VERSION ) - -unset( __toolchainIdx ) -unset( __availableToolchains ) -unset( __availableToolchainMachines ) -unset( __availableToolchainArchs ) -unset( __availableToolchainCompilerVersions ) - -# choose native API level -__INIT_VARIABLE( ANDROID_NATIVE_API_LEVEL ENV_ANDROID_NATIVE_API_LEVEL ANDROID_API_LEVEL ENV_ANDROID_API_LEVEL ANDROID_STANDALONE_TOOLCHAIN_API_LEVEL ANDROID_DEFAULT_NDK_API_LEVEL_${ANDROID_ARCH_NAME} ANDROID_DEFAULT_NDK_API_LEVEL ) -string( REPLACE "android-" "" ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" ) -string( STRIP "${ANDROID_NATIVE_API_LEVEL}" ANDROID_NATIVE_API_LEVEL ) -# adjust API level -set( __real_api_level ${ANDROID_DEFAULT_NDK_API_LEVEL_${ANDROID_ARCH_NAME}} ) -foreach( __level ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} ) - if( (__level LESS ANDROID_NATIVE_API_LEVEL OR __level STREQUAL ANDROID_NATIVE_API_LEVEL) AND NOT __level LESS __real_api_level ) - set( __real_api_level ${__level} ) - endif() -endforeach() -if( __real_api_level AND NOT ANDROID_NATIVE_API_LEVEL STREQUAL __real_api_level ) - message( STATUS "Adjusting Android API level 'android-${ANDROID_NATIVE_API_LEVEL}' to 'android-${__real_api_level}'") - set( ANDROID_NATIVE_API_LEVEL ${__real_api_level} ) -endif() -unset(__real_api_level) -# validate -list( FIND ANDROID_SUPPORTED_NATIVE_API_LEVELS "${ANDROID_NATIVE_API_LEVEL}" __levelIdx ) -if( __levelIdx EQUAL -1 ) - message( SEND_ERROR "Specified Android native API level 'android-${ANDROID_NATIVE_API_LEVEL}' is not supported by your NDK/toolchain." ) -else() - if( BUILD_WITH_ANDROID_NDK ) - __DETECT_NATIVE_API_LEVEL( __realApiLevel "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}/usr/include/android/api-level.h" ) - if( NOT __realApiLevel EQUAL ANDROID_NATIVE_API_LEVEL AND NOT __realApiLevel GREATER 9000 ) - message( SEND_ERROR "Specified Android API level (${ANDROID_NATIVE_API_LEVEL}) does not match to the level found (${__realApiLevel}). Probably your copy of NDK is broken." ) - endif() - unset( __realApiLevel ) - endif() - set( ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" CACHE STRING "Android API level for native code" FORCE ) - set( CMAKE_ANDROID_API ${ANDROID_NATIVE_API_LEVEL} ) - if( CMAKE_VERSION VERSION_GREATER "2.8" ) - list( SORT ANDROID_SUPPORTED_NATIVE_API_LEVELS ) - set_property( CACHE ANDROID_NATIVE_API_LEVEL PROPERTY STRINGS ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} ) - endif() -endif() -unset( __levelIdx ) - - -# remember target ABI -set( ANDROID_ABI "${ANDROID_ABI}" CACHE STRING "The target ABI for Android. If arm, then armeabi-v7a is recommended for hardware floating point." FORCE ) -if( CMAKE_VERSION VERSION_GREATER "2.8" ) - list( SORT ANDROID_SUPPORTED_ABIS_${ANDROID_ARCH_NAME} ) - set_property( CACHE ANDROID_ABI PROPERTY STRINGS ${ANDROID_SUPPORTED_ABIS_${ANDROID_ARCH_NAME}} ) -endif() - - -# runtime choice (STL, rtti, exceptions) -if( NOT ANDROID_STL ) - set( ANDROID_STL gnustl_static ) -endif() -set( ANDROID_STL "${ANDROID_STL}" CACHE STRING "C++ runtime" ) -set( ANDROID_STL_FORCE_FEATURES ON CACHE BOOL "automatically configure rtti and exceptions support based on C++ runtime" ) -mark_as_advanced( ANDROID_STL ANDROID_STL_FORCE_FEATURES ) - -if( BUILD_WITH_ANDROID_NDK ) - if( NOT "${ANDROID_STL}" MATCHES "^(none|system|system_re|gabi\\+\\+_static|gabi\\+\\+_shared|stlport_static|stlport_shared|gnustl_static|gnustl_shared|c\\+\\+_static|c\\+\\+_shared)$") - message( FATAL_ERROR "ANDROID_STL is set to invalid value \"${ANDROID_STL}\". -The possible values are: - none -> Do not configure the runtime. - system -> Use the default minimal system C++ runtime library. - system_re -> Same as system but with rtti and exceptions. - gabi++_static -> Use the GAbi++ runtime as a static library. - gabi++_shared -> Use the GAbi++ runtime as a shared library. - stlport_static -> Use the STLport runtime as a static library. - stlport_shared -> Use the STLport runtime as a shared library. - gnustl_static -> (default) Use the GNU STL as a static library. - gnustl_shared -> Use the GNU STL as a shared library. - c++_static -> Use the LLVM libc++ runtime as a static library. - c++_shared -> Use the LLVM libc++ runtime as a shared library. -" ) - endif() -elseif( BUILD_WITH_STANDALONE_TOOLCHAIN ) - if( NOT "${ANDROID_STL}" MATCHES "^(none|gnustl_static|gnustl_shared)$") - message( FATAL_ERROR "ANDROID_STL is set to invalid value \"${ANDROID_STL}\". -The possible values are: - none -> Do not configure the runtime. - gnustl_static -> (default) Use the GNU STL as a static library. - gnustl_shared -> Use the GNU STL as a shared library. -" ) - endif() -endif() - -unset( ANDROID_RTTI ) -unset( ANDROID_EXCEPTIONS ) -unset( ANDROID_STL_INCLUDE_DIRS ) -unset( __libstl ) -unset( __libsupcxx ) - -if( NOT _CMAKE_IN_TRY_COMPILE AND ANDROID_NDK_RELEASE STREQUAL "r7b" AND ARMEABI_V7A AND NOT VFPV3 AND ANDROID_STL MATCHES "gnustl" ) - message( WARNING "The GNU STL armeabi-v7a binaries from NDK r7b can crash non-NEON devices. The files provided with NDK r7b were not configured properly, resulting in crashes on Tegra2-based devices and others when trying to use certain floating-point functions (e.g., cosf, sinf, expf). -You are strongly recommended to switch to another NDK release. -" ) -endif() - -if( NOT _CMAKE_IN_TRY_COMPILE AND X86 AND ANDROID_STL MATCHES "gnustl" AND ANDROID_NDK_RELEASE STREQUAL "r6" ) - message( WARNING "The x86 system header file from NDK r6 has incorrect definition for ptrdiff_t. You are recommended to upgrade to a newer NDK release or manually patch the header: -See https://android.googlesource.com/platform/development.git f907f4f9d4e56ccc8093df6fee54454b8bcab6c2 - diff --git a/ndk/platforms/android-9/arch-x86/include/machine/_types.h b/ndk/platforms/android-9/arch-x86/include/machine/_types.h - index 5e28c64..65892a1 100644 - --- a/ndk/platforms/android-9/arch-x86/include/machine/_types.h - +++ b/ndk/platforms/android-9/arch-x86/include/machine/_types.h - @@ -51,7 +51,11 @@ typedef long int ssize_t; - #endif - #ifndef _PTRDIFF_T - #define _PTRDIFF_T - -typedef long ptrdiff_t; - +# ifdef __ANDROID__ - + typedef int ptrdiff_t; - +# else - + typedef long ptrdiff_t; - +# endif - #endif -" ) -endif() - - -# setup paths and STL for standalone toolchain -if( BUILD_WITH_STANDALONE_TOOLCHAIN ) - set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_STANDALONE_TOOLCHAIN}" ) - set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_STANDALONE_TOOLCHAIN}" ) - set( ANDROID_SYSROOT "${ANDROID_STANDALONE_TOOLCHAIN}/sysroot" ) - - if( NOT ANDROID_STL STREQUAL "none" ) - set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_STANDALONE_TOOLCHAIN}/include/c++/${ANDROID_COMPILER_VERSION}" ) - if( NOT EXISTS "${ANDROID_STL_INCLUDE_DIRS}" ) - # old location ( pre r8c ) - set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/include/c++/${ANDROID_COMPILER_VERSION}" ) - endif() - if( ARMEABI_V7A AND EXISTS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}/bits" ) - list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/${CMAKE_SYSTEM_PROCESSOR}" ) - elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/thumb/bits" ) - list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/thumb" ) - else() - list( APPEND ANDROID_STL_INCLUDE_DIRS "${ANDROID_STL_INCLUDE_DIRS}/${ANDROID_TOOLCHAIN_MACHINE_NAME}" ) - endif() - # always search static GNU STL to get the location of libsupc++.a - if( ARMEABI_V7A AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb/libstdc++.a" ) - set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb" ) - elseif( ARMEABI_V7A AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libstdc++.a" ) - set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}" ) - elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libstdc++.a" ) - set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb" ) - elseif( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libstdc++.a" ) - set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib" ) - endif() - if( __libstl ) - set( __libsupcxx "${__libstl}/libsupc++.a" ) - set( __libstl "${__libstl}/libstdc++.a" ) - endif() - if( NOT EXISTS "${__libsupcxx}" ) - message( FATAL_ERROR "The required libstdsupc++.a is missing in your standalone toolchain. - Usually it happens because of bug in make-standalone-toolchain.sh script from NDK r7, r7b and r7c. - You need to either upgrade to newer NDK or manually copy - $ANDROID_NDK/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a - to - ${__libsupcxx} - " ) - endif() - if( ANDROID_STL STREQUAL "gnustl_shared" ) - if( ARMEABI_V7A AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libgnustl_shared.so" ) - set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libgnustl_shared.so" ) - elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD AND EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libgnustl_shared.so" ) - set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libgnustl_shared.so" ) - elseif( EXISTS "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libgnustl_shared.so" ) - set( __libstl "${ANDROID_STANDALONE_TOOLCHAIN}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libgnustl_shared.so" ) - endif() - endif() - endif() -endif() - -# clang -if( "${ANDROID_TOOLCHAIN_NAME}" STREQUAL "standalone-clang" ) - set( ANDROID_COMPILER_IS_CLANG 1 ) - execute_process( COMMAND "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/clang${TOOL_OS_SUFFIX}" --version OUTPUT_VARIABLE ANDROID_CLANG_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE ) - string( REGEX MATCH "[0-9]+[.][0-9]+" ANDROID_CLANG_VERSION "${ANDROID_CLANG_VERSION}") -elseif( "${ANDROID_TOOLCHAIN_NAME}" MATCHES "-clang3[.][0-9]?$" ) - string( REGEX MATCH "3[.][0-9]$" ANDROID_CLANG_VERSION "${ANDROID_TOOLCHAIN_NAME}") - string( REGEX REPLACE "-clang${ANDROID_CLANG_VERSION}$" "-${ANDROID_COMPILER_VERSION}" ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" ) - if( NOT EXISTS "${ANDROID_NDK_TOOLCHAINS_PATH}/llvm-${ANDROID_CLANG_VERSION}${ANDROID_NDK_TOOLCHAINS_SUBPATH}/bin/clang${TOOL_OS_SUFFIX}" ) - message( FATAL_ERROR "Could not find the Clang compiler driver" ) - endif() - set( ANDROID_COMPILER_IS_CLANG 1 ) - set( ANDROID_CLANG_TOOLCHAIN_ROOT "${ANDROID_NDK_TOOLCHAINS_PATH}/llvm-${ANDROID_CLANG_VERSION}${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) -else() - set( ANDROID_GCC_TOOLCHAIN_NAME "${ANDROID_TOOLCHAIN_NAME}" ) - unset( ANDROID_COMPILER_IS_CLANG CACHE ) -endif() - -string( REPLACE "." "" _clang_name "clang${ANDROID_CLANG_VERSION}" ) -if( NOT EXISTS "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" ) - set( _clang_name "clang" ) -endif() - - -# setup paths and STL for NDK -if( BUILD_WITH_ANDROID_NDK ) - set( ANDROID_TOOLCHAIN_ROOT "${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}${ANDROID_NDK_TOOLCHAINS_SUBPATH}" ) - set( ANDROID_SYSROOT "${ANDROID_NDK}/platforms/android-${ANDROID_NATIVE_API_LEVEL}/arch-${ANDROID_ARCH_NAME}" ) - - if( ANDROID_STL STREQUAL "none" ) - # do nothing - elseif( ANDROID_STL STREQUAL "system" ) - set( ANDROID_RTTI OFF ) - set( ANDROID_EXCEPTIONS OFF ) - set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/system/include" ) - elseif( ANDROID_STL STREQUAL "system_re" ) - set( ANDROID_RTTI ON ) - set( ANDROID_EXCEPTIONS ON ) - set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/system/include" ) - elseif( ANDROID_STL MATCHES "gabi" ) - if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7 - message( FATAL_ERROR "gabi++ is not available in your NDK. You have to upgrade to NDK r7 or newer to use gabi++.") - endif() - set( ANDROID_RTTI ON ) - set( ANDROID_EXCEPTIONS OFF ) - set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/gabi++/include" ) - set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gabi++/libs/${ANDROID_NDK_ABI_NAME}/libgabi++_static.a" ) - elseif( ANDROID_STL MATCHES "stlport" ) - if( NOT ANDROID_NDK_RELEASE_NUM LESS 8004 ) # before r8d - set( ANDROID_EXCEPTIONS ON ) - else() - set( ANDROID_EXCEPTIONS OFF ) - endif() - if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7 - set( ANDROID_RTTI OFF ) - else() - set( ANDROID_RTTI ON ) - endif() - set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/stlport/stlport" ) - set( __libstl "${ANDROID_NDK}/sources/cxx-stl/stlport/libs/${ANDROID_NDK_ABI_NAME}/libstlport_static.a" ) - elseif( ANDROID_STL MATCHES "gnustl" ) - set( ANDROID_EXCEPTIONS ON ) - set( ANDROID_RTTI ON ) - if( EXISTS "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}" ) - if( ARMEABI_V7A AND ANDROID_COMPILER_VERSION VERSION_EQUAL "4.7" AND ANDROID_NDK_RELEASE STREQUAL "r8d" ) - # gnustl binary for 4.7 compiler is buggy :( - # TODO: look for right fix - set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/4.6" ) - else() - set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}" ) - endif() - else() - set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++" ) - endif() - set( ANDROID_STL_INCLUDE_DIRS "${__libstl}/include" "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/include" "${__libstl}/include/backward" ) - if( EXISTS "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" ) - set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" ) - else() - set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libstdc++.a" ) - endif() - elseif( ANDROID_STL MATCHES "c\\+\\+_shared" OR ANDROID_STL MATCHES "c\\+\\+_static" ) - set( ANDROID_EXCEPTIONS ON ) - set( ANDROID_RTTI ON ) - set( ANDROID_CXX_ROOT "${ANDROID_NDK}/sources/cxx-stl/" ) - set( ANDROID_LLVM_ROOT "${ANDROID_CXX_ROOT}/llvm-libc++" ) - - if( X86 ) - set( ANDROID_ABI_INCLUDE_DIRS "${ANDROID_CXX_ROOT}/gabi++/include" ) - else() - set( ANDROID_ABI_INCLUDE_DIRS "${ANDROID_CXX_ROOT}/llvm-libc++abi/include" ) - endif() - - set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_LLVM_ROOT}/libcxx/include" "${ANDROID_ABI_INCLUDE_DIRS}" ) - - # android support sfiles - include_directories ( SYSTEM ${ANDROID_NDK}/sources/android/support/include ) - - if(ANDROID_STL MATCHES "c\\+\\+_shared") - set ( LLVM_LIBRARY_NAME "libc++_shared.so") - else() - set ( LLVM_LIBRARY_NAME "libc++_static.a" ) - endif () - - if( EXISTS "${ANDROID_LLVM_ROOT}/libs/${ANDROID_NDK_ABI_NAME}/${LLVM_LIBRARY_NAME}" ) - set( __libstl "${ANDROID_LLVM_ROOT}/libs/${ANDROID_NDK_ABI_NAME}/${LLVM_LIBRARY_NAME}" ) - else() - message( FATAL_ERROR "Could not find libc++ library" ) - endif() - else() - message( FATAL_ERROR "Unknown runtime: ${ANDROID_STL}" ) - endif() - # find libsupc++.a - rtti & exceptions - if( ANDROID_STL STREQUAL "system_re" OR ANDROID_STL MATCHES "gnustl" ) - set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/${ANDROID_COMPILER_VERSION}/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) # r8b or newer - if( NOT EXISTS "${__libsupcxx}" ) - set( __libsupcxx "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/libs/${ANDROID_NDK_ABI_NAME}/libsupc++.a" ) # r7-r8 - endif() - if( NOT EXISTS "${__libsupcxx}" ) # before r7 - if( ARMEABI_V7A ) - if( ANDROID_FORCE_ARM_BUILD ) - set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/libsupc++.a" ) - else() - set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/${CMAKE_SYSTEM_PROCESSOR}/thumb/libsupc++.a" ) - endif() - elseif( ARMEABI AND NOT ANDROID_FORCE_ARM_BUILD ) - set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/thumb/libsupc++.a" ) - else() - set( __libsupcxx "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}/lib/libsupc++.a" ) - endif() - endif() - if( NOT EXISTS "${__libsupcxx}") - message( ERROR "Could not find libsupc++.a for a chosen platform. Either your NDK is not supported or is broken.") - endif() - endif() -endif() - - -# case of shared STL linkage -if( ANDROID_STL MATCHES "shared" AND DEFINED __libstl ) - string( REPLACE "_static.a" "_shared.so" __libstl "${__libstl}" ) - # TODO: check if .so file exists before the renaming -endif() - - -# ccache support -__INIT_VARIABLE( _ndk_ccache NDK_CCACHE ENV_NDK_CCACHE ) -if( _ndk_ccache ) - if( DEFINED NDK_CCACHE AND NOT EXISTS NDK_CCACHE ) - unset( NDK_CCACHE CACHE ) - endif() - find_program( NDK_CCACHE "${_ndk_ccache}" DOC "The path to ccache binary") -else() - unset( NDK_CCACHE CACHE ) -endif() -unset( _ndk_ccache ) - - -# setup the cross-compiler -if( NOT CMAKE_C_COMPILER ) - if( NDK_CCACHE AND NOT ANDROID_SYSROOT MATCHES "[ ;\"]" ) - set( CMAKE_C_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C compiler" ) - set( CMAKE_CXX_COMPILER "${NDK_CCACHE}" CACHE PATH "ccache as C++ compiler" ) - if( ANDROID_COMPILER_IS_CLANG ) - set( CMAKE_C_COMPILER_ARG1 "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" CACHE PATH "C compiler") - set( CMAKE_CXX_COMPILER_ARG1 "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") - else() - set( CMAKE_C_COMPILER_ARG1 "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "C compiler") - set( CMAKE_CXX_COMPILER_ARG1 "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-g++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") - endif() - else() - if( ANDROID_COMPILER_IS_CLANG ) - set( CMAKE_C_COMPILER "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}${TOOL_OS_SUFFIX}" CACHE PATH "C compiler") - set( CMAKE_CXX_COMPILER "${ANDROID_CLANG_TOOLCHAIN_ROOT}/bin/${_clang_name}++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler") - else() - set( CMAKE_C_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "C compiler" ) - set( CMAKE_CXX_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-g++${TOOL_OS_SUFFIX}" CACHE PATH "C++ compiler" ) - endif() - endif() - set( CMAKE_ASM_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "assembler" ) - set( CMAKE_STRIP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-strip${TOOL_OS_SUFFIX}" CACHE PATH "strip" ) - if( EXISTS "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc-ar${TOOL_OS_SUFFIX}" ) - # Use gcc-ar if we have it for better LTO support. - set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" ) - else() - set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" ) - endif() - set( CMAKE_LINKER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ld${TOOL_OS_SUFFIX}" CACHE PATH "linker" ) - set( CMAKE_NM "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-nm${TOOL_OS_SUFFIX}" CACHE PATH "nm" ) - set( CMAKE_OBJCOPY "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objcopy${TOOL_OS_SUFFIX}" CACHE PATH "objcopy" ) - set( CMAKE_OBJDUMP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objdump${TOOL_OS_SUFFIX}" CACHE PATH "objdump" ) - set( CMAKE_RANLIB "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ranlib${TOOL_OS_SUFFIX}" CACHE PATH "ranlib" ) -endif() - -set( _CMAKE_TOOLCHAIN_PREFIX "${ANDROID_TOOLCHAIN_MACHINE_NAME}-" ) -if( CMAKE_VERSION VERSION_LESS 2.8.5 ) - set( CMAKE_ASM_COMPILER_ARG1 "-c" ) -endif() -if( APPLE ) - find_program( CMAKE_INSTALL_NAME_TOOL NAMES install_name_tool ) - if( NOT CMAKE_INSTALL_NAME_TOOL ) - message( FATAL_ERROR "Could not find install_name_tool, please check your installation." ) - endif() - mark_as_advanced( CMAKE_INSTALL_NAME_TOOL ) -endif() - -# Force set compilers because standard identification works badly for us -include( CMakeForceCompiler ) -CMAKE_FORCE_C_COMPILER( "${CMAKE_C_COMPILER}" GNU ) -if( ANDROID_COMPILER_IS_CLANG ) - set( CMAKE_C_COMPILER_ID Clang ) -endif() -set( CMAKE_C_PLATFORM_ID Linux ) -if( X86_64 OR MIPS64 OR ARM64_V8A ) - set( CMAKE_C_SIZEOF_DATA_PTR 8 ) -else() - set( CMAKE_C_SIZEOF_DATA_PTR 4 ) -endif() -set( CMAKE_C_HAS_ISYSROOT 1 ) -set( CMAKE_C_COMPILER_ABI ELF ) -CMAKE_FORCE_CXX_COMPILER( "${CMAKE_CXX_COMPILER}" GNU ) -if( ANDROID_COMPILER_IS_CLANG ) - set( CMAKE_CXX_COMPILER_ID Clang) -endif() -set( CMAKE_CXX_PLATFORM_ID Linux ) -set( CMAKE_CXX_SIZEOF_DATA_PTR ${CMAKE_C_SIZEOF_DATA_PTR} ) -set( CMAKE_CXX_HAS_ISYSROOT 1 ) -set( CMAKE_CXX_COMPILER_ABI ELF ) -set( CMAKE_CXX_SOURCE_FILE_EXTENSIONS cc cp cxx cpp CPP c++ C ) -# force ASM compiler (required for CMake < 2.8.5) -set( CMAKE_ASM_COMPILER_ID_RUN TRUE ) -set( CMAKE_ASM_COMPILER_ID GNU ) -set( CMAKE_ASM_COMPILER_WORKS TRUE ) -set( CMAKE_ASM_COMPILER_FORCED TRUE ) -set( CMAKE_COMPILER_IS_GNUASM 1) -set( CMAKE_ASM_SOURCE_FILE_EXTENSIONS s S asm ) - -foreach( lang C CXX ASM ) - if( ANDROID_COMPILER_IS_CLANG ) - set( CMAKE_${lang}_COMPILER_VERSION ${ANDROID_CLANG_VERSION} ) - else() - set( CMAKE_${lang}_COMPILER_VERSION ${ANDROID_COMPILER_VERSION} ) - endif() -endforeach() - -# flags and definitions -remove_definitions( -DANDROID ) -add_definitions( -DANDROID ) - -if( ANDROID_SYSROOT MATCHES "[ ;\"]" ) - if( CMAKE_HOST_WIN32 ) - # try to convert path to 8.3 form - file( WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/cvt83.cmd" "@echo %~s1" ) - execute_process( COMMAND "$ENV{ComSpec}" /c "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/cvt83.cmd" "${ANDROID_SYSROOT}" - OUTPUT_VARIABLE __path OUTPUT_STRIP_TRAILING_WHITESPACE - RESULT_VARIABLE __result ERROR_QUIET ) - if( __result EQUAL 0 ) - file( TO_CMAKE_PATH "${__path}" ANDROID_SYSROOT ) - set( ANDROID_CXX_FLAGS "--sysroot=${ANDROID_SYSROOT}" ) - else() - set( ANDROID_CXX_FLAGS "--sysroot=\"${ANDROID_SYSROOT}\"" ) - endif() - else() - set( ANDROID_CXX_FLAGS "'--sysroot=${ANDROID_SYSROOT}'" ) - endif() - if( NOT _CMAKE_IN_TRY_COMPILE ) - # quotes can break try_compile and compiler identification - message(WARNING "Path to your Android NDK (or toolchain) has non-alphanumeric symbols.\nThe build might be broken.\n") - endif() -else() - set( ANDROID_CXX_FLAGS "--sysroot=${ANDROID_SYSROOT}" ) -endif() - -# NDK flags -if (ARM64_V8A ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" ) - set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer -fstrict-aliasing" ) - set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer -fno-strict-aliasing" ) - if( NOT ANDROID_COMPILER_IS_CLANG ) - set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} -funswitch-loops -finline-limit=300" ) - endif() -elseif( ARMEABI OR ARMEABI_V7A) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" ) - if( NOT ANDROID_FORCE_ARM_BUILD AND NOT ARMEABI_V6 ) - set( ANDROID_CXX_FLAGS_RELEASE "-mthumb -fomit-frame-pointer -fno-strict-aliasing" ) - set( ANDROID_CXX_FLAGS_DEBUG "-marm -fno-omit-frame-pointer -fno-strict-aliasing" ) - if( NOT ANDROID_COMPILER_IS_CLANG ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -finline-limit=64" ) - endif() - else() - # always compile ARMEABI_V6 in arm mode; otherwise there is no difference from ARMEABI - set( ANDROID_CXX_FLAGS_RELEASE "-marm -fomit-frame-pointer -fstrict-aliasing" ) - set( ANDROID_CXX_FLAGS_DEBUG "-marm -fno-omit-frame-pointer -fno-strict-aliasing" ) - if( NOT ANDROID_COMPILER_IS_CLANG ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funswitch-loops -finline-limit=300" ) - endif() - endif() -elseif( X86 OR X86_64 ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" ) - if( NOT ANDROID_COMPILER_IS_CLANG ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funswitch-loops -finline-limit=300" ) - endif() - set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer -fstrict-aliasing" ) - set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer -fno-strict-aliasing" ) -elseif( MIPS OR MIPS64 ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fno-strict-aliasing -finline-functions -funwind-tables -fmessage-length=0" ) - set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer" ) - set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer" ) - if( NOT ANDROID_COMPILER_IS_CLANG ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fno-inline-functions-called-once -fgcse-after-reload -frerun-cse-after-loop -frename-registers" ) - set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} -funswitch-loops -finline-limit=300" ) - endif() -elseif() - set( ANDROID_CXX_FLAGS_RELEASE "" ) - set( ANDROID_CXX_FLAGS_DEBUG "" ) -endif() - -set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fsigned-char" ) # good/necessary when porting desktop libraries - -if( NOT X86 AND NOT ANDROID_COMPILER_IS_CLANG ) - set( ANDROID_CXX_FLAGS "-Wno-psabi ${ANDROID_CXX_FLAGS}" ) -endif() - -if( NOT ANDROID_COMPILER_VERSION VERSION_LESS "4.6" ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -no-canonical-prefixes" ) # see https://android-review.googlesource.com/#/c/47564/ -endif() - -# ABI-specific flags -if( ARMEABI_V7A ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv7-a -mfloat-abi=softfp" ) - if( NEON ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=neon" ) - elseif( VFPV3 ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=vfpv3" ) - else() - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -mfpu=vfpv3-d16" ) - endif() -elseif( ARMEABI_V6 ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv6 -mfloat-abi=softfp -mfpu=vfp" ) # vfp == vfpv2 -elseif( ARMEABI ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -march=armv5te -mtune=xscale -msoft-float" ) -endif() - -if( ANDROID_STL MATCHES "gnustl" AND (EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}") ) - set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) - set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) - set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) -else() - set( CMAKE_CXX_CREATE_SHARED_LIBRARY " -o " ) - set( CMAKE_CXX_CREATE_SHARED_MODULE " -o " ) - set( CMAKE_CXX_LINK_EXECUTABLE " -o " ) -endif() - -# STL -if( EXISTS "${__libstl}" OR EXISTS "${__libsupcxx}" ) - if( EXISTS "${__libstl}" ) - set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${__libstl}\"" ) - set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${__libstl}\"" ) - set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} \"${__libstl}\"" ) - endif() - if( EXISTS "${__libsupcxx}" ) - set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${__libsupcxx}\"" ) - set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${__libsupcxx}\"" ) - set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} \"${__libsupcxx}\"" ) - # C objects: - set( CMAKE_C_CREATE_SHARED_LIBRARY " -o " ) - set( CMAKE_C_CREATE_SHARED_MODULE " -o " ) - set( CMAKE_C_LINK_EXECUTABLE " -o " ) - set( CMAKE_C_CREATE_SHARED_LIBRARY "${CMAKE_C_CREATE_SHARED_LIBRARY} \"${__libsupcxx}\"" ) - set( CMAKE_C_CREATE_SHARED_MODULE "${CMAKE_C_CREATE_SHARED_MODULE} \"${__libsupcxx}\"" ) - set( CMAKE_C_LINK_EXECUTABLE "${CMAKE_C_LINK_EXECUTABLE} \"${__libsupcxx}\"" ) - endif() - if( ANDROID_STL MATCHES "gnustl" ) - if( NOT EXISTS "${ANDROID_LIBM_PATH}" ) - set( ANDROID_LIBM_PATH -lm ) - endif() - set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} ${ANDROID_LIBM_PATH}" ) - set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} ${ANDROID_LIBM_PATH}" ) - set( CMAKE_CXX_LINK_EXECUTABLE "${CMAKE_CXX_LINK_EXECUTABLE} ${ANDROID_LIBM_PATH}" ) - endif() -endif() - -# variables controlling optional build flags -if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7 - # libGLESv2.so in NDK's prior to r7 refers to missing external symbols. - # So this flag option is required for all projects using OpenGL from native. - __INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES ON ) -else() - __INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES OFF ) -endif() -__INIT_VARIABLE( ANDROID_NO_UNDEFINED VALUES ON ) -__INIT_VARIABLE( ANDROID_FUNCTION_LEVEL_LINKING VALUES ON ) -__INIT_VARIABLE( ANDROID_GOLD_LINKER VALUES ON ) -__INIT_VARIABLE( ANDROID_NOEXECSTACK VALUES ON ) -__INIT_VARIABLE( ANDROID_RELRO VALUES ON ) - -set( ANDROID_NO_UNDEFINED ${ANDROID_NO_UNDEFINED} CACHE BOOL "Show all undefined symbols as linker errors" ) -set( ANDROID_SO_UNDEFINED ${ANDROID_SO_UNDEFINED} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" ) -set( ANDROID_FUNCTION_LEVEL_LINKING ${ANDROID_FUNCTION_LEVEL_LINKING} CACHE BOOL "Put each function in separate section and enable garbage collection of unused input sections at link time" ) -set( ANDROID_GOLD_LINKER ${ANDROID_GOLD_LINKER} CACHE BOOL "Enables gold linker" ) -set( ANDROID_NOEXECSTACK ${ANDROID_NOEXECSTACK} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" ) -set( ANDROID_RELRO ${ANDROID_RELRO} CACHE BOOL "Enables RELRO - a memory corruption mitigation technique" ) -mark_as_advanced( ANDROID_NO_UNDEFINED ANDROID_SO_UNDEFINED ANDROID_FUNCTION_LEVEL_LINKING ANDROID_GOLD_LINKER ANDROID_NOEXECSTACK ANDROID_RELRO ) - -# linker flags -set( ANDROID_LINKER_FLAGS "" ) - -if( ARMEABI_V7A ) - # this is *required* to use the following linker flags that routes around - # a CPU bug in some Cortex-A8 implementations: - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--fix-cortex-a8" ) -endif() - -if( ANDROID_NO_UNDEFINED ) - if( MIPS ) - # there is some sysroot-related problem in mips linker... - if( NOT ANDROID_SYSROOT MATCHES "[ ;\"]" ) - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined -Wl,-rpath-link,${ANDROID_SYSROOT}/usr/lib" ) - endif() - else() - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--no-undefined" ) - endif() -endif() - -if( ANDROID_SO_UNDEFINED ) - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-allow-shlib-undefined" ) -endif() - -if( ANDROID_FUNCTION_LEVEL_LINKING ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fdata-sections -ffunction-sections" ) - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,--gc-sections" ) -endif() - -if( ANDROID_COMPILER_VERSION VERSION_EQUAL "4.6" ) - if( ANDROID_GOLD_LINKER AND (CMAKE_HOST_UNIX OR ANDROID_NDK_RELEASE_NUM GREATER 8002) AND (ARMEABI OR ARMEABI_V7A OR X86) ) - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -fuse-ld=gold" ) - elseif( ANDROID_NDK_RELEASE_NUM GREATER 8002 ) # after r8b - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -fuse-ld=bfd" ) - elseif( ANDROID_NDK_RELEASE STREQUAL "r8b" AND ARMEABI AND NOT _CMAKE_IN_TRY_COMPILE ) - message( WARNING "The default bfd linker from arm GCC 4.6 toolchain can fail with 'unresolvable R_ARM_THM_CALL relocation' error message. See https://code.google.com/p/android/issues/detail?id=35342 - On Linux and OS X host platform you can workaround this problem using gold linker (default). - Rerun cmake with -DANDROID_GOLD_LINKER=ON option in case of problems. -" ) - endif() -endif() # version 4.6 - -if( ANDROID_NOEXECSTACK ) - if( ANDROID_COMPILER_IS_CLANG ) - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -Xclang -mnoexecstack" ) - else() - set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -Wa,--noexecstack" ) - endif() - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-z,noexecstack" ) -endif() - -if( ANDROID_RELRO ) - set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} -Wl,-z,relro -Wl,-z,now" ) -endif() - -if( ANDROID_COMPILER_IS_CLANG ) - set( ANDROID_CXX_FLAGS "-target ${ANDROID_LLVM_TRIPLE} -Qunused-arguments ${ANDROID_CXX_FLAGS}" ) - if( BUILD_WITH_ANDROID_NDK ) - set( ANDROID_CXX_FLAGS "-gcc-toolchain ${ANDROID_TOOLCHAIN_ROOT} ${ANDROID_CXX_FLAGS}" ) - endif() -endif() - -# cache flags -set( CMAKE_CXX_FLAGS "" CACHE STRING "c++ flags" ) -set( CMAKE_C_FLAGS "" CACHE STRING "c flags" ) -set( CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "c++ Release flags" ) -set( CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG" CACHE STRING "c Release flags" ) -set( CMAKE_CXX_FLAGS_DEBUG "-O0 -g -DDEBUG -D_DEBUG" CACHE STRING "c++ Debug flags" ) -set( CMAKE_C_FLAGS_DEBUG "-O0 -g -DDEBUG -D_DEBUG" CACHE STRING "c Debug flags" ) -set( CMAKE_SHARED_LINKER_FLAGS "" CACHE STRING "shared linker flags" ) -set( CMAKE_MODULE_LINKER_FLAGS "" CACHE STRING "module linker flags" ) -set( CMAKE_EXE_LINKER_FLAGS "-Wl,-z,nocopyreloc" CACHE STRING "executable linker flags" ) - -# put flags to cache (for debug purpose only) -set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS}" CACHE INTERNAL "Android specific c/c++ flags" ) -set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE}" CACHE INTERNAL "Android specific c/c++ Release flags" ) -set( ANDROID_CXX_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG}" CACHE INTERNAL "Android specific c/c++ Debug flags" ) -set( ANDROID_LINKER_FLAGS "${ANDROID_LINKER_FLAGS}" CACHE INTERNAL "Android specific c/c++ linker flags" ) - -# finish flags -set( CMAKE_CXX_FLAGS "${ANDROID_CXX_FLAGS} ${CMAKE_CXX_FLAGS}" ) -set( CMAKE_C_FLAGS "${ANDROID_CXX_FLAGS} ${CMAKE_C_FLAGS}" ) -set( CMAKE_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} ${CMAKE_CXX_FLAGS_RELEASE}" ) -set( CMAKE_C_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} ${CMAKE_C_FLAGS_RELEASE}" ) -set( CMAKE_CXX_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG} ${CMAKE_CXX_FLAGS_DEBUG}" ) -set( CMAKE_C_FLAGS_DEBUG "${ANDROID_CXX_FLAGS_DEBUG} ${CMAKE_C_FLAGS_DEBUG}" ) -set( CMAKE_SHARED_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}" ) -set( CMAKE_MODULE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}" ) -set( CMAKE_EXE_LINKER_FLAGS "${ANDROID_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS}" ) - -if( MIPS AND BUILD_WITH_ANDROID_NDK AND ANDROID_NDK_RELEASE STREQUAL "r8" ) - set( CMAKE_SHARED_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_SHARED_LINKER_FLAGS}" ) - set( CMAKE_MODULE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.xsc ${CMAKE_MODULE_LINKER_FLAGS}" ) - set( CMAKE_EXE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.x ${CMAKE_EXE_LINKER_FLAGS}" ) -endif() - -# pie/pic -if( NOT (ANDROID_NATIVE_API_LEVEL LESS 16) AND (NOT DEFINED ANDROID_APP_PIE OR ANDROID_APP_PIE) AND (CMAKE_VERSION VERSION_GREATER 2.8.8) ) - set( CMAKE_POSITION_INDEPENDENT_CODE TRUE ) - set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fPIE -pie") -else() - set( CMAKE_POSITION_INDEPENDENT_CODE FALSE ) - set( CMAKE_CXX_FLAGS "-fpic ${CMAKE_CXX_FLAGS}" ) - set( CMAKE_C_FLAGS "-fpic ${CMAKE_C_FLAGS}" ) -endif() - -# configure rtti -if( DEFINED ANDROID_RTTI AND ANDROID_STL_FORCE_FEATURES ) - if( ANDROID_RTTI ) - set( CMAKE_CXX_FLAGS "-frtti ${CMAKE_CXX_FLAGS}" ) - else() - set( CMAKE_CXX_FLAGS "-fno-rtti ${CMAKE_CXX_FLAGS}" ) - endif() -endif() - -# configure exceptios -if( DEFINED ANDROID_EXCEPTIONS AND ANDROID_STL_FORCE_FEATURES ) - if( ANDROID_EXCEPTIONS ) - set( CMAKE_CXX_FLAGS "-fexceptions ${CMAKE_CXX_FLAGS}" ) - set( CMAKE_C_FLAGS "-fexceptions ${CMAKE_C_FLAGS}" ) - else() - set( CMAKE_CXX_FLAGS "-fno-exceptions ${CMAKE_CXX_FLAGS}" ) - set( CMAKE_C_FLAGS "-fno-exceptions ${CMAKE_C_FLAGS}" ) - endif() -endif() - -# global includes and link directories -include_directories( SYSTEM "${ANDROID_SYSROOT}/usr/include" ${ANDROID_STL_INCLUDE_DIRS} ) -get_filename_component(__android_install_path "${CMAKE_INSTALL_PREFIX}/libs/${ANDROID_NDK_ABI_NAME}" ABSOLUTE) # avoid CMP0015 policy warning -link_directories( "${__android_install_path}" ) - -# detect if need link crtbegin_so.o explicitly -if( NOT DEFINED ANDROID_EXPLICIT_CRT_LINK ) - set( __cmd "${CMAKE_CXX_CREATE_SHARED_LIBRARY}" ) - string( REPLACE "" "${CMAKE_CXX_COMPILER} ${CMAKE_CXX_COMPILER_ARG1}" __cmd "${__cmd}" ) - string( REPLACE "" "${CMAKE_C_COMPILER} ${CMAKE_C_COMPILER_ARG1}" __cmd "${__cmd}" ) - string( REPLACE "" "${CMAKE_CXX_FLAGS}" __cmd "${__cmd}" ) - string( REPLACE "" "" __cmd "${__cmd}" ) - string( REPLACE "" "${CMAKE_SHARED_LINKER_FLAGS}" __cmd "${__cmd}" ) - string( REPLACE "" "-shared" __cmd "${__cmd}" ) - string( REPLACE "" "" __cmd "${__cmd}" ) - string( REPLACE "" "" __cmd "${__cmd}" ) - string( REPLACE "" "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/toolchain_crtlink_test.so" __cmd "${__cmd}" ) - string( REPLACE "" "\"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" __cmd "${__cmd}" ) - string( REPLACE "" "" __cmd "${__cmd}" ) - separate_arguments( __cmd ) - foreach( __var ANDROID_NDK ANDROID_NDK_TOOLCHAINS_PATH ANDROID_STANDALONE_TOOLCHAIN ) - if( ${__var} ) - set( __tmp "${${__var}}" ) - separate_arguments( __tmp ) - string( REPLACE "${__tmp}" "${${__var}}" __cmd "${__cmd}") - endif() - endforeach() - string( REPLACE "'" "" __cmd "${__cmd}" ) - string( REPLACE "\"" "" __cmd "${__cmd}" ) - execute_process( COMMAND ${__cmd} RESULT_VARIABLE __cmd_result OUTPUT_QUIET ERROR_QUIET ) - if( __cmd_result EQUAL 0 ) - set( ANDROID_EXPLICIT_CRT_LINK ON ) - else() - set( ANDROID_EXPLICIT_CRT_LINK OFF ) - endif() -endif() - -if( ANDROID_EXPLICIT_CRT_LINK ) - set( CMAKE_CXX_CREATE_SHARED_LIBRARY "${CMAKE_CXX_CREATE_SHARED_LIBRARY} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) - set( CMAKE_CXX_CREATE_SHARED_MODULE "${CMAKE_CXX_CREATE_SHARED_MODULE} \"${ANDROID_SYSROOT}/usr/lib/crtbegin_so.o\"" ) -endif() - -# setup output directories -set( CMAKE_INSTALL_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/user" CACHE STRING "path for installing" ) - -if( DEFINED LIBRARY_OUTPUT_PATH_ROOT - OR EXISTS "${CMAKE_SOURCE_DIR}/AndroidManifest.xml" - OR (EXISTS "${CMAKE_SOURCE_DIR}/../AndroidManifest.xml" AND EXISTS "${CMAKE_SOURCE_DIR}/../jni/") ) - set( LIBRARY_OUTPUT_PATH_ROOT ${CMAKE_SOURCE_DIR} CACHE PATH "Root for binaries output, set this to change where Android libs are installed to" ) - if( NOT _CMAKE_IN_TRY_COMPILE ) - if( EXISTS "${CMAKE_SOURCE_DIR}/jni/CMakeLists.txt" ) - set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for applications" ) - else() - set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin" CACHE PATH "Output directory for applications" ) - endif() - set( LIBRARY_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for Android libs" ) - endif() -endif() - -# copy shaed stl library to build directory -if( NOT _CMAKE_IN_TRY_COMPILE AND __libstl MATCHES "[.]so$" AND DEFINED LIBRARY_OUTPUT_PATH ) - get_filename_component( __libstlname "${__libstl}" NAME ) - execute_process( COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${__libstl}" "${LIBRARY_OUTPUT_PATH}/${__libstlname}" RESULT_VARIABLE __fileCopyProcess ) - if( NOT __fileCopyProcess EQUAL 0 OR NOT EXISTS "${LIBRARY_OUTPUT_PATH}/${__libstlname}") - message( SEND_ERROR "Failed copying of ${__libstl} to the ${LIBRARY_OUTPUT_PATH}/${__libstlname}" ) - endif() - unset( __fileCopyProcess ) - unset( __libstlname ) -endif() - - -# set these global flags for cmake client scripts to change behavior -set( ANDROID True ) -set( BUILD_ANDROID True ) - -# where is the target environment -set( CMAKE_FIND_ROOT_PATH "${ANDROID_TOOLCHAIN_ROOT}/bin" "${ANDROID_TOOLCHAIN_ROOT}/${ANDROID_TOOLCHAIN_MACHINE_NAME}" "${ANDROID_SYSROOT}" "${CMAKE_INSTALL_PREFIX}" "${CMAKE_INSTALL_PREFIX}/share" ) - -# only search for libraries and includes in the ndk toolchain -set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) -set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) -set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) - - -# macro to find packages on the host OS -macro( find_host_package ) - set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER ) - set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER ) - set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER ) - if( CMAKE_HOST_WIN32 ) - SET( WIN32 1 ) - SET( UNIX ) - elseif( CMAKE_HOST_APPLE ) - SET( APPLE 1 ) - SET( UNIX ) - endif() - find_package( ${ARGN} ) - SET( WIN32 ) - SET( APPLE ) - SET( UNIX 1 ) - set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) - set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) - set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) -endmacro() - - -# macro to find programs on the host OS -macro( find_host_program ) - set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER ) - set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY NEVER ) - set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE NEVER ) - if( CMAKE_HOST_WIN32 ) - SET( WIN32 1 ) - SET( UNIX ) - elseif( CMAKE_HOST_APPLE ) - SET( APPLE 1 ) - SET( UNIX ) - endif() - find_program( ${ARGN} ) - SET( WIN32 ) - SET( APPLE ) - SET( UNIX 1 ) - set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY ) - set( CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY ) - set( CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY ) -endmacro() - - -# export toolchain settings for the try_compile() command -if( NOT _CMAKE_IN_TRY_COMPILE ) - set( __toolchain_config "") - foreach( __var NDK_CCACHE LIBRARY_OUTPUT_PATH_ROOT ANDROID_FORBID_SYGWIN - ANDROID_NDK_HOST_X64 - ANDROID_NDK - ANDROID_NDK_LAYOUT - ANDROID_STANDALONE_TOOLCHAIN - ANDROID_TOOLCHAIN_NAME - ANDROID_ABI - ANDROID_NATIVE_API_LEVEL - ANDROID_STL - ANDROID_STL_FORCE_FEATURES - ANDROID_FORCE_ARM_BUILD - ANDROID_NO_UNDEFINED - ANDROID_SO_UNDEFINED - ANDROID_FUNCTION_LEVEL_LINKING - ANDROID_GOLD_LINKER - ANDROID_NOEXECSTACK - ANDROID_RELRO - ANDROID_LIBM_PATH - ANDROID_EXPLICIT_CRT_LINK - ANDROID_APP_PIE - ) - if( DEFINED ${__var} ) - if( ${__var} MATCHES " ") - set( __toolchain_config "${__toolchain_config}set( ${__var} \"${${__var}}\" CACHE INTERNAL \"\" )\n" ) - else() - set( __toolchain_config "${__toolchain_config}set( ${__var} ${${__var}} CACHE INTERNAL \"\" )\n" ) - endif() - endif() - endforeach() - file( WRITE "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/android.toolchain.config.cmake" "${__toolchain_config}" ) - unset( __toolchain_config ) -endif() - - -# force cmake to produce / instead of \ in build commands for Ninja generator -if( CMAKE_GENERATOR MATCHES "Ninja" AND CMAKE_HOST_WIN32 ) - # it is a bad hack after all - # CMake generates Ninja makefiles with UNIX paths only if it thinks that we are going to build with MinGW - set( CMAKE_COMPILER_IS_MINGW TRUE ) # tell CMake that we are MinGW - set( CMAKE_CROSSCOMPILING TRUE ) # stop recursion - enable_language( C ) - enable_language( CXX ) - # unset( CMAKE_COMPILER_IS_MINGW ) # can't unset because CMake does not convert back-slashes in response files without it - unset( MINGW ) -endif() - - -# Variables controlling behavior or set by cmake toolchain: -# ANDROID_ABI : "armeabi-v7a" (default), "armeabi", "armeabi-v7a with NEON", "armeabi-v7a with VFPV3", "armeabi-v6 with VFP", "x86", "mips", "arm64-v8a", "x86_64", "mips64" -# ANDROID_NATIVE_API_LEVEL : 3,4,5,8,9,14,15,16,17,18,19,21 (depends on NDK version) -# ANDROID_STL : gnustl_static/gnustl_shared/stlport_static/stlport_shared/gabi++_static/gabi++_shared/system_re/system/none -# ANDROID_FORBID_SYGWIN : ON/OFF -# ANDROID_NO_UNDEFINED : ON/OFF -# ANDROID_SO_UNDEFINED : OFF/ON (default depends on NDK version) -# ANDROID_FUNCTION_LEVEL_LINKING : ON/OFF -# ANDROID_GOLD_LINKER : ON/OFF -# ANDROID_NOEXECSTACK : ON/OFF -# ANDROID_RELRO : ON/OFF -# ANDROID_FORCE_ARM_BUILD : ON/OFF -# ANDROID_STL_FORCE_FEATURES : ON/OFF -# ANDROID_LIBM_PATH : path to libm.so (set to something like $(TOP)/out/target/product//obj/lib/libm.so) to workaround unresolved `sincos` -# Can be set only at the first run: -# ANDROID_NDK : path to your NDK install -# NDK_CCACHE : path to your ccache executable -# ANDROID_TOOLCHAIN_NAME : the NDK name of compiler toolchain -# ANDROID_NDK_HOST_X64 : try to use x86_64 toolchain (default for x64 host systems) -# ANDROID_NDK_LAYOUT : the inner NDK structure (RELEASE, LINARO, ANDROID) -# LIBRARY_OUTPUT_PATH_ROOT : -# ANDROID_STANDALONE_TOOLCHAIN -# -# Primary read-only variables: -# ANDROID : always TRUE -# ARMEABI : TRUE for arm v6 and older devices -# ARMEABI_V6 : TRUE for arm v6 -# ARMEABI_V7A : TRUE for arm v7a -# ARM64_V8A : TRUE for arm64-v8a -# NEON : TRUE if NEON unit is enabled -# VFPV3 : TRUE if VFP version 3 is enabled -# X86 : TRUE if configured for x86 -# X86_64 : TRUE if configured for x86_64 -# MIPS : TRUE if configured for mips -# MIPS64 : TRUE if configured for mips64 -# BUILD_WITH_ANDROID_NDK : TRUE if NDK is used -# BUILD_WITH_STANDALONE_TOOLCHAIN : TRUE if standalone toolchain is used -# ANDROID_NDK_HOST_SYSTEM_NAME : "windows", "linux-x86" or "darwin-x86" depending on host platform -# ANDROID_NDK_ABI_NAME : "armeabi", "armeabi-v7a", "x86", "mips", "arm64-v8a", "x86_64", "mips64" depending on ANDROID_ABI -# ANDROID_NDK_RELEASE : from r5 to r10d; set only for NDK -# ANDROID_NDK_RELEASE_NUM : numeric ANDROID_NDK_RELEASE version (1000*major+minor) -# ANDROID_ARCH_NAME : "arm", "x86", "mips", "arm64", "x86_64", "mips64" depending on ANDROID_ABI -# ANDROID_SYSROOT : path to the compiler sysroot -# TOOL_OS_SUFFIX : "" or ".exe" depending on host platform -# ANDROID_COMPILER_IS_CLANG : TRUE if clang compiler is used -# -# Secondary (less stable) read-only variables: -# ANDROID_COMPILER_VERSION : GCC version used (not Clang version) -# ANDROID_CLANG_VERSION : version of clang compiler if clang is used -# ANDROID_CXX_FLAGS : C/C++ compiler flags required by Android platform -# ANDROID_SUPPORTED_ABIS : list of currently allowed values for ANDROID_ABI -# ANDROID_TOOLCHAIN_MACHINE_NAME : "arm-linux-androideabi", "arm-eabi" or "i686-android-linux" -# ANDROID_TOOLCHAIN_ROOT : path to the top level of toolchain (standalone or placed inside NDK) -# ANDROID_CLANG_TOOLCHAIN_ROOT : path to clang tools -# ANDROID_SUPPORTED_NATIVE_API_LEVELS : list of native API levels found inside NDK -# ANDROID_STL_INCLUDE_DIRS : stl include paths -# ANDROID_RTTI : if rtti is enabled by the runtime -# ANDROID_EXCEPTIONS : if exceptions are enabled by the runtime -# ANDROID_GCC_TOOLCHAIN_NAME : read-only, differs from ANDROID_TOOLCHAIN_NAME only if clang is used -# -# Defaults: -# ANDROID_DEFAULT_NDK_API_LEVEL -# ANDROID_DEFAULT_NDK_API_LEVEL_${ARCH} -# ANDROID_NDK_SEARCH_PATHS -# ANDROID_SUPPORTED_ABIS_${ARCH} -# ANDROID_SUPPORTED_NDK_VERSIONS diff --git a/cmake/android/deployment-file.json.in b/cmake/android/deployment-file.json.in deleted file mode 100644 index 81ed8a6ecc..0000000000 --- a/cmake/android/deployment-file.json.in +++ /dev/null @@ -1,13 +0,0 @@ -{ - "qt": "@QT_DIR@", - "sdk": "@ANDROID_SDK_ROOT@", - "ndk": "@ANDROID_NDK@", - "toolchain-prefix": "@ANDROID_TOOLCHAIN_MACHINE_NAME@", - "tool-prefix": "@ANDROID_TOOLCHAIN_MACHINE_NAME@", - "toolchain-version": "@ANDROID_COMPILER_VERSION@", - "ndk-host": "@ANDROID_NDK_HOST_SYSTEM_NAME@", - "target-architecture": "@ANDROID_ABI@", - "application-binary": "@EXECUTABLE_DESTINATION_PATH@", - "android-extra-libs": "@_DEPS@", - "android-package-source-directory": "@ANDROID_APK_BUILD_DIR@" -} diff --git a/cmake/android/strings.xml.in b/cmake/android/strings.xml.in deleted file mode 100644 index 6e6ce7b12e..0000000000 --- a/cmake/android/strings.xml.in +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - ${ANDROID_APP_DISPLAY_NAME} - - Can\'t find Ministro service.\nThe application can\'t start. - This application requires Ministro service. Would you like to install it? - Your application encountered a fatal error and cannot continue. - diff --git a/cmake/compiler.cmake b/cmake/compiler.cmake new file mode 100644 index 0000000000..968a65a6dd --- /dev/null +++ b/cmake/compiler.cmake @@ -0,0 +1,106 @@ +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") + +if (WIN32) + if (NOT "${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + message( FATAL_ERROR "Only 64 bit builds supported." ) + endif() + + add_definitions(-DNOMINMAX -D_CRT_SECURE_NO_WARNINGS) + + if (NOT WINDOW_SDK_PATH) + set(DEBUG_DISCOVERED_SDK_PATH TRUE) + endif() + + # sets path for Microsoft SDKs + # if you get build error about missing 'glu32' this path is likely wrong + if (MSVC_VERSION GREATER_EQUAL 1910) # VS 2017 + set(WINDOW_SDK_PATH "C:/Program Files (x86)/Windows Kits/10/Lib/${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}/x64" CACHE PATH "Windows SDK PATH") + elseif (MSVC_VERSION GREATER_EQUAL 1800) # VS 2013 + set(WINDOW_SDK_PATH "C:\\Program Files (x86)\\Windows Kits\\8.1\\Lib\\winv6.3\\um\\${WINDOW_SDK_FOLDER}" CACHE PATH "Windows SDK PATH") + else() + message( FATAL_ERROR "Visual Studio 2013 or higher required." ) + endif() + + if (DEBUG_DISCOVERED_SDK_PATH) + message(STATUS "The discovered Windows SDK path is ${WINDOW_SDK_PATH}") + endif () + + list(APPEND CMAKE_PREFIX_PATH "${WINDOW_SDK_PATH}") + + # /wd4351 disables warning C4351: new behavior: elements of array will be default initialized + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4351") + # /LARGEADDRESSAWARE enables 32-bit apps to use more than 2GB of memory. + # Caveats: http://stackoverflow.com/questions/2288728/drawbacks-of-using-largeaddressaware-for-32-bit-windows-executables + # TODO: Remove when building 64-bit. + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /LARGEADDRESSAWARE") + # always produce symbols as PDB files + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG /OPT:REF /OPT:ICF") +else () + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -fno-strict-aliasing -Wno-unused-parameter") + if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb -Woverloaded-virtual -Wdouble-promotion") + if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "5.1") # gcc 5.1 and on have suggest-override + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override") + endif () + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.3") + # GLM 0.9.8 on Ubuntu 14 (gcc 4.4) has issues with the simd declarations + add_definitions(-DGLM_FORCE_PURE) + endif() + endif () +endif() + +if (ANDROID) + # assume that the toolchain selected for android has C++11 support + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +elseif ((NOT MSVC12) AND (NOT MSVC14)) + include(CheckCXXCompilerFlag) + CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) + CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) + if (COMPILER_SUPPORTS_CXX11) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + elseif(COMPILER_SUPPORTS_CXX0X) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") + else() + message(FATAL_ERROR "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.") + endif() +endif () + +if (APPLE) + set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++11") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --stdlib=libc++") +endif () + +if (NOT ANDROID_LIB_DIR) + set(ANDROID_LIB_DIR $ENV{ANDROID_LIB_DIR}) +endif () + +if (APPLE) + exec_program(sw_vers ARGS -productVersion OUTPUT_VARIABLE OSX_VERSION) + string(REGEX MATCH "^[0-9]+\\.[0-9]+" OSX_VERSION ${OSX_VERSION}) + message(STATUS "Detected OS X version = ${OSX_VERSION}") + + set(OSX_SDK "${OSX_VERSION}" CACHE String "OS X SDK version to look for inside Xcode bundle or at OSX_SDK_PATH") + + # set our OS X deployment target + set(CMAKE_OSX_DEPLOYMENT_TARGET 10.8) + + # find the SDK path for the desired SDK + find_path( + _OSX_DESIRED_SDK_PATH + NAME MacOSX${OSX_SDK}.sdk + HINTS ${OSX_SDK_PATH} + PATHS /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/ + /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/ + ) + + if (NOT _OSX_DESIRED_SDK_PATH) + message(STATUS "Could not find OS X ${OSX_SDK} SDK. Will fall back to default. If you want a specific SDK, please pass OSX_SDK and optionally OSX_SDK_PATH to CMake.") + else () + message(STATUS "Found OS X ${OSX_SDK} SDK at ${_OSX_DESIRED_SDK_PATH}/MacOSX${OSX_SDK}.sdk") + + # set that as the SDK to use + set(CMAKE_OSX_SYSROOT ${_OSX_DESIRED_SDK_PATH}/MacOSX${OSX_SDK}.sdk) + endif () +endif () \ No newline at end of file diff --git a/cmake/externals/LibOVR/CMakeLists.txt b/cmake/externals/LibOVR/CMakeLists.txt index c98aa8a04a..97508be0c5 100644 --- a/cmake/externals/LibOVR/CMakeLists.txt +++ b/cmake/externals/LibOVR/CMakeLists.txt @@ -33,7 +33,6 @@ if (WIN32) include(SelectLibraryConfigurations) select_library_configurations(LIBOVR) set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE TYPE INTERNAL) - message("Libs ${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES}") elseif(APPLE) diff --git a/cmake/externals/draco/CMakeLists.txt b/cmake/externals/draco/CMakeLists.txt new file mode 100644 index 0000000000..44ddd6d3de --- /dev/null +++ b/cmake/externals/draco/CMakeLists.txt @@ -0,0 +1,40 @@ +set(EXTERNAL_NAME draco) + +if (ANDROID) + set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19") +endif () + +if (APPLE) + set(EXTRA_CMAKE_FLAGS -DCMAKE_CXX_FLAGS=-stdlib=libc++ -DCMAKE_EXE_LINKER_FLAGS=-stdlib=libc++) +endif () + +include(ExternalProject) +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= ${EXTRA_CMAKE_FLAGS} + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 +) + +# Hide this external target (for ide users) +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") + +ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) + +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) + +set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of Draco include directories") + +if (UNIX) + set(LIB_PREFIX "lib") + set(LIB_EXT "a") +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") diff --git a/cmake/externals/glm/CMakeLists.txt b/cmake/externals/glm/CMakeLists.txt index 79a44fa48e..bc8089074f 100644 --- a/cmake/externals/glm/CMakeLists.txt +++ b/cmake/externals/glm/CMakeLists.txt @@ -6,7 +6,7 @@ ExternalProject_Add( URL https://hifi-public.s3.amazonaws.com/dependencies/glm-0.9.8.zip URL_MD5 579ac77a3110befa3244d68c0ceb7281 BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= ${EXTERNAL_ARGS} LOG_DOWNLOAD 1 LOG_CONFIGURE 1 LOG_BUILD 1 diff --git a/cmake/externals/quazip/CMakeLists.txt b/cmake/externals/quazip/CMakeLists.txt index 01650a432d..0d66b365a2 100644 --- a/cmake/externals/quazip/CMakeLists.txt +++ b/cmake/externals/quazip/CMakeLists.txt @@ -4,14 +4,6 @@ cmake_policy(SET CMP0046 OLD) include(ExternalProject) -if (WIN32) - # windows shell does not like backslashes expanded on the command line, - # so convert all backslashes in the QT path to forward slashes - string(REPLACE \\ / QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH}) -elseif ($ENV{QT_CMAKE_PREFIX_PATH}) - set(QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH}) -endif () - set(QUAZIP_CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= -DCMAKE_PREFIX_PATH=${QT_CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_NAME_DIR:PATH=/lib -DZLIB_ROOT=${ZLIB_ROOT} -DCMAKE_POSITION_INDEPENDENT_CODE=ON) if (APPLE) @@ -34,9 +26,9 @@ add_dependencies(quazip zlib) # Hide this external target (for ide users) set_target_properties(${EXTERNAL_NAME} PROPERTIES - FOLDER "hidden/externals" - INSTALL_NAME_DIR ${INSTALL_DIR}/lib - BUILD_WITH_INSTALL_RPATH True) + FOLDER "hidden/externals" + INSTALL_NAME_DIR ${INSTALL_DIR}/lib + BUILD_WITH_INSTALL_RPATH True) ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIR ${INSTALL_DIR}/include CACHE PATH "List of QuaZip include directories") diff --git a/cmake/externals/tbb/CMakeLists.txt b/cmake/externals/tbb/CMakeLists.txt index 74f7e10859..9664fe7250 100644 --- a/cmake/externals/tbb/CMakeLists.txt +++ b/cmake/externals/tbb/CMakeLists.txt @@ -2,57 +2,27 @@ set(EXTERNAL_NAME tbb) include(ExternalProject) -if (ANDROID) - - find_program(NDK_BUILD_COMMAND NAMES ndk-build DOC "Path to the ndk-build command") - - ExternalProject_Add( - ${EXTERNAL_NAME} - URL http://hifi-public.s3.amazonaws.com/dependencies/tbb43_20150316oss_src.tgz - URL_MD5 bf090eaa86cf89ea014b7b462786a440 - BUILD_COMMAND ${NDK_BUILD_COMMAND} --directory=jni target=android tbb tbbmalloc arch=arm - BUILD_IN_SOURCE 1 - CONFIGURE_COMMAND "" - INSTALL_COMMAND ${CMAKE_COMMAND} -DTBB_LIBS_SUFFIX=so -P ${CMAKE_CURRENT_SOURCE_DIR}/TBBLibCopy.cmake - LOG_DOWNLOAD 1 - LOG_CONFIGURE 1 - LOG_BUILD 1 - ) +if (WIN32) + set(DOWNLOAD_URL http://hifi-public.s3.amazonaws.com/dependencies/tbb2017_20170604oss_win_slim.zip) + set(DOWNLOAD_MD5 065934458e3db88397f3d10e7eea536c) elseif (APPLE) - find_program(MAKE_COMMAND NAMES make DOC "Path to the make command") - - ExternalProject_Add( - ${EXTERNAL_NAME} - URL http://hifi-public.s3.amazonaws.com/dependencies/tbb43_20150316oss_src.tgz - URL_MD5 bf090eaa86cf89ea014b7b462786a440 - BUILD_COMMAND ${MAKE_COMMAND} tbb_os=macos - BUILD_IN_SOURCE 1 - CONFIGURE_COMMAND "" - INSTALL_COMMAND ${CMAKE_COMMAND} -DTBB_LIBS_SUFFIX=dylib -P ${CMAKE_CURRENT_SOURCE_DIR}/TBBLibCopy.cmake - LOG_DOWNLOAD 1 - LOG_CONFIGURE 1 - LOG_BUILD 1 - ) + set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb2017_20170604oss_mac_slim.tar.gz) + set(DOWNLOAD_MD5 62bde626b396f8e1a85c6a8ded1d8105) else () - if (WIN32) - set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150316oss_win.zip) - set(DOWNLOAD_MD5 d250d40bb93b255f75bcbb19e976a440) - else () - set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150316oss_lin.tgz) - set(DOWNLOAD_MD5 7830ba2bc62438325fba2ec0c95367a5) - endif () - - ExternalProject_Add( - ${EXTERNAL_NAME} - URL ${DOWNLOAD_URL} - URL_MD5 ${DOWNLOAD_MD5} - BUILD_COMMAND "" - CONFIGURE_COMMAND "" - INSTALL_COMMAND "" - LOG_DOWNLOAD ON - ) + set(DOWNLOAD_URL http://hifi-public.s3.amazonaws.com/dependencies/tbb2017_20170604oss_lin_slim.tar.gz) + set(DOWNLOAD_MD5 2a5c721f40fa3503ffc12c18dd00011c) endif () +ExternalProject_Add( + ${EXTERNAL_NAME} + URL ${DOWNLOAD_URL} + URL_MD5 ${DOWNLOAD_MD5} + BUILD_COMMAND "" + CONFIGURE_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD ON +) + # Hide this external target (for ide users) set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") @@ -70,22 +40,32 @@ if (APPLE) change-install-name COMMENT "Calling install_name_tool on TBB libraries to fix install name for dylib linking" COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${_TBB_LIB_DIR} -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake - DEPENDEES install + DEPENDEES download WORKING_DIRECTORY LOG 1 ) - elseif (WIN32) - if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/intel64/vc12") - set(${EXTERNAL_NAME_UPPER}_DLL_PATH "${SOURCE_DIR}/bin/intel64/vc12" CACHE PATH "Path to TBB DLLs") + if (MSVC_VERSION GREATER_EQUAL 1900) + set(_TBB_MSVC_DIR "vc14") + elseif (MSVC_VERSION GREATER_EQUAL 1800) + set(_TBB_MSVC_DIR "vc12") + elseif (MSVC_VERSION GREATER_EQUAL 1700) + set(_TBB_MSVC_DIR "vc11") else() - set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/ia32/vc12") - set(${EXTERNAL_NAME_UPPER}_DLL_PATH "${SOURCE_DIR}/bin/ia32/vc12" CACHE PATH "Path to TBB DLLs") + message(FATAL_ERROR "MSVC ${MSVC_VERSION} not supported by Intel TBB") endif() + + if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/intel64/${_TBB_MSVC_DIR}") + set(${EXTERNAL_NAME_UPPER}_DLL_PATH "${SOURCE_DIR}/bin/intel64/${_TBB_MSVC_DIR}" CACHE PATH "Path to TBB DLLs") + else() + set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/ia32/${_TBB_MSVC_DIR}") + set(${EXTERNAL_NAME_UPPER}_DLL_PATH "${SOURCE_DIR}/bin/ia32/${_TBB_MSVC_DIR}" CACHE PATH "Path to TBB DLLs") + endif() + set(_LIB_EXT "lib") elseif (ANDROID) - set(_TBB_LIB_DIR "${SOURCE_DIR}/lib") + set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/android") set(_LIB_PREFIX "lib") set(_LIB_EXT "so") elseif (UNIX) @@ -103,15 +83,15 @@ elseif (UNIX) OUTPUT_VARIABLE GCC_VERSION ) - if (GCC_VERSION VERSION_GREATER 4.4 OR GCC_VERSION VERSION_EQUAL 4.4) + if (GCC_VERSION VERSION_GREATER 4.7 OR GCC_VERSION VERSION_EQUAL 4.7) + set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/${_TBB_ARCH_DIR}/gcc4.7") + elseif (GCC_VERSION VERSION_GREATER 4.4 OR GCC_VERSION VERSION_EQUAL 4.4) set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/${_TBB_ARCH_DIR}/gcc4.4") elseif (GCC_VERSION VERSION_GREATER 4.1 OR GCC_VERSION VERSION_EQUAL 4.1) set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/${_TBB_ARCH_DIR}/gcc4.1") else () message(STATUS "Could not find a compatible version of Threading Building Blocks library for your compiler.") endif () - - endif () if (DEFINED _TBB_LIB_DIR) @@ -124,3 +104,4 @@ endif () if (DEFINED ${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE) set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE "List of tbb include directories") endif () + diff --git a/cmake/init.cmake b/cmake/init.cmake new file mode 100644 index 0000000000..9d7b0fd94c --- /dev/null +++ b/cmake/init.cmake @@ -0,0 +1,56 @@ +if (WIN32) + cmake_policy(SET CMP0020 NEW) +endif (WIN32) + +if (POLICY CMP0043) + cmake_policy(SET CMP0043 OLD) +endif () + +if (POLICY CMP0042) + cmake_policy(SET CMP0042 OLD) +endif () + +set_property(GLOBAL PROPERTY USE_FOLDERS ON) +set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "CMakeTargets") +# Hide automoc folders (for IDEs) +set(AUTOGEN_TARGETS_FOLDER "hidden/generated") +# Find includes in corresponding build directories +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +if (CMAKE_BUILD_TYPE) + string(TOUPPER ${CMAKE_BUILD_TYPE} UPPER_CMAKE_BUILD_TYPE) +else () + set(UPPER_CMAKE_BUILD_TYPE DEBUG) +endif () + +# CMAKE_CURRENT_SOURCE_DIR is the parent folder here +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/") + +set(HF_CMAKE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/cmake") +set(MACRO_DIR "${HF_CMAKE_DIR}/macros") +set(EXTERNAL_PROJECT_DIR "${HF_CMAKE_DIR}/externals") + +file(GLOB HIFI_CUSTOM_MACROS "cmake/macros/*.cmake") +foreach(CUSTOM_MACRO ${HIFI_CUSTOM_MACROS}) + include(${CUSTOM_MACRO}) +endforeach() +unset(HIFI_CUSTOM_MACROS) + +if (ANDROID) + set(BUILD_SHARED_LIBS ON) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH) + + string(REGEX REPLACE "\\\\" "/" ANDROID_NDK ${ANDROID_NDK}) + string(REGEX REPLACE "\\\\" "/" CMAKE_TOOLCHAIN_FILE ${CMAKE_TOOLCHAIN_FILE}) + string(REGEX REPLACE "\\\\" "/" ANDROID_TOOLCHAIN ${ANDROID_TOOLCHAIN}) + string(REGEX REPLACE "\\\\" "/" CMAKE_MAKE_PROGRAM ${CMAKE_MAKE_PROGRAM}) + list(APPEND EXTERNAL_ARGS -DANDROID_ABI=${ANDROID_ABI}) + list(APPEND EXTERNAL_ARGS -DANDROID_NDK=${ANDROID_NDK}) + list(APPEND EXTERNAL_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}) + list(APPEND EXTERNAL_ARGS -DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}) + list(APPEND EXTERNAL_ARGS -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}) + list(APPEND EXTERNAL_ARGS -DHIFI_ANDROID=${HIFI_ANDROID}) + list(APPEND EXTERNAL_ARGS -DANDROID_PLATFORM=${ANDROID_PLATFORM}) + list(APPEND EXTERNAL_ARGS -DANDROID_TOOLCHAIN=${ANDROID_TOOLCHAIN}) + list(APPEND EXTERNAL_ARGS -DANDROID_STL=${ANDROID_STL}) +endif () diff --git a/cmake/macros/AutoScribeShader.cmake b/cmake/macros/AutoScribeShader.cmake index c43ade45d2..c5b35b7e90 100755 --- a/cmake/macros/AutoScribeShader.cmake +++ b/cmake/macros/AutoScribeShader.cmake @@ -62,7 +62,9 @@ function(AUTOSCRIBE_SHADER SHADER_FILE) # since it's unrunnable by the cross-compiling build machine # so, we require the compiling user to point us at a compiled executable version for their native toolchain - find_program(NATIVE_SCRIBE scribe PATHS ${SCRIBE_PATH} ENV SCRIBE_PATH) + if (NOT NATIVE_SCRIBE) + find_program(NATIVE_SCRIBE scribe PATHS ${SCRIBE_PATH} ENV SCRIBE_PATH) + endif() if (NOT NATIVE_SCRIBE) message(FATAL_ERROR "The High Fidelity scribe tool is required for shader pre-processing. \ diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index 6f35b76f1d..8faa4e6d96 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -111,14 +111,14 @@ macro(SET_PACKAGING_PARAMETERS) # shortcut names if (PRODUCTION_BUILD) - set(INTERFACE_SHORTCUT_NAME "Interface") + set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface") set(CONSOLE_SHORTCUT_NAME "Sandbox") else () - set(INTERFACE_SHORTCUT_NAME "Interface - ${BUILD_VERSION}") + set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface - ${BUILD_VERSION}") set(CONSOLE_SHORTCUT_NAME "Sandbox - ${BUILD_VERSION}") endif () - set(INTERFACE_HF_SHORTCUT_NAME "High Fidelity ${INTERFACE_SHORTCUT_NAME}") + set(INTERFACE_HF_SHORTCUT_NAME "${INTERFACE_SHORTCUT_NAME}") set(CONSOLE_HF_SHORTCUT_NAME "High Fidelity ${CONSOLE_SHORTCUT_NAME}") set(PRE_SANDBOX_INTERFACE_SHORTCUT_NAME "High Fidelity") @@ -133,7 +133,7 @@ macro(SET_PACKAGING_PARAMETERS) else() message( FATAL_ERROR "Visual Studio 2013 or higher required." ) endif() - + if (NOT SIGNTOOL_EXECUTABLE) message(FATAL_ERROR "Code signing of executables was requested but signtool.exe could not be found.") endif () @@ -147,6 +147,7 @@ macro(SET_PACKAGING_PARAMETERS) set(CONSOLE_STARTUP_REG_KEY "ConsoleStartupShortcut") set(CLIENT_LAUNCH_NOW_REG_KEY "ClientLaunchAfterInstall") set(SERVER_LAUNCH_NOW_REG_KEY "ServerLaunchAfterInstall") + set(CUSTOM_INSTALL_REG_KEY "CustomInstall") endif () # setup component categories for installer @@ -161,5 +162,6 @@ macro(SET_PACKAGING_PARAMETERS) # create a header file our targets can use to find out the application version file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/includes") configure_file("${HF_CMAKE_DIR}/templates/BuildInfo.h.in" "${CMAKE_BINARY_DIR}/includes/BuildInfo.h") + include_directories("${CMAKE_BINARY_DIR}/includes") endmacro(SET_PACKAGING_PARAMETERS) diff --git a/cmake/macros/SetupHifiClientServerPlugin.cmake b/cmake/macros/SetupHifiClientServerPlugin.cmake index 37b000efb5..bc66484c30 100644 --- a/cmake/macros/SetupHifiClientServerPlugin.cmake +++ b/cmake/macros/SetupHifiClientServerPlugin.cmake @@ -9,11 +9,13 @@ macro(SETUP_HIFI_CLIENT_SERVER_PLUGIN) set(${TARGET_NAME}_SHARED 1) setup_hifi_library(${ARGV}) - if (NOT DEFINED SERVER_ONLY) + if (BUILD_CLIENT) add_dependencies(interface ${TARGET_NAME}) endif() - add_dependencies(assignment-client ${TARGET_NAME}) + if (BUILD_SERVER) + add_dependencies(assignment-client ${TARGET_NAME}) + endif() set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Plugins") diff --git a/cmake/macros/SetupHifiLibrary.cmake b/cmake/macros/SetupHifiLibrary.cmake index d0fc58af0c..04687e2c84 100644 --- a/cmake/macros/SetupHifiLibrary.cmake +++ b/cmake/macros/SetupHifiLibrary.cmake @@ -12,7 +12,7 @@ macro(SETUP_HIFI_LIBRARY) project(${TARGET_NAME}) # grab the implementation and header files - file(GLOB_RECURSE LIB_SRCS "src/*.h" "src/*.cpp" "src/*.c") + file(GLOB_RECURSE LIB_SRCS "src/*.h" "src/*.cpp" "src/*.c" "src/*.qrc") list(APPEND ${TARGET_NAME}_SRCS ${LIB_SRCS}) # add compiler flags to AVX source files @@ -65,7 +65,7 @@ macro(SETUP_HIFI_LIBRARY) list(APPEND ${TARGET_NAME}_DEPENDENCY_QT_MODULES Core) # find these Qt modules and link them to our own target - find_package(Qt5 COMPONENTS ${${TARGET_NAME}_DEPENDENCY_QT_MODULES} REQUIRED) + find_package(Qt5 COMPONENTS ${${TARGET_NAME}_DEPENDENCY_QT_MODULES} REQUIRED CMAKE_FIND_ROOT_PATH_BOTH) foreach(QT_MODULE ${${TARGET_NAME}_DEPENDENCY_QT_MODULES}) target_link_libraries(${TARGET_NAME} Qt5::${QT_MODULE}) diff --git a/cmake/macros/SetupHifiPlugin.cmake b/cmake/macros/SetupHifiPlugin.cmake index 7e56ea3db2..023c7603dc 100644 --- a/cmake/macros/SetupHifiPlugin.cmake +++ b/cmake/macros/SetupHifiPlugin.cmake @@ -8,7 +8,9 @@ macro(SETUP_HIFI_PLUGIN) set(${TARGET_NAME}_SHARED 1) setup_hifi_library(${ARGV}) - add_dependencies(interface ${TARGET_NAME}) + if (BUILD_CLIENT) + add_dependencies(interface ${TARGET_NAME}) + endif() target_link_libraries(${TARGET_NAME} ${CMAKE_THREAD_LIBS_INIT}) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Plugins") diff --git a/cmake/macros/SetupQt.cmake b/cmake/macros/SetupQt.cmake new file mode 100644 index 0000000000..ece8607b9b --- /dev/null +++ b/cmake/macros/SetupQt.cmake @@ -0,0 +1,84 @@ +# +# Copyright 2015 High Fidelity, Inc. +# Created by Bradley Austin Davis on 2015/10/10 +# +# 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) + set(QT_DEFAULT_ARCH "android_armv7") + elseif(UWP) + set(QT_DEFAULT_ARCH "winrt_x64_msvc2017") + elseif(APPLE) + set(QT_DEFAULT_ARCH "clang_64") + elseif(WIN32) + set(QT_DEFAULT_ARCH "msvc2017_64") + else() + set(QT_DEFAULT_ARCH "gcc_64") + endif() + + if (WIN32 OR (ANDROID AND ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows"))) + set(QT_DEFAULT_ROOT "c:/Qt") + else() + set(QT_DEFAULT_ROOT "$ENV{HOME}/Qt") + endif() + + set_from_env(QT_ROOT QT_ROOT ${QT_DEFAULT_ROOT}) + set_from_env(QT_VERSION QT_VERSION "5.9.1") + set_from_env(QT_ARCH QT_ARCH ${QT_DEFAULT_ARCH}) + + set(${_RESULT_NAME} "${QT_ROOT}/${QT_VERSION}/${QT_ARCH}" PARENT_SCOPE) +endfunction() + +# Sets the QT_CMAKE_PREFIX_PATH and QT_DIR variables +# Also enables CMAKE_AUTOMOC and CMAKE_AUTORCC +macro(setup_qt) + set(QT_CMAKE_PREFIX_PATH "$ENV{QT_CMAKE_PREFIX_PATH}") + if (("QT_CMAKE_PREFIX_PATH" STREQUAL "") OR (NOT EXISTS "${QT_CMAKE_PREFIX_PATH}")) + calculate_default_qt_dir(QT_DIR) + set(QT_CMAKE_PREFIX_PATH "${QT_DIR}/lib/cmake") + else() + # figure out where the qt dir is + get_filename_component(QT_DIR "${QT_CMAKE_PREFIX_PATH}/../../" ABSOLUTE) + endif() + + if (WIN32) + # windows shell does not like backslashes expanded on the command line, + # so convert all backslashes in the QT path to forward slashes + string(REPLACE \\ / QT_CMAKE_PREFIX_PATH ${QT_CMAKE_PREFIX_PATH}) + string(REPLACE \\ / QT_DIR ${QT_DIR}) + endif() + + # This check doesn't work on Mac + #if (NOT EXISTS "${QT_DIR}/include/QtCore/QtGlobal") + # message(FATAL_ERROR "Unable to locate Qt includes in ${QT_DIR}") + #endif() + + if (NOT EXISTS "${QT_CMAKE_PREFIX_PATH}/Qt5Core/Qt5CoreConfig.cmake") + message(FATAL_ERROR "Unable to locate Qt cmake config in ${QT_CMAKE_PREFIX_PATH}") + endif() + + message(STATUS "The Qt build in use is: \"${QT_DIR}\"") + + # Instruct CMake to run moc automatically when needed. + set(CMAKE_AUTOMOC ON) + + # Instruct CMake to run rcc automatically when needed + set(CMAKE_AUTORCC ON) + + if (WIN32) + add_paths_to_fixup_libs("${QT_DIR}/bin") + endif () + +endmacro() \ No newline at end of file diff --git a/cmake/macros/TargetGlew.cmake b/cmake/macros/TargetGlew.cmake index 5f71f021ec..bc4d5cb033 100644 --- a/cmake/macros/TargetGlew.cmake +++ b/cmake/macros/TargetGlew.cmake @@ -6,9 +6,11 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # macro(TARGET_GLEW) - add_dependency_external_projects(glew) - find_package(GLEW REQUIRED) - add_definitions(-DGLEW_STATIC) - target_include_directories(${TARGET_NAME} PUBLIC ${GLEW_INCLUDE_DIRS}) - target_link_libraries(${TARGET_NAME} ${GLEW_LIBRARY}) + if (NOT ANDROID) + add_definitions(-DGLEW_STATIC) + add_dependency_external_projects(glew) + find_package(GLEW REQUIRED) + target_include_directories(${TARGET_NAME} PUBLIC ${GLEW_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${GLEW_LIBRARY}) + endif() endmacro() \ No newline at end of file diff --git a/cmake/macros/TargetOpenGL.cmake b/cmake/macros/TargetOpenGL.cmake index 73c92e651a..6ad92259bb 100644 --- a/cmake/macros/TargetOpenGL.cmake +++ b/cmake/macros/TargetOpenGL.cmake @@ -6,15 +6,13 @@ # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # macro(TARGET_OPENGL) - add_definitions(-DGLEW_STATIC) if (APPLE) # link in required OS X frameworks and include the right GL headers find_library(OpenGL OpenGL) target_link_libraries(${TARGET_NAME} ${OpenGL}) elseif(ANDROID) - target_link_libraries(${TARGET_NAME} "-lGLESv3" "-lEGL") + target_link_libraries(${TARGET_NAME} GLESv3 EGL) else() - target_nsight() find_package(OpenGL REQUIRED) if (${OPENGL_INCLUDE_DIR}) include_directories(SYSTEM "${OPENGL_INCLUDE_DIR}") @@ -22,4 +20,6 @@ macro(TARGET_OPENGL) target_link_libraries(${TARGET_NAME} "${OPENGL_LIBRARY}") target_include_directories(${TARGET_NAME} PUBLIC ${OPENGL_INCLUDE_DIR}) endif() + target_nsight() + target_glew() endmacro() diff --git a/cmake/macros/TargetOpenSSL.cmake b/cmake/macros/TargetOpenSSL.cmake new file mode 100644 index 0000000000..7ee0283a48 --- /dev/null +++ b/cmake/macros/TargetOpenSSL.cmake @@ -0,0 +1,32 @@ +# +# Copyright 2015 High Fidelity, Inc. +# Created by Bradley Austin Davis on 2015/10/10 +# +# 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_OPENSSL) + + if (ANDROID) + + # FIXME use a distributable binary + set(OPENSSL_INSTALL_DIR C:/Android/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) + + if (APPLE AND ${OPENSSL_INCLUDE_DIR} STREQUAL "/usr/include") + # this is a user on OS X using system OpenSSL, which is going to throw warnings since they're deprecating for their common crypto + message(WARNING "The found version of OpenSSL is the OS X system version. This will produce deprecation warnings." + "\nWe recommend you install a newer version (at least 1.0.1h) in a different directory and set OPENSSL_ROOT_DIR in your env so Cmake can find it.") + endif() + + endif() + + include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") + target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES}) + +endmacro() diff --git a/cmake/macros/TargetTBB.cmake b/cmake/macros/TargetTBB.cmake new file mode 100644 index 0000000000..e9c4639c3d --- /dev/null +++ b/cmake/macros/TargetTBB.cmake @@ -0,0 +1,24 @@ +# +# Copyright 2015 High Fidelity, Inc. +# Created by Bradley Austin Davis on 2015/10/10 +# +# 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_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_LIBRARIES ${TBB_LIBRARY} ${TBB_MALLOC_LIBRARY}) +else() + add_dependency_external_projects(tbb) + find_package(TBB REQUIRED) +endif() + +target_link_libraries(${TARGET_NAME} ${TBB_LIBRARIES}) +target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${TBB_INCLUDE_DIRS}) + +endmacro() diff --git a/cmake/modules/FindDraco.cmake b/cmake/modules/FindDraco.cmake new file mode 100644 index 0000000000..342797b62e --- /dev/null +++ b/cmake/modules/FindDraco.cmake @@ -0,0 +1,30 @@ +# +# FindDraco.cmake +# +# Try to find Draco libraries and include path. +# Once done this will define +# +# DRACO_FOUND +# DRACO_INCLUDE_DIRS +# DRACO_LIBRARY +# DRACO_ENCODER_LIBRARY +# DRACO_DECODER_LIBRARY +# +# Created on 8/8/2017 by Stephen Birarda +# Copyright 2017 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + +include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") +hifi_library_search_hints("draco") + +find_path(DRACO_INCLUDE_DIRS draco/core/draco_types.h PATH_SUFFIXES include/draco/src include HINTS ${DRACO_SEARCH_DIRS}) + +find_library(DRACO_LIBRARY draco PATH_SUFFIXES "lib" HINTS ${DRACO_SEARCH_DIRS}) +find_library(DRACO_ENCODER_LIBRARY draco PATH_SUFFIXES "lib" HINTS ${DRACO_SEARCH_DIRS}) +find_library(DRACO_DECODER_LIBRARY draco PATH_SUFFIXES "lib" HINTS ${DRACO_SEARCH_DIRS}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(DRACO DEFAULT_MSG DRACO_INCLUDE_DIRS DRACO_LIBRARY DRACO_ENCODER_LIBRARY DRACO_DECODER_LIBRARY) diff --git a/cmake/modules/FindFBX.cmake b/cmake/modules/FindFBX.cmake deleted file mode 100644 index 9a1d08a010..0000000000 --- a/cmake/modules/FindFBX.cmake +++ /dev/null @@ -1,115 +0,0 @@ -# Locate the FBX SDK -# -# Defines the following variables: -# -# FBX_FOUND - Found the FBX SDK -# FBX_VERSION - Version number -# FBX_INCLUDE_DIRS - Include directories -# FBX_LIBRARIES - The libraries to link to -# -# Accepts the following variables as input: -# -# FBX_VERSION - as a CMake variable, e.g. 2017.0.1 -# FBX_ROOT - (as a CMake or environment variable) -# The root directory of the FBX SDK install - -# adapted from https://github.com/ufz-vislab/VtkFbxConverter/blob/master/FindFBX.cmake -# which uses the MIT license (https://github.com/ufz-vislab/VtkFbxConverter/blob/master/LICENSE.txt) - -if (NOT FBX_VERSION) - if (WIN32) - set(FBX_VERSION 2017.1) - else() - set(FBX_VERSION 2017.0.1) - endif() -endif() - -string(REGEX REPLACE "^([0-9]+).*$" "\\1" FBX_VERSION_MAJOR "${FBX_VERSION}") -string(REGEX REPLACE "^[0-9]+\\.([0-9]+).*$" "\\1" FBX_VERSION_MINOR "${FBX_VERSION}") -string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*$" "\\1" FBX_VERSION_PATCH "${FBX_VERSION}") - -set(FBX_MAC_LOCATIONS "/Applications/Autodesk/FBX\ SDK/${FBX_VERSION}") -set(FBX_LINUX_LOCATIONS "/usr/local/lib/gcc4/x64/debug/") - -if (WIN32) - string(REGEX REPLACE "\\\\" "/" WIN_PROGRAM_FILES_X64_DIRECTORY $ENV{ProgramW6432}) -endif() - -set(FBX_WIN_LOCATIONS "${WIN_PROGRAM_FILES_X64_DIRECTORY}/Autodesk/FBX/FBX SDK/${FBX_VERSION}") - -set(FBX_SEARCH_LOCATIONS $ENV{FBX_ROOT} ${FBX_ROOT} ${FBX_MAC_LOCATIONS} ${FBX_WIN_LOCATIONS} ${FBX_LINUX_LOCATIONS}) - -function(_fbx_append_debugs _endvar _library) - if (${_library} AND ${_library}_DEBUG) - set(_output optimized ${${_library}} debug ${${_library}_DEBUG}) - else() - set(_output ${${_library}}) - endif() - - set(${_endvar} ${_output} PARENT_SCOPE) -endfunction() - -if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") - set(fbx_compiler clang) -elseif (${CMAKE_CXX_COMPILER_ID} MATCHES "GNU") - set(fbx_compiler gcc4) -endif() - -function(_fbx_find_library _name _lib _suffix) - if (MSVC_VERSION EQUAL 1910) - set(VS_PREFIX vs2017) - elseif (MSVC_VERSION EQUAL 1900) - set(VS_PREFIX vs2015) - elseif (MSVC_VERSION EQUAL 1800) - set(VS_PREFIX vs2013) - elseif (MSVC_VERSION EQUAL 1700) - set(VS_PREFIX vs2012) - elseif (MSVC_VERSION EQUAL 1600) - set(VS_PREFIX vs2010) - elseif (MSVC_VERSION EQUAL 1500) - set(VS_PREFIX vs2008) - endif() - - find_library(${_name} - NAMES ${_lib} - HINTS ${FBX_SEARCH_LOCATIONS} - PATH_SUFFIXES lib/${fbx_compiler}/${_suffix} lib/${fbx_compiler}/ub/${_suffix} lib/${VS_PREFIX}/x64/${_suffix} - ) - - mark_as_advanced(${_name}) -endfunction() - -find_path(FBX_INCLUDE_DIR fbxsdk.h - PATHS ${FBX_SEARCH_LOCATIONS} - PATH_SUFFIXES include -) -mark_as_advanced(FBX_INCLUDE_DIR) - -if (WIN32) - _fbx_find_library(FBX_LIBRARY libfbxsdk-md release) - _fbx_find_library(FBX_LIBRARY_DEBUG libfbxsdk-md debug) -elseif (APPLE) - find_library(CARBON NAMES Carbon) - find_library(SYSTEM_CONFIGURATION NAMES SystemConfiguration) - _fbx_find_library(FBX_LIBRARY libfbxsdk.a release) - _fbx_find_library(FBX_LIBRARY_DEBUG libfbxsdk.a debug) -else () - _fbx_find_library(FBX_LIBRARY libfbxsdk.a release) -endif() - -include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(FBX DEFAULT_MSG FBX_LIBRARY FBX_INCLUDE_DIR) - -if (FBX_FOUND) - set(FBX_INCLUDE_DIRS ${FBX_INCLUDE_DIR}) - _fbx_append_debugs(FBX_LIBRARIES FBX_LIBRARY) - add_definitions(-DFBXSDK_NEW_API) - - if (WIN32) - add_definitions(-DK_PLUGIN -DK_FBXSDK -DK_NODLL) - set(CMAKE_EXE_LINKER_FLAGS /NODEFAULTLIB:\"LIBCMT\") - set(FBX_LIBRARIES ${FBX_LIBRARIES} Wininet.lib) - elseif (APPLE) - set(FBX_LIBRARIES ${FBX_LIBRARIES} ${CARBON} ${SYSTEM_CONFIGURATION}) - endif() -endif() diff --git a/cmake/modules/FindOpenSSL.cmake b/cmake/modules/FindOpenSSL.cmake index 69b6c367ca..338dee7bc8 100644 --- a/cmake/modules/FindOpenSSL.cmake +++ b/cmake/modules/FindOpenSSL.cmake @@ -34,26 +34,11 @@ if (UNIX) endif () if (WIN32) - - file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles) - - if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - # http://www.slproweb.com/products/Win32OpenSSL.html - set(_OPENSSL_ROOT_HINTS ${OPENSSL_ROOT_DIR} $ENV{OPENSSL_ROOT_DIR} $ENV{HIFI_LIB_DIR}/openssl - "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1;Inno Setup: App Path]" - ) - set(_OPENSSL_ROOT_PATHS "${_programfiles}/OpenSSL" "${_programfiles}/OpenSSL-Win64" "C:/OpenSSL/" "C:/OpenSSL-Win64/") + if (("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")) + set(_OPENSSL_ROOT_HINTS_AND_PATHS $ENV{VCPKG_ROOT}/installed/x64-windows) else() - # http://www.slproweb.com/products/Win32OpenSSL.html - set(_OPENSSL_ROOT_HINTS ${OPENSSL_ROOT_DIR} $ENV{OPENSSL_ROOT_DIR} $ENV{HIFI_LIB_DIR}/openssl - "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (32-bit)_is1;Inno Setup: App Path]" - ) - set(_OPENSSL_ROOT_PATHS "${_programfiles}/OpenSSL" "${_programfiles}/OpenSSL-Win32" "C:/OpenSSL/" "C:/OpenSSL-Win32/") + set(_OPENSSL_ROOT_HINTS_AND_PATHS $ENV{VCPKG_ROOT}/installed/x86-windows) endif() - - unset(_programfiles) - set(_OPENSSL_ROOT_HINTS_AND_PATHS HINTS ${_OPENSSL_ROOT_HINTS} PATHS ${_OPENSSL_ROOT_PATHS}) - else () include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") hifi_library_search_hints("openssl") @@ -67,47 +52,14 @@ find_path(OPENSSL_INCLUDE_DIR NAMES openssl/ssl.h HINTS ${_OPENSSL_ROOT_HINTS_AN if (WIN32 AND NOT CYGWIN) if (MSVC) - - # In Visual C++ naming convention each of these four kinds of Windows libraries has it's standard suffix: - # * MD for dynamic-release - # * MDd for dynamic-debug - # * MT for static-release - # * MTd for static-debug - - # Implementation details: - # We are using the libraries located in the VC subdir instead of the parent directory eventhough : - # libeay32MD.lib is identical to ../libeay32.lib, and - # ssleay32MD.lib is identical to ../ssleay32.lib - - # The Kitware FindOpenSSL module has been modified here by High Fidelity to look specifically for static libraries - - find_library(LIB_EAY_DEBUG NAMES libeay32MTd - ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib/VC/static" - ) - - find_library(LIB_EAY_RELEASE NAMES libeay32MT - ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib/VC/static" - ) - - find_library(SSL_EAY_DEBUG NAMES ssleay32MTd - ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib/VC/static" - ) - - find_library(SSL_EAY_RELEASE NAMES ssleay32MT - ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib/VC/static" - ) - - set(LIB_EAY_LIBRARY_DEBUG "${LIB_EAY_DEBUG}") - set(LIB_EAY_LIBRARY_RELEASE "${LIB_EAY_RELEASE}") - set(SSL_EAY_LIBRARY_DEBUG "${SSL_EAY_DEBUG}") - set(SSL_EAY_LIBRARY_RELEASE "${SSL_EAY_RELEASE}") + # Using vcpkg builds of openssl + find_library(LIB_EAY_LIBRARY_RELEASE NAMES libeay32 HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib") + find_library(SSL_EAY_LIBRARY_RELEASE NAMES ssleay32 HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib") include(SelectLibraryConfigurations) 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}) endif() else() diff --git a/cmake/modules/FindTBB.cmake b/cmake/modules/FindTBB.cmake index 1ccdcd792d..12d4deec36 100644 --- a/cmake/modules/FindTBB.cmake +++ b/cmake/modules/FindTBB.cmake @@ -1,6 +1,6 @@ -# +# # FindTBB.cmake -# +# # Try to find the Intel Threading Building Blocks library # # You can provide a TBB_ROOT_DIR which contains lib and include directories @@ -16,7 +16,7 @@ # # Distributed under the Apache License, Version 2.0. # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -# +# include("${MACRO_DIR}/HifiLibrarySearchHints.cmake") hifi_library_search_hints("tbb") @@ -34,31 +34,43 @@ elseif (UNIX AND NOT ANDROID) else() set(_TBB_ARCH_DIR "ia32") endif() - + execute_process( COMMAND ${CMAKE_C_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION ) - - if (GCC_VERSION VERSION_GREATER 4.4 OR GCC_VERSION VERSION_EQUAL 4.4) + + if (GCC_VERSION VERSION_GREATER 4.7 OR GCC_VERSION VERSION_EQUAL 4.7) + set(_TBB_LIB_DIR "lib/${_TBB_ARCH_DIR}/gcc4.7") + elseif (GCC_VERSION VERSION_GREATER 4.4 OR GCC_VERSION VERSION_EQUAL 4.4) set(_TBB_LIB_DIR "lib/${_TBB_ARCH_DIR}/gcc4.4") elseif (GCC_VERSION VERSION_GREATER 4.1 OR GCC_VERSION VERSION_EQUAL 4.1) set(_TBB_LIB_DIR "lib/${_TBB_ARCH_DIR}/gcc4.1") else () message(FATAL_ERROR "Could not find a compatible version of Threading Building Blocks library for your compiler.") endif () - + elseif (WIN32) if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(_TBB_ARCH_DIR "intel64") else() set(_TBB_ARCH_DIR "ia32") endif() - - set(_TBB_LIB_DIR "lib/${_TBB_ARCH_DIR}/vc12") - - find_path(TBB_DLL_PATH tbb_debug.dll PATH_SUFFIXES "bin/${_TBB_ARCH_DIR}/vc12" HINTS ${TBB_SEARCH_DIRS}) - + + if (MSVC_VERSION GREATER_EQUAL 1900) + set(_TBB_MSVC_DIR "vc14") + elseif (MSVC_VERSION GREATER_EQUAL 1800) + set(_TBB_MSVC_DIR "vc12") + elseif (MSVC_VERSION GREATER_EQUAL 1700) + set(_TBB_MSVC_DIR "vc11") + else() + message(FATAL_ERROR "MSVC ${MSVC_VERSION} not supported by Intel TBB") + endif() + + set(_TBB_LIB_DIR "lib/${_TBB_ARCH_DIR}/${_TBB_MSVC_DIR}") + + find_path(TBB_DLL_PATH tbb_debug.dll PATH_SUFFIXES "bin/${_TBB_ARCH_DIR}/${_TBB_MSVC_DIR}" HINTS ${TBB_SEARCH_DIRS}) + elseif (ANDROID) set(_TBB_DEFAULT_INSTALL_DIR "/tbb") set(_TBB_LIB_NAME "tbb") diff --git a/cmake/templates/CPackProperties.cmake.in b/cmake/templates/CPackProperties.cmake.in index c1e3d9d773..b91d78f628 100644 --- a/cmake/templates/CPackProperties.cmake.in +++ b/cmake/templates/CPackProperties.cmake.in @@ -40,6 +40,7 @@ set(CONSOLE_DESKTOP_SHORTCUT_REG_KEY "@CONSOLE_DESKTOP_SHORTCUT_REG_KEY@") set(CONSOLE_STARTUP_REG_KEY "@CONSOLE_STARTUP_REG_KEY@") set(SERVER_LAUNCH_NOW_REG_KEY "@SERVER_LAUNCH_NOW_REG_KEY@") set(CLIENT_LAUNCH_NOW_REG_KEY "@CLIENT_LAUNCH_NOW_REG_KEY@") +set(CUSTOM_INSTALL_REG_KEY "@CUSTOM_INSTALL_REG_KEY@") set(INSTALLER_HEADER_IMAGE "@INSTALLER_HEADER_IMAGE@") set(UNINSTALLER_HEADER_IMAGE "@UNINSTALLER_HEADER_IMAGE@") set(ADD_REMOVE_ICON_PATH "@ADD_REMOVE_ICON_PATH@") diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index 5af51ff8c9..8abb202bd4 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -49,7 +49,7 @@ Var STR_CONTAINS_VAR_3 Var STR_CONTAINS_VAR_4 Var STR_RETURN_VAR - + Function StrContains Exch $STR_NEEDLE Exch 1 @@ -343,29 +343,29 @@ SectionEnd ;-------------------------------- ;Pages !insertmacro MUI_PAGE_WELCOME - + !insertmacro MUI_PAGE_LICENSE "@CPACK_RESOURCE_FILE_LICENSE@" - + Page custom InstallTypesPage ReadInstallTypes - + !define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction !insertmacro MUI_PAGE_DIRECTORY - + ;Start Menu Folder Page Configuration !define MUI_STARTMENUPAGE_REGISTRY_ROOT "HKLM" !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder" - + !define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction !insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER - + !define MUI_PAGE_CUSTOMFUNCTION_PRE AbortFunction @CPACK_NSIS_PAGE_COMPONENTS@ - + Page custom PostInstallOptionsPage ReadPostInstallOptions !insertmacro MUI_PAGE_INSTFILES - + !insertmacro MUI_UNPAGE_CONFIRM !insertmacro MUI_UNPAGE_INSTFILES @@ -453,9 +453,10 @@ Var ExpressInstallRadioButton Var CustomInstallRadioButton Var InstallTypeDialog Var Express +Var CustomInstallTemporaryState -!macro SetPostInstallOption Checkbox OptionName Default - ; reads the value for the given post install option to the registry +!macro SetInstallOption Checkbox OptionName Default + ; reads the value for the given install option to the registry ReadRegStr $0 HKLM "@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\@POST_INSTALL_OPTIONS_REG_GROUP@" "${OptionName}" ${If} $0 == "NO" @@ -472,33 +473,39 @@ Var Express Function InstallTypesPage !insertmacro MUI_HEADER_TEXT "Choose Installation Type" "Express or Custom Install" - + nsDialogs::Create 1018 Pop $InstallTypeDialog - + ${If} $InstallTypeDialog == error Abort ${EndIf} - + StrCpy $CurrentOffset 0 StrCpy $OffsetUnits u - StrCpy $Express "0" - - ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} - ${NSD_CreateRadioButton} 30% $CurrentOffset$OffsetUnits 100% 10u "Express Install (Recommended)"; $\nInstalls High Fidelity Interface and High Fidelity Sandbox" - pop $ExpressInstallRadioButton - ${NSD_OnClick} $ExpressInstallRadioButton ChangeExpressLabel - IntOp $CurrentOffset $CurrentOffset + 15 - - ${NSD_CreateRadiobutton} 30% $CurrentOffset$OffsetUnits 100% 10u "Custom Install (Advanced)" - pop $CustomInstallRadioButton - ${NSD_OnClick} $CustomInstallRadioButton ChangeCustomLabel + StrCpy $Express "0" + + ${NSD_CreateRadioButton} 30% $CurrentOffset$OffsetUnits 100% 10u "Express Install (Recommended)"; $\nInstalls High Fidelity Interface and High Fidelity Sandbox" + pop $ExpressInstallRadioButton + ${NSD_OnClick} $ExpressInstallRadioButton ChangeExpressLabel + IntOp $CurrentOffset $CurrentOffset + 15 + + ${NSD_CreateRadiobutton} 30% $CurrentOffset$OffsetUnits 100% 10u "Custom Install (Advanced)" + pop $CustomInstallRadioButton + ${NSD_OnClick} $CustomInstallRadioButton ChangeCustomLabel + + ; check install type from the registry, express install by default + !insertmacro SetInstallOption $CustomInstallRadioButton @CUSTOM_INSTALL_REG_KEY@ ${BST_UNCHECKED} + + ; set the express install value based on the custom install value from registry + ${NSD_GetState} $CustomInstallRadioButton $CustomInstallTemporaryState + + ${If} $CustomInstallTemporaryState == ${BST_UNCHECKED} + ${NSD_Check} $ExpressInstallRadioButton ${EndIf} - - ; Express Install selected by default - ${NSD_Check} $ExpressInstallRadioButton + Call ChangeExpressLabel - + nsDialogs::Show FunctionEnd @@ -518,11 +525,10 @@ FunctionEnd Function AbortFunction ; Check if Express is set, if so, abort the post install options page - Call HandleInstallTypes ; Sets Express if ExpressInstallRadioButton is checked and installs with defaults StrCmp $Express "1" 0 end Abort end: -FunctionEnd +FunctionEnd Function PostInstallOptionsPage !insertmacro MUI_HEADER_TEXT "Setup Options" "" @@ -534,6 +540,11 @@ Function PostInstallOptionsPage Abort ${EndIf} + ; Check if Express is set, if so, abort the post install options page + StrCmp $Express "1" 0 end + Abort + end: + StrCpy $CurrentOffset 0 StrCpy $OffsetUnits u @@ -541,18 +552,18 @@ Function PostInstallOptionsPage ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @INTERFACE_HF_SHORTCUT_NAME@" Pop $DesktopClientCheckbox IntOp $CurrentOffset $CurrentOffset + 15 - + ; set the checkbox state depending on what is present in the registry - !insertmacro SetPostInstallOption $DesktopClientCheckbox @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ ${BST_CHECKED} + !insertmacro SetInstallOption $DesktopClientCheckbox @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ ${BST_CHECKED} ${EndIf} - + ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @CONSOLE_HF_SHORTCUT_NAME@" Pop $DesktopServerCheckbox IntOp $CurrentOffset $CurrentOffset + 15 - + ; set the checkbox state depending on what is present in the registry - !insertmacro SetPostInstallOption $DesktopServerCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED} + !insertmacro SetInstallOption $DesktopServerCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED} ${EndIf} ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} @@ -560,7 +571,7 @@ Function PostInstallOptionsPage Pop $LaunchServerNowCheckbox ; set the checkbox state depending on what is present in the registry - !insertmacro SetPostInstallOption $LaunchServerNowCheckbox @SERVER_LAUNCH_NOW_REG_KEY@ ${BST_CHECKED} + !insertmacro SetInstallOption $LaunchServerNowCheckbox @SERVER_LAUNCH_NOW_REG_KEY@ ${BST_CHECKED} ${StrContains} $substringResult "/forceNoLaunchServer" $CMDLINE ${IfNot} $substringResult == "" ${NSD_SetState} $LaunchServerNowCheckbox ${BST_UNCHECKED} @@ -568,29 +579,29 @@ Function PostInstallOptionsPage IntOp $CurrentOffset $CurrentOffset + 15 ${EndIf} - + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @INTERFACE_HF_SHORTCUT_NAME@ after install" Pop $LaunchClientNowCheckbox IntOp $CurrentOffset $CurrentOffset + 30 - + ; set the checkbox state depending on what is present in the registry - !insertmacro SetPostInstallOption $LaunchClientNowCheckbox @CLIENT_LAUNCH_NOW_REG_KEY@ ${BST_CHECKED} + !insertmacro SetInstallOption $LaunchClientNowCheckbox @CLIENT_LAUNCH_NOW_REG_KEY@ ${BST_CHECKED} ${StrContains} $substringResult "/forceNoLaunchClient" $CMDLINE ${IfNot} $substringResult == "" ${NSD_SetState} $LaunchClientNowCheckbox ${BST_UNCHECKED} ${EndIf} ${EndIf} - + ${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@} ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ on startup" Pop $ServerStartupCheckbox IntOp $CurrentOffset $CurrentOffset + 15 - + ; set the checkbox state depending on what is present in the registry - !insertmacro SetPostInstallOption $ServerStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED} + !insertmacro SetInstallOption $ServerStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED} ${EndIf} - + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Perform a clean install (Delete older settings and content)" Pop $CleanInstallCheckbox @@ -616,18 +627,12 @@ Function PostInstallOptionsPage ${NSD_SetState} $CopyFromProductionCheckbox ${BST_UNCHECKED} ${EndIf} - - ; Check if Express is set, if so, abort the post install options page - Call HandleInstallTypes ; Sets Express if ExpressInstallRadioButton is checked and installs with defaults - StrCmp $Express "1" 0 end - Abort - end: - + nsDialogs::Show FunctionEnd -!macro WritePostInstallOption OptionName Option - ; writes the value for the given post install option to the registry +!macro WriteInstallOption OptionName Option + ; writes the value for the given install option to the registry WriteRegStr HKLM "@REGISTRY_HKLM_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\@POST_INSTALL_OPTIONS_REG_GROUP@" "${OptionName}" ${Option} !macroend @@ -638,15 +643,33 @@ Var LaunchServerNowState Var LaunchClientNowState Var CopyFromProductionState Var CleanInstallState -Var ExpressInstallState +Var ExpressInstallState Var CustomInstallState Function ReadInstallTypes - ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} - ; check if the user asked for express/custom install - ${NSD_GetState} $ExpressInstallRadioButton $ExpressInstallState - ${NSD_GetState} $CustomInstallRadioButton $CustomInstallState + ; check if the user asked for express/custom install + ${NSD_GetState} $ExpressInstallRadioButton $ExpressInstallState + ${NSD_GetState} $CustomInstallRadioButton $CustomInstallState + + ${If} $ExpressInstallState == ${BST_CHECKED} + StrCpy $Express "1" + + StrCpy $DesktopClientState ${BST_CHECKED} + StrCpy $ServerStartupState ${BST_CHECKED} + StrCpy $LaunchServerNowState ${BST_CHECKED} + StrCpy $LaunchClientNowState ${BST_CHECKED} + StrCpy $CleanInstallState ${BST_UNCHECKED} + StrCpy $DesktopServerState ${BST_UNCHECKED} + + ${If} @PR_BUILD@ == 1 + StrCpy $CopyFromProductionState ${BST_UNCHECKED} + ${EndIf} + + !insertmacro WriteInstallOption "@CUSTOM_INSTALL_REG_KEY@" NO + ${Else} + !insertmacro WriteInstallOption "@CUSTOM_INSTALL_REG_KEY@" YES ${EndIf} + FunctionEnd Function ReadPostInstallOptions @@ -672,48 +695,26 @@ Function ReadPostInstallOptions ; check if we need to launch the server post-install ${NSD_GetState} $LaunchServerNowCheckbox $LaunchServerNowState ${EndIf} - + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} ; check if we need to launch the client post-install ${NSD_GetState} $LaunchClientNowCheckbox $LaunchClientNowState ${EndIf} - + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} ; check if the user asked for a clean install ${NSD_GetState} $CleanInstallCheckbox $CleanInstallState ${EndIf} FunctionEnd -Function HandleInstallTypes - ${If} $ExpressInstallState == ${BST_CHECKED} - - StrCpy $Express "1" - - ; over ride custom checkboxes and select defaults - ${NSD_SetState} $DesktopClientCheckbox ${BST_CHECKED} - ${NSD_SetState} $ServerStartupCheckbox ${BST_CHECKED} - ${NSD_SetState} $LaunchServerNowCheckbox ${BST_CHECKED} - ${NSD_SetState} $LaunchClientNowCheckbox ${BST_CHECKED} - - ${If} @PR_BUILD@ == 1 - ${NSD_SetState} $CopyFromProductionCheckbox ${BST_UNCHECKED} - ${EndIf} - - ; call ReadPostInstallOptions and HandlePostInstallOptions with defaults selected - Call ReadPostInstallOptions - Call HandlePostInstallOptions - - ${EndIf} -FunctionEnd - Function HandlePostInstallOptions ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} ; check if the user asked for a desktop shortcut to High Fidelity ${If} $DesktopClientState == ${BST_CHECKED} CreateShortCut "$DESKTOP\@INTERFACE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@" - !insertmacro WritePostInstallOption "@CLIENT_DESKTOP_SHORTCUT_REG_KEY@" YES + !insertmacro WriteInstallOption "@CLIENT_DESKTOP_SHORTCUT_REG_KEY@" YES ${Else} - !insertmacro WritePostInstallOption @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ NO + !insertmacro WriteInstallOption @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ NO ${EndIf} ${EndIf} @@ -722,12 +723,12 @@ Function HandlePostInstallOptions ; check if the user asked for a desktop shortcut to Sandbox ${If} $DesktopServerState == ${BST_CHECKED} CreateShortCut "$DESKTOP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" - !insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ YES + !insertmacro WriteInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ YES ${Else} - !insertmacro WritePostInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ NO + !insertmacro WriteInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ NO ${EndIf} - + ; check if the user asked to have Sandbox launched every startup ${If} $ServerStartupState == ${BST_CHECKED} ; in case we added a shortcut in the global context, pull that now @@ -741,12 +742,12 @@ Function HandlePostInstallOptions ; reset the shell var context back SetShellVarContext all - !insertmacro WritePostInstallOption @CONSOLE_STARTUP_REG_KEY@ YES + !insertmacro WriteInstallOption @CONSOLE_STARTUP_REG_KEY@ YES ${Else} - !insertmacro WritePostInstallOption @CONSOLE_STARTUP_REG_KEY@ NO + !insertmacro WriteInstallOption @CONSOLE_STARTUP_REG_KEY@ NO ${EndIf} ${EndIf} - + ${If} ${SectionIsSelected} ${@CLIENT_COMPONENT_NAME@} ; check if the user asked for a clean install ${If} $CleanInstallState == ${BST_CHECKED} @@ -785,28 +786,28 @@ Function HandlePostInstallOptions ${EndIf} ${If} $LaunchServerNowState == ${BST_CHECKED} - !insertmacro WritePostInstallOption @SERVER_LAUNCH_NOW_REG_KEY@ YES + !insertmacro WriteInstallOption @SERVER_LAUNCH_NOW_REG_KEY@ YES ; both launches use the explorer trick in case the user has elevated permissions for the installer ${If} $LaunchClientNowState == ${BST_CHECKED} - !insertmacro WritePostInstallOption @CLIENT_LAUNCH_NOW_REG_KEY@ YES + !insertmacro WriteInstallOption @CLIENT_LAUNCH_NOW_REG_KEY@ YES ; create shortcut with ARGUMENTS CreateShortCut "$TEMP\SandboxShortcut.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" "-- --launchInterface" Exec '"$WINDIR\explorer.exe" "$TEMP\SandboxShortcut.lnk"' ${Else} - !insertmacro WritePostInstallOption @CLIENT_LAUNCH_NOW_REG_KEY@ NO + !insertmacro WriteInstallOption @CLIENT_LAUNCH_NOW_REG_KEY@ NO Exec '"$WINDIR\explorer.exe" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"' ${EndIf} ${Else} - !insertmacro WritePostInstallOption @SERVER_LAUNCH_NOW_REG_KEY@ NO + !insertmacro WriteInstallOption @SERVER_LAUNCH_NOW_REG_KEY@ NO ; launch uses the explorer trick in case the user has elevated permissions for the installer ${If} $LaunchClientNowState == ${BST_CHECKED} - !insertmacro WritePostInstallOption @CLIENT_LAUNCH_NOW_REG_KEY@ YES + !insertmacro WriteInstallOption @CLIENT_LAUNCH_NOW_REG_KEY@ YES Exec '"$WINDIR\explorer.exe" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"' ${Else} - !insertmacro WritePostInstallOption @CLIENT_LAUNCH_NOW_REG_KEY@ NO + !insertmacro WriteInstallOption @CLIENT_LAUNCH_NOW_REG_KEY@ NO ${EndIf} ${EndIf} @@ -835,6 +836,9 @@ Section "-Core installation" Delete "$INSTDIR\ui_resources_200_percent.pak" Delete "$INSTDIR\vccorlib120.dll" Delete "$INSTDIR\version" + Delete "$INSTDIR\msvcr140.dll" + Delete "$INSTDIR\msvcp140.dll" + Delete "$INSTDIR\vcruntime140.dll" Delete "$INSTDIR\xinput1_3.dll" ; Delete old desktop shortcuts before they were renamed during Sandbox rename @@ -955,7 +959,7 @@ Section "-Core installation" !insertmacro MUI_STARTMENU_WRITE_END -@CPACK_NSIS_EXTRA_INSTALL_COMMANDS@ + @CPACK_NSIS_EXTRA_INSTALL_COMMANDS@ ; Handle whichever post install options were set Call HandlePostInstallOptions @@ -974,9 +978,18 @@ SectionEnd ${If} $R0 == 0 ; the process is running, ask the user to close it + + ${If} "${displayName}" == "@CONSOLE_DISPLAY_NAME@" MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION \ - "${displayName} cannot be ${action} while ${displayName} is running.$\r$\nPlease close it and click Retry to continue." \ + "${displayName} cannot be ${action} while ${displayName} is running.$\r$\nPlease close it in the system tray and click Retry to continue." \ /SD IDCANCEL IDRETRY Prompt_${UniqueID} IDCANCEL 0 + ${EndIf} + + ${If} "${displayName}" == "@INTERFACE_DISPLAY_NAME@" + MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION \ + "${displayName} cannot be ${action} while ${displayName} is running.$\r$\nPlease close it in the task bar and click Retry to continue." \ + /SD IDCANCEL IDRETRY Prompt_${UniqueID} IDCANCEL 0 + ${EndIf} ; If the user decided to cancel, stop the current installer/uninstaller Abort diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index c1eff76d78..8d0e949ff3 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1,5 +1,5 @@ { - "version": 1.7, + "version": 1.8, "settings": [ { "name": "metaverse", @@ -112,7 +112,6 @@ "label": "Adult (18+)" } ] - }, { "name": "hosts", @@ -161,15 +160,15 @@ "label": "HTTP Password", "type": "password", "help": "Password used for basic HTTP authentication. Leave this alone if you do not want to change it.", - "password_placeholder" : "******", + "password_placeholder": "******", "value-hidden": true }, { - "name": "verify_http_password", - "label": "Verify HTTP Password", - "type": "password", - "help": "Must match the password entered above for change to be saved.", - "value-hidden": true + "name": "verify_http_password", + "label": "Verify HTTP Password", + "type": "password", + "help": "Must match the password entered above for change to be saved.", + "value-hidden": true }, { "name": "maximum_user_capacity", @@ -208,21 +207,19 @@ "name": "standard_permissions", "type": "table", "label": "Domain-Wide User Permissions", - "help": "Indicate which types of users can have which domain-wide permissions.", + "help": "Indicate which types of users can have which domain-wide permissions.", "caption": "Standard Permissions", "can_add_new_rows": false, - "groups": [ { "label": "Type of User", "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -276,9 +273,15 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ], - "non-deletable-row-key": "permissions_id", "non-deletable-row-values": ["localhost", "anonymous", "logged-in"] }, @@ -291,18 +294,16 @@ "can_add_new_rows": false, "new_category_placeholder": "Add Group", "new_category_message": "Save and reload to see ranks", - "groups": [ { "label": "Rank", "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -381,6 +382,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -393,18 +401,16 @@ "can_add_new_rows": false, "new_category_placeholder": "Add Blacklist Group", "new_category_message": "Save and reload to see ranks", - "groups": [ { "label": "Rank", "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -480,6 +486,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -488,18 +501,16 @@ "type": "table", "caption": "Permissions for Specific Users", "can_add_new_rows": true, - "groups": [ { "label": "User", "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -553,6 +564,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -567,11 +585,10 @@ "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -625,6 +642,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -639,11 +663,10 @@ "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -697,6 +720,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -711,11 +741,10 @@ "span": 1 }, { - "label": "Permissions ?", - "span": 7 + "label": "Permissions ?", + "span": 8 } ], - "columns": [ { "name": "permissions_id", @@ -769,6 +798,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_replace_content", + "label": "Replace Content", + "type": "checkbox", + "editable": true, + "default": false } ] } @@ -784,7 +820,6 @@ "label": "Persistent Scripts", "help": "Add the URLs for scripts that you would like to ensure are always running in your domain.", "can_add_new_rows": true, - "columns": [ { "name": "url", @@ -946,7 +981,6 @@ "help": "In this table you can define a set of zones in which you can specify various audio properties.", "numbered": false, "can_add_new_rows": true, - "key": { "name": "name", "label": "Name", @@ -999,7 +1033,6 @@ "numbered": true, "can_order": true, "can_add_new_rows": true, - "columns": [ { "name": "source", @@ -1028,7 +1061,6 @@ "help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.", "numbered": true, "can_add_new_rows": true, - "columns": [ { "name": "zone", @@ -1063,7 +1095,9 @@ { "name": "audio_buffer", "label": "Audio Buffers", - "assignment-types": [0], + "assignment-types": [ + 0 + ], "settings": [ { "name": "dynamic_jitter_buffer", @@ -1082,35 +1116,37 @@ "advanced": true }, { - "name": "max_frames_over_desired", - "deprecated": true + "name": "max_frames_over_desired", + "deprecated": true }, { - "name": "window_starve_threshold", - "deprecated": true + "name": "window_starve_threshold", + "deprecated": true }, { - "name": "window_seconds_for_desired_calc_on_too_many_starves", - "deprecated": true + "name": "window_seconds_for_desired_calc_on_too_many_starves", + "deprecated": true }, { - "name": "window_seconds_for_desired_reduction", - "deprecated": true + "name": "window_seconds_for_desired_reduction", + "deprecated": true }, { - "name": "use_stdev_for_desired_calc", - "deprecated": true + "name": "use_stdev_for_desired_calc", + "deprecated": true }, { - "name": "repetition_with_fade", - "deprecated": true + "name": "repetition_with_fade", + "deprecated": true } ] }, { "name": "entity_server_settings", "label": "Entity Server Settings", - "assignment-types": [6], + "assignment-types": [ + 6 + ], "settings": [ { "name": "maxTmpLifetime", @@ -1167,13 +1203,32 @@ "help": "In this table you can define a set of rules for how frequently to backup copies of your entites content file.", "numbered": false, "can_add_new_rows": true, - - "default": [ - {"Name":"Half Hourly Rolling","backupInterval":1800,"format":".backup.halfhourly.%N","maxBackupVersions":5}, - {"Name":"Daily Rolling","backupInterval":86400,"format":".backup.daily.%N","maxBackupVersions":7}, - {"Name":"Weekly Rolling","backupInterval":604800,"format":".backup.weekly.%N","maxBackupVersions":4}, - {"Name":"Thirty Day Rolling","backupInterval":2592000,"format":".backup.thirtyday.%N","maxBackupVersions":12} - ], + "default": [ + { + "Name": "Half Hourly Rolling", + "backupInterval": 1800, + "format": ".backup.halfhourly.%N", + "maxBackupVersions": 5 + }, + { + "Name": "Daily Rolling", + "backupInterval": 86400, + "format": ".backup.daily.%N", + "maxBackupVersions": 7 + }, + { + "Name": "Weekly Rolling", + "backupInterval": 604800, + "format": ".backup.weekly.%N", + "maxBackupVersions": 4 + }, + { + "Name": "Thirty Day Rolling", + "backupInterval": 2592000, + "format": ".backup.thirtyday.%N", + "maxBackupVersions": 12 + } + ], "columns": [ { "name": "Name", @@ -1309,7 +1364,9 @@ { "name": "avatar_mixer", "label": "Avatar Mixer", - "assignment-types": [1], + "assignment-types": [ + 1 + ], "settings": [ { "name": "max_node_send_bandwidth", @@ -1362,7 +1419,10 @@ { "name": "downstream_servers", "label": "Receiving Servers", - "assignment-types": [0,1], + "assignment-types": [ + 0, + 1 + ], "type": "table", "advanced": true, "can_add_new_rows": true, @@ -1402,7 +1462,10 @@ { "name": "upstream_servers", "label": "Broadcasting Servers", - "assignment-types": [0,1], + "assignment-types": [ + 0, + 1 + ], "type": "table", "advanced": true, "can_add_new_rows": true, @@ -1442,4 +1505,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/domain-server/resources/web/assignment/placeholder.js b/domain-server/resources/web/assignment/placeholder.js index 2c1d8253aa..bf64539bea 100644 --- a/domain-server/resources/web/assignment/placeholder.js +++ b/domain-server/resources/web/assignment/placeholder.js @@ -1,3 +1,3 @@ // Here you can put a script that will be run by an assignment-client (AC) -// For examples, please go to http://public.highfidelity.io/scripts +// For examples, please go to https://github.com/highfidelity/hifi/tree/master/script-archive/acScripts // The directory named acScripts contains assignment-client specific scripts you can try. diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index fc595be67e..09bebf806a 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -54,6 +54,7 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointergetSize() == 0) { return; } + QDataStream packetStream(message->getMessage()); // read a NodeConnectionData object from the packet so we can pass around this data while we're inspecting it @@ -268,24 +269,27 @@ void DomainGatekeeper::updateNodePermissions() { userPerms.permissions |= NodePermissions::Permission::canAdjustLocks; userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities; userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities; + userPerms.permissions |= NodePermissions::Permission::canRezPermanentCertifiedEntities; + userPerms.permissions |= NodePermissions::Permission::canRezTemporaryCertifiedEntities; userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer; + userPerms.permissions |= NodePermissions::Permission::canReplaceDomainContent; } else { - // this node is an agent - const QHostAddress& addr = node->getLocalSocket().getAddress(); - bool isLocalUser = (addr == limitedNodeList->getLocalSockAddr().getAddress() || - addr == QHostAddress::LocalHost); - // at this point we don't have a sending socket for packets from this node - assume it is the active socket // or the public socket if we haven't activated a socket for the node yet HifiSockAddr connectingAddr = node->getActiveSocket() ? *node->getActiveSocket() : node->getPublicSocket(); QString hardwareAddress; QUuid machineFingerprint; + bool isLocalUser { false }; DomainServerNodeData* nodeData = static_cast(node->getLinkedData()); if (nodeData) { hardwareAddress = nodeData->getHardwareAddress(); machineFingerprint = nodeData->getMachineFingerprint(); + + auto sendingAddress = nodeData->getSendingSockAddr().getAddress(); + isLocalUser = (sendingAddress == limitedNodeList->getLocalSockAddr().getAddress() || + sendingAddress == QHostAddress::LocalHost); } userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, connectingAddr.getAddress(), hardwareAddress, machineFingerprint); @@ -356,7 +360,10 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo userPerms.permissions |= NodePermissions::Permission::canAdjustLocks; userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities; userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities; + userPerms.permissions |= NodePermissions::Permission::canRezPermanentCertifiedEntities; + userPerms.permissions |= NodePermissions::Permission::canRezTemporaryCertifiedEntities; userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer; + userPerms.permissions |= NodePermissions::Permission::canReplaceDomainContent; newNode->setPermissions(userPerms); return newNode; } diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 163bd48f1b..436f49c7ca 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -458,7 +458,7 @@ const QString DISABLED_AUTOMATIC_NETWORKING_VALUE = "disabled"; -bool DomainServer::packetVersionMatch(const udt::Packet& packet) { +bool DomainServer::isPacketVerified(const udt::Packet& packet) { PacketType headerType = NLPacket::typeInHeader(packet); PacketVersion headerVersion = NLPacket::versionInHeader(packet); @@ -471,7 +471,48 @@ bool DomainServer::packetVersionMatch(const udt::Packet& packet) { DomainGatekeeper::sendProtocolMismatchConnectionDenial(packet.getSenderSockAddr()); } - // let the normal nodeList implementation handle all other packets. + if (!PacketTypeEnum::getNonSourcedPackets().contains(headerType)) { + // this is a sourced packet - first check if we have a node that matches + QUuid sourceID = NLPacket::sourceIDInHeader(packet); + SharedNodePointer sourceNode = nodeList->nodeWithUUID(sourceID); + + if (sourceNode) { + // unverified DS packets (due to a lack of connection secret between DS + node) + // must come either from the same public IP address or a local IP address (set by RFC 1918) + + DomainServerNodeData* nodeData = static_cast(sourceNode->getLinkedData()); + + bool exactAddressMatch = nodeData->getSendingSockAddr() == packet.getSenderSockAddr(); + bool bothPrivateAddresses = nodeData->getSendingSockAddr().hasPrivateAddress() + && packet.getSenderSockAddr().hasPrivateAddress(); + + if (nodeData && (exactAddressMatch || bothPrivateAddresses)) { + // to the best of our ability we've verified that this packet comes from the right place + // let the NodeList do its checks now (but pass it the sourceNode so it doesn't need to look it up again) + return nodeList->isPacketVerifiedWithSource(packet, sourceNode.data()); + } else { + static const QString UNKNOWN_REGEX = "Packet of type \\d+ \\([\\sa-zA-Z:]+\\) received from unmatched IP for UUID"; + static QString repeatedMessage + = LogHandler::getInstance().addRepeatedMessageRegex(UNKNOWN_REGEX); + + qDebug() << "Packet of type" << headerType + << "received from unmatched IP for UUID" << uuidStringWithoutCurlyBraces(sourceID); + + return false; + } + } else { + static const QString UNKNOWN_REGEX = "Packet of type \\d+ \\([\\sa-zA-Z:]+\\) received from unknown node with UUID"; + static QString repeatedMessage + = LogHandler::getInstance().addRepeatedMessageRegex(UNKNOWN_REGEX); + + qDebug() << "Packet of type" << headerType + << "received from unknown node with UUID" << uuidStringWithoutCurlyBraces(sourceID); + + return false; + } + } + + // fallback to allow the normal NodeList implementation to verify packets return nodeList->isPacketVerified(packet); } @@ -570,7 +611,7 @@ void DomainServer::setupNodeListAndAssignments() { addStaticAssignmentsToQueue(); // set a custom packetVersionMatch as the verify packet operator for the udt::Socket - nodeList->setPacketFilterOperator(&DomainServer::packetVersionMatch); + nodeList->setPacketFilterOperator(&DomainServer::isPacketVerified); } const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token"; @@ -711,8 +752,28 @@ void DomainServer::setupICEHeartbeatForFullNetworking() { } void DomainServer::updateICEServerAddresses() { - if (_iceAddressLookupID == -1) { + 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(); } } @@ -853,7 +914,6 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet message, SharedNodePointer sendingNode) { - QDataStream packetStream(message->getMessage()); NodeConnectionData nodeRequestData = NodeConnectionData::fromDataStream(packetStream, message->getSenderSockAddr(), false); @@ -899,7 +959,8 @@ bool DomainServer::isInInterestSet(const SharedNodePointer& nodeA, const SharedN bool isAgentWithoutRights = nodeA->getType() == NodeType::Agent && nodeB->getType() == NodeType::EntityScriptServer - && !nodeA->getCanRez() && !nodeA->getCanRezTmp(); + && !nodeA->getCanRez() && !nodeA->getCanRezTmp() + && !nodeA->getCanRezCertified() && !nodeA->getCanRezTmpCertified(); if (isAgentWithoutRights) { return false; @@ -908,7 +969,7 @@ bool DomainServer::isInInterestSet(const SharedNodePointer& nodeA, const SharedN bool isScriptServerForIneffectiveAgent = (nodeA->getType() == NodeType::EntityScriptServer && nodeB->getType() == NodeType::Agent) && ((nodeBData && !nodeBData->getNodeInterestSet().contains(NodeType::EntityScriptServer)) - || (!nodeB->getCanRez() && !nodeB->getCanRezTmp())); + || (!nodeB->getCanRez() && !nodeB->getCanRezTmp() && !nodeB->getCanRezCertified() && !nodeB->getCanRezTmpCertified())); return !isScriptServerForIneffectiveAgent; } else { diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 4808297c89..52ac435517 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -39,6 +39,8 @@ typedef QMultiHash TransactionHash; using Subnet = QPair; using SubnetList = std::vector; +const int INVALID_ICE_LOOKUP_ID = -1; + enum ReplicationServerDirection { Upstream, Downstream @@ -71,6 +73,7 @@ public slots: void restart(); +private slots: void processRequestAssignmentPacket(QSharedPointer packet); void processListRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); void processNodeJSONStatsPacket(QSharedPointer packetList, SharedNodePointer sendingNode); @@ -79,7 +82,6 @@ public slots: void processICEServerHeartbeatDenialPacket(QSharedPointer message); void processICEServerHeartbeatACK(QSharedPointer message); -private slots: void setupPendingAssignmentCredits(); void sendPendingTransactionsToServer(); @@ -114,6 +116,8 @@ private slots: void tokenGrantFinished(); void profileRequestFinished(); + void timeoutICEAddressLookup(); + signals: void iceServerChanged(); void userConnected(); @@ -129,7 +133,7 @@ private: void getTemporaryName(bool force = false); - static bool packetVersionMatch(const udt::Packet& packet); + static bool isPacketVerified(const udt::Packet& packet); bool resetAccountManagerAccessToken(); @@ -223,7 +227,7 @@ private: QList _iceServerAddresses; QSet _failedIceServerAddresses; - int _iceAddressLookupID { -1 }; + int _iceAddressLookupID { INVALID_ICE_LOOKUP_ID }; int _noReplyICEHeartbeats { 0 }; int _numHeartbeatDenials { 0 }; bool _connectedToICEServer { false }; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 7a2cfa645a..c801a4a71a 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -112,6 +112,7 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList const QString RESTRICTED_ACCESS_SETTINGS_KEYPATH = "security.restricted_access"; const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors"; const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers"; + const QString EDITORS_CAN_REPLACE_CONTENT_KEYPATH = "security.editors_can_replace_content"; qDebug() << "Previous domain-server settings version was" << QString::number(oldVersion, 'g', 8) << "and the new version is" @@ -294,6 +295,21 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList // 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 + _standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canReplaceDomainContent); + packPermissions(); + } + + if (oldVersion < 1.9) { + unpackPermissions(); + // This was prior to addition of canRez(Tmp)Certified; add those to localhost permissions by default + _standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canRezPermanentCertifiedEntities); + _standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canRezTemporaryCertifiedEntities); + packPermissions(); + } } unpackPermissions(); diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 81c8a44baf..305a6475f6 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -18,6 +18,7 @@ find_package(Qt5LinguistTools REQUIRED) find_package(Qt5LinguistToolsMacros) if (WIN32) + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -bigobj") add_definitions(-D_USE_MATH_DEFINES) # apparently needed to get M_PI and other defines from cmath/math.h add_definitions(-DWINDOWS_LEAN_AND_MEAN) # needed to make sure windows doesn't go to crazy with its defines endif() @@ -170,8 +171,6 @@ else () add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM}) endif () -target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/includes") - if (WIN32) # These are external plugins, but we need to do the 'add dependency' here so that their # binary directories get added to the fixup path @@ -183,6 +182,12 @@ if (WIN32) add_dependency_external_projects(steamworks) endif() +# include OPENSSL +include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}") + +# append OpenSSL to our list of libraries to link +target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES}) + # disable /OPT:REF and /OPT:ICF for the Debug builds # This will prevent the following linker warnings # LINK : warning LNK4075: ignoring '/INCREMENTAL' due to '/OPT:ICF' specification @@ -207,10 +212,6 @@ target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries target_bullet() target_opengl() -if (NOT ANDROID) - target_glew() -endif () - # perform standard include and linking for found externals foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) diff --git a/interface/resources/controllers/keyboardMouse.json b/interface/resources/controllers/keyboardMouse.json index f377e02e5f..54d2467f78 100644 --- a/interface/resources/controllers/keyboardMouse.json +++ b/interface/resources/controllers/keyboardMouse.json @@ -131,6 +131,6 @@ { "from": "Keyboard.Space", "to": "Actions.SHIFT" }, { "from": "Keyboard.R", "to": "Actions.ACTION1" }, { "from": "Keyboard.T", "to": "Actions.ACTION2" }, - { "from": "Keyboard.RightMouseClicked", "to": "Actions.ContextMenu" } + { "from": "Keyboard.Tab", "to": "Actions.ContextMenu" } ] } diff --git a/interface/resources/controllers/standard.json b/interface/resources/controllers/standard.json index 0d5c095585..28f15605e0 100644 --- a/interface/resources/controllers/standard.json +++ b/interface/resources/controllers/standard.json @@ -25,7 +25,7 @@ }, { "from": "Standard.RX", - "when": [ "Application.InHMD", "Application.SnapTurn" ], + "when": [ "Application.SnapTurn" ], "to": "Actions.StepYaw", "filters": [ @@ -128,4 +128,4 @@ { "from": "Standard.TrackedObject14", "to" : "Actions.TrackedObject14" }, { "from": "Standard.TrackedObject15", "to" : "Actions.TrackedObject15" } ] -} +} \ No newline at end of file diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf index 8733349227..3db48602b1 100644 Binary files a/interface/resources/fonts/hifi-glyphs.ttf and b/interface/resources/fonts/hifi-glyphs.ttf differ diff --git a/interface/resources/fonts/hifi-glyphs/icons-reference.html b/interface/resources/fonts/hifi-glyphs/icons-reference.html new file mode 100644 index 0000000000..99e826e0b9 --- /dev/null +++ b/interface/resources/fonts/hifi-glyphs/icons-reference.html @@ -0,0 +1,1056 @@ + + + + + + + Font Reference - HiFi Glyphs + + + + + +
+

HiFi Glyphs

+

This font was created inHigh Fidelity

+

CSS mapping

+
    +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • + + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • + + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
+

Character mapping

+
    +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
+
+ + + diff --git a/interface/resources/fonts/hifi-glyphs/styles.css b/interface/resources/fonts/hifi-glyphs/styles.css new file mode 100644 index 0000000000..66277740fc --- /dev/null +++ b/interface/resources/fonts/hifi-glyphs/styles.css @@ -0,0 +1,421 @@ +@charset "UTF-8"; + +@font-face { + font-family: "hifi-glyphs"; + src:url("fonts/hifi-glyphs.eot"); + src:url("fonts/hifi-glyphs.eot?#iefix") format("embedded-opentype"), + url("fonts/hifi-glyphs.woff") format("woff"), + url("fonts/hifi-glyphs.ttf") format("truetype"), + url("fonts/hifi-glyphs.svg#hifi-glyphs") format("svg"); + font-weight: normal; + font-style: normal; + +} + +[data-icon]:before { + font-family: "hifi-glyphs" !important; + content: attr(data-icon); + font-style: normal !important; + font-weight: normal !important; + font-variant: normal !important; + text-transform: none !important; + speak: none; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +[class^="icon-"]:before, +[class*=" icon-"]:before { + font-family: "hifi-glyphs" !important; + font-style: normal !important; + font-weight: normal !important; + font-variant: normal !important; + text-transform: none !important; + speak: none; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-hmd:before { + content: "\62"; +} +.icon-2d-screen:before { + content: "\63"; +} +.icon-keyboard:before { + content: "\64"; +} +.icon-hand-controllers:before { + content: "\65"; +} +.icon-headphones-mic:before { + content: "\66"; +} +.icon-gamepad:before { + content: "\67"; +} +.icon-headphones:before { + content: "\68"; +} +.icon-mic:before { + content: "\69"; +} +.icon-upload:before { + content: "\6a"; +} +.icon-script:before { + content: "\6b"; +} +.icon-text:before { + content: "\6c"; +} +.icon-cube:before { + content: "\6d"; +} +.icon-sphere:before { + content: "\6e"; +} +.icon-zone:before { + content: "\6f"; +} +.icon-light:before { + content: "\70"; +} +.icon-web:before { + content: "\71"; +} +.icon-web-2:before { + content: "\72"; +} +.icon-edit:before { + content: "\73"; +} +.icon-market:before { + content: "\74"; +} +.icon-directory:before { + content: "\75"; +} +.icon-menu:before { + content: "\76"; +} +.icon-close:before { + content: "\77"; +} +.icon-close-inverted:before { + content: "\78"; +} +.icon-pin:before { + content: "\79"; +} +.icon-pin-inverted:before { + content: "\7a"; +} +.icon-resize-handle:before { + content: "\41"; +} +.icon-diclosure-expand:before { + content: "\42"; +} +.icon-reload-small:before { + content: "\61"; +} +.icon-close-small:before { + content: "\43"; +} +.icon-backward:before { + content: "\45"; +} +.icon-reload:before { + content: "\46"; +} +.icon-minimize:before { + content: "\49"; +} +.icon-maximize:before { + content: "\4a"; +} +.icon-maximize-inverted:before { + content: "\4b"; +} +.icon-disclosure-button-expand:before { + content: "\4c"; +} +.icon-disclosure-button-collapse:before { + content: "\4d"; +} +.icon-script-stop:before { + content: "\4e"; +} +.icon-script-reload:before { + content: "\4f"; +} +.icon-script-run:before { + content: "\50"; +} +.icon-script-new:before { + content: "\51"; +} +.icon-hifi-forum:before { + content: "\32"; +} +.icon-hifi-logo-small:before { + content: "\53"; +} +.icon-avatar-1:before { + content: "\54"; +} +.icon-placemark:before { + content: "\55"; +} +.icon-box:before { + content: "\56"; +} +.icon-community:before { + content: "\30"; +} +.icon-grab-handle:before { + content: "\58"; +} +.icon-search:before { + content: "\59"; +} +.icon-disclosure-collapse:before { + content: "\5a"; +} +.icon-script-upload:before { + content: "\52"; +} +.icon-code:before { + content: "\57"; +} +.icon-avatar:before { + content: "\3c"; +} +.icon-arrows-h:before { + content: "\3a"; +} +.icon-arrows-v:before { + content: "\3b"; +} +.icon-arrows:before { + content: "\60"; +} +.icon-compress:before { + content: "\21"; +} +.icon-expand:before { + content: "\22"; +} +.icon-placemark-1:before { + content: "\23"; +} +.icon-circle:before { + content: "\24"; +} +.icon-hand-pointer:before { + content: "\39"; +} +.icon-plus-square-o:before { + content: "\25"; +} +.icon-square:before { + content: "\27"; +} +.icon-align-center:before { + content: "\38"; +} +.icon-align-justify:before { + content: "\29"; +} +.icon-align-left:before { + content: "\2a"; +} +.icon-align-right:before { + content: "\5e"; +} +.icon-bars:before { + content: "\37"; +} +.icon-circle-slash:before { + content: "\2c"; +} +.icon-sync:before { + content: "\28"; +} +.icon-key:before { + content: "\2d"; +} +.icon-link:before { + content: "\2e"; +} +.icon-location:before { + content: "\2f"; +} +.icon-carat-r:before { + content: "\33"; +} +.icon-carat-l:before { + content: "\34"; +} +.icon-folder-lg:before { + content: "\3e"; +} +.icon-folder-sm:before { + content: "\3f"; +} +.icon-level-up:before { + content: "\31"; +} +.icon-info:before { + content: "\5b"; +} +.icon-question:before { + content: "\5d"; +} +.icon-alert:before { + content: "\2b"; +} +.icon-home:before { + content: "\5f"; +} +.icon-error:before { + content: "\3d"; +} +.icon-settings:before { + content: "\40"; +} +.icon-trash:before { + content: "\7b"; +} +.icon-object-group:before { + content: "\e000"; +} +.icon-cm:before { + content: "\7d"; +} +.icon-msvg:before { + content: "\7e"; +} +.icon-deg:before { + content: "\5c"; +} +.icon-px:before { + content: "\7c"; +} +.icon-m-sq:before { + content: "\e001"; +} +.icon-m-cubed:before { + content: "\e002"; +} +.icon-acceleration:before { + content: "\e003"; +} +.icon-particles:before { + content: "\e004"; +} +.icon-voxels:before { + content: "\e005"; +} +.icon-lock:before { + content: "\e006"; +} +.icon-visible:before { + content: "\e007"; +} +.icon-model:before { + content: "\e008"; +} +.icon-forward:before { + content: "\44"; +} +.icon-avatar-2:before { + content: "\e009"; +} +.icon-arrow-dn:before { + content: "\35"; +} +.icon-arrow-up:before { + content: "\36"; +} +.icon-time:before { + content: "\e00a"; +} +.icon-transparency:before { + content: "\e00b"; +} +.icon-unmuted:before { + content: "\47"; +} +.icon-user:before { + content: "\e00c"; +} +.icon-edit-pencil:before { + content: "\e00d"; +} +.icon-muted:before { + content: "\48"; +} +.icon-vol-0:before { + content: "\e00e"; +} +.icon-vol-1:before { + content: "\e00f"; +} +.icon-vol-2:before { + content: "\e010"; +} +.icon-vol-3:before { + content: "\e011"; +} +.icon-vol-4:before { + content: "\e012"; +} +.icon-vol-x-0:before { + content: "\e013"; +} +.icon-vol-x-1:before { + content: "\e014"; +} +.icon-vol-x-2:before { + content: "\e015"; +} +.icon-vol-x-3:before { + content: "\e016"; +} +.icon-vol-x-4:before { + content: "\e017"; +} +.icon-share-ext:before { + content: "\e018"; +} +.icon-ellipsis:before { + content: "\e019"; +} +.icon-check:before { + content: "\e01a"; +} +.icon-sliders:before { + content: "\26"; +} +.icon-polyline:before { + content: "\e01b"; +} +.icon-source:before { + content: "\e01c"; +} +.icon-playback-play:before { + content: "\e01d"; +} +.icon-stop-square:before { + content: "\e01e"; +} +.icon-avatar-t-pose:before { + content: "\e01f"; +} +.icon-check-2-01:before { + content: "\e020"; +} diff --git a/interface/resources/html/img/tablet-help-gamepad.jpg b/interface/resources/html/img/tablet-help-gamepad.jpg index 5abb38d66b..3f084ecc4d 100644 Binary files a/interface/resources/html/img/tablet-help-gamepad.jpg and b/interface/resources/html/img/tablet-help-gamepad.jpg differ diff --git a/interface/resources/html/img/tablet-help-keyboard.jpg b/interface/resources/html/img/tablet-help-keyboard.jpg index 40c6017561..80f19fd372 100644 Binary files a/interface/resources/html/img/tablet-help-keyboard.jpg and b/interface/resources/html/img/tablet-help-keyboard.jpg differ diff --git a/interface/resources/html/img/tablet-help-oculus.jpg b/interface/resources/html/img/tablet-help-oculus.jpg index b4f54396e0..1f493039d3 100644 Binary files a/interface/resources/html/img/tablet-help-oculus.jpg and b/interface/resources/html/img/tablet-help-oculus.jpg differ diff --git a/interface/resources/html/img/tablet-help-vive.jpg b/interface/resources/html/img/tablet-help-vive.jpg index 98e57eef47..2f87daf835 100644 Binary files a/interface/resources/html/img/tablet-help-vive.jpg and b/interface/resources/html/img/tablet-help-vive.jpg differ diff --git a/interface/resources/html/raiseAndLowerKeyboard.js b/interface/resources/html/raiseAndLowerKeyboard.js index 27ead23124..ad1d889556 100644 --- a/interface/resources/html/raiseAndLowerKeyboard.js +++ b/interface/resources/html/raiseAndLowerKeyboard.js @@ -14,7 +14,6 @@ var isWindowFocused = true; var isKeyboardRaised = false; var isNumericKeyboard = false; - var KEYBOARD_HEIGHT = 200; function shouldRaiseKeyboard() { var nodeName = document.activeElement.nodeName; @@ -38,6 +37,19 @@ return document.activeElement.type === "number"; }; + function scheduleBringToView(timeout) { + + var timer = setTimeout(function () { + clearTimeout(timer); + + var elementRect = document.activeElement.getBoundingClientRect(); + var absoluteElementTop = elementRect.top + window.scrollY; + var middle = absoluteElementTop - (window.innerHeight / 2); + + window.scrollTo(0, middle); + }, timeout); + } + setInterval(function () { var keyboardRaised = shouldRaiseKeyboard(); var numericKeyboard = shouldSetNumeric(); @@ -56,13 +68,8 @@ } if (!isKeyboardRaised) { - var delta = document.activeElement.getBoundingClientRect().bottom + 10 - - (document.body.clientHeight - KEYBOARD_HEIGHT); - if (delta > 0) { - setTimeout(function () { - document.body.scrollTop += delta; - }, 500); // Allow time for keyboard to be raised in QML. - } + 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; @@ -70,6 +77,13 @@ } }, POLL_FREQUENCY); + window.addEventListener("click", function () { + var keyboardRaised = shouldRaiseKeyboard(); + if(keyboardRaised && isKeyboardRaised) { + scheduleBringToView(150); + } + }); + window.addEventListener("focus", function () { isWindowFocused = true; }); diff --git a/interface/resources/html/tabletHelp.html b/interface/resources/html/tabletHelp.html index cbd7ffcfe7..a6588be083 100644 --- a/interface/resources/html/tabletHelp.html +++ b/interface/resources/html/tabletHelp.html @@ -6,7 +6,14 @@ Welcome to Interface - - - - - - - - - -
-
-
- -
-
- - -
-
- - -
+ + + Properties + + + + + + + + + + + + + + + +
+ +
+
+
-
+
+ + +
+
+ + +
+
+ + +
-
- - +
+
+
- -
- -
-
- - - - Saved! +
+
+
+
+ +
-
-
- -
-
- - -
- - -
- M -
-
- +
+ + +
+ +
+
+
+ + Collides With + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + Grabbing + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + + +
+ + PhysicalM + +
+
+ Linear velocity m/s +
+
+
+
+
+
+
+ + +
+
+
+
+ Angular velocity deg/s +
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+ Gravity m/s2 +
+
+
+
+
+
+
+ Acceleration m/s2 +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ + +
+ + SpatialM + +
+ Position m
-
-
- + +
+ Rotation deg
-
-
- + +
+ Dimensions m
-
-
- + +
+ Registration (pivot offset as ratio of dimension)
-
-
- + +
+ Scale %
-
+
@@ -153,278 +294,315 @@
-
-
- + + + +
+ + BehaviorM + +
+ +
+
+ + + + Saved! +
+
+
+ +
+
+ + +
+ +
+
+ + +
+
+ + +
+
+
+
+ + + + +
+
+
+
+ + + +
+
+ + +
+
+ +
+
+
+ + +
+
+ + + + + + +
+ + LightM + +
+
+ Light color
-
-
-
+
+
+
-
- -
- - -
-
- - -
-
- - -
-
- M -
-
- + +
-
-
-
+
+
+
+
+
+ +
-
- - -
-
-
- +
-
-
-
+
+
+
-
-
- - -
-
-
-
-
-
-
+ + + + +
+ + ModelM + +
+
+ +
-
-
-
- -
-
-
-
+ -
-
- -
-
-
-
+
+ +
-
-
-
-
- -
-
-
-
+ +
+
+ +
-
-
- M -
-
- - -
-
- - -
-
-
-
- Collides With + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
-
-
- - +
+
+ +
-
- - +
+ +
-
- - -
-
- - -
-
- - +
+ +
-
-
- Grabbing -
-
-
- - -
-
- - -
-
- - -
-
- - -
+ +
+
+ + +
+
+ + +
+
+ + + +
+ + ZoneM + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ +
+
+
+ +
+
+
+
-
+
+ + Stage + +
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+ + Background + + +
+
+ + Skybox + +
+
+ Skybox color +
+
+
+
+
+
+
+ + +
+
+
+ + +
+ + TextM +
@@ -446,116 +624,22 @@
-
+
- + Background color
-
-
- M -
-
- - -
-
- - -
-
- - -
-
-
- - -
-
- -
-
-
- -
-
-
-
-
-
-
- - -
-
-
-
-
-
-
-
-
- - -
-
- - -
-
- -
-
-
-
-
-
-
-
-
- - -
-
-
-
-
-
-
-
-
- -
- -
- -
-
-
- -
-
-
-
-
-
-
- - -
-
- M -
+ + + + +
+ + WebM +
@@ -564,36 +648,41 @@
-
- M +
+ +
+ + Voxel volume size m + +
+
+
+
-
-
- -
-
-
-
-
+ -
-
-
-
-
-
+
+ +
-
- - +
+ +
-
-
-
-
-
-
+
+ +
-
- +
+ +
+ + diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index ce2f766946..aad26cc7ba 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -488,6 +488,7 @@ function loaded() { openEventBridge(function() { var allSections = []; + var elPropertiesList = document.getElementById("properties-list"); var elID = document.getElementById("property-id"); var elType = document.getElementById("property-type"); var elTypeIcon = document.getElementById("type-icon"); @@ -553,6 +554,7 @@ function loaded() { var elCloneable = document.getElementById("property-cloneable"); var elCloneableDynamic = document.getElementById("property-cloneable-dynamic"); + var elCloneableAvatarEntity = document.getElementById("property-cloneable-avatarEntity"); var elCloneableGroup = document.getElementById("group-cloneable-group"); var elCloneableLifetime = document.getElementById("property-cloneable-lifetime"); var elCloneableLimit = document.getElementById("property-cloneable-limit"); @@ -574,7 +576,8 @@ function loaded() { var elJSONEditor = document.getElementById("userdata-editor"); var elNewJSONEditor = document.getElementById('userdata-new-editor'); var elColorSections = document.querySelectorAll(".color-section"); - var elColor = document.getElementById("property-color"); + var elColorControl1 = document.getElementById("property-color-control1"); + var elColorControl2 = document.getElementById("property-color-control2"); var elColorRed = document.getElementById("property-color-red"); var elColorGreen = document.getElementById("property-color-green"); var elColorBlue = document.getElementById("property-color-blue"); @@ -609,6 +612,7 @@ function loaded() { var elModelAnimationLastFrame = document.getElementById("property-model-animation-last-frame"); var elModelAnimationLoop = document.getElementById("property-model-animation-loop"); var elModelAnimationHold = document.getElementById("property-model-animation-hold"); + var elModelAnimationAllowTranslation = document.getElementById("property-model-animation-allow-translation"); var elModelTextures = document.getElementById("property-model-textures"); var elModelOriginalTextures = document.getElementById("property-model-original-textures"); @@ -688,7 +692,10 @@ function loaded() { data = JSON.parse(data); if (data.type == "server_script_status") { elServerScriptError.value = data.errorInfo; - elServerScriptError.style.display = data.errorInfo ? "block" : "none"; + // If we just set elServerScriptError's diplay to block or none, we still end up with + //it's parent contributing 21px bottom padding even when elServerScriptError is display:none. + // So set it's parent to block or none + elServerScriptError.parentElement.style.display = data.errorInfo ? "block" : "none"; if (data.statusRetrieved === false) { elServerScriptStatus.innerText = "Failed to retrieve status"; } else if (data.isRunning) { @@ -714,6 +721,7 @@ function loaded() { elTypeIcon.style.display = "none"; elType.innerHTML = "No selection"; elID.value = ""; + elPropertiesList.className = ''; disableProperties(); } else if (data.selections && data.selections.length > 1) { deleteJSONEditor(); @@ -742,6 +750,7 @@ function loaded() { elType.innerHTML = type + " (" + data.selections.length + ")"; elTypeIcon.innerHTML = ICON_FOR_TYPE[type]; elTypeIcon.style.display = "inline-block"; + elPropertiesList.className = ''; elID.value = ""; @@ -758,6 +767,7 @@ function loaded() { lastEntityID = '"' + properties.id + '"'; elID.value = properties.id; + elPropertiesList.className = properties.type + 'Menu'; elType.innerHTML = properties.type; elTypeIcon.innerHTML = ICON_FOR_TYPE[properties.type]; elTypeIcon.style.display = "inline-block"; @@ -830,36 +840,59 @@ function loaded() { elCloneableLimit.value = 0; elCloneableLifetime.value = 300; + var grabbablesSet = false; var parsedUserData = {} try { parsedUserData = JSON.parse(properties.userData); if ("grabbableKey" in parsedUserData) { - if ("grabbable" in parsedUserData["grabbableKey"]) { - elGrabbable.checked = parsedUserData["grabbableKey"].grabbable; + grabbablesSet = true; + var grabbableData = parsedUserData["grabbableKey"]; + if ("grabbable" in grabbableData) { + elGrabbable.checked = grabbableData.grabbable; + } else { + elGrabbable.checked = true; } - if ("wantsTrigger" in parsedUserData["grabbableKey"]) { - elWantsTrigger.checked = parsedUserData["grabbableKey"].wantsTrigger; + if ("wantsTrigger" in grabbableData) { + elWantsTrigger.checked = grabbableData.wantsTrigger; + } else { + elWantsTrigger.checked = false; } - if ("ignoreIK" in parsedUserData["grabbableKey"]) { - elIgnoreIK.checked = parsedUserData["grabbableKey"].ignoreIK; + if ("ignoreIK" in grabbableData) { + elIgnoreIK.checked = grabbableData.ignoreIK; + } else { + elIgnoreIK.checked = true; } - if ("cloneable" in parsedUserData["grabbableKey"]) { - elCloneable.checked = parsedUserData["grabbableKey"].cloneable; + if ("cloneable" in grabbableData) { + elCloneable.checked = grabbableData.cloneable; elCloneableGroup.style.display = elCloneable.checked ? "block": "none"; - elCloneableDynamic.checked = parsedUserData["grabbableKey"].cloneDynamic ? parsedUserData["grabbableKey"].cloneDynamic : properties.dynamic; + elCloneableDynamic.checked = + grabbableData.cloneDynamic ? grabbableData.cloneDynamic : properties.dynamic; if (elCloneable.checked) { - if ("cloneLifetime" in parsedUserData["grabbableKey"]) { - elCloneableLifetime.value = parsedUserData["grabbableKey"].cloneLifetime ? parsedUserData["grabbableKey"].cloneLifetime : 300; + if ("cloneLifetime" in grabbableData) { + elCloneableLifetime.value = + grabbableData.cloneLifetime ? grabbableData.cloneLifetime : 300; } - if ("cloneLimit" in parsedUserData["grabbableKey"]) { - elCloneableLimit.value = parsedUserData["grabbableKey"].cloneLimit ? parsedUserData["grabbableKey"].cloneLimit : 0; + if ("cloneLimit" in grabbableData) { + elCloneableLimit.value = grabbableData.cloneLimit ? grabbableData.cloneLimit : 0; + } + if ("cloneAvatarEntity" in grabbableData) { + elCloneableAvatarEntity.checked = + grabbableData.cloneAvatarEntity ? grabbableData.cloneAvatarEntity : false; } } + } else { + elCloneable.checked = false; } } } catch (e) { } + if (!grabbablesSet) { + elGrabbable.checked = true; + elWantsTrigger.checked = false; + elIgnoreIK.checked = true; + elCloneable.checked = false; + } elCollisionSoundURL.value = properties.collisionSoundURL; elLifetime.value = properties.lifetime; @@ -892,48 +925,20 @@ function loaded() { elHyperlinkHref.value = properties.href; elDescription.value = properties.description; - for (var i = 0; i < allSections.length; i++) { - for (var j = 0; j < allSections[i].length; j++) { - allSections[i][j].style.display = 'none'; - } - } - - for (var i = 0; i < elHyperlinkSections.length; i++) { - elHyperlinkSections[i].style.display = 'table'; - } if (properties.type == "Shape" || properties.type == "Box" || properties.type == "Sphere") { - for (var i = 0; i < elShapeSections.length; i++) { - elShapeSections[i].style.display = 'table'; - } elShape.value = properties.shape; setDropdownText(elShape); - - } else { - for (var i = 0; i < elShapeSections.length; i++) { - elShapeSections[i].style.display = 'none'; - } } if (properties.type == "Shape" || properties.type == "Box" || properties.type == "Sphere" || properties.type == "ParticleEffect") { - for (var i = 0; i < elColorSections.length; i++) { - elColorSections[i].style.display = 'table'; - } elColorRed.value = properties.color.red; elColorGreen.value = properties.color.green; elColorBlue.value = properties.color.blue; - elColor.style.backgroundColor = "rgb(" + properties.color.red + "," + properties.color.green + "," + properties.color.blue + ")"; - } else { - for (var i = 0; i < elColorSections.length; i++) { - elColorSections[i].style.display = 'none'; - } + elColorControl1.style.backgroundColor = elColorControl2.style.backgroundColor = "rgb(" + properties.color.red + "," + properties.color.green + "," + properties.color.blue + ")"; } if (properties.type == "Model") { - for (var i = 0; i < elModelSections.length; i++) { - elModelSections[i].style.display = 'table'; - } - elModelURL.value = properties.modelURL; elShapeType.value = properties.shapeType; setDropdownText(elShapeType); @@ -946,25 +951,15 @@ function loaded() { elModelAnimationLastFrame.value = properties.animation.lastFrame; elModelAnimationLoop.checked = properties.animation.loop; elModelAnimationHold.checked = properties.animation.hold; + elModelAnimationAllowTranslation.checked = properties.animation.allowTranslation; elModelTextures.value = properties.textures; setTextareaScrolling(elModelTextures); elModelOriginalTextures.value = properties.originalTextures; setTextareaScrolling(elModelOriginalTextures); } else if (properties.type == "Web") { - for (var i = 0; i < elWebSections.length; i++) { - elWebSections[i].style.display = 'table'; - } - for (var i = 0; i < elHyperlinkSections.length; i++) { - elHyperlinkSections[i].style.display = 'none'; - } - elWebSourceURL.value = properties.sourceUrl; elWebDPI.value = properties.dpi; } else if (properties.type == "Text") { - for (var i = 0; i < elTextSections.length; i++) { - elTextSections[i].style.display = 'table'; - } - elTextText.value = properties.text; elTextLineHeight.value = properties.lineHeight.toFixed(4); elTextFaceCamera.checked = properties.faceCamera; @@ -976,10 +971,6 @@ function loaded() { elTextBackgroundColorGreen.value = properties.backgroundColor.green; elTextBackgroundColorBlue.value = properties.backgroundColor.blue; } else if (properties.type == "Light") { - for (var i = 0; i < elLightSections.length; i++) { - elLightSections[i].style.display = 'table'; - } - elLightSpotLight.checked = properties.isSpotlight; elLightColor.style.backgroundColor = "rgb(" + properties.color.red + "," + properties.color.green + "," + properties.color.blue + ")"; @@ -992,10 +983,6 @@ function loaded() { elLightExponent.value = properties.exponent.toFixed(2); elLightCutoff.value = properties.cutoff.toFixed(2); } else if (properties.type == "Zone") { - for (var i = 0; i < elZoneSections.length; i++) { - elZoneSections[i].style.display = 'table'; - } - elZoneStageSunModelEnabled.checked = properties.stage.sunModelEnabled; elZoneKeyLightColor.style.backgroundColor = "rgb(" + properties.keyLight.color.red + "," + properties.keyLight.color.green + "," + properties.keyLight.color.blue + ")"; elZoneKeyLightColorRed.value = properties.keyLight.color.red; @@ -1032,10 +1019,6 @@ function loaded() { showElements(document.getElementsByClassName('skybox-section'), elZoneBackgroundMode.value == 'skybox'); } else if (properties.type == "PolyVox") { - for (var i = 0; i < elPolyVoxSections.length; i++) { - elPolyVoxSections[i].style.display = 'table'; - } - elVoxelVolumeSizeX.value = properties.voxelVolumeSize.x.toFixed(2); elVoxelVolumeSizeY.value = properties.voxelVolumeSize.y.toFixed(2); elVoxelVolumeSizeZ.value = properties.voxelVolumeSize.z.toFixed(2); @@ -1155,9 +1138,14 @@ function loaded() { } userDataChanger("grabbableKey", "grabbable", elGrabbable, elUserData, properties.dynamic); }); - elCloneableDynamic.addEventListener('change', function (event){ + elCloneableDynamic.addEventListener('change', function(event) { userDataChanger("grabbableKey", "cloneDynamic", event.target, elUserData, -1); }); + + elCloneableAvatarEntity.addEventListener('change', function(event) { + userDataChanger("grabbableKey", "cloneAvatarEntity", event.target, elUserData, -1); + }); + elCloneable.addEventListener('change', function (event) { var checked = event.target.checked; if (checked) { @@ -1165,6 +1153,7 @@ function loaded() { cloneLifetime: elCloneableLifetime, cloneLimit: elCloneableLimit, cloneDynamic: elCloneableDynamic, + cloneAvatarEntity: elCloneableAvatarEntity, cloneable: event.target, grabbable: null }, elUserData, {}); @@ -1175,6 +1164,7 @@ function loaded() { cloneLifetime: null, cloneLimit: null, cloneDynamic: null, + cloneAvatarEntity: null, cloneable: false }, elUserData, {}); elCloneableGroup.style.display = "none"; @@ -1235,20 +1225,41 @@ function loaded() { elColorRed.addEventListener('change', colorChangeFunction); elColorGreen.addEventListener('change', colorChangeFunction); elColorBlue.addEventListener('change', colorChangeFunction); - colorPickers.push($('#property-color').colpick({ + colorPickers.push($('#property-color-control1').colpick({ colorScheme: 'dark', layout: 'hex', color: '000000', onShow: function(colpick) { - $('#property-color').attr('active', 'true'); + $('#property-color-control1').attr('active', 'true'); }, onHide: function(colpick) { - $('#property-color').attr('active', 'false'); + $('#property-color-control1').attr('active', 'false'); }, onSubmit: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); $(el).colpickHide(); emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); + // Keep the companion control in sync + elColorControl2.style.backgroundColor = "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")"; + } + })); + colorPickers.push($('#property-color-control2').colpick({ + colorScheme: 'dark', + layout: 'hex', + color: '000000', + onShow: function(colpick) { + $('#property-color-control2').attr('active', 'true'); + }, + onHide: function(colpick) { + $('#property-color-control2').attr('active', 'false'); + }, + onSubmit: function(hsb, hex, rgb, el) { + $(el).css('background-color', '#' + hex); + $(el).colpickHide(); + emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b); + // Keep the companion control in sync + elColorControl1.style.backgroundColor = "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")"; + } })); @@ -1298,6 +1309,7 @@ function loaded() { elModelAnimationLastFrame.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('animation', 'lastFrame')); elModelAnimationLoop.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'loop')); elModelAnimationHold.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'hold')); + elModelAnimationAllowTranslation.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'allowTranslation')); elModelTextures.addEventListener('change', createEmitTextPropertyUpdateFunction('textures')); @@ -1512,11 +1524,9 @@ function loaded() { var elCollapsible = document.getElementsByClassName("section-header"); var toggleCollapsedEvent = function(event) { - var element = event.target; - if (element.nodeName !== "DIV") { - element = element.parentNode; - } - var isCollapsed = element.getAttribute("collapsed") !== "true"; + var element = event.target.parentNode.parentNode; + var isCollapsed = element.dataset.collapsed !== "true"; + element.dataset.collapsed = isCollapsed ? "true" : false element.setAttribute("collapsed", isCollapsed ? "true" : "false"); element.getElementsByTagName("span")[0].textContent = isCollapsed ? "L" : "M"; }; diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 8a8cf62008..ded4542c51 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -25,7 +25,11 @@ var canWriteAssets = false; var xmlHttpRequest = null; var isPreparing = false; // Explicitly track download request status. - + + var confirmAllPurchases = false; // Set this to "true" to cause Checkout.qml to popup for all items, even if free + var userIsLoggedIn = false; + var walletNeedsSetup = false; + function injectCommonCode(isDirectoryPage) { // Supporting styles from marketplaces.css. @@ -65,7 +69,7 @@ // Footer actions. $("#back-button").on("click", function () { - (window.history.state != null) ? window.history.back() : window.location = "https://metaverse.highfidelity.com/marketplace?"; + (document.referrer !== "") ? window.history.back() : window.location = "https://metaverse.highfidelity.com/marketplace?"; }); $("#all-markets").on("click", function () { EventBridge.emitWebEvent(GOTO_DIRECTORY); @@ -79,6 +83,7 @@ letUsKnow.replaceWith(letUsKnow.html()); // Add button links. + $('#exploreClaraMarketplace').on('click', function () { window.location = "https://clara.io/library?gameCheck=true&public=true"; }); @@ -87,8 +92,278 @@ }); } + emitWalletSetupEvent = function() { + EventBridge.emitWebEvent(JSON.stringify({ + type: "WALLET_SETUP" + })); + } + + function maybeAddSetupWalletButton() { + if (userIsLoggedIn && walletNeedsSetup) { + var resultsElement = document.getElementById('results'); + var setupWalletElement = document.createElement('div'); + setupWalletElement.classList.add("row"); + setupWalletElement.id = "setupWalletDiv"; + setupWalletElement.style = "height:60px;margin:20px 10px 10px 10px;padding:12px 5px;" + + "background-color:#D6F4D8;border-color:#aee9b2;border-width:2px;border-style:solid;border-radius:5px;"; + + var span = document.createElement('span'); + span.style = "margin:10px 5px;color:#1b6420;font-size:15px;"; + span.innerHTML = "Setup your Wallet to get money and shop in Marketplace."; + + var xButton = document.createElement('a'); + xButton.id = "xButton"; + xButton.setAttribute('href', "#"); + xButton.style = "width:50px;height:100%;margin:0;color:#ccc;font-size:20px;"; + xButton.innerHTML = "X"; + xButton.onclick = function () { + setupWalletElement.remove(); + dummyRow.remove(); + }; + + setupWalletElement.appendChild(span); + setupWalletElement.appendChild(xButton); + + resultsElement.insertBefore(setupWalletElement, resultsElement.firstChild); + + // Dummy row for padding + var dummyRow = document.createElement('div'); + dummyRow.classList.add("row"); + dummyRow.style = "height:15px;"; + resultsElement.insertBefore(dummyRow, resultsElement.firstChild); + } + } + + function maybeAddLogInButton() { + if (!userIsLoggedIn) { + var resultsElement = document.getElementById('results'); + var logInElement = document.createElement('div'); + logInElement.classList.add("row"); + logInElement.id = "logInDiv"; + logInElement.style = "height:60px;margin:20px 10px 10px 10px;padding:5px;" + + "background-color:#D6F4D8;border-color:#aee9b2;border-width:2px;border-style:solid;border-radius:5px;"; + + var button = document.createElement('a'); + button.classList.add("btn"); + button.classList.add("btn-default"); + button.id = "logInButton"; + button.setAttribute('href', "#"); + button.innerHTML = "LOG IN"; + button.style = "width:80px;height:100%;margin-top:0;margin-left:10px;padding:13px;font-weight:bold;background:linear-gradient(white, #ccc);"; + button.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "LOGIN" + })); + }; + + var span = document.createElement('span'); + span.style = "margin:10px;color:#1b6420;font-size:15px;"; + span.innerHTML = "to purchase items from the Marketplace."; + + var xButton = document.createElement('a'); + xButton.id = "xButton"; + xButton.setAttribute('href', "#"); + xButton.style = "width:50px;height:100%;margin:0;color:#ccc;font-size:20px;"; + xButton.innerHTML = "X"; + xButton.onclick = function () { + logInElement.remove(); + dummyRow.remove(); + }; + + logInElement.appendChild(button); + logInElement.appendChild(span); + logInElement.appendChild(xButton); + + resultsElement.insertBefore(logInElement, resultsElement.firstChild); + + // Dummy row for padding + var dummyRow = document.createElement('div'); + dummyRow.classList.add("row"); + dummyRow.style = "height:15px;"; + resultsElement.insertBefore(dummyRow, resultsElement.firstChild); + } + } + + function maybeAddPurchasesButton() { + if (userIsLoggedIn) { + // Why isn't this an id?! This really shouldn't be a class on the website, but it is. + var navbarBrandElement = document.getElementsByClassName('navbar-brand')[0]; + var purchasesElement = document.createElement('a'); + var dropDownElement = document.getElementById('user-dropdown'); + purchasesElement.id = "purchasesButton"; + purchasesElement.setAttribute('href', "#"); + purchasesElement.innerHTML = "My Purchases"; + // FRONTEND WEBDEV RANT: The username dropdown should REALLY not be programmed to be on the same + // line as the search bar, overlaid on top of the search bar, floated right, and then relatively bumped up using "top:-50px". + purchasesElement.style = "height:100%;margin-top:18px;font-weight:bold;float:right;margin-right:" + (dropDownElement.offsetWidth + 30) + + "px;position:relative;z-index:999;"; + navbarBrandElement.parentNode.insertAdjacentElement('beforeend', purchasesElement); + $('#purchasesButton').on('click', function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "PURCHASES", + referrerURL: window.location.href + })); + }); + } + } + + function changeDropdownMenu() { + var logInOrOutButton = document.createElement('a'); + logInOrOutButton.id = "logInOrOutButton"; + logInOrOutButton.setAttribute('href', "#"); + logInOrOutButton.innerHTML = userIsLoggedIn ? "Log Out" : "Log In"; + logInOrOutButton.onclick = function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "LOGIN" + })); + }; + + $($('.dropdown-menu').find('li')[0]).append(logInOrOutButton); + + $('a[href="/marketplace?view=mine"]').each(function () { + $(this).attr('href', '#'); + $(this).on('click', function () { + EventBridge.emitWebEvent(JSON.stringify({ + type: "MY_ITEMS" + })); + }); + }); + } + + function buyButtonClicked(id, name, author, price, href) { + EventBridge.emitWebEvent(JSON.stringify({ + type: "CHECKOUT", + itemId: id, + itemName: name, + itemPrice: price ? parseInt(price, 10) : 0, + itemHref: href + })); + } + + function injectBuyButtonOnMainPage() { + var cost; + + // Unbind original mouseenter and mouseleave behavior + $('body').off('mouseenter', '#price-or-edit .price'); + $('body').off('mouseleave', '#price-or-edit .price'); + + $('.grid-item').find('#price-or-edit').each(function () { + $(this).css({ "margin-top": "0" }); + }); + + $('.grid-item').find('#price-or-edit').find('a').each(function() { + $(this).attr('data-href', $(this).attr('href')); + $(this).attr('href', '#'); + cost = $(this).closest('.col-xs-3').find('.item-cost').text(); + + $(this).closest('.col-xs-3').prev().attr("class", 'col-xs-6'); + $(this).closest('.col-xs-3').attr("class", 'col-xs-6'); + + var priceElement = $(this).find('.price') + priceElement.css({ + "padding": "3px 5px", + "height": "40px", + "background": "linear-gradient(#00b4ef, #0093C5)", + "color": "#FFF", + "font-weight": "600", + "line-height": "34px" + }); + + if (parseInt(cost) > 0) { + priceElement.css({ "width": "auto" }); + priceElement.html(' ' + cost); + priceElement.css({ "min-width": priceElement.width() + 30 }); + } + }); + + // change pricing to GET on button hover + $('body').on('mouseenter', '#price-or-edit .price', function () { + var $this = $(this); + $this.data('initialHtml', $this.html()); + $this.text('GET'); + }); + + $('body').on('mouseleave', '#price-or-edit .price', function () { + var $this = $(this); + $this.html($this.data('initialHtml')); + }); + + + $('.grid-item').find('#price-or-edit').find('a').on('click', function () { + buyButtonClicked($(this).closest('.grid-item').attr('data-item-id'), + $(this).closest('.grid-item').find('.item-title').text(), + $(this).closest('.grid-item').find('.creator').find('.value').text(), + $(this).closest('.grid-item').find('.item-cost').text(), + $(this).attr('data-href')); + }); + } + function injectHiFiCode() { - // Nothing to do. + if (!$('body').hasClass("code-injected") && confirmAllPurchases) { + + $('body').addClass("code-injected"); + + maybeAddLogInButton(); + maybeAddSetupWalletButton(); + changeDropdownMenu(); + + var target = document.getElementById('templated-items'); + // MutationObserver is necessary because the DOM is populated after the page is loaded. + // We're searching for changes to the element whose ID is '#templated-items' - this is + // the element that gets filled in by the AJAX. + var observer = new MutationObserver(function (mutations) { + mutations.forEach(function (mutation) { + injectBuyButtonOnMainPage(); + }); + //observer.disconnect(); + }); + var config = { attributes: true, childList: true, characterData: true }; + observer.observe(target, config); + + // Try this here in case it works (it will if the user just pressed the "back" button, + // since that doesn't trigger another AJAX request. + injectBuyButtonOnMainPage(); + maybeAddPurchasesButton(); + } + } + + function injectHiFiItemPageCode() { + if (!$('body').hasClass("code-injected") && confirmAllPurchases) { + + $('body').addClass("code-injected"); + + maybeAddLogInButton(); + maybeAddSetupWalletButton(); + changeDropdownMenu(); + + var purchaseButton = $('#side-info').find('.btn').first(); + + var href = purchaseButton.attr('href'); + purchaseButton.attr('href', '#'); + purchaseButton.css({ + "background": "linear-gradient(#00b4ef, #0093C5)", + "color": "#FFF", + "font-weight": "600", + "padding-bottom": "10px" + }); + + var cost = $('.item-cost').text(); + + if (parseInt(cost) > 0 && $('#side-info').find('#buyItemButton').size() === 0) { + purchaseButton.html('PURCHASE ' + cost); + } + + purchaseButton.on('click', function () { + buyButtonClicked(window.location.pathname.split("/")[3], + $('#top-center').find('h1').text(), + $('#creator').find('.value').text(), + cost, + href); + }); + maybeAddPurchasesButton(); + } } function updateClaraCode() { @@ -140,7 +415,7 @@ // One file request at a time. if (isPreparing) { - console.log("WARNIKNG: Clara.io FBX: Prepare only one download at a time"); + console.log("WARNING: Clara.io FBX: Prepare only one download at a time"); return; } @@ -162,7 +437,7 @@ // Reference: https://clara.io/learn/sdk/api/export //var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?zip=true¢erScene=true&alignSceneGround=true&fbxUnit=Meter&fbxVersion=7&fbxEmbedTextures=true&imageFormat=WebGL"; - // 13 Jan 2017: Specify FBX version 5 and remove some options in order to make Clara.io site more likely to + // 13 Jan 2017: Specify FBX version 5 and remove some options in order to make Clara.io site more likely to // be successful in generating zip files. var XMLHTTPREQUEST_URL = "https://clara.io/api/scenes/{uuid}/export/fbx?fbxUnit=Meter&fbxVersion=5&fbxEmbedTextures=true&imageFormat=WebGL"; @@ -308,25 +583,16 @@ } } - function onLoad() { - - EventBridge.scriptEventReceived.connect(function (message) { - if (message.slice(0, CAN_WRITE_ASSETS.length) === CAN_WRITE_ASSETS) { - canWriteAssets = message.slice(-4) === "true"; - } - - if (message.slice(0, CLARA_IO_CANCEL_DOWNLOAD.length) === CLARA_IO_CANCEL_DOWNLOAD) { - cancelClaraDownload(); - } - }); - + function injectCode() { var DIRECTORY = 0; var HIFI = 1; var CLARA = 2; + var HIFI_ITEM_PAGE = 3; var pageType = DIRECTORY; if (location.href.indexOf("highfidelity.com/") !== -1) { pageType = HIFI; } if (location.href.indexOf("clara.io/") !== -1) { pageType = CLARA; } + if (location.href.indexOf("highfidelity.com/marketplace/items/") !== -1) { pageType = HIFI_ITEM_PAGE; } injectCommonCode(pageType === DIRECTORY); switch (pageType) { @@ -339,10 +605,41 @@ case CLARA: injectClaraCode(); break; + case HIFI_ITEM_PAGE: + injectHiFiItemPageCode(); + break; + } } + function onLoad() { + EventBridge.scriptEventReceived.connect(function (message) { + if (message.slice(0, CAN_WRITE_ASSETS.length) === CAN_WRITE_ASSETS) { + canWriteAssets = message.slice(-4) === "true"; + } else if (message.slice(0, CLARA_IO_CANCEL_DOWNLOAD.length) === CLARA_IO_CANCEL_DOWNLOAD) { + cancelClaraDownload(); + } else { + var parsedJsonMessage = JSON.parse(message); + + if (parsedJsonMessage.type === "marketplaces") { + if (parsedJsonMessage.action === "commerceSetting") { + confirmAllPurchases = !!parsedJsonMessage.data.commerceMode; + userIsLoggedIn = !!parsedJsonMessage.data.userIsLoggedIn; + walletNeedsSetup = !!parsedJsonMessage.data.walletNeedsSetup; + injectCode(); + } + } + } + }); + + // Request commerce setting + // Code is injected into the webpage after the setting comes back. + EventBridge.emitWebEvent(JSON.stringify({ + type: "REQUEST_SETTING" + })); + } + // Load / unload. window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready(). - + window.addEventListener("page:change", onLoad); // Triggered after Marketplace HTML is changed }()); diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index c3d55d5875..92a5857390 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -8,7 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // /* global getControllerWorldLocation, Tablet, WebTablet:true, HMD, Settings, Script, - Vec3, Quat, MyAvatar, Entities, Overlays, Camera, Messages, Xform, clamp, Controller, Mat4 */ + Vec3, Quat, MyAvatar, Entities, Overlays, Camera, Messages, Xform, clamp, Controller, Mat4, resizeTablet */ Script.include(Script.resolvePath("../libraries/utils.js")); Script.include(Script.resolvePath("../libraries/controllers.js")); @@ -42,56 +42,20 @@ var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home- // returns object with two fields: // * position - position in front of the user // * rotation - rotation of entity so it faces the user. -function calcSpawnInfo(hand, tabletHeight, landscape) { +function calcSpawnInfo(hand, landscape) { var finalPosition; var headPos = (HMD.active && Camera.mode === "first person") ? HMD.position : Camera.position; var headRot = (HMD.active && Camera.mode === "first person") ? HMD.orientation : Camera.orientation; - if (!hand) { - hand = NO_HANDS; - } - - var handController = null; - if (HMD.active && hand !== NO_HANDS) { - handController = getControllerWorldLocation(hand, true); - } - - if (handController && handController.valid) { - // Orient tablet per hand pitch and yaw. - // Angle it back similar to holding it like a book. - // Move tablet up so that hand is at bottom. - // Move tablet back so that hand is in front. - - var position = handController.position; - var rotation = handController.rotation; - - if (hand === Controller.Standard.LeftHand) { - rotation = Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, 90, 0)); - } else { - rotation = Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, -90, 0)); - } - var normal = Vec3.multiplyQbyV(rotation, Vec3.UNIT_NEG_Y); - var lookAt = Quat.lookAt(Vec3.ZERO, normal, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.UNIT_Y)); - var TABLET_RAKE_ANGLE = 30; - rotation = Quat.multiply(Quat.angleAxis(TABLET_RAKE_ANGLE, Vec3.multiplyQbyV(lookAt, Vec3.UNIT_X)), lookAt); - - var RELATIVE_SPAWN_OFFSET = { x: 0, y: 0.6, z: 0.1 }; - position = Vec3.sum(position, Vec3.multiplyQbyV(rotation, Vec3.multiply(tabletHeight, RELATIVE_SPAWN_OFFSET))); - - return { - position: position, - rotation: landscape ? Quat.multiply(rotation, { x: 0.0, y: 0.0, z: 0.707, w: 0.707 }) : rotation - }; - } else { - var forward = Quat.getForward(headRot); - finalPosition = Vec3.sum(headPos, Vec3.multiply(0.6, forward)); - var orientation = Quat.lookAt({x: 0, y: 0, z: 0}, forward, {x: 0, y: 1, z: 0}); - return { - position: finalPosition, - rotation: landscape ? Quat.multiply(orientation, ROT_LANDSCAPE) : Quat.multiply(orientation, ROT_Y_180) - }; - } + var forward = Quat.getForward(headRot); + var FORWARD_OFFSET = 0.5 * MyAvatar.sensorToWorldScale; + finalPosition = Vec3.sum(headPos, Vec3.multiply(FORWARD_OFFSET, forward)); + var orientation = Quat.lookAt({x: 0, y: 0, z: 0}, forward, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.UNIT_Y)); + return { + position: finalPosition, + rotation: landscape ? Quat.multiply(orientation, ROT_LANDSCAPE) : Quat.multiply(orientation, ROT_Y_180) + }; } /** @@ -106,19 +70,22 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { var _this = this; + var sensorScaleFactor = MyAvatar.sensorToWorldScale; + // scale factor of natural tablet dimensions. - this.width = width || DEFAULT_WIDTH; - var tabletScaleFactor = this.width / TABLET_NATURAL_DIMENSIONS.x; - this.height = TABLET_NATURAL_DIMENSIONS.y * tabletScaleFactor; - this.depth = TABLET_NATURAL_DIMENSIONS.z * tabletScaleFactor; + var tabletWidth = (width || DEFAULT_WIDTH) * sensorScaleFactor; + var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x; + var tabletHeight = TABLET_NATURAL_DIMENSIONS.y * tabletScaleFactor; + var tabletDepth = TABLET_NATURAL_DIMENSIONS.z * tabletScaleFactor; this.landscape = false; visible = visible === true; + var tabletDpi; if (dpi) { - this.dpi = dpi; + tabletDpi = dpi; } else { - this.dpi = DEFAULT_DPI * (DEFAULT_WIDTH / this.width); + tabletDpi = DEFAULT_DPI * (DEFAULT_WIDTH / tabletWidth); } var modelURL = LOCAL_TABLET_MODEL_PATH; @@ -132,7 +99,7 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { userData: JSON.stringify({ "grabbableKey": {"grabbable": true} }), - dimensions: this.getDimensions(), + dimensions: { x: tabletWidth, y: tabletHeight, z: tabletDepth }, parentID: AVATAR_SELF_ID, visible: visible }; @@ -152,8 +119,8 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { Overlays.deleteOverlay(this.webOverlayID); } - var WEB_ENTITY_Z_OFFSET = (this.depth / 2); - var WEB_ENTITY_Y_OFFSET = 0.004; + var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2) * (1 / sensorScaleFactor); + var WEB_ENTITY_Y_OFFSET = 0.004 * (1 / sensorScaleFactor); this.webOverlayID = Overlays.addOverlay("web3d", { name: "WebTablet Web", @@ -161,17 +128,16 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, localRotation: Quat.angleAxis(180, Y_AXIS), resolution: this.getTabletTextureResolution(), - dpi: this.dpi, + dpi: tabletDpi, color: { red: 255, green: 255, blue: 255 }, alpha: 1.0, parentID: this.tabletEntityID, parentJointIndex: -1, showKeyboardFocusHighlight: false, - isAA: HMD.active, visible: visible }); - var HOME_BUTTON_Y_OFFSET = (this.height / 2) - (this.height / 20); + var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20)) * (1 / sensorScaleFactor); this.homeButtonID = Overlays.addOverlay("sphere", { name: "homeButton", localPosition: {x: -0.001, y: -HOME_BUTTON_Y_OFFSET, z: 0.0}, @@ -248,10 +214,6 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { Camera.modeUpdated.connect(this.myCameraModeChanged); }; -WebTablet.prototype.getDimensions = function() { - return { x: this.width, y: this.height, z: this.depth }; -}; - WebTablet.prototype.getTabletTextureResolution = function() { if (this.landscape) { return { x: TABLET_TEXTURE_RESOLUTION.y , y: TABLET_TEXTURE_RESOLUTION.x }; @@ -260,6 +222,10 @@ WebTablet.prototype.getTabletTextureResolution = function() { } }; +WebTablet.prototype.getLandscape = function() { + return this.landscape; +} + WebTablet.prototype.setLandscape = function(newLandscapeValue) { if (this.landscape === newLandscapeValue) { return; @@ -302,31 +268,8 @@ WebTablet.prototype.getOverlayObject = function () { }; WebTablet.prototype.setWidth = function (width) { - - // scale factor of natural tablet dimensions. - this.width = width || DEFAULT_WIDTH; - var tabletScaleFactor = this.width / TABLET_NATURAL_DIMENSIONS.x; - this.height = TABLET_NATURAL_DIMENSIONS.y * tabletScaleFactor; - this.depth = TABLET_NATURAL_DIMENSIONS.z * tabletScaleFactor; - this.dpi = DEFAULT_DPI * (DEFAULT_WIDTH / this.width); - - // update tablet model dimensions - Overlays.editOverlay(this.tabletEntityID, { dimensions: this.getDimensions() }); - - // update webOverlay - var WEB_ENTITY_Z_OFFSET = (this.depth / 2); - var WEB_ENTITY_Y_OFFSET = 0.004; - Overlays.editOverlay(this.webOverlayID, { - localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, - dpi: this.dpi - }); - - // update homeButton - var HOME_BUTTON_Y_OFFSET = (this.height / 2) - (this.height / 20); - Overlays.editOverlay(this.homeButtonID, { - localPosition: {x: -0.001, y: -HOME_BUTTON_Y_OFFSET, z: 0.0}, - dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor} - }); + // imported from libraries/utils.js + resizeTablet(width); }; WebTablet.prototype.destroy = function () { @@ -346,11 +289,9 @@ WebTablet.prototype.destroy = function () { WebTablet.prototype.geometryChanged = function (geometry) { if (!HMD.active) { var tabletProperties = {}; - // compute position, rotation & parentJointIndex of the tablet this.calculateTabletAttachmentProperties(NO_HANDS, false, tabletProperties); - // TODO -- is this still needed? - // Entities.editEntity(this.tabletEntityID, tabletProperties); + Overlays.editOverlay(HMD.tabletID, tabletProperties); } }; @@ -381,10 +322,19 @@ WebTablet.prototype.calculateWorldAttitudeRelativeToCamera = function (windowPos windowPos.y = clamp(windowPos.y, Y_CLAMP, Window.innerHeight - Y_CLAMP); var fov = (Settings.getValue('fieldOfView') || DEFAULT_VERTICAL_FIELD_OF_VIEW) * (Math.PI / 180); + + // scale factor of natural tablet dimensions. + var sensorScaleFactor = MyAvatar.sensorToWorldScale; + var tabletWidth = getTabletWidthFromSettings() * sensorScaleFactor; + var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x; + var tabletHeight = TABLET_NATURAL_DIMENSIONS.y * tabletScaleFactor; + var tabletDepth = TABLET_NATURAL_DIMENSIONS.z * tabletScaleFactor; + var tabletDpi = DEFAULT_DPI * (DEFAULT_WIDTH / tabletWidth); + var MAX_PADDING_FACTOR = 2.2; var PADDING_FACTOR = Math.min(Window.innerHeight / this.getTabletTextureResolution().y, MAX_PADDING_FACTOR); - var TABLET_HEIGHT = (this.getTabletTextureResolution().y / this.dpi) * INCHES_TO_METERS; - var WEB_ENTITY_Z_OFFSET = (this.depth / 2); + var TABLET_HEIGHT = (this.getTabletTextureResolution().y / tabletDpi) * INCHES_TO_METERS; + var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2); // calcualte distance from camera var dist = (PADDING_FACTOR * TABLET_HEIGHT) / (2 * Math.tan(fov / 2) * (DESKTOP_TABLET_SCALE / 100)) - WEB_ENTITY_Z_OFFSET; @@ -424,7 +374,7 @@ WebTablet.prototype.calculateTabletAttachmentProperties = function (hand, useMou tabletProperties.parentJointIndex = SENSOR_TO_ROOM_MATRIX; // compute the appropriate position of the tablet, near the hand controller that was used to spawn it. - var spawnInfo = calcSpawnInfo(hand, this.height, this.landscape); + var spawnInfo = calcSpawnInfo(hand, this.landscape); tabletProperties.position = spawnInfo.position; tabletProperties.rotation = spawnInfo.rotation; } else { @@ -447,16 +397,10 @@ WebTablet.prototype.calculateTabletAttachmentProperties = function (hand, useMou }; WebTablet.prototype.onHmdChanged = function () { - var tabletProperties = {}; // compute position, rotation & parentJointIndex of the tablet this.calculateTabletAttachmentProperties(NO_HANDS, false, tabletProperties); - // TODO -- is this still needed? - // Entities.editEntity(this.tabletEntityID, tabletProperties); - - // Full scene FXAA should be disabled on the overlay when the tablet in desktop mode. - // This should make the text more readable. - Overlays.editOverlay(this.webOverlayID, { isAA: HMD.active }); + Overlays.editOverlay(HMD.tabletID, tabletProperties); }; WebTablet.prototype.pickle = function () { @@ -536,16 +480,7 @@ WebTablet.prototype.mousePressEvent = function (event) { }; WebTablet.prototype.cameraModeChanged = function (newMode) { - // reposition the tablet. - // This allows HMD.position to reflect the new camera mode. - if (HMD.active) { - var self = this; - var tabletProperties = {}; - // compute position, rotation & parentJointIndex of the tablet - self.calculateTabletAttachmentProperties(NO_HANDS, false, tabletProperties); - // TODO -- is this still needed? - // Entities.editEntity(self.tabletEntityID, tabletProperties); - } + ; }; function rayIntersectPlane(planePosition, planeNormal, rayStart, rayDirection) { diff --git a/scripts/system/libraries/accountUtils.js b/scripts/system/libraries/accountUtils.js new file mode 100644 index 0000000000..6df0aa3a87 --- /dev/null +++ b/scripts/system/libraries/accountUtils.js @@ -0,0 +1,16 @@ +// +// accountUtils.js +// scripts/system/libraries/libraries +// +// Copyright 2017 High Fidelity, Inc. +// + +openLoginWindow = function openLoginWindow() { + if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false)) + || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { + Menu.triggerOption("Login / Sign Up"); + } else { + tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); + HMD.openTablet(); + } +}; diff --git a/scripts/system/libraries/cloneEntityUtils.js b/scripts/system/libraries/cloneEntityUtils.js new file mode 100644 index 0000000000..777504b16d --- /dev/null +++ b/scripts/system/libraries/cloneEntityUtils.js @@ -0,0 +1,98 @@ +"use strict"; + +// cloneEntity.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* global entityIsCloneable:true, getGrabbableData:true, cloneEntity:true, propsAreCloneDynamic:true, Script, + propsAreCloneDynamic:true, Entities*/ + +Script.include("/~/system/controllers/controllerDispatcherUtils.js"); + + +// Object assign polyfill +if (typeof Object.assign !== 'function') { + Object.assign = function(target, varArgs) { + if (target === null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + var to = Object(target); + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + if (nextSource !== null) { + for (var nextKey in nextSource) { + if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }; +} + +entityIsCloneable = function(props) { + if (props) { + var grabbableData = getGrabbableData(props); + return grabbableData.cloneable; + } + return false; +}; + +propsAreCloneDynamic = function(props) { + var cloneable = entityIsCloneable(props); + if (cloneable) { + var grabInfo = getGrabbableData(props); + if (grabInfo.cloneDynamic) { + return true; + } + } + return false; +}; + + +cloneEntity = function(props, worldEntityProps) { + // we need all the properties, for this + var cloneableProps = Entities.getEntityProperties(props.id); + + var count = 0; + worldEntityProps.forEach(function(itemWE) { + if (itemWE.name.indexOf('-clone-' + cloneableProps.id) !== -1) { + count++; + } + }); + + var grabInfo = getGrabbableData(cloneableProps); + var limit = grabInfo.cloneLimit ? grabInfo.cloneLimit : 0; + if (count >= limit && limit !== 0) { + return null; + } + + cloneableProps.name = cloneableProps.name + '-clone-' + cloneableProps.id; + var lifetime = grabInfo.cloneLifetime ? grabInfo.cloneLifetime : 300; + var dynamic = grabInfo.cloneDynamic ? grabInfo.cloneDynamic : false; + var triggerable = grabInfo.triggerable ? grabInfo.triggerable : false; + var avatarEntity = grabInfo.cloneAvatarEntity ? grabInfo.cloneAvatarEntity : false; + var cUserData = Object.assign({}, JSON.parse(cloneableProps.userData)); + var cProperties = Object.assign({}, cloneableProps); + + + delete cUserData.grabbableKey.cloneLifetime; + delete cUserData.grabbableKey.cloneable; + delete cUserData.grabbableKey.cloneDynamic; + delete cUserData.grabbableKey.cloneLimit; + delete cUserData.grabbableKey.cloneAvatarEntity; + delete cProperties.id; + + + cProperties.dynamic = dynamic; + cProperties.locked = false; + cUserData.grabbableKey.triggerable = triggerable; + cUserData.grabbableKey.grabbable = true; + cProperties.lifetime = lifetime; + cProperties.userData = JSON.stringify(cUserData); + + var cloneID = Entities.addEntity(cProperties, avatarEntity); + return cloneID; +}; diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js new file mode 100644 index 0000000000..e9e25b058b --- /dev/null +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -0,0 +1,374 @@ +"use strict"; + +// controllerDispatcherUtils.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + +/* global module, Camera, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays, Xform, + MSECS_PER_SEC:true , LEFT_HAND:true, RIGHT_HAND:true, NULL_UUID:true, AVATAR_SELF_ID:true, FORBIDDEN_GRAB_TYPES:true, + HAPTIC_PULSE_STRENGTH:true, HAPTIC_PULSE_DURATION:true, ZERO_VEC:true, ONE_VEC:true, + DEFAULT_REGISTRATION_POINT:true, INCHES_TO_METERS:true, + TRIGGER_OFF_VALUE:true, + TRIGGER_ON_VALUE:true, + PICK_MAX_DISTANCE:true, + DEFAULT_SEARCH_SPHERE_DISTANCE:true, + NEAR_GRAB_PICK_RADIUS:true, + COLORS_GRAB_SEARCHING_HALF_SQUEEZE:true, + COLORS_GRAB_SEARCHING_FULL_SQUEEZE:true, + COLORS_GRAB_DISTANCE_HOLD:true, + NEAR_GRAB_RADIUS:true, + DISPATCHER_PROPERTIES:true, + HAPTIC_PULSE_STRENGTH:true, + HAPTIC_PULSE_DURATION:true, + Entities, + makeDispatcherModuleParameters:true, + makeRunningValues:true, + enableDispatcherModule:true, + disableDispatcherModule:true, + getEnabledModuleByName:true, + getGrabbableData:true, + entityIsGrabbable:true, + entityIsDistanceGrabbable:true, + getControllerJointIndex:true, + propsArePhysical:true, + controllerDispatcherPluginsNeedSort:true, + projectOntoXYPlane:true, + projectOntoEntityXYPlane:true, + projectOntoOverlayXYPlane:true, + entityHasActions:true, + ensureDynamic:true, + findGroupParent:true, + BUMPER_ON_VALUE:true, + findHandChildEntities:true, + TEAR_AWAY_DISTANCE:true, + TEAR_AWAY_COUNT:true, + TEAR_AWAY_CHECK_TIME:true, + distanceBetweenPointAndEntityBoundingBox:true +*/ + +MSECS_PER_SEC = 1000.0; +INCHES_TO_METERS = 1.0 / 39.3701; + +HAPTIC_PULSE_STRENGTH = 1.0; +HAPTIC_PULSE_DURATION = 13.0; + +ZERO_VEC = { x: 0, y: 0, z: 0 }; +ONE_VEC = { x: 1, y: 1, z: 1 }; + +LEFT_HAND = 0; +RIGHT_HAND = 1; + +NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; +AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}"; + +FORBIDDEN_GRAB_TYPES = ["Unknown", "Light", "PolyLine", "Zone"]; + +HAPTIC_PULSE_STRENGTH = 1.0; +HAPTIC_PULSE_DURATION = 13.0; + +DEFAULT_REGISTRATION_POINT = { x: 0.5, y: 0.5, z: 0.5 }; + +TRIGGER_OFF_VALUE = 0.1; +TRIGGER_ON_VALUE = TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab +BUMPER_ON_VALUE = 0.5; + +PICK_MAX_DISTANCE = 500; // max length of pick-ray +DEFAULT_SEARCH_SPHERE_DISTANCE = 1000; // how far from camera to search intersection? +NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing. + +COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { red: 10, green: 10, blue: 255 }; +COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 }; +COLORS_GRAB_DISTANCE_HOLD = { red: 238, green: 75, blue: 214 }; + +NEAR_GRAB_RADIUS = 1.0; + +TEAR_AWAY_DISTANCE = 0.1; // ungrab an entity if its bounding-box moves this far from the hand +TEAR_AWAY_COUNT = 2; // multiply by TEAR_AWAY_CHECK_TIME to know how long the item must be away +TEAR_AWAY_CHECK_TIME = 0.15; // seconds, duration between checks + +DISPATCHER_PROPERTIES = [ + "position", + "registrationPoint", + "rotation", + "gravity", + "collidesWith", + "dynamic", + "collisionless", + "locked", + "name", + "shapeType", + "parentID", + "parentJointIndex", + "density", + "dimensions", + "userData" +]; + +// priority -- a lower priority means the module will be asked sooner than one with a higher priority in a given update step +// activitySlots -- indicates which "slots" must not yet be in use for this module to start +// requiredDataForReady -- which "situation" parts this module looks at to decide if it will start +// sleepMSBetweenRuns -- how long to wait between calls to this module's "run" method +makeDispatcherModuleParameters = function (priority, activitySlots, requiredDataForReady, sleepMSBetweenRuns) { + return { + priority: priority, + activitySlots: activitySlots, + requiredDataForReady: requiredDataForReady, + sleepMSBetweenRuns: sleepMSBetweenRuns + }; +}; + +makeRunningValues = function (active, targets, requiredDataForRun) { + return { + active: active, + targets: targets, + requiredDataForRun: requiredDataForRun + }; +}; + +enableDispatcherModule = function (moduleName, module, priority) { + if (!controllerDispatcherPlugins) { + controllerDispatcherPlugins = {}; + } + controllerDispatcherPlugins[moduleName] = module; + controllerDispatcherPluginsNeedSort = true; +}; + +disableDispatcherModule = function (moduleName) { + delete controllerDispatcherPlugins[moduleName]; + controllerDispatcherPluginsNeedSort = true; +}; + +getEnabledModuleByName = function (moduleName) { + if (controllerDispatcherPlugins.hasOwnProperty(moduleName)) { + return controllerDispatcherPlugins[moduleName]; + } + return null; +}; + +getGrabbableData = function (props) { + // look in userData for a "grabbable" key, return the value or some defaults + var grabbableData = {}; + var userDataParsed = null; + try { + if (!props.userDataParsed) { + props.userDataParsed = JSON.parse(props.userData); + } + userDataParsed = props.userDataParsed; + } catch (err) { + userDataParsed = {}; + } + if (userDataParsed.grabbableKey) { + grabbableData = userDataParsed.grabbableKey; + } + if (!grabbableData.hasOwnProperty("grabbable")) { + grabbableData.grabbable = true; + } + if (!grabbableData.hasOwnProperty("ignoreIK")) { + grabbableData.ignoreIK = true; + } + if (!grabbableData.hasOwnProperty("kinematic")) { + grabbableData.kinematic = true; + } + if (!grabbableData.hasOwnProperty("wantsTrigger")) { + grabbableData.wantsTrigger = false; + } + if (!grabbableData.hasOwnProperty("triggerable")) { + grabbableData.triggerable = false; + } + + return grabbableData; +}; + +entityIsGrabbable = function (props) { + var grabbable = getGrabbableData(props).grabbable; + if (!grabbable || + props.locked || + FORBIDDEN_GRAB_TYPES.indexOf(props.type) >= 0) { + return false; + } + return true; +}; + +entityIsDistanceGrabbable = function(props) { + if (!entityIsGrabbable(props)) { + return false; + } + + // we can't distance-grab non-physical + var isPhysical = propsArePhysical(props); + if (!isPhysical) { + return false; + } + + return true; +}; + + +getControllerJointIndex = function (hand) { + if (HMD.isHandControllerAvailable()) { + var controllerJointIndex = -1; + if (Camera.mode === "first person") { + controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? + "_CONTROLLER_RIGHTHAND" : + "_CONTROLLER_LEFTHAND"); + } else if (Camera.mode === "third person") { + controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? + "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : + "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); + } + + return controllerJointIndex; + } + + return MyAvatar.getJointIndex("Head"); +}; + +propsArePhysical = function (props) { + if (!props.dynamic) { + return false; + } + var isPhysical = (props.shapeType && props.shapeType !== 'none'); + return isPhysical; +}; + +projectOntoXYPlane = function (worldPos, position, rotation, dimensions, registrationPoint) { + var invRot = Quat.inverse(rotation); + var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(worldPos, position)); + var invDimensions = { + x: 1 / dimensions.x, + y: 1 / dimensions.y, + z: 1 / dimensions.z + }; + + var normalizedPos = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), registrationPoint); + return { + x: normalizedPos.x * dimensions.x, + y: (1 - normalizedPos.y) * dimensions.y // flip y-axis + }; +}; + +projectOntoEntityXYPlane = function (entityID, worldPos, props) { + return projectOntoXYPlane(worldPos, props.position, props.rotation, props.dimensions, props.registrationPoint); +}; + +projectOntoOverlayXYPlane = function projectOntoOverlayXYPlane(overlayID, worldPos) { + var position = Overlays.getProperty(overlayID, "position"); + var rotation = Overlays.getProperty(overlayID, "rotation"); + var dimensions; + + var dpi = Overlays.getProperty(overlayID, "dpi"); + if (dpi) { + // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property is used as a scale. + var resolution = Overlays.getProperty(overlayID, "resolution"); + resolution.z = 1; // Circumvent divide-by-zero. + var scale = Overlays.getProperty(overlayID, "dimensions"); + scale.z = 0.01; // overlay dimensions are 2D, not 3D. + dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); + } else { + dimensions = Overlays.getProperty(overlayID, "dimensions"); + if (dimensions.z) { + dimensions.z = 0.01; // overlay dimensions are 2D, not 3D. + } + } + + return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT); +}; + +entityHasActions = function (entityID) { + return Entities.getActionIDs(entityID).length > 0; +}; + +ensureDynamic = function (entityID) { + // if we distance hold something and keep it very still before releasing it, it ends up + // non-dynamic in bullet. If it's too still, give it a little bounce so it will fall. + var props = Entities.getEntityProperties(entityID, ["velocity", "dynamic", "parentID"]); + if (props.dynamic && props.parentID === NULL_UUID) { + var velocity = props.velocity; + if (Vec3.length(velocity) < 0.05) { // see EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD + velocity = { x: 0.0, y: 0.2, z: 0.0 }; + Entities.editEntity(entityID, { velocity: velocity }); + } + } +}; + +findGroupParent = function (controllerData, targetProps) { + while (targetProps.parentID && + targetProps.parentID !== NULL_UUID && + Entities.getNestableType(targetProps.parentID) == "entity") { + var parentProps = Entities.getEntityProperties(targetProps.parentID, DISPATCHER_PROPERTIES); + if (!parentProps) { + break; + } + parentProps.id = targetProps.parentID; + targetProps = parentProps; + controllerData.nearbyEntityPropertiesByID[targetProps.id] = targetProps; + } + + return targetProps; +}; + + +findHandChildEntities = function(hand) { + // find children of avatar's hand joint + var handJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, handJointIndex); + children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, handJointIndex)); + + // find children of faux controller joint + var controllerJointIndex = getControllerJointIndex(hand); + children = children.concat(Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, controllerJointIndex)); + children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, controllerJointIndex)); + + // find children of faux camera-relative controller joint + var controllerCRJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? + "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : + "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); + children = children.concat(Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, controllerCRJointIndex)); + children = children.concat(Entities.getChildrenIDsOfJoint(AVATAR_SELF_ID, controllerCRJointIndex)); + + return children.filter(function (childID) { + var childType = Entities.getNestableType(childID); + return childType == "entity"; + }); +}; + +distanceBetweenPointAndEntityBoundingBox = function(point, entityProps) { + var entityXform = new Xform(entityProps.rotation, entityProps.position); + var localPoint = entityXform.inv().xformPoint(point); + var minOffset = Vec3.multiplyVbyV(entityProps.registrationPoint, entityProps.dimensions); + var maxOffset = Vec3.multiplyVbyV(Vec3.subtract(ONE_VEC, entityProps.registrationPoint), entityProps.dimensions); + var localMin = Vec3.subtract(entityXform.trans, minOffset); + var localMax = Vec3.sum(entityXform.trans, maxOffset); + + var v = {x: localPoint.x, y: localPoint.y, z: localPoint.z}; + v.x = Math.max(v.x, localMin.x); + v.x = Math.min(v.x, localMax.x); + v.y = Math.max(v.y, localMin.y); + v.y = Math.min(v.y, localMax.y); + v.z = Math.max(v.z, localMin.z); + v.z = Math.min(v.z, localMax.z); + + return Vec3.distance(v, localPoint); +}; + +if (typeof module !== 'undefined') { + module.exports = { + makeDispatcherModuleParameters: makeDispatcherModuleParameters, + enableDispatcherModule: enableDispatcherModule, + disableDispatcherModule: disableDispatcherModule, + makeRunningValues: makeRunningValues, + LEFT_HAND: LEFT_HAND, + RIGHT_HAND: RIGHT_HAND, + BUMPER_ON_VALUE: BUMPER_ON_VALUE, + TEAR_AWAY_DISTANCE: TEAR_AWAY_DISTANCE, + propsArePhysical: propsArePhysical, + entityIsGrabbable: entityIsGrabbable, + NEAR_GRAB_RADIUS: NEAR_GRAB_RADIUS, + projectOntoOverlayXYPlane: projectOntoOverlayXYPlane, + projectOntoEntityXYPlane: projectOntoEntityXYPlane, + TRIGGER_OFF_VALUE: TRIGGER_OFF_VALUE, + TRIGGER_ON_VALUE: TRIGGER_ON_VALUE + }; +} diff --git a/scripts/system/libraries/controllers.js b/scripts/system/libraries/controllers.js index 75bca34ed6..d99fd0db48 100644 --- a/scripts/system/libraries/controllers.js +++ b/scripts/system/libraries/controllers.js @@ -18,15 +18,20 @@ getGrabCommunications = function getFarGrabCommunications() { // this offset needs to match the one in libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp:378 var GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }; // x = upward, y = forward, z = lateral -getGrabPointSphereOffset = function(handController) { - if (handController === Controller.Standard.RightHand) { - return GRAB_POINT_SPHERE_OFFSET; +getGrabPointSphereOffset = function(handController, ignoreSensorToWorldScale) { + var offset = GRAB_POINT_SPHERE_OFFSET; + if (handController === Controller.Standard.LeftHand) { + offset = { + x: -GRAB_POINT_SPHERE_OFFSET.x, + y: GRAB_POINT_SPHERE_OFFSET.y, + z: GRAB_POINT_SPHERE_OFFSET.z + }; + } + if (ignoreSensorToWorldScale) { + return offset; + } else { + return Vec3.multiply(MyAvatar.sensorToWorldScale, offset); } - return { - x: GRAB_POINT_SPHERE_OFFSET.x * -1, - y: GRAB_POINT_SPHERE_OFFSET.y, - z: GRAB_POINT_SPHERE_OFFSET.z - }; }; // controllerWorldLocation is where the controller would be, in-world, with an added offset @@ -53,7 +58,7 @@ getControllerWorldLocation = function (handController, doOffset) { } else if (!HMD.isHandControllerAvailable()) { // NOTE: keep this offset in sync with scripts/system/controllers/handControllerPointer.js:493 - var VERTICAL_HEAD_LASER_OFFSET = 0.1; + var VERTICAL_HEAD_LASER_OFFSET = 0.1 * MyAvatar.sensorToWorldScale; position = Vec3.sum(Camera.position, Vec3.multiplyQbyV(Camera.orientation, {x: 0, y: VERTICAL_HEAD_LASER_OFFSET, z: 0})); orientation = Quat.multiply(Camera.orientation, Quat.angleAxis(-90, { x: 1, y: 0, z: 0 })); valid = true; diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index 64a05fcebf..9d9689000e 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -108,6 +108,16 @@ EntityListTool = function(opts) { webView.emitScriptEvent(JSON.stringify(data)); }; + function onFileSaveChanged(filename) { + Window.saveFileChanged.disconnect(onFileSaveChanged); + if (filename !== "") { + var success = Clipboard.exportEntities(filename, selectionManager.selections); + if (!success) { + Window.notifyEditError("Export failed."); + } + } + } + webView.webEventReceived.connect(function(data) { try { data = JSON.parse(data); @@ -139,13 +149,8 @@ EntityListTool = function(opts) { if (!selectionManager.hasSelection()) { Window.notifyEditError("No entities have been selected."); } else { - var filename = Window.save("Select Where to Save", "", "*.json"); - if (filename) { - var success = Clipboard.exportEntities(filename, selectionManager.selections); - if (!success) { - Window.notifyEditError("Export failed."); - } - } + Window.saveFileChanged.connect(onFileSaveChanged); + Window.saveAsync("Select Where to Save", "", "*.json"); } } else if (data.type == "pal") { var sessionIds = {}; // Collect the sessionsIds of all selected entitities, w/o duplicates. diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 77b62913bf..4df25c41b7 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -30,11 +30,13 @@ function objectTranslationPlanePoint(position, dimensions) { SelectionManager = (function() { var that = {}; + // FUNCTION: SUBSCRIBE TO UPDATE MESSAGES function subscribeToUpdateMessages() { Messages.subscribe("entityToolUpdates"); Messages.messageReceived.connect(handleEntitySelectionToolUpdates); } + // FUNCTION: HANDLE ENTITY SELECTION TOOL UDPATES function handleEntitySelectionToolUpdates(channel, message, sender) { if (channel !== 'entityToolUpdates') { return; @@ -238,6 +240,7 @@ function normalizeDegrees(degrees) { return degrees; } +// FUNCTION: getRelativeCenterPosition // Return the enter position of an entity relative to it's registrationPoint // A registration point of (0.5, 0.5, 0.5) will have an offset of (0, 0, 0) // A registration point of (1.0, 1.0, 1.0) will have an offset of (-dimensions.x / 2, -dimensions.y / 2, -dimensions.z / 2) @@ -249,6 +252,7 @@ function getRelativeCenterPosition(dimensions, registrationPoint) { }; } +// SELECTION DISPLAY DEFINITION SelectionDisplay = (function() { var that = {}; @@ -272,6 +276,10 @@ SelectionDisplay = (function() { var overlayNames = []; var lastCameraPosition = Camera.getPosition(); var lastCameraOrientation = Camera.getOrientation(); + var lastControllerPoses = [ + getControllerWorldLocation(Controller.Standard.LeftHand, true), + getControllerWorldLocation(Controller.Standard.RightHand, true) + ]; var handleHoverColor = { red: 224, @@ -1152,6 +1160,7 @@ SelectionDisplay = (function() { that.updateHandles(); }; + // FUNCTION: UPDATE ROTATION HANDLES that.updateRotationHandles = function() { var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); @@ -1545,11 +1554,6 @@ SelectionDisplay = (function() { translateHandlesVisible = false; } - var rotation = selectionManager.worldRotation; - var dimensions = selectionManager.worldDimensions; - var position = selectionManager.worldPosition; - - Overlays.editOverlay(rotateOverlayTarget, { visible: rotationOverlaysVisible }); @@ -1577,6 +1581,7 @@ SelectionDisplay = (function() { }); }; + // FUNCTION: SET SPACE MODE that.setSpaceMode = function(newSpaceMode) { if (spaceMode != newSpaceMode) { spaceMode = newSpaceMode; @@ -1584,6 +1589,7 @@ SelectionDisplay = (function() { } }; + // FUNCTION: TOGGLE SPACE MODE that.toggleSpaceMode = function() { if (spaceMode == SPACE_WORLD && SelectionManager.selections.length > 1) { print("Local space editing is not available with multiple selections"); @@ -1593,8 +1599,11 @@ SelectionDisplay = (function() { that.updateHandles(); }; + // FUNCTION: UNSELECT ALL + // TODO?: Needs implementation that.unselectAll = function() {}; + // FUNCTION: UPDATE HANDLES that.updateHandles = function() { if (SelectionManager.selections.length === 0) { that.setOverlaysVisible(false); @@ -2168,10 +2177,10 @@ SelectionDisplay = (function() { position: EdgeTR }); - var boxPosition = Vec3.multiplyQbyV(rotation, center); - boxPosition = Vec3.sum(position, boxPosition); + var selectionBoxPosition = Vec3.multiplyQbyV(rotation, center); + selectionBoxPosition = Vec3.sum(position, selectionBoxPosition); Overlays.editOverlay(selectionBox, { - position: boxPosition, + position: selectionBoxPosition, dimensions: dimensions, rotation: rotation, visible: !(mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL"), @@ -2218,13 +2227,13 @@ SelectionDisplay = (function() { var offset = vec3Mult(props.dimensions, centeredRP); offset = Vec3.multiply(-1, offset); offset = Vec3.multiplyQbyV(props.rotation, offset); - var boxPosition = Vec3.sum(props.position, offset); + var curBoxPosition = Vec3.sum(props.position, offset); var color = {red: 255, green: 128, blue: 0}; if (i >= selectionManager.selections.length - 1) color = {red: 255, green: 255, blue: 64}; Overlays.editOverlay(selectionBoxes[i], { - position: boxPosition, + position: curBoxPosition, color: color, rotation: props.rotation, dimensions: props.dimensions, @@ -2305,9 +2314,9 @@ SelectionDisplay = (function() { x: position.x, y: position.y + worldTop + grabberMoveUpOffset, z: position.z - } + }; Overlays.editOverlay(grabberMoveUp, { - visible: activeTool == null || mode == "TRANSLATE_UP_DOWN" + visible: (activeTool === null) || (mode == "TRANSLATE_UP_DOWN") }); Overlays.editOverlay(baseOfEntityProjectionOverlay, { @@ -2327,21 +2336,24 @@ SelectionDisplay = (function() { }; + // FUNCTION: SET OVERLAYS VISIBLE that.setOverlaysVisible = function(isVisible) { var length = allOverlays.length; - for (var i = 0; i < length; i++) { - Overlays.editOverlay(allOverlays[i], { + for (var overlayIndex = 0; overlayIndex < length; overlayIndex++) { + Overlays.editOverlay(allOverlays[overlayIndex], { visible: isVisible }); } length = selectionBoxes.length; - for (var i = 0; i < length; i++) { - Overlays.editOverlay(selectionBoxes[i], { + for (var boxIndex = 0; boxIndex < length; boxIndex++) { + Overlays.editOverlay(selectionBoxes[boxIndex], { visible: isVisible }); } }; + // FUNCTION: UNSELECT + // TODO?: Needs implementation that.unselect = function(entityID) {}; var initialXZPick = null; @@ -2350,6 +2362,7 @@ SelectionDisplay = (function() { var startPosition = null; var duplicatedEntityIDs = null; + // TOOL DEFINITION: TRANSLATE XZ TOOL var translateXZTool = { mode: 'TRANSLATE_XZ', pickPlanePosition: { x: 0, y: 0, z: 0 }, @@ -2538,7 +2551,8 @@ SelectionDisplay = (function() { } }; - var lastXYPick = null + // GRABBER TOOL: GRABBER MOVE UP + var lastXYPick = null; var upDownPickNormal = null; addGrabberTool(grabberMoveUp, { mode: "TRANSLATE_UP_DOWN", @@ -2594,7 +2608,7 @@ SelectionDisplay = (function() { print(" event.y:" + event.y); Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - Vec3.print(" newPosition:", newPosition); + //Vec3.print(" newPosition:", newPosition); } for (var i = 0; i < SelectionManager.selections.length; i++) { var id = SelectionManager.selections[i]; @@ -2612,6 +2626,7 @@ SelectionDisplay = (function() { }, }); + // GRABBER TOOL: GRABBER CLONER addGrabberTool(grabberCloner, { mode: "CLONE", onBegin: function(event) { @@ -2639,7 +2654,7 @@ SelectionDisplay = (function() { - + // FUNCTION: VEC 3 MULT var vec3Mult = function(v1, v2) { return { x: v1.x * v2.x, @@ -2647,6 +2662,8 @@ SelectionDisplay = (function() { z: v1.z * v2.z }; }; + + // FUNCTION: MAKE STRETCH TOOL // stretchMode - name of mode // direction - direction to stretch in // pivot - point to use as a pivot @@ -2898,13 +2915,14 @@ SelectionDisplay = (function() { // Are we using handControllers or Mouse - only relevant for 3D tools var controllerPose = getControllerWorldLocation(activeHand, true); - if (HMD.isHMDAvailable() - && HMD.isHandControllerAvailable() && controllerPose.valid && that.triggered && directionFor3DStretch) { + var vector = null; + if (HMD.isHMDAvailable() && HMD.isHandControllerAvailable() && + controllerPose.valid && that.triggered && directionFor3DStretch) { localDeltaPivot = deltaPivot3D; newPick = pickRay.origin; - var vector = Vec3.subtract(newPick, lastPick3D); + vector = Vec3.subtract(newPick, lastPick3D); vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector); @@ -2919,7 +2937,7 @@ SelectionDisplay = (function() { newPick = rayPlaneIntersection(pickRay, pickRayPosition, planeNormal); - var vector = Vec3.subtract(newPick, lastPick); + vector = Vec3.subtract(newPick, lastPick); vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector); @@ -2955,41 +2973,39 @@ SelectionDisplay = (function() { } else { newDimensions = Vec3.sum(initialDimensions, changeInDimensions); } - } - - - newDimensions.x = Math.max(newDimensions.x, MINIMUM_DIMENSION); - newDimensions.y = Math.max(newDimensions.y, MINIMUM_DIMENSION); - newDimensions.z = Math.max(newDimensions.z, MINIMUM_DIMENSION); - var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(localDeltaPivot, changeInDimensions)); - var newPosition = Vec3.sum(initialPosition, changeInPosition); + newDimensions.x = Math.max(newDimensions.x, MINIMUM_DIMENSION); + newDimensions.y = Math.max(newDimensions.y, MINIMUM_DIMENSION); + newDimensions.z = Math.max(newDimensions.z, MINIMUM_DIMENSION); - for (var i = 0; i < SelectionManager.selections.length; i++) { - Entities.editEntity(SelectionManager.selections[i], { - position: newPosition, - dimensions: newDimensions, - }); - } - + var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(localDeltaPivot, changeInDimensions)); + var newPosition = Vec3.sum(initialPosition, changeInPosition); - var wantDebug = false; - if (wantDebug) { - print(stretchMode); - //Vec3.print(" newIntersection:", newIntersection); - Vec3.print(" vector:", vector); - //Vec3.print(" oldPOS:", oldPOS); - //Vec3.print(" newPOS:", newPOS); - Vec3.print(" changeInDimensions:", changeInDimensions); - Vec3.print(" newDimensions:", newDimensions); + for (var i = 0; i < SelectionManager.selections.length; i++) { + Entities.editEntity(SelectionManager.selections[i], { + position: newPosition, + dimensions: newDimensions, + }); + } + - Vec3.print(" changeInPosition:", changeInPosition); - Vec3.print(" newPosition:", newPosition); + var wantDebug = false; + if (wantDebug) { + print(stretchMode); + //Vec3.print(" newIntersection:", newIntersection); + Vec3.print(" vector:", vector); + //Vec3.print(" oldPOS:", oldPOS); + //Vec3.print(" newPOS:", newPOS); + Vec3.print(" changeInDimensions:", changeInDimensions); + Vec3.print(" newDimensions:", newDimensions); + + Vec3.print(" changeInPosition:", changeInPosition); + Vec3.print(" newPosition:", newPosition); + } } SelectionManager._update(); - - }; + };//--End of onMove def return { mode: stretchMode, @@ -3042,7 +3058,8 @@ SelectionDisplay = (function() { z: -1 } }; - + + // FUNCTION: GET DIRECTION FOR 3D STRETCH // Returns a vector with directions for the stretch tool in 3D using hand controllers function getDirectionsFor3DStretch(mode) { if (mode === "STRETCH_LBN") { @@ -3067,7 +3084,7 @@ SelectionDisplay = (function() { } - + // FUNCTION: ADD STRETCH TOOL function addStretchTool(overlay, mode, pivot, direction, offset, handleMove) { if (!pivot) { pivot = direction; @@ -3077,6 +3094,7 @@ SelectionDisplay = (function() { addGrabberTool(overlay, tool); } + // FUNCTION: CUTOFF STRETCH FUNC function cutoffStretchFunc(vector, change) { vector = change; Vec3.print("Radius stretch: ", vector); @@ -3097,6 +3115,7 @@ SelectionDisplay = (function() { SelectionManager._update(); } + // FUNCTION: RADIUS STRETCH FUNC function radiusStretchFunc(vector, change) { var props = selectionManager.savedProperties[selectionManager.selections[0]]; @@ -3123,6 +3142,7 @@ SelectionDisplay = (function() { SelectionManager._update(); } + // STRETCH TOOL DEF SECTION addStretchTool(grabberNEAR, "STRETCH_NEAR", { x: 0, y: 0, @@ -3529,6 +3549,7 @@ SelectionDisplay = (function() { z: 1 }); + // FUNCTION: UPDATE ROTATION DEGREES OVERLAY function updateRotationDegreesOverlay(angleFromZero, handleRotation, centerPosition) { var angle = angleFromZero * (Math.PI / 180); var position = { @@ -3549,6 +3570,7 @@ SelectionDisplay = (function() { }); } + // YAW GRABBER TOOL DEFINITION var initialPosition = SelectionManager.worldPosition; addGrabberTool(yawHandle, { mode: "ROTATE_YAW", @@ -3625,10 +3647,10 @@ SelectionDisplay = (function() { if (result.intersects) { var center = yawCenter; var zero = yawZero; - // TODO: these vectors are backwards to their names, doesn't matter for this use case (inverted vectors still give same angle) - var centerToZero = Vec3.subtract(center, zero); - var centerToIntersect = Vec3.subtract(center, result.intersection); - // TODO: orientedAngle wants normalized centerToZero and centerToIntersect + var centerToZero = Vec3.subtract(zero, center); + var centerToIntersect = Vec3.subtract(result.intersection, center); + // Note: orientedAngle which wants normalized centerToZero and centerToIntersect + // handles that internally, so it's to pass unnormalized vectors here. var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); var distanceFromCenter = Vec3.distance(center, result.intersection); var snapToInner = distanceFromCenter < innerRadius; @@ -3717,6 +3739,7 @@ SelectionDisplay = (function() { } }); + // PITCH GRABBER TOOL DEFINITION addGrabberTool(pitchHandle, { mode: "ROTATE_PITCH", onBegin: function(event) { @@ -3789,11 +3812,12 @@ SelectionDisplay = (function() { var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); if (result.intersects) { - var properties = Entities.getEntityProperties(selectionManager.selections[0]); var center = pitchCenter; var zero = pitchZero; - var centerToZero = Vec3.subtract(center, zero); - var centerToIntersect = Vec3.subtract(center, result.intersection); + var centerToZero = Vec3.subtract(zero, center); + var centerToIntersect = Vec3.subtract(result.intersection, center); + // Note: orientedAngle which wants normalized centerToZero & centerToIntersect, handles + // this internally, so it's fine to pass non-normalized versions here. var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); var distanceFromCenter = Vec3.distance(center, result.intersection); @@ -3809,7 +3833,6 @@ SelectionDisplay = (function() { for (var i = 0; i < SelectionManager.selections.length; i++) { var entityID = SelectionManager.selections[i]; - var properties = Entities.getEntityProperties(entityID); var initialProperties = SelectionManager.savedProperties[entityID]; var dPos = Vec3.subtract(initialProperties.position, initialPosition); dPos = Vec3.multiplyQbyV(pitchChange, dPos); @@ -3874,6 +3897,7 @@ SelectionDisplay = (function() { } }); + // ROLL GRABBER TOOL DEFINITION addGrabberTool(rollHandle, { mode: "ROTATE_ROLL", onBegin: function(event) { @@ -3946,11 +3970,12 @@ SelectionDisplay = (function() { var result = Overlays.findRayIntersection(pickRay, true, [rotateOverlayTarget]); if (result.intersects) { - var properties = Entities.getEntityProperties(selectionManager.selections[0]); var center = rollCenter; var zero = rollZero; - var centerToZero = Vec3.subtract(center, zero); - var centerToIntersect = Vec3.subtract(center, result.intersection); + var centerToZero = Vec3.subtract(zero, center); + var centerToIntersect = Vec3.subtract(result.intersection, center); + // Note: orientedAngle which wants normalized centerToZero & centerToIntersect, handles + // this internally, so it's fine to pass non-normalized versions here. var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); var distanceFromCenter = Vec3.distance(center, result.intersection); @@ -3965,7 +3990,6 @@ SelectionDisplay = (function() { }); for (var i = 0; i < SelectionManager.selections.length; i++) { var entityID = SelectionManager.selections[i]; - var properties = Entities.getEntityProperties(entityID); var initialProperties = SelectionManager.savedProperties[entityID]; var dPos = Vec3.subtract(initialProperties.position, initialPosition); dPos = Vec3.multiplyQbyV(rollChange, dPos); @@ -4030,6 +4054,7 @@ SelectionDisplay = (function() { } }); + // FUNCTION: CHECK MOVE that.checkMove = function() { if (SelectionManager.hasSelection()) { @@ -4044,6 +4069,22 @@ SelectionDisplay = (function() { } }; + that.checkControllerMove = function() { + if (SelectionManager.hasSelection()) { + var controllerPose = getControllerWorldLocation(activeHand, true); + var hand = (activeHand === Controller.Standard.LeftHand) ? 0 : 1; + if (controllerPose.valid && lastControllerPoses[hand].valid) { + if (!Vec3.equal(controllerPose.position, lastControllerPoses[hand].position) || + !Vec3.equal(controllerPose.rotation, lastControllerPoses[hand].rotation)) { + print("setting controller pose"); + that.mouseMoveEvent({}); + } + } + lastControllerPoses[hand] = controllerPose; + } + }; + + // FUNCTION: MOUSE PRESS EVENT that.mousePressEvent = function(event) { var wantDebug = false; if (!event.isLeftButton && !that.triggered) { @@ -4167,7 +4208,7 @@ SelectionDisplay = (function() { // Only intersect versus yaw/pitch/roll. - var result = Overlays.findRayIntersection(pickRay, true, [ yawHandle, pitchHandle, rollHandle ] ); + result = Overlays.findRayIntersection(pickRay, true, [ yawHandle, pitchHandle, rollHandle ] ); var overlayOrientation; var overlayCenter; @@ -4184,10 +4225,10 @@ SelectionDisplay = (function() { originalRoll = roll; if (result.intersects) { - var tool = grabberTools[result.overlayID]; - if (tool) { - activeTool = tool; - mode = tool.mode; + var resultTool = grabberTools[result.overlayID]; + if (resultTool) { + activeTool = resultTool; + mode = resultTool.mode; somethingClicked = 'tool'; if (activeTool && activeTool.onBegin) { activeTool.onBegin(event); @@ -4370,7 +4411,7 @@ SelectionDisplay = (function() { if (!somethingClicked) { // Only intersect versus selectionBox. - var result = Overlays.findRayIntersection(pickRay, true, [selectionBox]); + result = Overlays.findRayIntersection(pickRay, true, [selectionBox]); if (result.intersects) { switch (result.overlayID) { case selectionBox: @@ -4425,6 +4466,7 @@ SelectionDisplay = (function() { return somethingClicked; }; + // FUNCTION: MOUSE MOVE EVENT that.mouseMoveEvent = function(event) { if (activeTool) { activeTool.onMove(event); @@ -4554,7 +4596,7 @@ SelectionDisplay = (function() { return false; }; - + // FUNCTION: UPDATE HANDLE SIZES that.updateHandleSizes = function() { if (selectionManager.hasSelection()) { var diff = Vec3.subtract(selectionManager.worldPosition, Camera.getPosition()); @@ -4593,6 +4635,7 @@ SelectionDisplay = (function() { }; Script.update.connect(that.updateHandleSizes); + // FUNCTION: MOUSE RELEASE EVENT that.mouseReleaseEvent = function(event) { var showHandles = false; if (activeTool && activeTool.onEnd) { diff --git a/scripts/system/libraries/touchEventUtils.js b/scripts/system/libraries/touchEventUtils.js new file mode 100644 index 0000000000..fbd56e16ae --- /dev/null +++ b/scripts/system/libraries/touchEventUtils.js @@ -0,0 +1,270 @@ +"use strict"; + +// touchEventUtils.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, + controllerDispatcher.NULL_UUID, enableDispatcherModule, disableDispatcherModule, makeRunningValues, + Messages, Quat, Vec3, getControllerWorldLocation, makeDispatcherModuleParameters, Overlays, controllerDispatcher.ZERO_VEC, + AVATAR_SELF_ID, HMD, INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, Settings, getGrabPointSphereOffset +*/ + +var controllerDispatcher = Script.require("/~/system/libraries/controllerDispatcherUtils.js"); +function touchTargetHasKeyboardFocus(touchTarget) { + if (touchTarget.entityID && touchTarget.entityID !== controllerDispatcher.NULL_UUID) { + return Entities.keyboardFocusEntity === touchTarget.entityID; + } else if (touchTarget.overlayID && touchTarget.overlayID !== controllerDispatcher.NULL_UUID) { + return Overlays.keyboardFocusOverlay === touchTarget.overlayID; + } +} + +function setKeyboardFocusOnTouchTarget(touchTarget) { + if (touchTarget.entityID && touchTarget.entityID !== controllerDispatcher.NULL_UUID && + Entities.wantsHandControllerPointerEvents(touchTarget.entityID)) { + Overlays.keyboardFocusOverlay = controllerDispatcher.NULL_UUID; + Entities.keyboardFocusEntity = touchTarget.entityID; + } else if (touchTarget.overlayID && touchTarget.overlayID !== controllerDispatcher.NULL_UUID) { + Overlays.keyboardFocusOverlay = touchTarget.overlayID; + Entities.keyboardFocusEntity = controllerDispatcher.NULL_UUID; + } +} + +function sendHoverEnterEventToTouchTarget(hand, touchTarget) { + var pointerEvent = { + type: "Move", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: touchTarget.position2D, + pos3D: touchTarget.position, + normal: touchTarget.normal, + direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal), + button: "None" + }; + + if (touchTarget.entityID && touchTarget.entityID !== controllerDispatcher.NULL_UUID) { + Entities.sendHoverEnterEntity(touchTarget.entityID, pointerEvent); + } else if (touchTarget.overlayID && touchTarget.overlayID !== controllerDispatcher.NULL_UUID) { + Overlays.sendHoverEnterOverlay(touchTarget.overlayID, pointerEvent); + } +} + +function sendHoverOverEventToTouchTarget(hand, touchTarget) { + var pointerEvent = { + type: "Move", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: touchTarget.position2D, + pos3D: touchTarget.position, + normal: touchTarget.normal, + direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal), + button: "None" + }; + + if (touchTarget.entityID && touchTarget.entityID !== controllerDispatcher.NULL_UUID) { + Entities.sendMouseMoveOnEntity(touchTarget.entityID, pointerEvent); + Entities.sendHoverOverEntity(touchTarget.entityID, pointerEvent); + } else if (touchTarget.overlayID && touchTarget.overlayID !== controllerDispatcher.NULL_UUID) { + Overlays.sendMouseMoveOnOverlay(touchTarget.overlayID, pointerEvent); + Overlays.sendHoverOverOverlay(touchTarget.overlayID, pointerEvent); + } +} + +function sendTouchStartEventToTouchTarget(hand, touchTarget) { + var pointerEvent = { + type: "Press", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: touchTarget.position2D, + pos3D: touchTarget.position, + normal: touchTarget.normal, + direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal), + button: "Primary", + isPrimaryHeld: true + }; + + if (touchTarget.entityID && touchTarget.entityID !== controllerDispatcher.NULL_UUID) { + Entities.sendMousePressOnEntity(touchTarget.entityID, pointerEvent); + Entities.sendClickDownOnEntity(touchTarget.entityID, pointerEvent); + } else if (touchTarget.overlayID && touchTarget.overlayID !== controllerDispatcher.NULL_UUID) { + Overlays.sendMousePressOnOverlay(touchTarget.overlayID, pointerEvent); + } +} + +function sendTouchEndEventToTouchTarget(hand, touchTarget) { + var pointerEvent = { + type: "Release", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: touchTarget.position2D, + pos3D: touchTarget.position, + normal: touchTarget.normal, + direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal), + button: "Primary" + }; + + if (touchTarget.entityID && touchTarget.entityID !== controllerDispatcher.NULL_UUID) { + Entities.sendMouseReleaseOnEntity(touchTarget.entityID, pointerEvent); + Entities.sendClickReleaseOnEntity(touchTarget.entityID, pointerEvent); + Entities.sendHoverLeaveEntity(touchTarget.entityID, pointerEvent); + } else if (touchTarget.overlayID && touchTarget.overlayID !== controllerDispatcher.NULL_UUID) { + Overlays.sendMouseReleaseOnOverlay(touchTarget.overlayID, pointerEvent); + } +} + +function sendTouchMoveEventToTouchTarget(hand, touchTarget) { + var pointerEvent = { + type: "Move", + id: hand + 1, // 0 is reserved for hardware mouse + pos2D: touchTarget.position2D, + pos3D: touchTarget.position, + normal: touchTarget.normal, + direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal), + button: "Primary", + isPrimaryHeld: true + }; + + if (touchTarget.entityID && touchTarget.entityID !== controllerDispatcher.NULL_UUID) { + Entities.sendMouseMoveOnEntity(touchTarget.entityID, pointerEvent); + Entities.sendHoldingClickOnEntity(touchTarget.entityID, pointerEvent); + } else if (touchTarget.overlayID && touchTarget.overlayID !== controllerDispatcher.NULL_UUID) { + Overlays.sendMouseMoveOnOverlay(touchTarget.overlayID, pointerEvent); + } +} + +function composeTouchTargetFromIntersection(intersection) { + var isEntity = (intersection.type === RayPick.INTERSECTED_ENTITY); + var objectID = intersection.objectID; + var worldPos = intersection.intersection; + var props = null; + if (isEntity) { + props = Entities.getProperties(intersection.objectID); + } + + var position2D =(isEntity ? controllerDispatcher.projectOntoEntityXYPlane(objectID, worldPos, props) : + controllerDispatcher.projectOntoOverlayXYPlane(objectID, worldPos)); + return { + entityID: isEntity ? objectID : null, + overlayID: isEntity ? null : objectID, + distance: intersection.distance, + position: worldPos, + position2D: position2D, + normal: intersection.surfaceNormal + }; +} + +// will return undefined if overlayID does not exist. +function calculateTouchTargetFromOverlay(touchTip, overlayID) { + var overlayPosition = Overlays.getProperty(overlayID, "position"); + if (overlayPosition === undefined) { + return; + } + + // project touchTip onto overlay plane. + var overlayRotation = Overlays.getProperty(overlayID, "rotation"); + if (overlayRotation === undefined) { + return; + } + var normal = Vec3.multiplyQbyV(overlayRotation, {x: 0, y: 0, z: 1}); + var distance = Vec3.dot(Vec3.subtract(touchTip.position, overlayPosition), normal); + var position = Vec3.subtract(touchTip.position, Vec3.multiply(normal, distance)); + + // calclulate normalized position + var invRot = Quat.inverse(overlayRotation); + var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, overlayPosition)); + var dpi = Overlays.getProperty(overlayID, "dpi"); + + var dimensions; + if (dpi) { + // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property + // is used as a scale. + var resolution = Overlays.getProperty(overlayID, "resolution"); + if (resolution === undefined) { + return; + } + resolution.z = 1; // Circumvent divide-by-zero. + var scale = Overlays.getProperty(overlayID, "dimensions"); + if (scale === undefined) { + return; + } + scale.z = 0.01; // overlay dimensions are 2D, not 3D. + dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); + } else { + dimensions = Overlays.getProperty(overlayID, "dimensions"); + if (dimensions === undefined) { + return; + } + if (!dimensions.z) { + dimensions.z = 0.01; // sometimes overlay dimensions are 2D, not 3D. + } + } + var invDimensions = { x: 1 / dimensions.x, y: 1 / dimensions.y, z: 1 / dimensions.z }; + var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), DEFAULT_REGISTRATION_POINT); + + // 2D position on overlay plane in meters, relative to the bounding box upper-left hand corner. + var position2D = { + x: normalizedPosition.x * dimensions.x, + y: (1 - normalizedPosition.y) * dimensions.y // flip y-axis + }; + + return { + entityID: null, + overlayID: overlayID, + distance: distance, + position: position, + position2D: position2D, + normal: normal, + normalizedPosition: normalizedPosition, + dimensions: dimensions, + valid: true + }; +} + +// will return undefined if entity does not exist. +function calculateTouchTargetFromEntity(touchTip, props) { + if (props.rotation === undefined) { + // if rotation is missing from props object, then this entity has probably been deleted. + return; + } + + // project touch tip onto entity plane. + var normal = Vec3.multiplyQbyV(props.rotation, {x: 0, y: 0, z: 1}); + Vec3.multiplyQbyV(props.rotation, {x: 0, y: 1, z: 0}); + var distance = Vec3.dot(Vec3.subtract(touchTip.position, props.position), normal); + var position = Vec3.subtract(touchTip.position, Vec3.multiply(normal, distance)); + + // generate normalized coordinates + var invRot = Quat.inverse(props.rotation); + var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, props.position)); + var invDimensions = { x: 1 / props.dimensions.x, y: 1 / props.dimensions.y, z: 1 / props.dimensions.z }; + var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), props.registrationPoint); + + // 2D position on entity plane in meters, relative to the bounding box upper-left hand corner. + var position2D = { + x: normalizedPosition.x * props.dimensions.x, + y: (1 - normalizedPosition.y) * props.dimensions.y // flip y-axis + }; + + return { + entityID: props.id, + entityProps: props, + overlayID: null, + distance: distance, + position: position, + position2D: position2D, + normal: normal, + normalizedPosition: normalizedPosition, + dimensions: props.dimensions, + valid: true + }; +} + +module.exports = { + calculateTouchTargetFromEntity: calculateTouchTargetFromEntity, + calculateTouchTargetFromOverlay: calculateTouchTargetFromOverlay, + touchTargetHasKeyboardFocus: touchTargetHasKeyboardFocus, + setKeyboardFocusOnTouchTarget: setKeyboardFocusOnTouchTarget, + sendHoverEnterEventToTouchTarget: sendHoverEnterEventToTouchTarget, + sendHoverOverEventToTouchTarget: sendHoverOverEventToTouchTarget, + sendTouchStartEventToTouchTarget: sendTouchStartEventToTouchTarget, + sendTouchEndEventToTouchTarget: sendTouchEndEventToTouchTarget, + sendTouchMoveEventToTouchTarget: sendTouchMoveEventToTouchTarget, + composeTouchTargetFromIntersection: composeTouchTargetFromIntersection +}; diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index a5e97d8949..162edcaea0 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -351,3 +351,66 @@ clamp = function(val, min, max){ flatten = function(array) { return [].concat.apply([], array); } + +getTabletWidthFromSettings = function () { + var DEFAULT_TABLET_WIDTH = 0.4375; + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var toolbarMode = tablet.toolbarMode; + var DEFAULT_TABLET_SCALE = 100; + var tabletScalePercentage = DEFAULT_TABLET_SCALE; + if (!toolbarMode) { + if (HMD.active) { + tabletScalePercentage = Settings.getValue("hmdTabletScale") || DEFAULT_TABLET_SCALE; + } else { + tabletScalePercentage = Settings.getValue("desktopTabletScale") || DEFAULT_TABLET_SCALE; + } + } + return DEFAULT_TABLET_WIDTH * (tabletScalePercentage / 100); +}; + +resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) { + + if (!HMD.tabletID || !HMD.tabletScreenID || !HMD.homeButtonID) { + return; + } + + var sensorScaleFactor = sensorToWorldScaleOverride || MyAvatar.sensorToWorldScale; + var sensorScaleOffsetOverride = 1; + var SENSOR_TO_ROOM_MATRIX = 65534; + var parentJointIndex = newParentJointIndex || Overlays.getProperty(HMD.tabletID, "parentJointIndex"); + if (parentJointIndex === SENSOR_TO_ROOM_MATRIX) { + sensorScaleOffsetOverride = 1 / sensorScaleFactor; + } + + // will need to be recaclulated if dimensions of fbx model change. + var TABLET_NATURAL_DIMENSIONS = {x: 33.797, y: 50.129, z: 2.269}; + var DEFAULT_DPI = 34; + var DEFAULT_WIDTH = 0.4375; + + // scale factor of natural tablet dimensions. + var tabletWidth = (width || DEFAULT_WIDTH) * sensorScaleFactor; + var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x; + var tabletHeight = TABLET_NATURAL_DIMENSIONS.y * tabletScaleFactor; + var tabletDepth = TABLET_NATURAL_DIMENSIONS.z * tabletScaleFactor; + var tabletDpi = DEFAULT_DPI * (DEFAULT_WIDTH / tabletWidth); + + // update tablet model dimensions + Overlays.editOverlay(HMD.tabletID, { + dimensions: { x: tabletWidth, y: tabletHeight, z: tabletDepth } + }); + + // update webOverlay + var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2) * sensorScaleOffsetOverride; + var WEB_ENTITY_Y_OFFSET = 0.004 * sensorScaleOffsetOverride; + Overlays.editOverlay(HMD.tabletScreenID, { + localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, + dpi: tabletDpi + }); + + // update homeButton + var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20)) * sensorScaleOffsetOverride; + Overlays.editOverlay(HMD.homeButtonID, { + localPosition: {x: -0.001, y: -HOME_BUTTON_Y_OFFSET, z: 0.0}, + dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor} + }); +}; diff --git a/scripts/system/makeUserConnection.js b/scripts/system/makeUserConnection.js index d95ad919b6..bfad959ffc 100644 --- a/scripts/system/makeUserConnection.js +++ b/scripts/system/makeUserConnection.js @@ -16,6 +16,7 @@ var request = Script.require('request').request; + var WANT_DEBUG = Settings.getValue('MAKE_USER_CONNECTION_DEBUG', false); var LABEL = "makeUserConnection"; var MAX_AVATAR_DISTANCE = 0.2; // m var GRIP_MIN = 0.75; // goes from 0-1, so 75% pressed is pressed @@ -120,6 +121,9 @@ var successfulHandshakeSound; function debug() { + if (!WANT_DEBUG) { + return; + } var stateString = "<" + STATE_STRINGS[state] + ">"; var connecting = "[" + connectingId + "/" + connectingHandJointIndex + "]"; var current = "[" + currentHand + "/" + currentHandJointIndex + "]" @@ -372,7 +376,7 @@ var myHeadIndex = MyAvatar.getJointIndex("Head"); var otherHeadIndex = avatar.getJointIndex("Head"); var diff = (avatar.getJointPosition(otherHeadIndex).y - MyAvatar.getJointPosition(myHeadIndex).y) / 2; - print("head height difference: " + diff); + debug("head height difference: " + diff); updateAnimationData(diff); } } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 7b25589e92..e94b227a4a 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -19,6 +19,11 @@ var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); + var MARKETPLACE_CHECKOUT_QML_PATH_BASE = "qml/hifi/commerce/checkout/Checkout.qml"; + var MARKETPLACE_CHECKOUT_QML_PATH = Script.resourcesPath() + MARKETPLACE_CHECKOUT_QML_PATH_BASE; + var MARKETPLACE_PURCHASES_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/purchases/Purchases.qml"; + var MARKETPLACE_WALLET_QML_PATH = Script.resourcesPath() + "qml/hifi/commerce/wallet/Wallet.qml"; + var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml"; var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; @@ -53,50 +58,26 @@ Window.messageBoxClosed.connect(onMessageBoxClosed); var onMarketplaceScreen = false; + var onCommerceScreen = false; + var debugCheckout = false; + var debugError = false; function showMarketplace() { - UserActivityLogger.openedMarketplace(); - tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); - tablet.webEventReceived.connect(function (message) { - - if (message === GOTO_DIRECTORY) { - tablet.gotoWebScreen(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL); - } - - if (message === QUERY_CAN_WRITE_ASSETS) { - tablet.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets()); - } - - if (message === WARN_USER_NO_PERMISSIONS) { - Window.alert(NO_PERMISSIONS_ERROR_MESSAGE); - } - - if (message.slice(0, CLARA_IO_STATUS.length) === CLARA_IO_STATUS) { - if (isDownloadBeingCancelled) { - return; - } - - var text = message.slice(CLARA_IO_STATUS.length); - if (messageBox === null) { - messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); - } else { - Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); - } - return; - } - - if (message.slice(0, CLARA_IO_DOWNLOAD.length) === CLARA_IO_DOWNLOAD) { - if (messageBox !== null) { - Window.closeMessageBox(messageBox); - messageBox = null; - } - return; - } - - if (message === CLARA_IO_CANCELLED_DOWNLOAD) { - isDownloadBeingCancelled = false; - } - }); + if (!debugCheckout) { + UserActivityLogger.openedMarketplace(); + tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + } else { + tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH); + tablet.sendToQml({ + method: 'updateCheckoutQML', params: { + itemId: '0d90d21c-ce7a-4990-ad18-e9d2cf991027', + itemName: 'Test Flaregun', + itemPrice: (debugError ? 10 : 17), + itemHref: 'http://mpassets.highfidelity.com/0d90d21c-ce7a-4990-ad18-e9d2cf991027-v1/flaregun.json', + }, + canRezCertifiedItems: Entities.canRezCertified || Entities.canRezTmpCertified + }); + } } var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); @@ -113,7 +94,7 @@ } function onClick() { - if (onMarketplaceScreen) { + if (onMarketplaceScreen || onCommerceScreen) { // for toolbar-mode: go back to home screen, this will close the window. tablet.gotoHomeScreen(); } else { @@ -123,10 +104,24 @@ } } + var referrerURL; // Used for updating Purchases QML + var filterText; // Used for updating Purchases QML function onScreenChanged(type, url) { - onMarketplaceScreen = type === "Web" && url === MARKETPLACE_URL_INITIAL + onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1; + onCommerceScreen = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH_BASE) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH || url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1); + wireEventBridge(onCommerceScreen); + + if (url === MARKETPLACE_PURCHASES_QML_PATH) { + tablet.sendToQml({ + method: 'updatePurchases', + canRezCertifiedItems: Entities.canRezCertified || Entities.canRezTmpCertified, + referrerURL: referrerURL, + filterText: filterText + }); + } + // for toolbar mode: change button to active when window is first openend, false otherwise. - marketplaceButton.editProperties({ isActive: onMarketplaceScreen }); + marketplaceButton.editProperties({ isActive: onMarketplaceScreen || onCommerceScreen }); if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) { ContextOverlay.isInMarketplaceInspectionMode = true; } else { @@ -134,17 +129,237 @@ } } + function setCertificateInfo(currentEntityWithContextOverlay, itemMarketplaceId) { + wireEventBridge(true); + tablet.sendToQml({ + method: 'inspectionCertificate_setMarketplaceId', + marketplaceId: itemMarketplaceId || Entities.getEntityProperties(currentEntityWithContextOverlay, ['marketplaceID']).marketplaceID + }); + // ZRF FIXME! Make a call to the endpoint to get item info instead of this silliness + Script.setTimeout(function () { + var randomNumber = Math.floor((Math.random() * 150) + 1); + tablet.sendToQml({ + method: 'inspectionCertificate_setItemInfo', + itemName: "The Greatest Item", + itemOwner: "ABCDEFG1234567", + itemEdition: (Math.floor(Math.random() * randomNumber) + " / " + randomNumber) + }); + }, 500); + } + + function onUsernameChanged() { + if (onMarketplaceScreen) { + tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + } + } + marketplaceButton.clicked.connect(onClick); tablet.screenChanged.connect(onScreenChanged); Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged); + ContextOverlay.contextOverlayClicked.connect(setCertificateInfo); + GlobalServices.myUsernameChanged.connect(onUsernameChanged); + + function onMessage(message) { + + if (message === GOTO_DIRECTORY) { + tablet.gotoWebScreen(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL); + } else if (message === QUERY_CAN_WRITE_ASSETS) { + tablet.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets()); + } else if (message === WARN_USER_NO_PERMISSIONS) { + Window.alert(NO_PERMISSIONS_ERROR_MESSAGE); + } else if (message.slice(0, CLARA_IO_STATUS.length) === CLARA_IO_STATUS) { + if (isDownloadBeingCancelled) { + return; + } + + var text = message.slice(CLARA_IO_STATUS.length); + if (messageBox === null) { + messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); + } else { + Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON); + } + return; + } else if (message.slice(0, CLARA_IO_DOWNLOAD.length) === CLARA_IO_DOWNLOAD) { + if (messageBox !== null) { + Window.closeMessageBox(messageBox); + messageBox = null; + } + return; + } else if (message === CLARA_IO_CANCELLED_DOWNLOAD) { + isDownloadBeingCancelled = false; + } else { + var parsedJsonMessage = JSON.parse(message); + if (parsedJsonMessage.type === "CHECKOUT") { + wireEventBridge(true); + tablet.pushOntoStack(MARKETPLACE_CHECKOUT_QML_PATH); + tablet.sendToQml({ + method: 'updateCheckoutQML', + params: parsedJsonMessage, + canRezCertifiedItems: Entities.canRezCertified || Entities.canRezTmpCertified + }); + } else if (parsedJsonMessage.type === "REQUEST_SETTING") { + tablet.emitScriptEvent(JSON.stringify({ + type: "marketplaces", + action: "commerceSetting", + data: { + commerceMode: Settings.getValue("commerce", false), + userIsLoggedIn: Account.loggedIn, + walletNeedsSetup: Wallet.walletStatus === 1 + } + })); + } else if (parsedJsonMessage.type === "PURCHASES") { + referrerURL = parsedJsonMessage.referrerURL; + filterText = ""; + tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); + } else if (parsedJsonMessage.type === "LOGIN") { + openLoginWindow(); + } else if (parsedJsonMessage.type === "WALLET_SETUP") { + tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); + } else if (parsedJsonMessage.type === "MY_ITEMS") { + referrerURL = MARKETPLACE_URL_INITIAL; + filterText = ""; + tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); + wireEventBridge(true); + tablet.sendToQml({ + method: 'purchases_showMyItems' + }); + } + } + } + + tablet.webEventReceived.connect(onMessage); Script.scriptEnding.connect(function () { - if (onMarketplaceScreen) { + if (onMarketplaceScreen || onCommerceScreen) { tablet.gotoHomeScreen(); } tablet.removeButton(marketplaceButton); tablet.screenChanged.disconnect(onScreenChanged); + ContextOverlay.contextOverlayClicked.disconnect(setCertificateInfo); + tablet.webEventReceived.disconnect(onMessage); Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged); + GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); }); + + + // Function Name: wireEventBridge() + // + // Description: + // -Used to connect/disconnect the script's response to the tablet's "fromQml" signal. Set the "on" argument to enable or + // disable to event bridge. + // + // Relevant Variables: + // -hasEventBridge: true/false depending on whether we've already connected the event bridge. + var hasEventBridge = false; + function wireEventBridge(on) { + if (!tablet) { + print("Warning in wireEventBridge(): 'tablet' undefined!"); + return; + } + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } + } + + // Function Name: fromQml() + // + // Description: + // -Called when a message is received from Checkout.qml. The "message" argument is what is sent from the Checkout QML + // in the format "{method, params}", like json-rpc. + var isHmdPreviewDisabled = true; + function fromQml(message) { + switch (message.method) { + case 'purchases_openWallet': + case 'checkout_openWallet': + case 'checkout_setUpClicked': + tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); + break; + case 'purchases_walletNotSetUp': + case 'checkout_walletNotSetUp': + wireEventBridge(true); + tablet.sendToQml({ + method: 'updateWalletReferrer', + referrer: "purchases" + }); + tablet.pushOntoStack(MARKETPLACE_WALLET_QML_PATH); + break; + case 'checkout_cancelClicked': + tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.params, MARKETPLACES_INJECT_SCRIPT_URL); + // TODO: Make Marketplace a QML app that's a WebView wrapper so we can use the app stack. + // I don't think this is trivial to do since we also want to inject some JS into the DOM. + //tablet.popFromStack(); + break; + case 'header_goToPurchases': + case 'checkout_goToPurchases': + referrerURL = MARKETPLACE_URL_INITIAL; + filterText = message.filterText; + tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); + break; + case 'checkout_itemLinkClicked': + case 'checkout_continueShopping': + tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + //tablet.popFromStack(); + break; + case 'purchases_itemInfoClicked': + var itemId = message.itemId; + if (itemId && itemId !== "") { + tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + itemId, MARKETPLACES_INJECT_SCRIPT_URL); + } + break; + case 'header_marketplaceImageClicked': + case 'purchases_backClicked': + tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'purchases_goToMarketplaceClicked': + tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'passphrasePopup_cancelClicked': + case 'needsLogIn_cancelClicked': + tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'needsLogIn_loginClicked': + openLoginWindow(); + break; + case 'disableHmdPreview': + isHmdPreviewDisabled = Menu.isOptionChecked("Disable Preview"); + Menu.setIsOptionChecked("Disable Preview", true); + break; + case 'maybeEnableHmdPreview': + Menu.setIsOptionChecked("Disable Preview", isHmdPreviewDisabled); + break; + case 'purchases_openGoTo': + tablet.loadQMLSource("TabletAddressDialog.qml"); + break; + case 'purchases_itemCertificateClicked': + setCertificateInfo("", message.itemMarketplaceId); + break; + case 'inspectionCertificate_closeClicked': + tablet.gotoHomeScreen(); + break; + case 'inspectionCertificate_showInMarketplaceClicked': + tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + break; + case 'header_myItemsClicked': + referrerURL = MARKETPLACE_URL_INITIAL; + filterText = ""; + tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); + wireEventBridge(true); + tablet.sendToQml({ + method: 'purchases_showMyItems' + }); + break; + default: + print('Unrecognized message from Checkout.qml or Purchases.qml: ' + JSON.stringify(message)); + } + } + }()); // END LOCAL_SCOPE diff --git a/scripts/system/notifications.js b/scripts/system/notifications.js index b34630f3f8..ffe93d13e8 100644 --- a/scripts/system/notifications.js +++ b/scripts/system/notifications.js @@ -207,7 +207,7 @@ notificationOrientation, notificationPosition, buttonPosition; - + var sensorScaleFactor = MyAvatar.sensorToWorldScale; // Notification plane positions noticeY = -y * NOTIFICATION_3D_SCALE - noticeHeight / 2; notificationPosition = { x: 0, y: noticeY, z: 0 }; @@ -221,8 +221,8 @@ // Translate plane originOffset = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, NOTIFICATIONS_3D_DIRECTION, 0), - { x: 0, y: 0, z: -NOTIFICATIONS_3D_DISTANCE }); - originOffset.y += NOTIFICATIONS_3D_ELEVATION; + { x: 0, y: 0, z: -NOTIFICATIONS_3D_DISTANCE * sensorScaleFactor}); + originOffset.y += NOTIFICATIONS_3D_ELEVATION * sensorScaleFactor; notificationPosition = Vec3.sum(originOffset, notificationPosition); buttonPosition = Vec3.sum(originOffset, buttonPosition); @@ -242,7 +242,7 @@ noticeHeight, positions, last; - + var sensorScaleFactor = MyAvatar.sensorToWorldScale; if (isOnHMD) { // Calculate 3D values from 2D overlay properties. @@ -261,7 +261,7 @@ notice.leftMargin = 2 * notice.leftMargin * NOTIFICATION_3D_SCALE; notice.bottomMargin = 0; notice.rightMargin = 0; - notice.lineHeight = 10.0 * (fontSize / 12.0) * NOTIFICATION_3D_SCALE; + notice.lineHeight = 10.0 * (fontSize * sensorScaleFactor / 12.0) * NOTIFICATION_3D_SCALE; notice.isFacingAvatar = false; notificationText = Overlays.addOverlay("text3d", notice); @@ -366,6 +366,7 @@ buttonProperties, i; + var sensorScaleFactor = MyAvatar.sensorToWorldScale; if (text.length >= breakPoint) { breaks = count; } @@ -387,7 +388,7 @@ alpha: backgroundAlpha, topMargin: topMargin, leftMargin: leftMargin, - font: {size: fontSize}, + font: {size: fontSize * sensorScaleFactor}, text: text }; @@ -448,7 +449,23 @@ return finishedLines.join('\n'); } + function updateNotificationsTexts() { + var sensorScaleFactor = MyAvatar.sensorToWorldScale; + for (var i = 0; i < notifications.length; i++) { + var overlayType = Overlays.getOverlayType(notifications[i]); + + if (overlayType === "text3d") { + var props = { + font: {size: fontSize * sensorScaleFactor}, + lineHeight: 10.0 * (fontSize * sensorScaleFactor / 12.0) * NOTIFICATION_3D_SCALE + }; + Overlays.editOverlay(notifications[i], props); + } + } + } + function update() { + updateNotificationsTexts(); var noticeOut, buttonOut, arraysOut, diff --git a/scripts/system/particle_explorer/particleExplorer.js b/scripts/system/particle_explorer/particleExplorer.js index ca6a873b73..057bd1dd85 100644 --- a/scripts/system/particle_explorer/particleExplorer.js +++ b/scripts/system/particle_explorer/particleExplorer.js @@ -178,8 +178,8 @@ type: "Row" }, { - id: "emitShouldTrail", - name: "Emit Should Trail", + id: "emitterShouldTrail", + name: "Emitter Should Trail", type: "Boolean" }, { diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 37618253ee..8f08b29cff 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -10,7 +10,8 @@ /* globals Tablet, Script, HMD, Settings, DialogsManager, Menu, Reticle, OverlayWebWindow, Desktop, Account, MyAvatar, Snapshot */ /* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ -(function() { // BEGIN LOCAL_SCOPE +(function () { // BEGIN LOCAL_SCOPE +Script.include("/~/system/libraries/accountUtils.js"); var SNAPSHOT_DELAY = 500; // 500ms var FINISH_SOUND_DELAY = 350; @@ -52,15 +53,7 @@ try { print('Failed to resolve request api, error: ' + err); } -function openLoginWindow() { - if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false)) - || (!HMD.active && Settings.getValue("desktopTabletBecomesToolbar", true))) { - Menu.triggerOption("Login / Sign Up"); - } else { - tablet.loadQMLOnTop("../../dialogs/TabletLoginDialog.qml"); - HMD.openTablet(); - } -} + function removeFromStoryIDsToMaybeDelete(story_id) { storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(story_id), 1); @@ -120,15 +113,8 @@ function onMessage(message) { openLoginWindow(); break; case 'chooseSnapshotLocation': - var snapshotPath = Window.browseDir("Choose Snapshots Directory", "", ""); - - if (snapshotPath) { // not cancelled - Snapshot.setSnapshotsLocation(snapshotPath); - tablet.emitScriptEvent(JSON.stringify({ - type: "snapshot", - action: "snapshotLocationChosen" - })); - } + Window.browseDirChanged.connect(snapshotDirChanged); + Window.browseDirAsync("Choose Snapshots Directory", "", ""); break; case 'openSettings': if ((HMD.active && Settings.getValue("hmdTabletBecomesToolbar", false)) @@ -579,6 +565,17 @@ function stillSnapshotTaken(pathStillSnapshot, notify) { }); } +function snapshotDirChanged(snapshotPath) { + Window.browseDirChanged.disconnect(snapshotDirChanged); + if (snapshotPath !== "") { // not cancelled + Snapshot.setSnapshotsLocation(snapshotPath); + tablet.emitScriptEvent(JSON.stringify({ + type: "snapshot", + action: "snapshotLocationChosen" + })); + } +} + function processingGifStarted(pathStillSnapshot) { Window.processingGifStarted.disconnect(processingGifStarted); Window.processingGifCompleted.connect(processingGifCompleted); @@ -764,8 +761,8 @@ Script.scriptEnding.connect(function () { } Window.snapshotShared.disconnect(snapshotUploaded); Snapshot.snapshotLocationSet.disconnect(snapshotLocationSet); - Entities.canRezChanged.disconnect(processRezPermissionChange); - Entities.canRezTmpChanged.disconnect(processRezPermissionChange); + Entities.canRezChanged.disconnect(updatePrintPermissions); + Entities.canRezTmpChanged.disconnect(updatePrintPermissions); }); }()); // END LOCAL_SCOPE diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index fb842d1314..96ed3e3f59 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -42,6 +42,8 @@ }); function fromQml(message) { + console.debug('tablet-goto::fromQml: message = ', JSON.stringify(message)); + var response = {id: message.id, jsonrpc: "2.0"}; switch (message.method) { case 'request': @@ -98,6 +100,8 @@ button.editProperties({isActive: shouldActivateButton}); wireEventBridge(true); messagesWaiting(false); + tablet.sendToQml({ method: 'refreshFeeds' }) + } else { shouldActivateButton = false; onGotoScreen = false; diff --git a/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index 257a56bf09..78b14eb7c6 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -71,9 +71,9 @@ return tabletScalePercentage; } - function updateTabletWidthFromSettings() { + function updateTabletWidthFromSettings(force) { var newTabletScalePercentage = getTabletScalePercentageFromSettings(); - if (newTabletScalePercentage !== tabletScalePercentage && UIWebTablet) { + if ((force || (newTabletScalePercentage !== tabletScalePercentage)) && UIWebTablet) { tabletScalePercentage = newTabletScalePercentage; UIWebTablet.setWidth(DEFAULT_WIDTH * (tabletScalePercentage / 100)); } @@ -83,6 +83,13 @@ updateTabletWidthFromSettings(); } + function onSensorToWorldScaleChanged(sensorScaleFactor) { + if (HMD.active) { + var newTabletScalePercentage = getTabletScalePercentageFromSettings(); + resizeTablet(DEFAULT_WIDTH * (newTabletScalePercentage / 100), undefined, sensorScaleFactor); + } + } + function rezTablet() { if (debugTablet) { print("TABLET rezzing"); @@ -90,7 +97,7 @@ checkTablet() tabletScalePercentage = getTabletScalePercentageFromSettings(); - UIWebTablet = new WebTablet("qml/hifi/tablet/TabletRoot.qml", + UIWebTablet = new WebTablet("hifi/tablet/TabletRoot.qml", DEFAULT_WIDTH * (tabletScalePercentage / 100), null, activeHand, true, null, false); UIWebTablet.register(); @@ -98,6 +105,7 @@ HMD.homeButtonID = UIWebTablet.homeButtonID; HMD.tabletScreenID = UIWebTablet.webOverlayID; HMD.displayModeChanged.connect(onHmdChanged); + MyAvatar.sensorToWorldScaleChanged.connect(onSensorToWorldScaleChanged); tabletRezzed = true; } @@ -121,6 +129,7 @@ Overlays.editOverlay(HMD.homeButtonID, { visible: true }); Overlays.editOverlay(HMD.tabletScreenID, { visible: true }); Overlays.editOverlay(HMD.tabletScreenID, { maxFPS: 90 }); + updateTabletWidthFromSettings(true); } gTablet.tabletShown = true; } @@ -183,9 +192,13 @@ return; } - if (now - validCheckTime > MSECS_PER_SEC) { + var needInstantUpdate = UIWebTablet && UIWebTablet.getLandscape() !== landscape; + + if ((now - validCheckTime > MSECS_PER_SEC) || needInstantUpdate) { validCheckTime = now; + updateTabletWidthFromSettings(); + if (UIWebTablet) { UIWebTablet.setLandscape(landscape); } @@ -261,6 +274,36 @@ Messages.subscribe("home"); Messages.messageReceived.connect(handleMessage); + var clickMapping = Controller.newMapping('tabletToggle-click'); + var wantsMenu = 0; + clickMapping.from(function () { return wantsMenu; }).to(Controller.Actions.ContextMenu); + clickMapping.from(Controller.Standard.RightSecondaryThumb).peek().to(function (clicked) { + if (clicked) { + //activeHudPoint2d(Controller.Standard.RightHand); + Messages.sendLocalMessage("toggleHand", Controller.Standard.RightHand); + } + wantsMenu = clicked; + }); + + clickMapping.from(Controller.Standard.LeftSecondaryThumb).peek().to(function (clicked) { + if (clicked) { + //activeHudPoint2d(Controller.Standard.LeftHand); + Messages.sendLocalMessage("toggleHand", Controller.Standard.LeftHand); + } + wantsMenu = clicked; + }); + + clickMapping.from(Controller.Standard.Start).peek().to(function (clicked) { + if (clicked) { + //activeHudPoint2dGamePad(); + var noHands = -1; + Messages.sendLocalMessage("toggleHand", Controller.Standard.LeftHand); + } + + wantsMenu = clicked; + }); + clickMapping.enable(); + Script.setInterval(updateShowTablet, 100); Script.scriptEnding.connect(function () { diff --git a/tests/gpu-test/src/TestWindow.cpp b/tests/gpu-test/src/TestWindow.cpp index 39043805b8..9ae78527cc 100644 --- a/tests/gpu-test/src/TestWindow.cpp +++ b/tests/gpu-test/src/TestWindow.cpp @@ -24,7 +24,7 @@ #include #ifdef DEFERRED_LIGHTING -extern void initDeferredPipelines(render::ShapePlumber& plumber); +extern void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); extern void initStencilPipeline(gpu::PipelinePointer& pipeline); #endif @@ -77,7 +77,7 @@ void TestWindow::initGl() { #ifdef DEFERRED_LIGHTING auto deferredLightingEffect = DependencyManager::get(); deferredLightingEffect->init(); - initDeferredPipelines(*_shapePlumber); + initDeferredPipelines(*_shapePlumber, nullptr, nullptr); #endif } diff --git a/tests/render-perf/CMakeLists.txt b/tests/render-perf/CMakeLists.txt index 77c8ab2177..5b83ff313b 100644 --- a/tests/render-perf/CMakeLists.txt +++ b/tests/render-perf/CMakeLists.txt @@ -12,7 +12,7 @@ setup_hifi_project(Quick Gui OpenGL) set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") # link in the shared libraries -link_hifi_libraries(shared networking model fbx ktx image octree gl gpu gpu-gl render model-networking networking render-utils entities entities-renderer animation audio avatars script-engine physics procedural midi) +link_hifi_libraries(shared networking model fbx ktx image octree gl gpu gpu-gl render model-networking networking render-utils entities entities-renderer animation audio avatars script-engine physics procedural midi ui) package_libraries_for_deployment() diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index dbb315a9ae..c70a74cd7f 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -42,11 +42,15 @@ #include #include +#include + #include #include #include #include +#include + #include #include #include @@ -62,6 +66,8 @@ #include #include #include +#include +#include #include #include #include @@ -425,6 +431,10 @@ namespace render { } } +OffscreenGLCanvas* _chromiumShareContext{ nullptr }; +Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context); + + // Create a simple OpenGL window that renders text in various ways class QTestWindow : public QWindow, public AbstractViewStateInterface { @@ -433,10 +443,6 @@ protected: viewOut = _viewFrustum; } - void copyShadowViewFrustum(ViewFrustum& viewOut) const override { - viewOut = _shadowViewFrustum; - } - QThread* getMainThread() override { return QThread::currentThread(); } @@ -504,8 +510,6 @@ public: AbstractViewStateInterface::setInstance(this); _octree = DependencyManager::set(false, this, nullptr); _octree->init(); - // Prevent web entities from rendering - REGISTER_ENTITY_TYPE_WITH_FACTORY(Web, WebEntityItem::factory); DependencyManager::set(_octree->getTree()); auto nodeList = DependencyManager::get(); @@ -533,20 +537,33 @@ public: _renderThread.initialize(this, _initContext); _initContext.makeCurrent(); + if (nsightActive()) { + // Prevent web entities from rendering + REGISTER_ENTITY_TYPE_WITH_FACTORY(Web, WebEntityItem::factory); + } else { + _chromiumShareContext = new OffscreenGLCanvas(); + _chromiumShareContext->setObjectName("ChromiumShareContext"); + _chromiumShareContext->create(_initContext.qglContext()); + _chromiumShareContext->makeCurrent(); + qt_gl_set_global_share_context(_chromiumShareContext->getContext()); + + // Make sure all QML surfaces share the main thread GL context + OffscreenQmlSurface::setSharedContext(_initContext.qglContext()); + + _initContext.makeCurrent(); + } + + // FIXME use a wait condition QThread::msleep(1000); _renderThread.submitFrame(gpu::FramePointer()); _initContext.makeCurrent(); + DependencyManager::get()->initializeShapePipelines(); // Render engine init - _renderEngine->addJob("RenderShadowTask", _cullFunctor); - const auto items = _renderEngine->addJob("FetchCullSort", _cullFunctor); - assert(items.canCast()); static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD"; - if (QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD)) { - _renderEngine->addJob("RenderForwardTask", items); - } else { - _renderEngine->addJob("RenderDeferredTask", items); - } + bool isDeferred = !QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); + _renderEngine->addJob("UpdateScene"); + _renderEngine->addJob("RenderMainView", _cullFunctor, isDeferred); _renderEngine->load(); _renderEngine->registerScene(_main3DScene); @@ -681,6 +698,7 @@ private: _renderCount = _renderThread._presentCount.load(); update(); + _initContext.makeCurrent(); RenderArgs renderArgs(_renderThread._gpuContext, DEFAULT_OCTREE_SIZE_SCALE, 0, RenderArgs::DEFAULT_RENDER_MODE, RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE); @@ -721,6 +739,7 @@ private: // Viewport is assigned to the size of the framebuffer renderArgs._viewport = ivec4(0, 0, windowSize.width(), windowSize.height()); renderArgs.setViewFrustum(_viewFrustum); + renderArgs._scene = _main3DScene; // Final framebuffer that will be handled to the display-plugin render(&renderArgs); @@ -876,7 +895,7 @@ private: last = now; - getEntities()->update(); + getEntities()->update(false); // The pending changes collecting the changes here render::Transaction transaction; @@ -1095,7 +1114,6 @@ private: RenderThread _renderThread; QWindowCamera _camera; ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc. - ViewFrustum _shadowViewFrustum; // current state of view frustum, perspective, orientation, etc. model::SunSkyStage _sunSkyStage; model::LightPointer _globalLight { std::make_shared() }; bool _ready { false }; diff --git a/tests/shaders/src/main.cpp b/tests/shaders/src/main.cpp index 9847e9f7b9..7c6886ad93 100644 --- a/tests/shaders/src/main.cpp +++ b/tests/shaders/src/main.cpp @@ -19,6 +19,7 @@ #include #include +#include #include @@ -114,13 +115,37 @@ public: } }; + + +const std::string VERTEX_SHADER_DEFINES{ R"GLSL( +#version 410 core +#define GPU_VERTEX_SHADER +#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 410 core +#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" }; + void testShaderBuild(const char* vs_src, const char * fs_src) { - auto vs = gpu::Shader::createVertex(std::string(vs_src)); - auto fs = gpu::Shader::createPixel(std::string(fs_src)); - auto pr = gpu::Shader::createProgram(vs, fs); - if (!gpu::Shader::makeProgram(*pr)) { + 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 QTestWindow::draw() { diff --git a/tools/atp-client/src/ATPClientApp.cpp b/tools/atp-client/src/ATPClientApp.cpp index 7d091aec74..c5edf27b67 100644 --- a/tools/atp-client/src/ATPClientApp.cpp +++ b/tools/atp-client/src/ATPClientApp.cpp @@ -332,7 +332,7 @@ void ATPClientApp::listAssets() { } else if (result == GetAllMappingsRequest::NoError) { auto mappings = request->getMappings(); for (auto& kv : mappings ) { - qDebug() << kv.first << kv.second; + qDebug() << kv.first << kv.second.hash; } } else { qDebug() << "error -- " << request->getError() << " -- " << request->getErrorString(); diff --git a/tools/dimensions.mel b/tools/dimensions.mel new file mode 100644 index 0000000000..c477fd04e8 --- /dev/null +++ b/tools/dimensions.mel @@ -0,0 +1,30 @@ +// +// dimensions.mel +// +// Created by Anthony J. Thibault on September 5th, 2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// Maya Mel script to determine the High Fidelity "naturalDimensions" of a model. + +// get a list of all mesh objects +string $meshes[] = `ls -type mesh`; + +// compute the bounding box +float $boundingBox[] = `polyEvaluate -boundingBox $meshes`; + +// copy values into variables for readability +float $xmin = $boundingBox[0]; +float $xmax = $boundingBox[1]; +float $ymin = $boundingBox[2]; +float $ymax = $boundingBox[3]; +float $zmin = $boundingBox[4]; +float $zmax = $boundingBox[5]; + +// compute dimensions, and convert from cm to meters +vector $dim = <<($xmax - $xmin) / 100.0, ($ymax - $ymin) / 100.0, ($zmax - $zmin) / 100.0>>; + +// print result +print $dim; diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index f4fca3304c..1022c204c5 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -2,7 +2,7 @@ set(TARGET_NAME oven) setup_hifi_project(Widgets Gui Concurrent) -link_hifi_libraries(networking shared image gpu ktx) +link_hifi_libraries(networking shared image gpu ktx fbx baking model) setup_memory_debugger() @@ -17,16 +17,4 @@ if (UNIX) endif() endif () -# try to find the FBX SDK but fail silently if we don't -# because this tool is not built by default -find_package(FBX) -if (FBX_FOUND) - if (CMAKE_THREAD_LIBS_INIT) - target_link_libraries(${TARGET_NAME} ${FBX_LIBRARIES} "${CMAKE_THREAD_LIBS_INIT}") - else () - target_link_libraries(${TARGET_NAME} ${FBX_LIBRARIES}) - endif () - target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${FBX_INCLUDE_DIR}) -endif () - set_target_properties(${TARGET_NAME} PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE) diff --git a/tools/oven/src/BakerCLI.cpp b/tools/oven/src/BakerCLI.cpp index 14eb9de150..5ab995be95 100644 --- a/tools/oven/src/BakerCLI.cpp +++ b/tools/oven/src/BakerCLI.cpp @@ -41,8 +41,8 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) { // create our appropiate baker if (isFBX) { - _baker = std::unique_ptr { new FBXBaker(inputUrl, outputPath, []() -> QThread* { return qApp->getNextWorkerThread(); }) }; - _baker->moveToThread(qApp->getFBXBakerThread()); + _baker = std::unique_ptr { new FBXBaker(inputUrl, []() -> QThread* { return qApp->getNextWorkerThread(); }, outputPath) }; + _baker->moveToThread(qApp->getNextWorkerThread()); } else if (isSupportedImage) { _baker = std::unique_ptr { new TextureBaker(inputUrl, image::TextureUsage::CUBE_TEXTURE, outputPath) }; _baker->moveToThread(qApp->getNextWorkerThread()); @@ -61,4 +61,4 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) { void BakerCLI::handleFinishedBaker() { qCDebug(model_baking) << "Finished baking file."; QApplication::exit(_baker.get()->hasErrors()); -} \ No newline at end of file +} diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index 03bc350f42..535d9a49a9 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -192,10 +192,18 @@ void DomainBaker::enumerateEntities() { // setup an FBXBaker for this URL, as long as we don't already have one if (!_modelBakers.contains(modelURL)) { + auto filename = modelURL.fileName(); + auto baseName = filename.left(filename.lastIndexOf('.')); + auto subDirName = "/" + baseName; + int i = 0; + while (QDir(_contentOutputPath + subDirName).exists()) { + subDirName = "/" + baseName + "-" + i++; + } QSharedPointer baker { - new FBXBaker(modelURL, _contentOutputPath, []() -> QThread* { + new FBXBaker(modelURL, []() -> QThread* { return qApp->getNextWorkerThread(); - }), &FBXBaker::deleteLater + }, _contentOutputPath + subDirName + "/baked", _contentOutputPath + subDirName + "/original"), + &FBXBaker::deleteLater }; // make sure our handler is called when the baker is done @@ -206,7 +214,7 @@ void DomainBaker::enumerateEntities() { // move the baker to the baker thread // and kickoff the bake - baker->moveToThread(qApp->getFBXBakerThread()); + baker->moveToThread(qApp->getNextWorkerThread()); QMetaObject::invokeMethod(baker.data(), "bake"); // keep track of the total number of baking entities @@ -309,7 +317,11 @@ void DomainBaker::handleFinishedModelBaker() { QUrl oldModelURL { entity[ENTITY_MODEL_URL_KEY].toString() }; // setup a new URL using the prefix we were passed - QUrl newModelURL = _destinationPath.resolved(baker->getBakedFBXRelativePath()); + auto relativeFBXFilePath = baker->getBakedFBXFilePath().remove(_contentOutputPath); + if (relativeFBXFilePath.startsWith("/")) { + relativeFBXFilePath = relativeFBXFilePath.right(relativeFBXFilePath.length() - 1); + } + QUrl newModelURL = _destinationPath.resolved(relativeFBXFilePath); // copy the fragment and query, and user info from the old model URL newModelURL.setQuery(oldModelURL.query()); @@ -335,7 +347,7 @@ void DomainBaker::handleFinishedModelBaker() { if (oldAnimationURL.matches(oldModelURL, QUrl::RemoveQuery | QUrl::RemoveFragment)) { // the animation URL matched the old model URL, so make the animation URL point to the baked FBX // with its original query and fragment - auto newAnimationURL = _destinationPath.resolved(baker->getBakedFBXRelativePath()); + auto newAnimationURL = _destinationPath.resolved(relativeFBXFilePath); newAnimationURL.setQuery(oldAnimationURL.query()); newAnimationURL.setFragment(oldAnimationURL.fragment()); newAnimationURL.setUserInfo(oldAnimationURL.userInfo()); diff --git a/tools/oven/src/DomainBaker.h b/tools/oven/src/DomainBaker.h index 34c5e11e63..6426af0710 100644 --- a/tools/oven/src/DomainBaker.h +++ b/tools/oven/src/DomainBaker.h @@ -55,6 +55,8 @@ private: QString _baseOutputPath; QString _uniqueOutputPath; QString _contentOutputPath; + QString _bakedOutputPath; + QString _originalOutputPath; QUrl _destinationPath; QJsonArray _entities; diff --git a/tools/oven/src/FBXBaker.cpp b/tools/oven/src/FBXBaker.cpp deleted file mode 100644 index 0259a6baf8..0000000000 --- a/tools/oven/src/FBXBaker.cpp +++ /dev/null @@ -1,554 +0,0 @@ -// -// FBXBaker.cpp -// tools/oven/src -// -// Created by Stephen Birarda on 3/30/17. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include // need this include so we don't get an error looking for std::isnan - -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -#include "ModelBakingLoggingCategory.h" -#include "TextureBaker.h" - -#include "FBXBaker.h" - -std::once_flag onceFlag; -FBXSDKManagerUniquePointer FBXBaker::_sdkManager { nullptr }; - -FBXBaker::FBXBaker(const QUrl& fbxURL, const QString& baseOutputPath, - TextureBakerThreadGetter textureThreadGetter, bool copyOriginals) : - _fbxURL(fbxURL), - _baseOutputPath(baseOutputPath), - _textureThreadGetter(textureThreadGetter), - _copyOriginals(copyOriginals) -{ - std::call_once(onceFlag, [](){ - // create the static FBX SDK manager - _sdkManager = FBXSDKManagerUniquePointer(FbxManager::Create(), [](FbxManager* manager){ - manager->Destroy(); - }); - }); - - // grab the name of the FBX from the URL, this is used for folder output names - auto fileName = fbxURL.fileName(); - _fbxName = fileName.left(fileName.lastIndexOf('.')); -} - -static const QString BAKED_OUTPUT_SUBFOLDER = "baked/"; -static const QString ORIGINAL_OUTPUT_SUBFOLDER = "original/"; - -QString FBXBaker::pathToCopyOfOriginal() const { - return _uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + _fbxURL.fileName(); -} - -void FBXBaker::bake() { - qCDebug(model_baking) << "Baking" << _fbxURL; - - // setup the output folder for the results of this bake - setupOutputFolder(); - - if (hasErrors()) { - return; - } - - connect(this, &FBXBaker::sourceCopyReadyToLoad, this, &FBXBaker::bakeSourceCopy); - - // make a local copy of the FBX file - loadSourceFBX(); -} - -void FBXBaker::bakeSourceCopy() { - // load the scene from the FBX file - importScene(); - - if (hasErrors()) { - return; - } - - // enumerate the textures found in the scene and start a bake for them - rewriteAndBakeSceneTextures(); - - if (hasErrors()) { - return; - } - - // export the FBX with re-written texture references - exportScene(); - - if (hasErrors()) { - return; - } - - // check if we're already done with textures (in case we had none to re-write) - checkIfTexturesFinished(); -} - -void FBXBaker::setupOutputFolder() { - // construct the output path using the name of the fbx and the base output path - _uniqueOutputPath = _baseOutputPath + "/" + _fbxName + "/"; - - // make sure there isn't already an output directory using the same name - int iteration = 0; - - while (QDir(_uniqueOutputPath).exists()) { - _uniqueOutputPath = _baseOutputPath + "/" + _fbxName + "-" + QString::number(++iteration) + "/"; - } - - qCDebug(model_baking) << "Creating FBX output folder" << _uniqueOutputPath; - - // attempt to make the output folder - if (!QDir().mkdir(_uniqueOutputPath)) { - handleError("Failed to create FBX output folder " + _uniqueOutputPath); - return; - } - - // make the baked and original sub-folders used during export - QDir uniqueOutputDir = _uniqueOutputPath; - if (!uniqueOutputDir.mkdir(BAKED_OUTPUT_SUBFOLDER) || !uniqueOutputDir.mkdir(ORIGINAL_OUTPUT_SUBFOLDER)) { - handleError("Failed to create baked/original subfolders in " + _uniqueOutputPath); - return; - } -} - -void FBXBaker::loadSourceFBX() { - // check if the FBX is local or first needs to be downloaded - if (_fbxURL.isLocalFile()) { - // load up the local file - QFile localFBX { _fbxURL.toLocalFile() }; - - // make a copy in the output folder - localFBX.copy(pathToCopyOfOriginal()); - - // emit our signal to start the import of the FBX source copy - emit sourceCopyReadyToLoad(); - } else { - // remote file, kick off a download - auto& networkAccessManager = NetworkAccessManager::getInstance(); - - QNetworkRequest networkRequest; - - // setup the request to follow re-directs and always hit the network - networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); - networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - - - networkRequest.setUrl(_fbxURL); - - qCDebug(model_baking) << "Downloading" << _fbxURL; - auto networkReply = networkAccessManager.get(networkRequest); - - connect(networkReply, &QNetworkReply::finished, this, &FBXBaker::handleFBXNetworkReply); - } -} - -void FBXBaker::handleFBXNetworkReply() { - auto requestReply = qobject_cast(sender()); - - if (requestReply->error() == QNetworkReply::NoError) { - qCDebug(model_baking) << "Downloaded" << _fbxURL; - - // grab the contents of the reply and make a copy in the output folder - QFile copyOfOriginal(pathToCopyOfOriginal()); - - qDebug(model_baking) << "Writing copy of original FBX to" << copyOfOriginal.fileName(); - - if (!copyOfOriginal.open(QIODevice::WriteOnly) || (copyOfOriginal.write(requestReply->readAll()) == -1)) { - // add an error to the error list for this FBX stating that a duplicate of the original FBX could not be made - handleError("Could not create copy of " + _fbxURL.toString()); - return; - } - - // close that file now that we are done writing to it - copyOfOriginal.close(); - - // emit our signal to start the import of the FBX source copy - emit sourceCopyReadyToLoad(); - } else { - // add an error to our list stating that the FBX could not be downloaded - handleError("Failed to download " + _fbxURL.toString()); - } -} - -void FBXBaker::importScene() { - // create an FBX SDK importer - FbxImporter* importer = FbxImporter::Create(_sdkManager.get(), ""); - - // import the copy of the original FBX file - QString originalCopyPath = pathToCopyOfOriginal(); - bool importStatus = importer->Initialize(originalCopyPath.toLocal8Bit().data()); - - if (!importStatus) { - // failed to initialize importer, print an error and return - handleError("Failed to import " + _fbxURL.toString() + " - " + importer->GetStatus().GetErrorString()); - return; - } else { - qCDebug(model_baking) << "Imported" << _fbxURL << "to FbxScene"; - } - - // setup a new scene to hold the imported file - _scene = FbxScene::Create(_sdkManager.get(), "bakeScene"); - - // import the file to the created scene - importer->Import(_scene); - - // destroy the importer that is no longer needed - importer->Destroy(); -} - -QString texturePathRelativeToFBX(QUrl fbxURL, QUrl textureURL) { - auto fbxPath = fbxURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); - auto texturePath = textureURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); - - if (texturePath.startsWith(fbxPath)) { - // texture path is a child of the FBX path, return the texture path without the fbx path - return texturePath.mid(fbxPath.length()); - } else { - // the texture path was not a child of the FBX path, return the empty string - return ""; - } -} - -QString FBXBaker::createBakedTextureFileName(const QFileInfo& textureFileInfo) { - // first make sure we have a unique base name for this texture - // in case another texture referenced by this model has the same base name - auto nameMatches = _textureNameMatchCount[textureFileInfo.baseName()]; - - QString bakedTextureFileName { textureFileInfo.completeBaseName() }; - - if (nameMatches > 0) { - // there are already nameMatches texture with this name - // append - and that number to our baked texture file name so that it is unique - bakedTextureFileName += "-" + QString::number(nameMatches); - } - - bakedTextureFileName += BAKED_TEXTURE_EXT; - - // increment the number of name matches - ++nameMatches; - - return bakedTextureFileName; -} - -QUrl FBXBaker::getTextureURL(const QFileInfo& textureFileInfo, FbxFileTexture* fileTexture) { - QUrl urlToTexture; - - if (textureFileInfo.exists() && textureFileInfo.isFile()) { - // set the texture URL to the local texture that we have confirmed exists - urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath()); - } else { - // external texture that we'll need to download or find - - // first check if it the RelativePath to the texture in the FBX was relative - QString relativeFileName = fileTexture->GetRelativeFileName(); - auto apparentRelativePath = QFileInfo(relativeFileName.replace("\\", "/")); - - // this is a relative file path which will require different handling - // depending on the location of the original FBX - if (_fbxURL.isLocalFile() && apparentRelativePath.exists() && apparentRelativePath.isFile()) { - // the absolute path we ran into for the texture in the FBX exists on this machine - // so use that file - urlToTexture = QUrl::fromLocalFile(apparentRelativePath.absoluteFilePath()); - } else { - // we didn't find the texture on this machine at the absolute path - // so assume that it is right beside the FBX to match the behaviour of interface - urlToTexture = _fbxURL.resolved(apparentRelativePath.fileName()); - } - } - - return urlToTexture; -} - -image::TextureUsage::Type textureTypeForMaterialProperty(FbxProperty& property, FbxSurfaceMaterial* material) { - using namespace image::TextureUsage; - - // this is a property we know has a texture, we need to match it to a High Fidelity known texture type - // since that information is passed to the baking process - - // grab the hierarchical name for this property and lowercase it for case-insensitive compare - auto propertyName = QString(property.GetHierarchicalName()).toLower(); - - // figure out the type of the property based on what known value string it matches - if ((propertyName.contains("diffuse") && !propertyName.contains("tex_global_diffuse")) - || propertyName.contains("tex_color_map")) { - return ALBEDO_TEXTURE; - } else if (propertyName.contains("transparentcolor") || propertyName.contains("transparencyfactor")) { - return ALBEDO_TEXTURE; - } else if (propertyName.contains("bump")) { - return BUMP_TEXTURE; - } else if (propertyName.contains("normal")) { - return NORMAL_TEXTURE; - } else if ((propertyName.contains("specular") && !propertyName.contains("tex_global_specular")) - || propertyName.contains("reflection")) { - return SPECULAR_TEXTURE; - } else if (propertyName.contains("tex_metallic_map")) { - return METALLIC_TEXTURE; - } else if (propertyName.contains("shininess")) { - return GLOSS_TEXTURE; - } else if (propertyName.contains("tex_roughness_map")) { - return ROUGHNESS_TEXTURE; - } else if (propertyName.contains("emissive")) { - return EMISSIVE_TEXTURE; - } else if (propertyName.contains("ambientcolor")) { - return LIGHTMAP_TEXTURE; - } else if (propertyName.contains("ambientfactor")) { - // we need to check what the ambient factor is, since that tells Interface to process this texture - // either as an occlusion texture or a light map - auto lambertMaterial = FbxCast(material); - - if (lambertMaterial->AmbientFactor == 0) { - return LIGHTMAP_TEXTURE; - } else if (lambertMaterial->AmbientFactor > 0) { - return OCCLUSION_TEXTURE; - } else { - return UNUSED_TEXTURE; - } - - } else if (propertyName.contains("tex_ao_map")) { - return OCCLUSION_TEXTURE; - } - - return UNUSED_TEXTURE; -} - -void FBXBaker::rewriteAndBakeSceneTextures() { - - // enumerate the surface materials to find the textures used in the scene - int numMaterials = _scene->GetMaterialCount(); - for (int i = 0; i < numMaterials; i++) { - FbxSurfaceMaterial* material = _scene->GetMaterial(i); - - if (material) { - // enumerate the properties of this material to see what texture channels it might have - FbxProperty property = material->GetFirstProperty(); - - while (property.IsValid()) { - // first check if this property has connected textures, if not we don't need to bother with it here - if (property.GetSrcObjectCount() > 0) { - - // figure out the type of texture from the material property - auto textureType = textureTypeForMaterialProperty(property, material); - - if (textureType != image::TextureUsage::UNUSED_TEXTURE) { - int numTextures = property.GetSrcObjectCount(); - - for (int j = 0; j < numTextures; j++) { - FbxFileTexture* fileTexture = property.GetSrcObject(j); - - // use QFileInfo to easily split up the existing texture filename into its components - QString fbxTextureFileName { fileTexture->GetFileName() }; - QFileInfo textureFileInfo { fbxTextureFileName.replace("\\", "/") }; - - // make sure this texture points to something and isn't one we've already re-mapped - if (!textureFileInfo.filePath().isEmpty() - && textureFileInfo.suffix() != BAKED_TEXTURE_EXT.mid(1)) { - - // construct the new baked texture file name and file path - // ensuring that the baked texture will have a unique name - // even if there was another texture with the same name at a different path - auto bakedTextureFileName = createBakedTextureFileName(textureFileInfo); - QString bakedTextureFilePath { - _uniqueOutputPath + BAKED_OUTPUT_SUBFOLDER + bakedTextureFileName - }; - - qCDebug(model_baking).noquote() << "Re-mapping" << fileTexture->GetFileName() - << "to" << bakedTextureFilePath; - - // figure out the URL to this texture, embedded or external - auto urlToTexture = getTextureURL(textureFileInfo, fileTexture); - - // write the new filename into the FBX scene - fileTexture->SetFileName(bakedTextureFilePath.toLocal8Bit()); - - // write the relative filename to be the baked texture file name since it will - // be right beside the FBX - fileTexture->SetRelativeFileName(bakedTextureFileName.toLocal8Bit().constData()); - - if (!_bakingTextures.contains(urlToTexture)) { - // bake this texture asynchronously - bakeTexture(urlToTexture, textureType, _uniqueOutputPath + BAKED_OUTPUT_SUBFOLDER); - } - } - } - } - } - - property = material->GetNextProperty(property); - } - } - } -} - -void FBXBaker::bakeTexture(const QUrl& textureURL, image::TextureUsage::Type textureType, const QDir& outputDir) { - // start a bake for this texture and add it to our list to keep track of - QSharedPointer bakingTexture { - new TextureBaker(textureURL, textureType, outputDir), - &TextureBaker::deleteLater - }; - - // make sure we hear when the baking texture is done - connect(bakingTexture.data(), &Baker::finished, this, &FBXBaker::handleBakedTexture); - - // keep a shared pointer to the baking texture - _bakingTextures.insert(textureURL, bakingTexture); - - // start baking the texture on one of our available worker threads - bakingTexture->moveToThread(_textureThreadGetter()); - QMetaObject::invokeMethod(bakingTexture.data(), "bake"); -} - -void FBXBaker::handleBakedTexture() { - TextureBaker* bakedTexture = qobject_cast(sender()); - - // make sure we haven't already run into errors, and that this is a valid texture - if (bakedTexture) { - if (!hasErrors()) { - if (!bakedTexture->hasErrors()) { - if (_copyOriginals) { - // we've been asked to make copies of the originals, so we need to make copies of this if it is a linked texture - - // use the path to the texture being baked to determine if this was an embedded or a linked texture - - // it is embeddded if the texure being baked was inside the original output folder - // since that is where the FBX SDK places the .fbm folder it generates when importing the FBX - - auto originalOutputFolder = QUrl::fromLocalFile(_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER); - - if (!originalOutputFolder.isParentOf(bakedTexture->getTextureURL())) { - // for linked textures we want to save a copy of original texture beside the original FBX - - qCDebug(model_baking) << "Saving original texture for" << bakedTexture->getTextureURL(); - - // check if we have a relative path to use for the texture - auto relativeTexturePath = texturePathRelativeToFBX(_fbxURL, bakedTexture->getTextureURL()); - - QFile originalTextureFile { - _uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + relativeTexturePath + bakedTexture->getTextureURL().fileName() - }; - - if (relativeTexturePath.length() > 0) { - // make the folders needed by the relative path - } - - if (originalTextureFile.open(QIODevice::WriteOnly) && originalTextureFile.write(bakedTexture->getOriginalTexture()) != -1) { - qCDebug(model_baking) << "Saved original texture file" << originalTextureFile.fileName() - << "for" << _fbxURL; - } else { - handleError("Could not save original external texture " + originalTextureFile.fileName() - + " for " + _fbxURL.toString()); - return; - } - } - } - - - // now that this texture has been baked and handled, we can remove that TextureBaker from our hash - _bakingTextures.remove(bakedTexture->getTextureURL()); - - checkIfTexturesFinished(); - } else { - // there was an error baking this texture - add it to our list of errors - _errorList.append(bakedTexture->getErrors()); - - // we don't emit finished yet so that the other textures can finish baking first - _pendingErrorEmission = true; - - // now that this texture has been baked, even though it failed, we can remove that TextureBaker from our list - _bakingTextures.remove(bakedTexture->getTextureURL()); - - checkIfTexturesFinished(); - } - } else { - // we have errors to attend to, so we don't do extra processing for this texture - // but we do need to remove that TextureBaker from our list - // and then check if we're done with all textures - _bakingTextures.remove(bakedTexture->getTextureURL()); - - checkIfTexturesFinished(); - } - } -} - -void FBXBaker::exportScene() { - // setup the exporter - FbxExporter* exporter = FbxExporter::Create(_sdkManager.get(), ""); - - auto rewrittenFBXPath = _uniqueOutputPath + BAKED_OUTPUT_SUBFOLDER + _fbxName + BAKED_FBX_EXTENSION; - - // save the relative path to this FBX inside our passed output folder - _bakedFBXRelativePath = rewrittenFBXPath; - _bakedFBXRelativePath.remove(_baseOutputPath + "/"); - - bool exportStatus = exporter->Initialize(rewrittenFBXPath.toLocal8Bit().data()); - - if (!exportStatus) { - // failed to initialize exporter, print an error and return - handleError("Failed to export FBX file at " + _fbxURL.toString() + " to " + rewrittenFBXPath - + "- error: " + exporter->GetStatus().GetErrorString()); - } - - // export the scene - exporter->Export(_scene); - - qCDebug(model_baking) << "Exported" << _fbxURL << "with re-written paths to" << rewrittenFBXPath; -} - - -void FBXBaker::removeEmbeddedMediaFolder() { - // now that the bake is complete, remove the embedded media folder produced by the FBX SDK when it imports an FBX - auto embeddedMediaFolderName = _fbxURL.fileName().replace(".fbx", ".fbm"); - QDir(_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + embeddedMediaFolderName).removeRecursively(); -} - -void FBXBaker::possiblyCleanupOriginals() { - if (!_copyOriginals) { - // caller did not ask us to keep the original around, so delete the original output folder now - QDir(_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER).removeRecursively(); - } -} - -void FBXBaker::checkIfTexturesFinished() { - // check if we're done everything we need to do for this FBX - // and emit our finished signal if we're done - - if (_bakingTextures.isEmpty()) { - // remove the embedded media folder that the FBX SDK produces when reading the original - removeEmbeddedMediaFolder(); - - // cleanup the originals if we weren't asked to keep them around - possiblyCleanupOriginals(); - - if (hasErrors()) { - // if we're checking for completion but we have errors - // that means one or more of our texture baking operations failed - - if (_pendingErrorEmission) { - emit finished(); - } - - return; - } else { - qCDebug(model_baking) << "Finished baking" << _fbxURL; - - emit finished(); - } - } -} diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index d0b8c3cd65..d91206a592 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -44,18 +44,14 @@ Oven::Oven(int argc, char* argv[]) : parser.addHelpOption(); parser.process(*this); - // enable compression in image library, except for cube maps + // enable compression in image library image::setColorTexturesCompressionEnabled(true); image::setGrayscaleTexturesCompressionEnabled(true); image::setNormalTexturesCompressionEnabled(true); image::setCubeTexturesCompressionEnabled(true); // setup our worker threads - setupWorkerThreads(QThread::idealThreadCount() - 1); - - // Autodesk's SDK means that we need a single thread for all FBX importing/exporting in the same process - // setup the FBX Baker thread - setupFBXBakerThread(); + setupWorkerThreads(QThread::idealThreadCount()); // check if we were passed any command line arguments that would tell us just to run without the GUI if (parser.isSet(CLI_INPUT_PARAMETER) || parser.isSet(CLI_OUTPUT_PARAMETER)) { @@ -81,10 +77,6 @@ Oven::~Oven() { _workerThreads[i]->quit(); _workerThreads[i]->wait(); } - - // cleanup the FBX Baker thread - _fbxBakerThread->quit(); - _fbxBakerThread->wait(); } void Oven::setupWorkerThreads(int numWorkerThreads) { @@ -97,22 +89,6 @@ void Oven::setupWorkerThreads(int numWorkerThreads) { } } -void Oven::setupFBXBakerThread() { - // we're being asked for the FBX baker thread, but we don't have one yet - // so set that up now - _fbxBakerThread = new QThread(this); - _fbxBakerThread->setObjectName("Oven FBX Baker Thread"); -} - -QThread* Oven::getFBXBakerThread() { - if (!_fbxBakerThread->isRunning()) { - // start the FBX baker thread if it isn't running yet - _fbxBakerThread->start(); - } - - return _fbxBakerThread; -} - QThread* Oven::getNextWorkerThread() { // Here we replicate some of the functionality of QThreadPool by giving callers an available worker thread to use. // We can't use QThreadPool because we want to put QObjects with signals/slots on these threads. diff --git a/tools/oven/src/Oven.h b/tools/oven/src/Oven.h index 569b73a3e2..928ba4eb11 100644 --- a/tools/oven/src/Oven.h +++ b/tools/oven/src/Oven.h @@ -34,7 +34,6 @@ public: OvenMainWindow* getMainWindow() const { return _mainWindow; } - QThread* getFBXBakerThread(); QThread* getNextWorkerThread(); private: @@ -42,7 +41,6 @@ private: void setupFBXBakerThread(); OvenMainWindow* _mainWindow; - QThread* _fbxBakerThread; QList _workerThreads; std::atomic _nextWorkerThreadIndex; diff --git a/tools/oven/src/ui/BakeWidget.cpp b/tools/oven/src/ui/BakeWidget.cpp index 23a4822d82..9fb8f2f880 100644 --- a/tools/oven/src/ui/BakeWidget.cpp +++ b/tools/oven/src/ui/BakeWidget.cpp @@ -17,7 +17,7 @@ #include "BakeWidget.h" BakeWidget::BakeWidget(QWidget* parent, Qt::WindowFlags flags) : - QWidget(parent, flags) + QWidget(parent, flags) { } diff --git a/tools/oven/src/ui/BakeWidget.h b/tools/oven/src/ui/BakeWidget.h index e7ab8d1840..00996128ed 100644 --- a/tools/oven/src/ui/BakeWidget.h +++ b/tools/oven/src/ui/BakeWidget.h @@ -14,7 +14,7 @@ #include -#include "../Baker.h" +#include class BakeWidget : public QWidget { Q_OBJECT diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index c696fbad26..7963b3f3c4 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -155,15 +156,10 @@ void ModelBakeWidget::outputDirectoryChanged(const QString& newDirectory) { } void ModelBakeWidget::bakeButtonClicked() { - // make sure we have a valid output directory - QDir outputDirectory(_outputDirLineEdit->text()); - - if (!outputDirectory.exists()) { - return; - } // make sure we have a non empty URL to a model to bake if (_modelLineEdit->text().isEmpty()) { + QMessageBox::warning(this, "Model URL unspecified", "A model file is required."); return; } @@ -175,18 +171,49 @@ void ModelBakeWidget::bakeButtonClicked() { // if the URL doesn't have a scheme, assume it is a local file if (modelToBakeURL.scheme() != "http" && modelToBakeURL.scheme() != "https" && modelToBakeURL.scheme() != "ftp") { - modelToBakeURL.setScheme("file"); + qDebug() << modelToBakeURL.toString(); + qDebug() << modelToBakeURL.scheme(); + modelToBakeURL = QUrl::fromLocalFile(fileURLString); + qDebug() << "New url: " << modelToBakeURL; } + auto modelName = modelToBakeURL.fileName().left(modelToBakeURL.fileName().lastIndexOf('.')); + + // make sure we have a valid output directory + QDir outputDirectory(_outputDirLineEdit->text()); + QString subFolderName = modelName + "/"; + + // output in a sub-folder with the name of the fbx, potentially suffixed by a number to make it unique + int iteration = 0; + + while (outputDirectory.exists(subFolderName)) { + subFolderName = modelName + "-" + QString::number(++iteration) + "/"; + } + + outputDirectory.mkdir(subFolderName); + + if (!outputDirectory.exists()) { + QMessageBox::warning(this, "Unable to create directory", "Unable to create output directory. Please create it manually or choose a different directory."); + return; + } + + outputDirectory.cd(subFolderName); + + QDir bakedOutputDirectory = outputDirectory.absoluteFilePath("baked"); + QDir originalOutputDirectory = outputDirectory.absoluteFilePath("original"); + + bakedOutputDirectory.mkdir("."); + originalOutputDirectory.mkdir("."); + // everything seems to be in place, kick off a bake for this model now auto baker = std::unique_ptr { - new FBXBaker(modelToBakeURL, outputDirectory.absolutePath(), []() -> QThread* { + new FBXBaker(modelToBakeURL, []() -> QThread* { return qApp->getNextWorkerThread(); - }, false) + }, bakedOutputDirectory.absolutePath(), originalOutputDirectory.absolutePath()) }; // move the baker to the FBX baker thread - baker->moveToThread(qApp->getFBXBakerThread()); + baker->moveToThread(qApp->getNextWorkerThread()); // invoke the bake method on the baker thread QMetaObject::invokeMethod(baker.get(), "bake"); @@ -211,6 +238,10 @@ void ModelBakeWidget::handleFinishedBaker() { return value.first.get() == baker; }); + for (auto& file : baker->getOutputFiles()) { + qDebug() << "Baked file: " << file; + } + if (it != _bakers.end()) { auto resultRow = it->second; auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); diff --git a/tools/oven/src/ui/ModelBakeWidget.h b/tools/oven/src/ui/ModelBakeWidget.h index ed08990ba5..b42b8725f6 100644 --- a/tools/oven/src/ui/ModelBakeWidget.h +++ b/tools/oven/src/ui/ModelBakeWidget.h @@ -16,7 +16,7 @@ #include -#include "../FBXBaker.h" +#include #include "BakeWidget.h" diff --git a/tools/oven/src/ui/ResultsWindow.cpp b/tools/oven/src/ui/ResultsWindow.cpp index 35b5160f9b..3a37a328de 100644 --- a/tools/oven/src/ui/ResultsWindow.cpp +++ b/tools/oven/src/ui/ResultsWindow.cpp @@ -50,6 +50,7 @@ void ResultsWindow::setupUI() { // strech the last column of the table (that holds the results) to fill up the remaining available size _resultsTable->horizontalHeader()->resizeSection(0, 0.25 * FIXED_WINDOW_WIDTH); _resultsTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + _resultsTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // make sure we hear about cell clicks so that we can show the output directory for the given row connect(_resultsTable, &QTableWidget::cellClicked, this, &ResultsWindow::handleCellClicked); diff --git a/tools/oven/src/ui/SkyboxBakeWidget.cpp b/tools/oven/src/ui/SkyboxBakeWidget.cpp index d5c280aebd..cbaaa5ec0a 100644 --- a/tools/oven/src/ui/SkyboxBakeWidget.cpp +++ b/tools/oven/src/ui/SkyboxBakeWidget.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -155,7 +156,9 @@ void SkyboxBakeWidget::bakeButtonClicked() { // make sure we have a valid output directory QDir outputDirectory(_outputDirLineEdit->text()); + outputDirectory.mkdir("."); if (!outputDirectory.exists()) { + QMessageBox::warning(this, "Unable to create directory", "Unable to create output directory. Please create it manually or choose a different directory."); return; } diff --git a/tools/oven/src/ui/SkyboxBakeWidget.h b/tools/oven/src/ui/SkyboxBakeWidget.h index 4063a5459b..f00ab07f33 100644 --- a/tools/oven/src/ui/SkyboxBakeWidget.h +++ b/tools/oven/src/ui/SkyboxBakeWidget.h @@ -16,7 +16,7 @@ #include -#include "../TextureBaker.h" +#include #include "BakeWidget.h" diff --git a/unpublishedScripts/marketplace/blocks/blocksApp.js b/unpublishedScripts/marketplace/blocks/blocksApp.js new file mode 100644 index 0000000000..c9e8682b23 --- /dev/null +++ b/unpublishedScripts/marketplace/blocks/blocksApp.js @@ -0,0 +1,71 @@ +/// +/// blocksApp.js +/// A tablet app for downloading 3D assets from Google Blocks +/// +/// Author: Elisa Lupin-Jimenez +/// Copyright High Fidelity 2017 +/// +/// Licensed under the Apache 2.0 License +/// See accompanying license file or http://apache.org/ +/// +/// All assets are under CC Attribution Non-Commerical +/// http://creativecommons.org/licenses/ +/// + +(function () { + var APP_NAME = "BLOCKS"; + var APP_URL = "https://vr.google.com/objects/"; + var APP_OUTDATED_URL = "https://hifi-content.s3.amazonaws.com/elisalj/blocks/updateToBlocks.html"; + var APP_ICON = "https://hifi-content.s3.amazonaws.com/elisalj/blocks/blocks-i.svg"; + var APP_ICON_ACTIVE = "https://hifi-content.s3.amazonaws.com/elisalj/blocks/blocks-a.svg"; + + try { + print("Current Interface version: " + Window.checkVersion()); + } catch(err) { + print("Outdated Interface version does not support Blocks"); + APP_URL = APP_OUTDATED_URL; + } + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + icon: APP_ICON, + activeIcon: APP_ICON_ACTIVE, + text: APP_NAME + }); + + function onClicked() { + if (!shown) { + tablet.gotoWebScreen(APP_URL, "", true); + } else { + tablet.gotoHomeScreen(); + } + } + button.clicked.connect(onClicked); + + var shown = false; + + function checkIfBlocks(url) { + if (url.indexOf("google") !== -1) { + return true; + } + return false; + } + + function onScreenChanged(type, url) { + if ((type === 'Web' && checkIfBlocks(url)) || url === APP_OUTDATED_URL) { + button.editProperties({ isActive: true }); + shown = true; + } else { + button.editProperties({ isActive: false }); + shown = false; + } + } + + tablet.screenChanged.connect(onScreenChanged); + + function cleanup() { + tablet.removeButton(button); + } + + Script.scriptEnding.connect(cleanup); +}()); diff --git a/unpublishedScripts/marketplace/clap/animations/ClapAnimation.json b/unpublishedScripts/marketplace/clap/animations/ClapAnimation.json new file mode 100644 index 0000000000..2dca45a0de --- /dev/null +++ b/unpublishedScripts/marketplace/clap/animations/ClapAnimation.json @@ -0,0 +1,386 @@ +{ + "RightShoulder": { + "rotations": { + "x": 0.5578474402427673, + "y": -0.44214877486228943, + "z": 0.4979960322380066, + "w": 0.4952884614467621 + } + }, + "RightArm": { + "rotations": { + "x": 0.6266362071037292, + "y": -0.02882515825331211, + "z": -0.3233867585659027, + "w": 0.7084611654281616 + } + }, + "RightForeArm": { + "rotations": { + "x": 0.00004018074105260894, + "y": 0.06418406218290329, + "z": -0.5193823575973511, + "w": 0.8521281480789185 + } + }, + "RightHand": { + "rotations": { + "x": -0.21368850767612457, + "y": 0.02043931558728218, + "z": -0.02227853797376156, + "w": 0.9764339923858643 + } + }, + "RightHandPinky1": { + "rotations": { + "x": -0.003497191471979022, + "y": -0.01721140556037426, + "z": 0.19736060500144958, + "w": 0.980173647403717 + } + }, + "RightHandPinky2": { + "rotations": { + "x": 0.000005483317181642633, + "y": -0.00008023700502235442, + "z": -0.06867633014917374, + "w": 0.997639000415802 + } + }, + "RightHandPinky3": { + "rotations": { + "x": 7.18885644346301e-8, + "y": -2.7131383717460267e-7, + "z": 0.007620905060321093, + "w": 0.9999709725379944 + } + }, + "RightHandPinky4": { + "rotations": { + "x": -5.719068774112657e-9, + "y": 5.142213126418937e-7, + "z": -0.0075718737207353115, + "w": 0.999971330165863 + } + }, + "RightHandRing1": { + "rotations": { + "x": -0.0013452530838549137, + "y": -0.017564140260219574, + "z": 0.0761696845293045, + "w": 0.9969393610954285 + } + }, + "RightHandRing2": { + "rotations": { + "x": 0.0000010375651982030831, + "y": -0.00002211921673733741, + "z": -0.04599710553884506, + "w": 0.9989416003227234 + } + }, + "RightHandRing3": { + "rotations": { + "x": -2.7102969868408877e-10, + "y": 1.9202734335976857e-7, + "z": 0.0016911650309339166, + "w": 0.9999985694885254 + } + }, + "RightHandRing4": { + "rotations": { + "x": 2.3246689018208144e-9, + "y": -3.364403156069784e-8, + "z": 0.0004951066803187132, + "w": 0.9999998807907104 + } + }, + "RightHandMiddle1": { + "rotations": { + "x": 0.0012630893616005778, + "y": -0.017612185329198837, + "z": -0.07168931514024734, + "w": 0.9972707033157349 + } + }, + "RightHandMiddle2": { + "rotations": { + "x": 2.3561028683616314e-7, + "y": 0.000020313073036959395, + "z": 0.011195243336260319, + "w": 0.9999373555183411 + } + }, + "RightHandMiddle3": { + "rotations": { + "x": 6.375214667286855e-8, + "y": 4.750924631480302e-7, + "z": 0.00237679248675704, + "w": 0.9999971985816956 + } + }, + "RightHandMiddle4": { + "rotations": { + "x": 6.717256439969788e-8, + "y": 3.876683507542111e-8, + "z": -0.005236906465142965, + "w": 0.9999862909317017 + } + }, + "RightHandIndex1": { + "rotations": { + "x": 0.002164300065487623, + "y": -0.017346171662211418, + "z": -0.12158434838056564, + "w": 0.9924271702766418 + } + }, + "RightHandIndex2": { + "rotations": { + "x": -0.00000143755482895358, + "y": -0.0001614764187252149, + "z": 0.008941099047660828, + "w": 0.9999601244926453 + } + }, + "RightHandIndex3": { + "rotations": { + "x": 7.458467621290765e-8, + "y": 5.365728839024086e-7, + "z": 0.03373909369111061, + "w": 0.999430775642395 + } + }, + "RightHandIndex4": { + "rotations": { + "x": 4.511302997833866e-10, + "y": -2.259726272768603e-7, + "z": -0.009632252156734467, + "w": 0.9999536275863647 + } + }, + "RightHandThumb1": { + "rotations": { + "x": -0.0783928632736206, + "y": -0.3033908009529114, + "z": -0.26653754711151123, + "w": 0.9114638566970825 + } + }, + "RightHandThumb2": { + "rotations": { + "x": 0.0031029442325234413, + "y": 0.07382386922836304, + "z": 0.005253761075437069, + "w": 0.9972526431083679 + } + }, + "RightHandThumb3": { + "rotations": { + "x": 0.0040440745651721954, + "y": -0.04943573474884033, + "z": 0.007246015593409538, + "w": 0.9987428188323975 + } + }, + "RightHandThumb4": { + "rotations": { + "x": -0.00009280416270485148, + "y": -0.01658034883439541, + "z": -0.00014316302258521318, + "w": 0.999862551689148 + } + }, + "LeftShoulder": { + "rotations": { + "x": 0.5578474402427673, + "y": 0.44214877486228943, + "z": -0.4979960322380066, + "w": 0.4952884614467621 + } + }, + "LeftArm": { + "rotations": { + "x": 0.626636266708374, + "y": 0.028824958950281143, + "z": 0.3233867585659027, + "w": 0.7084611654281616 + } + }, + "LeftForeArm": { + "rotations": { + "x": 0.00004015670492663048, + "y": -0.06418408453464508, + "z": 0.5193824768066406, + "w": 0.8521282076835632 + } + }, + "LeftHand": { + "rotations": { + "x": -0.21368853747844696, + "y": -0.02043931558728218, + "z": 0.02227853797376156, + "w": 0.9764339923858643 + } + }, + "LeftHandPinky1": { + "rotations": { + "x": -0.003497188910841942, + "y": 0.01721140556037426, + "z": -0.19736060500144958, + "w": 0.980173647403717 + } + }, + "LeftHandPinky2": { + "rotations": { + "x": 0.000005479304491018411, + "y": 0.00008023556438274682, + "z": 0.06867631524801254, + "w": 0.997639000415802 + } + }, + "LeftHandPinky3": { + "rotations": { + "x": 7.229602516645173e-8, + "y": 2.709063835482084e-7, + "z": -0.007620909716933966, + "w": 0.9999709725379944 + } + }, + "LeftHandPinky4": { + "rotations": { + "x": -5.52988677071653e-9, + "y": -5.13755651354586e-7, + "z": 0.007571868598461151, + "w": 0.999971330165863 + } + }, + "LeftHandRing1": { + "rotations": { + "x": -0.001345252152532339, + "y": 0.017564138397574425, + "z": -0.0761696845293045, + "w": 0.9969393610954285 + } + }, + "LeftHandRing2": { + "rotations": { + "x": 0.000001034482806971937, + "y": 0.000022119218556326814, + "z": 0.04599710553884506, + "w": 0.9989416003227234 + } + }, + "LeftHandRing3": { + "rotations": { + "x": -2.8012464570181805e-10, + "y": -1.923183816643359e-7, + "z": -0.0016911652637645602, + "w": 0.9999985694885254 + } + }, + "LeftHandRing4": { + "rotations": { + "x": -1.1168596047994583e-9, + "y": 3.33529932561305e-8, + "z": -0.0004951058072037995, + "w": 0.9999998807907104 + } + }, + "LeftHandMiddle1": { + "rotations": { + "x": 0.0012630895944312215, + "y": 0.01761218160390854, + "z": 0.07168931514024734, + "w": 0.9972707033157349 + } + }, + "LeftHandMiddle2": { + "rotations": { + "x": 2.37378529277521e-7, + "y": -0.00002031277836067602, + "z": -0.011195233091711998, + "w": 0.9999373555183411 + } + }, + "LeftHandMiddle3": { + "rotations": { + "x": 7.146466884933034e-8, + "y": -4.7555812443533796e-7, + "z": -0.0023767934180796146, + "w": 0.9999971985816956 + } + }, + "LeftHandMiddle4": { + "rotations": { + "x": 6.549178976911207e-8, + "y": -3.865041975359418e-8, + "z": 0.005236904136836529, + "w": 0.9999862909317017 + } + }, + "LeftHandIndex1": { + "rotations": { + "x": 0.002164299599826336, + "y": 0.017346171662211418, + "z": 0.12158433347940445, + "w": 0.9924271702766418 + } + }, + "LeftHandIndex2": { + "rotations": { + "x": -0.000001437780269952782, + "y": 0.00016147761198226362, + "z": -0.008941099047660828, + "w": 0.9999601244926453 + } + }, + "LeftHandIndex3": { + "rotations": { + "x": 7.61426193207626e-8, + "y": -5.373883027459669e-7, + "z": -0.03373908996582031, + "w": 0.999430775642395 + } + }, + "LeftHandIndex4": { + "rotations": { + "x": -5.311697748311417e-10, + "y": 2.26380109324964e-7, + "z": 0.009632255882024765, + "w": 0.9999536275863647 + } + }, + "LeftHandThumb1": { + "rotations": { + "x": -0.07839284837245941, + "y": 0.3033908009529114, + "z": 0.26653754711151123, + "w": 0.9114638566970825 + } + }, + "LeftHandThumb2": { + "rotations": { + "x": 0.0031029372476041317, + "y": -0.07382386922836304, + "z": -0.005253763869404793, + "w": 0.9972526431083679 + } + }, + "LeftHandThumb3": { + "rotations": { + "x": 0.004044072702527046, + "y": 0.049435727298259735, + "z": -0.0072460174560546875, + "w": 0.9987428188323975 + } + }, + "LeftHandThumb4": { + "rotations": { + "x": -0.00009280881931772456, + "y": 0.016580356284976006, + "z": 0.00014314628788270056, + "w": 0.999862551689148 + } + } +} diff --git a/unpublishedScripts/marketplace/clap/animations/Clap_left.fbx b/unpublishedScripts/marketplace/clap/animations/Clap_left.fbx new file mode 100644 index 0000000000..a3830b9838 Binary files /dev/null and b/unpublishedScripts/marketplace/clap/animations/Clap_left.fbx differ diff --git a/unpublishedScripts/marketplace/clap/animations/Clap_right.fbx b/unpublishedScripts/marketplace/clap/animations/Clap_right.fbx new file mode 100644 index 0000000000..1877e963f9 Binary files /dev/null and b/unpublishedScripts/marketplace/clap/animations/Clap_right.fbx differ diff --git a/unpublishedScripts/marketplace/clap/clapApp.js b/unpublishedScripts/marketplace/clap/clapApp.js new file mode 100644 index 0000000000..b2d8ce55db --- /dev/null +++ b/unpublishedScripts/marketplace/clap/clapApp.js @@ -0,0 +1,61 @@ +"use strict"; + +/* + clapApp.js + unpublishedScripts/marketplace/clap/clapApp.js + + Created by Matti 'Menithal' Lahtinen on 9/11/2017 + Copyright 2017 High Fidelity, Inc. + + Distributed under the Apache License, Version 2.0. + See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +*/ + +// Entry Script for the clap app + +// Load up engine +var APP_NAME = "CLAP"; +var ClapEngine = Script.require(Script.resolvePath("scripts/ClapEngine.js?v9")); +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + +// Define Menu +var blackIcon = Script.resolvePath("icons/tablet-icons/clap-a.svg?foxv2"); +var whiteIcon = Script.resolvePath("icons/tablet-icons/clap-i.svg?foxv2"); + +if (Settings.getValue("clapAppEnabled") === undefined) { + Settings.setValue("clapAppEnabled", true); +} +var isActive = Settings.getValue("clapAppEnabled"); + +var activeButton = tablet.addButton({ + icon: whiteIcon, + activeIcon: blackIcon, + text: APP_NAME, + isActive: isActive, + sortOrder: 11 +}); + +if (isActive) { + ClapEngine.connectEngine(); +} + +function onClick(enabled) { + + isActive = !isActive; + Settings.setValue("clapAppEnabled", isActive); + activeButton.editProperties({ + isActive: isActive + }); + if (isActive) { + ClapEngine.connectEngine(); + } else { + ClapEngine.disconnectEngine(); + } +} +activeButton.clicked.connect(onClick); + +Script.scriptEnding.connect(function () { + ClapEngine.disconnectEngine(); + activeButton.clicked.disconnect(onClick); + tablet.removeButton(activeButton); +}); diff --git a/unpublishedScripts/marketplace/clap/entities/ClapParticle.json b/unpublishedScripts/marketplace/clap/entities/ClapParticle.json new file mode 100644 index 0000000000..bf1b70665b --- /dev/null +++ b/unpublishedScripts/marketplace/clap/entities/ClapParticle.json @@ -0,0 +1,58 @@ +{ + "alpha": 0.01, + "alphaFinish": 0.0, + "alphaSpread": 1, + "alphaStart": 0.05, + "color": { + "blue": 200, + "green": 200, + "red": 200 + }, + "colorFinish": { + "blue": 200, + "green": 200, + "red": 200 + }, + "colorStart": { + "blue": 200, + "green": 200, + "red": 200 + }, + "created": "2017-09-09T16:01:38Z", + "dimensions": { + "x": 0.5, + "y": 0.5, + "z": 0.5 + }, + "emitAcceleration": { + "x": 0, + "y": 0, + "z": 0 + }, + "emitDimensions": { + "x": 0.25, + "y": 0.25, + "z": 0.25 + }, + "emitOrientation": { + "w": 1, + "x": 0, + "y": 0, + "z": 0 + }, + "emitRate": 100, + "emitSpeed": 0.125, + "lifespan": 0.5, + "lifetime": 2, + "script": "(function(){return{preload:function(id){Script.setTimeout(function(){Entities.editEntity(id,{isEmitting:false});},200);}}})", + "maxParticles": 100, + "particleRadius": 0.05, + "polarFinish": 1.4311699867248535, + "polarStart": 1.3962633609771729, + "radiusFinish": 0.01, + "radiusSpread": 0.2, + "radiusStart": 0.05, + "speedSpread": 0, + "emitShouldTrail": true, + "type": "ParticleEffect" +} diff --git a/unpublishedScripts/marketplace/clap/icons/clap-a.svg b/unpublishedScripts/marketplace/clap/icons/clap-a.svg new file mode 100644 index 0000000000..60df3e0795 --- /dev/null +++ b/unpublishedScripts/marketplace/clap/icons/clap-a.svg @@ -0,0 +1,112 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/clap/icons/clap-i.svg b/unpublishedScripts/marketplace/clap/icons/clap-i.svg new file mode 100644 index 0000000000..fbd9ed0f9c --- /dev/null +++ b/unpublishedScripts/marketplace/clap/icons/clap-i.svg @@ -0,0 +1,112 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js b/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js new file mode 100644 index 0000000000..9e7a592c01 --- /dev/null +++ b/unpublishedScripts/marketplace/clap/scripts/ClapDebugger.js @@ -0,0 +1,144 @@ +"use strict"; + +/* + clapApp.js + unpublishedScripts/marketplace/clap/scripts/clapDebugger.js + + Created by Matti 'Menithal' Lahtinen on 9/11/2017 + Copyright 2017 High Fidelity, Inc. + + Distributed under the Apache License, Version 2.0. + See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +*/ + +var DEBUG_RIGHT_HAND; +var DEBUG_LEFT_HAND; +var DEBUG_CLAP_LEFT; +var DEBUG_CLAP_RIGHT; +var DEBUG_CLAP; +var DEBUG_CLAP_DIRECTION; + +// Debug Values: +var DEBUG_CORRECT = { + red: 0, + green: 255, + blue: 0 +}; +var DEBUG_WRONG = { + red: 255, + green: 0, + blue: 0 +}; + +var DEBUG_VOLUME = { + red: 255, + green: 255, + blue: 128 +}; + +module.exports = { + disableDebug: function () { + Overlays.deleteOverlay(DEBUG_RIGHT_HAND); + Overlays.deleteOverlay(DEBUG_LEFT_HAND); + Overlays.deleteOverlay(DEBUG_CLAP_LEFT); + Overlays.deleteOverlay(DEBUG_CLAP_RIGHT); + Overlays.deleteOverlay(DEBUG_CLAP_DIRECTION); + }, + + debugPositions: function (leftAlignmentWorld, leftHandPositionOffset, leftHandDownWorld, rightAlignmentWorld, rightHandPositionOffset, rightHandDownWorld, tolerance) { + + Overlays.editOverlay(DEBUG_CLAP_LEFT, { + color: leftAlignmentWorld > tolerance ? DEBUG_CORRECT : DEBUG_WRONG, + position: leftHandPositionOffset + }); + + Overlays.editOverlay(DEBUG_CLAP_RIGHT, { + color: rightAlignmentWorld > tolerance ? DEBUG_CORRECT : DEBUG_WRONG, + position: rightHandPositionOffset + }); + + Overlays.editOverlay(DEBUG_LEFT_HAND, { + color: leftAlignmentWorld > tolerance ? DEBUG_CORRECT : DEBUG_WRONG, + start: leftHandPositionOffset, + end: Vec3.sum(leftHandPositionOffset, Vec3.multiply(leftHandDownWorld, 0.2)) + }); + + Overlays.editOverlay(DEBUG_RIGHT_HAND, { + color: rightAlignmentWorld > tolerance ? DEBUG_CORRECT : DEBUG_WRONG, + start: rightHandPositionOffset, + end: Vec3.sum(rightHandPositionOffset, Vec3.multiply(rightHandDownWorld, 0.2)) + }); + }, + + debugClapLine: function (start, end, visible) { + Overlays.editOverlay(DEBUG_CLAP_DIRECTION, { + start: start, + end: end, + visible: visible + }); + }, + + enableDebug: function () { + DEBUG_RIGHT_HAND = Overlays.addOverlay("line3d", { + color: DEBUG_WRONG, + start: MyAvatar.position, + end: Vec3.sum(MyAvatar.position, { + x: 0, + y: 1, + z: 0 + }), + dimensions: { + x: 2, + y: 2, + z: 2 + } + }); + + DEBUG_LEFT_HAND = Overlays.addOverlay("line3d", { + color: DEBUG_WRONG, + start: MyAvatar.position, + end: Vec3.sum(MyAvatar.position, { + x: 0, + y: 1, + z: 0 + }), + dimensions: { + x: 2, + y: 2, + z: 2 + } + }); + + DEBUG_CLAP_LEFT = Overlays.addOverlay("sphere", { + position: MyAvatar.position, + color: DEBUG_WRONG, + scale: { + x: 0.05, + y: 0.05, + z: 0.05 + } + }); + + DEBUG_CLAP_RIGHT = Overlays.addOverlay("sphere", { + position: MyAvatar.position, + color: DEBUG_WRONG, + scale: { + x: 0.05, + y: 0.05, + z: 0.05 + } + }); + + + DEBUG_CLAP_DIRECTION = Overlays.addOverlay("line3d", { + color: DEBUG_VOLUME, + start: MyAvatar.position, + end: MyAvatar.position, + dimensions: { + x: 2, + y: 2, + z: 2 + } + }); + } +}; diff --git a/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js b/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js new file mode 100644 index 0000000000..1a5f6665e1 --- /dev/null +++ b/unpublishedScripts/marketplace/clap/scripts/ClapEngine.js @@ -0,0 +1,313 @@ +"use strict"; + +/* + clapEngine.js + unpublishedScripts/marketplace/clap/clapApp.js + + Created by Matti 'Menithal' Lahtinen on 9/11/2017 + Copyright 2017 High Fidelity, Inc. + + Distributed under the Apache License, Version 2.0. + See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + + Main Heart of the clap script> Does both keyboard binding and tracking of the gear.. + +*/ +var DEG_TO_RAD = Math.PI / 180; +// If angle is closer to 0 from 25 degrees, then "clap" can happen; +var COS_OF_TOLERANCE = Math.cos(25 * DEG_TO_RAD); +var DISTANCE = 0.3; +var CONTROL_MAP_PACKAGE = "com.highfidelity.avatar.clap.active"; + +var CLAP_MENU = "Clap"; +var ENABLE_PARTICLE_MENU = "Enable Clap Particles"; +var ENABLE_DEBUG_MENU = "Enable Claping Helper"; + +var sounds = [ + "clap1.wav", + "clap2.wav", + "clap3.wav", + "clap4.wav", + "clap5.wav", + "clap6.wav" +]; + + +var ClapParticle = Script.require(Script.resolvePath("../entities/ClapParticle.json?V3")); +var ClapAnimation = Script.require(Script.resolvePath("../animations/ClapAnimation.json?V3")); +var ClapDebugger = Script.require(Script.resolvePath("ClapDebugger.js?V3")); + +var settingDebug = false; +var settingParticlesOn = true; + +function setJointRotation(map) { + Object.keys(map).forEach(function (key, index) { + MyAvatar.setJointRotation(MyAvatar.getJointIndex(key), map[key].rotations); + }); +} +// Load Sounds to Cache +var cache = []; +for (var index in sounds) { + cache.push(SoundCache.getSound(Script.resolvePath("../sounds/" + sounds[index]))); +} + + +var previousIndex; +var clapOn = false; +var animClap = false; +var animThrottle; + +function clap(volume, position, rotation) { + var index; + // Make sure one does not generate consequtive sounds + do { + index = Math.floor(Math.random() * cache.length); + } while (index === previousIndex); + + previousIndex = index; + + Audio.playSound(cache[index], { + position: position, + volume: volume / 4 + Math.random() * (volume / 3) + }); + + if (settingParticlesOn) { + ClapParticle.orientation = Quat.multiply(MyAvatar.orientation, rotation); + ClapParticle.position = position; + ClapParticle.emitSpeed = volume > 1 ? 1 : volume; + Entities.addEntity(ClapParticle, true); + } + + +} +// Helper Functions +function getHandFingerAnim(side) { + return Script.resolvePath("../animations/Clap_" + side + '.fbx'); +} + +// Disable all role animations related to fingers for side +function overrideFingerRoleAnimation(side) { + var anim = getHandFingerAnim(side); + MyAvatar.overrideRoleAnimation(side + "HandGraspOpen", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "HandGraspClosed", anim, 30, true, 0, 0); + if (HMD.active) { + MyAvatar.overrideRoleAnimation(side + "HandPointIntro", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "HandPointHold", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "HandPointOutro", anim, 30, true, 0, 0); + + MyAvatar.overrideRoleAnimation(side + "IndexPointOpen", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "IndexPointClosed", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "IndexPointAndThumbRaiseOpen", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "IndexPointAndThumbRaiseClosed", anim, 30, true, 0, 0); + + MyAvatar.overrideRoleAnimation(side + "ThumbRaiseOpen", anim, 30, true, 0, 0); + MyAvatar.overrideRoleAnimation(side + "ThumbRaiseClosed", anim, 30, true, 0, 0); + } +} +// Re-enable all role animations for fingers +function restoreFingerRoleAnimation(side) { + MyAvatar.restoreRoleAnimation(side + "HandGraspOpen"); + MyAvatar.restoreRoleAnimation(side + "HandGraspClosed"); + if (HMD.active) { + MyAvatar.restoreRoleAnimation(side + "HandPointIntro"); + MyAvatar.restoreRoleAnimation(side + "HandPointHold"); + MyAvatar.restoreRoleAnimation(side + "HandPointOutro"); + MyAvatar.restoreRoleAnimation(side + "IndexPointOpen"); + + MyAvatar.restoreRoleAnimation(side + "IndexPointClosed"); + MyAvatar.restoreRoleAnimation(side + "IndexPointAndThumbRaiseOpen"); + MyAvatar.restoreRoleAnimation(side + "IndexPointAndThumbRaiseClosed"); + MyAvatar.restoreRoleAnimation(side + "ThumbRaiseOpen"); + MyAvatar.restoreRoleAnimation(side + "ThumbRaiseClosed"); + } +} + + +function menuListener(menuItem) { + if (menuItem === ENABLE_PARTICLE_MENU) { + settingParticlesOn = Menu.isOptionChecked(ENABLE_PARTICLE_MENU); + } else if (menuItem === ENABLE_DEBUG_MENU) { + var debugOn = Menu.isOptionChecked(ENABLE_DEBUG_MENU); + + if (debugOn) { + settingDebug = true; + ClapDebugger.enableDebug(); + } else { + settingDebug = false; + ClapDebugger.disableDebug(); + } + } +} + +function update(dt) { + + // NOTICE: Someof this stuff is unnessary for the actual: But they are done for Debug Purposes! + // Forexample, the controller doesnt really need to know where it is in the world, only its relation to the other controller! + + var leftHand = Controller.getPoseValue(Controller.Standard.LeftHand); + var rightHand = Controller.getPoseValue(Controller.Standard.RightHand); + + // Get Offset position for palms, not the controllers that are at the wrists (7.5 cm up) + + var leftWorldRotation = Quat.multiply(MyAvatar.orientation, leftHand.rotation); + var rightWorldRotation = Quat.multiply(MyAvatar.orientation, rightHand.rotation); + + var leftWorldUpNormal = Quat.getUp(leftWorldRotation); + var rightWorldUpNormal = Quat.getUp(rightWorldRotation); + + var leftHandDownWorld = Vec3.multiply(-1, Quat.getForward(leftWorldRotation)); + var rightHandDownWorld = Vec3.multiply(-1, Quat.getForward(rightWorldRotation)); + + // + var leftHandWorldPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, leftHand.translation)); + var rightHandWorldPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, rightHand.translation)); + + var rightHandPositionOffset = Vec3.sum(rightHandWorldPosition, Vec3.multiply(rightWorldUpNormal, 0.035)); + var leftHandPositionOffset = Vec3.sum(leftHandWorldPosition, Vec3.multiply(leftWorldUpNormal, 0.035)); + + var leftToRightWorld = Vec3.subtract(leftHandPositionOffset, rightHandPositionOffset); + var rightToLeftWorld = Vec3.subtract(rightHandPositionOffset, leftHandPositionOffset); + + var leftAlignmentWorld = -1 * Vec3.dot(Vec3.normalize(leftToRightWorld), leftHandDownWorld); + var rightAlignmentWorld = -1 * Vec3.dot(Vec3.normalize(rightToLeftWorld), rightHandDownWorld); + + var distance = Vec3.distance(rightHandPositionOffset, leftHandPositionOffset); + + var matchTolerance = leftAlignmentWorld > COS_OF_TOLERANCE && rightAlignmentWorld > COS_OF_TOLERANCE; + + if (settingDebug) { + ClapDebugger.debugPositions(leftAlignmentWorld, + leftHandPositionOffset, leftHandDownWorld, + rightAlignmentWorld, rightHandPositionOffset, + rightHandDownWorld, COS_OF_TOLERANCE); + } + // Using subtract, because these will be heading to opposite directions + var angularVelocity = Vec3.length(Vec3.subtract(leftHand.angularVelocity, rightHand.angularVelocity)); + var velocity = Vec3.length(Vec3.subtract(leftHand.velocity, rightHand.velocity)); + + if (matchTolerance && distance < DISTANCE && !animClap) { + if (settingDebug) { + ClapDebugger.debugClapLine(leftHandPositionOffset, rightHandPositionOffset, true); + } + if (!animThrottle) { + overrideFingerRoleAnimation("left"); + overrideFingerRoleAnimation("right"); + animClap = true; + } else { + Script.clearTimeout(animThrottle); + animThrottle = false; + } + } else if (animClap && distance > DISTANCE) { + if (settingDebug) { + ClapDebugger.debugClapLine(leftHandPositionOffset, rightHandPositionOffset, false); + } + animThrottle = Script.setTimeout(function () { + restoreFingerRoleAnimation("left"); + restoreFingerRoleAnimation("right"); + animClap = false; + }, 500); + } + + if (distance < DISTANCE && matchTolerance && !clapOn) { + clapOn = true; + + var midClap = Vec3.mix(rightHandPositionOffset, leftHandPositionOffset, 0.5); + var volume = velocity / 2 + angularVelocity / 5; + + clap(volume, midClap, Quat.lookAtSimple(rightHandPositionOffset, leftHandPositionOffset)); + } else if (distance > DISTANCE && !matchTolerance) { + clapOn = false; + } +} + + +module.exports = { + connectEngine: function () { + if (!Menu.menuExists(CLAP_MENU)) { + Menu.addMenu(CLAP_MENU); + } + + if (!Menu.menuItemExists(CLAP_MENU, ENABLE_PARTICLE_MENU)) { + Menu.addMenuItem({ + menuName: CLAP_MENU, + menuItemName: ENABLE_PARTICLE_MENU, + isCheckable: true, + isChecked: settingParticlesOn + }); + } + if (!Menu.menuItemExists(CLAP_MENU, ENABLE_DEBUG_MENU)) { + Menu.addMenuItem({ + menuName: CLAP_MENU, + menuItemName: ENABLE_DEBUG_MENU, + isCheckable: true, + isChecked: settingDebug + }); + } + + + Menu.menuItemEvent.connect(menuListener); + + var controls = Controller.newMapping(CONTROL_MAP_PACKAGE); + var Keyboard = Controller.Hardware.Keyboard; + + controls.from(Keyboard.K).to(function (down) { + if (down) { + + setJointRotation(ClapAnimation); + Script.setTimeout(function () { + // As soon as an animation bug is fixed, this will kick and get fixed.s. + overrideFingerRoleAnimation("left"); + overrideFingerRoleAnimation("right"); + + var lh = MyAvatar.getJointPosition("LeftHand"); + var rh = MyAvatar.getJointPosition("RightHand"); + var midClap = Vec3.mix(rh, lh, 0.5); + var volume = 0.5 + Math.random() * 0.5; + var position = midClap; + + clap(volume, position, Quat.fromVec3Degrees(0, 0, 0)); + }, 50); // delay is present to allow for frames to catch up. + } else { + + restoreFingerRoleAnimation("left"); + + restoreFingerRoleAnimation("right"); + MyAvatar.clearJointsData(); + } + }); + Controller.enableMapping(CONTROL_MAP_PACKAGE); + + if (settingDebug) { + ClapDebugger.enableDebug(); + } + + Script.update.connect(update); + + Script.scriptEnding.connect(this.disconnectEngine); + }, + disconnectEngine: function () { + if (settingDebug) { + ClapDebugger.disableDebug(); + } + if (Menu.menuItemExists(CLAP_MENU, ENABLE_PARTICLE_MENU)) { + Menu.removeMenuItem(CLAP_MENU, ENABLE_PARTICLE_MENU); + } + if (Menu.menuItemExists(CLAP_MENU, ENABLE_DEBUG_MENU)) { + Menu.removeMenuItem(CLAP_MENU, ENABLE_DEBUG_MENU); + } + + if (Menu.menuExists(CLAP_MENU)) { + Menu.removeMenu(CLAP_MENU); + } + restoreFingerRoleAnimation('left'); + restoreFingerRoleAnimation('right'); + + Controller.disableMapping(CONTROL_MAP_PACKAGE); + try { + Script.update.disconnect(update); + } catch (e) { + print("Script.update connection did not exist on disconnection. Skipping"); + } + } +}; diff --git a/unpublishedScripts/marketplace/clap/sounds/clap1.wav b/unpublishedScripts/marketplace/clap/sounds/clap1.wav new file mode 100644 index 0000000000..679cb7e732 Binary files /dev/null and b/unpublishedScripts/marketplace/clap/sounds/clap1.wav differ diff --git a/unpublishedScripts/marketplace/clap/sounds/clap2.wav b/unpublishedScripts/marketplace/clap/sounds/clap2.wav new file mode 100644 index 0000000000..b43c745e52 Binary files /dev/null and b/unpublishedScripts/marketplace/clap/sounds/clap2.wav differ diff --git a/unpublishedScripts/marketplace/clap/sounds/clap3.wav b/unpublishedScripts/marketplace/clap/sounds/clap3.wav new file mode 100644 index 0000000000..47a4a8d48f Binary files /dev/null and b/unpublishedScripts/marketplace/clap/sounds/clap3.wav differ diff --git a/unpublishedScripts/marketplace/clap/sounds/clap4.wav b/unpublishedScripts/marketplace/clap/sounds/clap4.wav new file mode 100644 index 0000000000..4e8dfca1a0 Binary files /dev/null and b/unpublishedScripts/marketplace/clap/sounds/clap4.wav differ diff --git a/unpublishedScripts/marketplace/clap/sounds/clap5.wav b/unpublishedScripts/marketplace/clap/sounds/clap5.wav new file mode 100644 index 0000000000..1f38864db4 Binary files /dev/null and b/unpublishedScripts/marketplace/clap/sounds/clap5.wav differ diff --git a/unpublishedScripts/marketplace/clap/sounds/clap6.wav b/unpublishedScripts/marketplace/clap/sounds/clap6.wav new file mode 100644 index 0000000000..4591f79d9d Binary files /dev/null and b/unpublishedScripts/marketplace/clap/sounds/clap6.wav differ diff --git a/unpublishedScripts/marketplace/dodgeBall/dodgeBall.js b/unpublishedScripts/marketplace/dodgeBall/dodgeBall.js new file mode 100644 index 0000000000..6afb82770f --- /dev/null +++ b/unpublishedScripts/marketplace/dodgeBall/dodgeBall.js @@ -0,0 +1,168 @@ +(function () { + + var FORCE_DROP_CHANNEL = "Hifi-Hand-Drop"; + + var proxInterval, + proxTimeout; + + var _entityID; + this.preload = function (entityID) { + _entityID = entityID; + + Entities.editEntity(_entityID, { + userData: '{"grabbableKey": {"grabbable": true}' + }); + }; + + var particleTrailEntity = null; + + function particleTrail() { + + var props = { + type: 'ParticleEffect', + name: 'Particle', + parentID: _entityID, + isEmitting: true, + lifespan: 2.0, + maxParticles: 100, + textures: 'https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png', + emitRate: 50, + emitSpeed: 0, + emitterShouldTrail: true, + particleRadius: 0, + radiusSpread: 0, + radiusStart: .2, + radiusFinish: 0.1, + color: { + red: 201, + blue: 201, + green: 34 + }, + accelerationSpread: { + x: 0, + y: 0, + z: 0 + }, + alpha: 0, + alphaSpread: 0, + alphaStart: 1, + alphaFinish: 0, + polarStart: 0, + polarFinish: 0, + azimuthStart: -180, + azimuthFinish: 180 + }; + + particleTrailEntity = Entities.addEntity(props); + } + + function particleExplode() { + var entPos = Entities.getEntityProperties(_entityID, 'position').position; + var props = { + type: 'ParticleEffect', + name: 'Particle', + parentID: _entityID, + isEmitting: true, + lifespan: 2, + maxParticles: 10, + position: entPos, + textures: 'https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png', + emitRate: 1, + emitSpeed: 0, + emitterShouldTrail: false, + particleRadius: 1, + radiusSpread: 0, + radiusStart: 0, + radiusFinish: 1, + color: { + red: 232, + blue: 232, + green: 26 + }, + emitAcceleration: { + x: 0, + y: 0, + z: 0 + }, + alpha: 0, + alphaSpread: 0, + alphaStart: 1, + alphaFinish: .5, + polarStart: 0, + polarFinish: 0, + azimuthStart: -180, + azimuthFinish: 180 + }; + var explosionParticles = Entities.addEntity(props); + Entities.editEntity(_entityID, { + velocity: Vec3.ZERO, + dynamic: false + }); + Script.setTimeout(function () { + Entities.deleteEntity(explosionParticles); + Entities.editEntity(_entityID, { + dynamic: true + }) + }, 500); + } + + + function clearProxCheck() { + if (proxInterval) { + Script.clearInterval(proxInterval); + Entities.deleteEntity(particleTrailEntity); + particleTrailEntity = null; + } + + if (proxTimeout) { + Script.clearTimeout(proxTimeout); + } + } + + function proxCheck() { + var ballPos = Entities.getEntityProperties(_entityID, ['position']).position; + var isAnyAvatarInRange = AvatarList.isAvatarInRange(ballPos, 1); + + if (isAnyAvatarInRange) { + clearProxCheck(); + particleExplode(); + } + } + + this.startDistanceGrab = function (thisEntityID, triggerHandAndAvatarUUIDArray) { + clearProxCheck(); + var triggerHand = triggerHandAndAvatarUUIDArray[0]; + var avatarUUID = triggerHandAndAvatarUUIDArray[1]; + + var ballPos = Entities.getEntityProperties(_entityID, ['position']).position; + var MAX_DISTANCE_GRAB = 2; //meter + + if (Vec3.distance(ballPos, AvatarList.getAvatar(avatarUUID).position) > MAX_DISTANCE_GRAB) { + Messages.sendMessage(FORCE_DROP_CHANNEL, triggerHand, true); + } + + }; + this.startNearGrab = function (thisEntityID, triggerHandAndAvatarUUIDArray) { + clearProxCheck(); + }; + + this.releaseGrab = function (thisEntityID) { + + if (particleTrailEntity === null) { + particleTrail(); + } + + Script.setTimeout(function () { + proxInterval = Script.setInterval(proxCheck, 50); + }, 200); // Setting a delay to give it time to leave initial avatar without proc. + + proxTimeout = Script.setTimeout(function () { + clearProxCheck(); + }, 10000) + }; + + this.collisionWithEntity = function (thisEntityID, collisionEntityID, collisionInfo) { + clearProxCheck(); + }; + +}); diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/dist/app-doppleganger-marketplace.js b/unpublishedScripts/marketplace/doppleganger-attachments/dist/app-doppleganger-marketplace.js index c2cf2a2353..bc5368ba5b 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/dist/app-doppleganger-marketplace.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/dist/app-doppleganger-marketplace.js @@ -599,6 +599,20 @@ Doppleganger.prototype = { return this.active; }, + // @private @method - get an avatar's "absolute joint translations in object frame" as local translations + // @param {AvatarData} - avatar to read translations from + // @return {glm::vec3[]} - the adapted translations + _getLocalAvatarJointTranslations: function(avatar) { + // NOTE: avatar.getJointTranslations() seems to return meters and avatar.getJointTranslation(jointIndex) centimeters... + // adapting meters -> centimeters on this side seems to fix the "scrunching into ball" problem (~Beta 54) + // and perform slightly faster than calling getJointTranslation(jointIndex) N times. + const CENTIMETERS_PER_METER = 100.0; + function scaleToMeters(v) { + return Vec3.multiply(CENTIMETERS_PER_METER, v); + } + return avatar.getJointTranslations().map(scaleToMeters); + }, + // @public @method - synchronize the joint data between Avatar / doppleganger update: function() { this.frame++; @@ -612,7 +626,7 @@ Doppleganger.prototype = { } var rotations = this.avatar.getJointRotations(); - var translations = this.avatar.getJointTranslations(); + var translations = this._getLocalAvatarJointTranslations(this.avatar); var size = rotations.length; // note: this mismatch can happen when the avatar's model is actively changing @@ -1488,13 +1502,13 @@ DebugControls.prototype = { /* 6 */ /***/ (function(module, exports) { -module.exports = "data:image/svg+xml;xml,\n\n\nimage/svg+xml\n\t.st0{fill:#FFFFFF;}\n"; +module.exports = "data:image/svg+xml;xml,\n\n\nimage/svg+xml\n\t.st0{fill:#FFFFFF;}\n" /***/ }), /* 7 */ /***/ (function(module, exports) { -module.exports = "data:image/svg+xml;xml,\n\n\nimage/svg+xml\n\t.st0{fill:#FFFFFF;}\n"; +module.exports = "data:image/svg+xml;xml,\n\n\nimage/svg+xml\n\t.st0{fill:#FFFFFF;}\n" /***/ }) /******/ ]); \ No newline at end of file diff --git a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js index 190a8aa69e..9e35d791d6 100644 --- a/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js +++ b/unpublishedScripts/marketplace/doppleganger-attachments/doppleganger.js @@ -78,6 +78,20 @@ Doppleganger.prototype = { return this.active; }, + // @private @method - get an avatar's "absolute joint translations in object frame" as local translations + // @param {AvatarData} - avatar to read translations from + // @return {glm::vec3[]} - the adapted translations + _getLocalAvatarJointTranslations: function(avatar) { + // NOTE: avatar.getJointTranslations() seems to return meters and avatar.getJointTranslation(jointIndex) centimeters... + // adapting meters -> centimeters on this side seems to fix the "scrunching into ball" problem (~Beta 54) + // and perform slightly faster than calling getJointTranslation(jointIndex) N times. + const CENTIMETERS_PER_METER = 100.0; + function scaleToMeters(v) { + return Vec3.multiply(CENTIMETERS_PER_METER, v); + } + return avatar.getJointTranslations().map(scaleToMeters); + }, + // @public @method - synchronize the joint data between Avatar / doppleganger update: function() { this.frame++; @@ -91,7 +105,7 @@ Doppleganger.prototype = { } var rotations = this.avatar.getJointRotations(); - var translations = this.avatar.getJointTranslations(); + var translations = this._getLocalAvatarJointTranslations(this.avatar); var size = rotations.length; // note: this mismatch can happen when the avatar's model is actively changing diff --git a/unpublishedScripts/marketplace/emoji-tablet/emojiTablet.js b/unpublishedScripts/marketplace/emoji-tablet/emojiTablet.js new file mode 100644 index 0000000000..b4d01e77cf --- /dev/null +++ b/unpublishedScripts/marketplace/emoji-tablet/emojiTablet.js @@ -0,0 +1,72 @@ +/// +/// emojiTablet.js +/// A tablet app for sending emojis to other users +/// +/// Author: Elisa Lupin-Jimenez +/// Copyright High Fidelity 2017 +/// +/// Licensed under the Apache 2.0 License +/// See accompanying license file or http://apache.org/ +/// +/// All assets are under CC Attribution Non-Commerical +/// http://creativecommons.org/licenses/ +/// + +var lib = Script.require("https://hifi-content.s3.amazonaws.com/elisalj/emoji_scripts/emojiLib.js?" + Date.now()); + +(function() { + + var APP_NAME = "EMOJIS"; + var APP_URL = "https://hifi-content.s3.amazonaws.com/elisalj/emoji_scripts/emojiTabletUI.html?" + Date.now(); + var APP_ICON = "https://hifi-content.s3.amazonaws.com/elisalj/emoji_scripts/icons/emoji-i.svg"; + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + + var button = tablet.addButton({ + icon: APP_ICON, + text: APP_NAME + }); + + // Activates tablet UI when selected from menu + function onClicked() { + tablet.gotoWebScreen(APP_URL); + }; + button.clicked.connect(onClicked); + + // Gives position right in front of user's avatar + function getPositionToCreateEntity() { + var direction = Quat.getFront(MyAvatar.orientation); + var distance = 0.3; + var position = Vec3.sum(MyAvatar.position, Vec3.multiply(direction, distance)); + position.y += 0.5; + return position; + }; + + var emojiJSON = null; + + // Handles emoji button clicks to retrieve the link to the emoji JSON from emojiLib + function onWebEventReceived(event) { + var emojiName = (JSON.parse(event)).data; + var url = lib.getEmoji(emojiName, lib.emojiLib); + if (url != null) { + emojiJSON = Script.require(url); + create3DEmoji(emojiJSON, null); + } else { + print("Unable to create emoji"); + } + }; + tablet.webEventReceived.connect(onWebEventReceived); + + function create3DEmoji(emojiJSON, userName) { + print("Creating " + emojiJSON.name + " emoji"); + emojiJSON.position = getPositionToCreateEntity(emojiJSON.personified); + var newEmoji = Entities.addEntity(emojiJSON); + }; + + // When tablet UI is closed and app is removed from menu + function cleanup() { + tablet.removeButton(button); + }; + Script.scriptEnding.connect(cleanup); + +}()); + diff --git a/unpublishedScripts/marketplace/record/record.js b/unpublishedScripts/marketplace/record/record.js index 5439d68c9a..68c7ea3f5a 100644 --- a/unpublishedScripts/marketplace/record/record.js +++ b/unpublishedScripts/marketplace/record/record.js @@ -486,6 +486,15 @@ return isFinishOnOpen; } + function onAssetsDirChanged(recording) { + Window.assetsDirChanged.disconnect(onAssetsDirChanged); + if (recording !== "") { + log("Load recording " + recording); + UserActivityLogger.logAction("record_load_recording", logDetails()); + Player.playRecording("atp:" + recording, MyAvatar.position, MyAvatar.orientation); + } + } + function onWebEventReceived(data) { var message, recording; @@ -520,12 +529,8 @@ break; case LOAD_RECORDING_ACTION: // User wants to select an ATP recording to play. - recording = Window.browseAssets("Select Recording to Play", "recordings", "*.hfr"); - if (recording) { - log("Load recording " + recording); - UserActivityLogger.logAction("record_load_recording", logDetails()); - Player.playRecording("atp:" + recording, MyAvatar.position, MyAvatar.orientation); - } + Window.assetsDirChanged.connect(onAssetsDirChanged); + Window.browseAssetsAsync("Select Recording to Play", "recordings", "*.hfr"); break; case START_RECORDING_ACTION: // Start making a recording. @@ -571,7 +576,7 @@ function onTabletScreenChanged(type, url) { // Opened/closed dialog in tablet or window. - var RECORD_URL = "/scripts/system/html/record.html"; + var RECORD_URL = "/html/record.html"; if (type === "Web" && url.slice(-RECORD_URL.length) === RECORD_URL) { if (Dialog.finishOnOpen()) { diff --git a/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js b/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js new file mode 100644 index 0000000000..e7a135ec9e --- /dev/null +++ b/unpublishedScripts/marketplace/skyboxChanger/skyboxchanger.js @@ -0,0 +1,118 @@ +"use strict"; + +// +// skyboxchanger.js +// +// Created by Cain Kilgore on 9th August 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + var TABLET_BUTTON_NAME = "SKYBOX"; + + var ICONS = { + icon: "http://mpassets.highfidelity.com/05904016-8f7d-4dfc-88e1-2bf9ba3fac20-v1/skyboxedit-i.svg", + activeIcon: "http://mpassets.highfidelity.com/05904016-8f7d-4dfc-88e1-2bf9ba3fac20-v1/skyboxedit-i.svg" + }; + + var onSkyboxChangerScreen = false; + + function onClicked() { + if (onSkyboxChangerScreen) { + tablet.gotoHomeScreen(); + } else { + tablet.loadQMLSource("../SkyboxChanger.qml"); + } + } + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + icon: ICONS.icon, + activeIcon: ICONS.activeIcon, + text: TABLET_BUTTON_NAME, + sortOrder: 1 + }); + + var hasEventBridge = false; + + function wireEventBridge(on) { + if (!tablet) { + print("Warning in wireEventBridge(): 'tablet' undefined!"); + return; + } + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } + } + + function onScreenChanged(type, url) { + if (url === "../SkyboxChanger.qml") { + onSkyboxChangerScreen = true; + } else { + onSkyboxChangerScreen = false; + } + + button.editProperties({isActive: onSkyboxChangerScreen}); + wireEventBridge(onSkyboxChangerScreen); + } + + function fromQml(message) { + switch (message.method) { + case 'changeSkybox': // changeSkybox Code + var standingZone; + if (!Entities.canRez()) { + Window.alert("You need to have rez permissions to change the Skybox."); + break; + } + + var nearbyEntities = Entities.findEntities(MyAvatar.position, 5); + for (var i = 0; i < nearbyEntities.length; i++) { + if (Entities.getEntityProperties(nearbyEntities[i]).type === "Zone") { + standingZone = nearbyEntities[i]; + } + } + + if (Entities.getEntityProperties(standingZone).locked) { + Window.alert("This zone is currently locked; the Skybox can't be changed."); + break; + } + + var newSkybox = { + skybox: { + url: message.url + }, + keyLight: { + ambientURL: message.url + } + }; + + Entities.editEntity(standingZone, newSkybox); + break; + default: + print('Unrecognized message from QML: ' + JSON.stringify(message)); + } + } + + button.clicked.connect(onClicked); + tablet.screenChanged.connect(onScreenChanged); + + Script.scriptEnding.connect(function () { + if (onSkyboxChangerScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + tablet.removeButton(button); + }); +}()); \ No newline at end of file diff --git a/unpublishedScripts/parent-ator/createParentator.js b/unpublishedScripts/parent-ator/createParentator.js new file mode 100644 index 0000000000..470b7d1845 --- /dev/null +++ b/unpublishedScripts/parent-ator/createParentator.js @@ -0,0 +1,66 @@ +// createParentator.js +// +// Script Type: Entity Spawner +// Created by Jeff Moyes on 6/30/2017 +// Copyright 2017 High Fidelity, Inc. +// +// This script creates a gun-looking item that, when tapped on an entity, and then a second entity, sets the second entity as the paernt of the first +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var scriptURL = Script.resolvePath('parentator.js'); +var MODEL_URL = Script.resolvePath('resources/Parent-Tool-Production.fbx'); +var COLLISION_HULL_URL = Script.resolvePath('resources/Parent-Tool-CollisionHull.obj'); + +// the fbx model needs to be rotated from where it would naturally face when it first initializes +var ROT_Y_180 = {x: 0, y: 1, z: 0, w: 0}; +var START_ROTATION = Quat.normalize(Quat.multiply(Camera.getOrientation(), ROT_Y_180)); +var START_POSITION = Vec3.sum(Vec3.sum(MyAvatar.position, { x: 0, y: 0.5, z: 0 }), Vec3.multiply(0.7, Quat.getForward(Camera.getOrientation()))); + + + +var parentator = Entities.addEntity({ + name: "Parent-ator", + type: "Model", + modelURL: MODEL_URL, + shapeType: 'compound', + compoundShapeURL: COLLISION_HULL_URL, + dynamic: true, + damping: 0.9, + script: scriptURL, + dimensions: { + x: 0.1270, + y: 0.2715, + z: 0.4672 + }, + position: START_POSITION, + rotation: START_ROTATION, + + + userData: JSON.stringify({ + "grabbableKey": {"grabbable": true}, + "equipHotspots": [ + { + "position": {"x": 0.0, "y": 0.0, "z": -0.170 }, + "radius": 0.15, + "joints":{ + "RightHand":[ + {"x":0.05, "y":0.25, "z":0.03}, + {"x":-0.5, "y":-0.5, "z":-0.5, "w":0.5} + ], + "LeftHand":[ + {"x":-0.05, "y":0.25, "z":0.03}, + {"x":-0.5, "y":0.5, "z":0.5, "w":0.5} + ] + } + } + ] + }) +}); + + +function cleanUp() { + Entities.deleteEntity(parentator); +} +Script.scriptEnding.connect(cleanUp); \ No newline at end of file diff --git a/unpublishedScripts/parent-ator/parentator.js b/unpublishedScripts/parent-ator/parentator.js new file mode 100644 index 0000000000..546ac38ba1 --- /dev/null +++ b/unpublishedScripts/parent-ator/parentator.js @@ -0,0 +1,152 @@ +// parentator.js +// +// Script Type: Entity +// Created by Jeff Moyes on 6/30/2017 +// Copyright 2017 High Fidelity, Inc. +// +// This script allows users to parent one object to another via the "parent-ator" entity +// (which looks like a purple gun-like object). The user: +// 1) equips their avatar with this parent-ator, +// 2) taps the end of the parent-ator on an entity (which becomes the child entity), and +// 3) taps the end of the parent-ator on a second entity (which becomes the parent entity) +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +(function() { + var MESSAGE_0_TEXTURE_URL = Script.resolvePath( 'resources/message-0-off.png' ); + var MESSAGE_1_TEXTURE_URL = Script.resolvePath( 'resources/message-1-start.png' ); + var MESSAGE_2_TEXTURE_URL = Script.resolvePath( 'resources/message-2-noperms.png' ); + var MESSAGE_3_TEXTURE_URL = Script.resolvePath( 'resources/message-3-tryagain.png' ); + var MESSAGE_4_TEXTURE_URL = Script.resolvePath( 'resources/message-4-setparent.png' ); + var MESSAGE_5_TEXTURE_URL = Script.resolvePath( 'resources/message-5-success.png' ); + + var SOUND_1_URL = Script.resolvePath( 'resources/parent-tool-sound1.wav' ); + var SOUND_2_URL = Script.resolvePath( 'resources/parent-tool-sound2.wav' ); + var SOUND_ERROR_URL = Script.resolvePath( 'resources/parent-tool-sound-error.wav' ); + var SOUND_SUCCESS_URL = Script.resolvePath( 'resources/parent-tool-sound-success.wav' ); + var SOUND_1, SOUND_2, SOUND_ERROR, SOUND_SUCCESS; + var childEntityID, parentEntityID; + var active = false; + + + + + function Parentator() { + return; + } + + Parentator.prototype.turnOff = function() { + childEntityID = 0; + parentEntityID = 0; + this.active = false; + Entities.editEntity( this.entityID, { textures: JSON.stringify({ "texture-message": MESSAGE_0_TEXTURE_URL }) }); + } + + Parentator.prototype.turnOn = function() { + childEntityID = 0; + parentEntityID = 0; + this.active = true; + if (Entities.canRez()) { + Entities.editEntity( this.entityID, { textures: JSON.stringify({ "texture-message": MESSAGE_1_TEXTURE_URL }) }); + this.playSoundAtCurrentPosition( SOUND_1 ); + } else { + Entities.editEntity( this.entityID, { textures: JSON.stringify({ "texture-message": MESSAGE_2_TEXTURE_URL }) }); + this.playSoundAtCurrentPosition( SOUND_ERROR ); + } + } + + + Parentator.prototype.preload = function( entityID ) { + this.entityID = entityID; + SOUND_1 = SoundCache.getSound( SOUND_1_URL ); + SOUND_2 = SoundCache.getSound( SOUND_2_URL ); + SOUND_ERROR = SoundCache.getSound( SOUND_ERROR_URL ); + SOUND_SUCCESS = SoundCache.getSound( SOUND_SUCCESS_URL ); + + // Makue sure it's off + this.turnOff(); + } + + Parentator.prototype.startEquip = function( args ) { + this.hand = args[0]; + this.turnOn(); + } + + + Parentator.prototype.releaseEquip = function( args ) { + this.hand = undefined; + this.turnOff(); + } + + Parentator.prototype.collisionWithEntity = function( parentatorID, collidedID, collisionInfo ) { + if ( this.active ) { + // We don't want to be able to select Lights, Zone, and Particles but they are not collidable anyway so we don't have to worry about them + var collidedEntityProperties = Entities.getEntityProperties( collidedID ); + + if ( !Entities.canRez() ) { + Entities.editEntity( this.entityID, { textures: JSON.stringify({ "texture-message": MESSAGE_2_TEXTURE_URL }) }); + this.playSoundAtCurrentPosition( SOUND_ERROR ); + } + + // User has just reclicked the first entity (or it's 'bounced') + if ( childEntityID == collidedID ) { + return; + } + + if ( collidedEntityProperties.locked ) { + Entities.editEntity( this.entityID, { textures: JSON.stringify({ "texture-message": MESSAGE_3_TEXTURE_URL }) }); + this.playSoundAtCurrentPosition( SOUND_ERROR ); + return; + } + + // If no entity has been chosen + if ( childEntityID == 0 ) { + childEntityID = collidedID; + + // if there is a parentID, remove it + if ( collidedEntityProperties.parentID != "{00000000-0000-0000-0000-000000000000}" ) { + Entities.editEntity( collidedID, { parentID: "{00000000-0000-0000-0000-000000000000}" }); + } + + if ( collidedEntityProperties.dynamic ) { + Entities.editEntity( collidedID, { dynamic: false }); + } + + Entities.editEntity( this.entityID, { textures: JSON.stringify({ "texture-message": MESSAGE_4_TEXTURE_URL }) }); + this.playSoundAtCurrentPosition( SOUND_2 ); + } else { + parentEntityID = collidedID; + this.setParent(); + } + } + } + + Parentator.prototype.setParent = function() { + var _this = this; + Entities.editEntity( childEntityID, { parentID: parentEntityID }); + Entities.editEntity( this.entityID, { textures: JSON.stringify({ "texture-message": MESSAGE_5_TEXTURE_URL }) }); + this.playSoundAtCurrentPosition( SOUND_SUCCESS ); + Script.setTimeout( function() { + _this.turnOn(); // reset + }, 5000 ); + } + + Parentator.prototype.playSoundAtCurrentPosition = function( sound ) { + var audioProperties = { + volume: 0.3, + position: Entities.getEntityProperties( this.entityID ).position, + localOnly: true + } + Audio.playSound( sound, audioProperties ); + } + + Parentator.prototype.unload = function () { + Entities.deleteEntity( this.entityID ); + } + + // entity scripts always need to return a newly constructed object of our type + return new Parentator(); +}); diff --git a/unpublishedScripts/parent-ator/resources/Parent-Tool-CollisionHull.obj b/unpublishedScripts/parent-ator/resources/Parent-Tool-CollisionHull.obj new file mode 100644 index 0000000000..0daf822605 --- /dev/null +++ b/unpublishedScripts/parent-ator/resources/Parent-Tool-CollisionHull.obj @@ -0,0 +1,38 @@ +# Blender v2.78 (sub 5) OBJ File: 'untitled.blend' +# www.blender.org +mtllib Parent-Tool-CollisionHull.mtl +o Cube.001 +v -0.153045 -0.072355 0.173769 +v -0.153045 0.040882 0.173769 +v -0.067387 0.040882 0.029156 +v -0.067387 -0.072355 0.029156 +v 0.062301 0.040882 0.029156 +v 0.062301 -0.072355 0.029156 +v 0.147960 -0.072355 0.173769 +v 0.147960 0.040882 0.173769 +v 0.147960 0.026770 0.337324 +v 0.147960 -0.047896 0.337324 +v -0.153045 0.026770 0.337324 +v -0.153045 -0.047896 0.337324 +vn -0.8604 0.0000 -0.5096 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -0.9890 0.1479 +vn 0.0000 0.9963 0.0860 +vn 0.0000 1.0000 0.0000 +vn 0.0000 -1.0000 -0.0000 +vn 0.8604 0.0000 -0.5096 +vn -1.0000 -0.0000 0.0000 +usemtl None +s 1 +f 1//1 2//1 3//1 4//1 +f 4//2 3//2 5//2 6//2 +f 7//3 8//3 9//3 10//3 +f 10//4 9//4 11//4 12//4 +f 1//5 7//5 10//5 12//5 +f 8//6 2//6 11//6 9//6 +f 5//7 3//7 2//7 8//7 +f 4//8 6//8 7//8 1//8 +f 6//9 5//9 8//9 7//9 +f 12//10 11//10 2//10 1//10 diff --git a/unpublishedScripts/parent-ator/resources/Parent-Tool-Production.fbx b/unpublishedScripts/parent-ator/resources/Parent-Tool-Production.fbx new file mode 100644 index 0000000000..3261bf62fd Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/Parent-Tool-Production.fbx differ diff --git a/unpublishedScripts/parent-ator/resources/message-0-off.png b/unpublishedScripts/parent-ator/resources/message-0-off.png new file mode 100644 index 0000000000..4985c8fda7 Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/message-0-off.png differ diff --git a/unpublishedScripts/parent-ator/resources/message-1-start.png b/unpublishedScripts/parent-ator/resources/message-1-start.png new file mode 100644 index 0000000000..43bf1654e6 Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/message-1-start.png differ diff --git a/unpublishedScripts/parent-ator/resources/message-2-noperms.png b/unpublishedScripts/parent-ator/resources/message-2-noperms.png new file mode 100644 index 0000000000..80b8f28ac2 Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/message-2-noperms.png differ diff --git a/unpublishedScripts/parent-ator/resources/message-3-tryagain.png b/unpublishedScripts/parent-ator/resources/message-3-tryagain.png new file mode 100644 index 0000000000..e85a5e4b17 Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/message-3-tryagain.png differ diff --git a/unpublishedScripts/parent-ator/resources/message-4-setparent.png b/unpublishedScripts/parent-ator/resources/message-4-setparent.png new file mode 100644 index 0000000000..c1e1143e0b Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/message-4-setparent.png differ diff --git a/unpublishedScripts/parent-ator/resources/message-5-success.png b/unpublishedScripts/parent-ator/resources/message-5-success.png new file mode 100644 index 0000000000..2ee88de911 Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/message-5-success.png differ diff --git a/unpublishedScripts/parent-ator/resources/parent-tool-sound-error.wav b/unpublishedScripts/parent-ator/resources/parent-tool-sound-error.wav new file mode 100644 index 0000000000..47115503dc Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/parent-tool-sound-error.wav differ diff --git a/unpublishedScripts/parent-ator/resources/parent-tool-sound-success.wav b/unpublishedScripts/parent-ator/resources/parent-tool-sound-success.wav new file mode 100644 index 0000000000..86cdd3e1e3 Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/parent-tool-sound-success.wav differ diff --git a/unpublishedScripts/parent-ator/resources/parent-tool-sound1.wav b/unpublishedScripts/parent-ator/resources/parent-tool-sound1.wav new file mode 100644 index 0000000000..1f0a2e6272 Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/parent-tool-sound1.wav differ diff --git a/unpublishedScripts/parent-ator/resources/parent-tool-sound2.wav b/unpublishedScripts/parent-ator/resources/parent-tool-sound2.wav new file mode 100644 index 0000000000..3ffa826b9f Binary files /dev/null and b/unpublishedScripts/parent-ator/resources/parent-tool-sound2.wav differ