diff --git a/.gitignore b/.gitignore index f5605d7090..3f58e46b69 100644 --- a/.gitignore +++ b/.gitignore @@ -14,11 +14,13 @@ Makefile # Android Studio *.iml +*.class local.properties android/gradle* android/.gradle android/**/src/main/jniLibs android/**/libs +android/**/bin android/**/src/main/res/values/libs.xml android/**/src/main/assets android/**/gradle* @@ -102,3 +104,4 @@ tools/unity-avatar-exporter/Logs tools/unity-avatar-exporter/Packages tools/unity-avatar-exporter/ProjectSettings tools/unity-avatar-exporter/Temp + diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e5dbe935a..d0a2e57dd5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ target_python() if (HIFI_ANDROID ) execute_process( - COMMAND ${HIFI_PYTHON_EXEC} ${CMAKE_CURRENT_SOURCE_DIR}/prebuild.py --android --build-root ${CMAKE_BINARY_DIR} + COMMAND ${HIFI_PYTHON_EXEC} ${CMAKE_CURRENT_SOURCE_DIR}/prebuild.py --android ${HIFI_ANDROID_APP} --build-root ${CMAKE_BINARY_DIR} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) else() @@ -174,7 +174,7 @@ set_packaging_parameters() # FIXME hack to work on the proper Android toolchain if (ANDROID) - add_subdirectory(android/app) + add_subdirectory(android/apps/${HIFI_ANDROID_APP}) return() endif() diff --git a/android/app/CMakeLists.txt b/android/apps/interface/CMakeLists.txt similarity index 61% rename from android/app/CMakeLists.txt rename to android/apps/interface/CMakeLists.txt index 19dce330c1..500d555915 100644 --- a/android/app/CMakeLists.txt +++ b/android/apps/interface/CMakeLists.txt @@ -4,10 +4,10 @@ link_hifi_libraries(shared task networking gl gpu qml image fbx hfm render-utils target_opengl() target_bullet() -set(INTERFACE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../interface") +set(INTERFACE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../interface") add_subdirectory("${INTERFACE_DIR}" "libraries/interface") include_directories("${INTERFACE_DIR}/src") -set(HIFI_CODEC_PLUGIN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../plugins/hifiCodec") +set(HIFI_CODEC_PLUGIN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../plugins/hifiCodec") add_subdirectory("${HIFI_CODEC_PLUGIN_DIR}" "libraries/hifiCodecPlugin") target_link_libraries(native-lib android log m interface) @@ -15,16 +15,3 @@ target_link_libraries(native-lib android log m interface) set(GVR_ROOT "${HIFI_ANDROID_PRECOMPILED}/gvr/gvr-android-sdk-1.101.0/") target_include_directories(native-lib PRIVATE "${GVR_ROOT}/libraries/headers" "libraries/ui/src") target_link_libraries(native-lib "${GVR_ROOT}/libraries/libgvr.so" ui) - -# finished libraries -# core -> qt -# networking -> openssl, tbb -# fbx -> draco -# physics -> bullet -# entities-renderer -> polyvox - -# unfinished libraries -# image -> nvtt (doesn't look good, but can be made optional) -# script-engine -> quazip (probably not required for the android client) - - diff --git a/android/app/build.gradle b/android/apps/interface/build.gradle similarity index 83% rename from android/app/build.gradle rename to android/apps/interface/build.gradle index e3c6989baf..4163df03b7 100644 --- a/android/app/build.gradle +++ b/android/apps/interface/build.gradle @@ -1,5 +1,37 @@ import org.apache.tools.ant.taskdefs.condition.Os +buildscript { + repositories { + jcenter() + google() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + } +} + +allprojects { + repositories { + jcenter() + google() + } +} + +task renameHifiACTaskDebug() { + doLast { + def sourceFile = new File("${appDir}/build/intermediates/cmake/debug/obj/arm64-v8a/","libhifiCodec.so") + def destinationFile = new File("${appDir}/src/main/jniLibs/arm64-v8a", "libplugins_libhifiCodec.so") + copy { from sourceFile; into destinationFile.parent; rename(sourceFile.name, destinationFile.name) } + } +} +task renameHifiACTaskRelease(type: Copy) { + doLast { + def sourceFile = new File("${appDir}/build/intermediates/cmake/release/obj/arm64-v8a/","libhifiCodec.so") + def destinationFile = new File("${appDir}/src/main/jniLibs/arm64-v8a", "libplugins_libhifiCodec.so") + copy { from sourceFile; into destinationFile.parent; rename(sourceFile.name, destinationFile.name) } + } +} + apply plugin: 'com.android.application' android { @@ -19,17 +51,17 @@ android { externalNativeBuild { cmake { arguments '-DHIFI_ANDROID=1', + '-DHIFI_ANDROID_APP=interface', '-DANDROID_PLATFORM=android-24', '-DANDROID_TOOLCHAIN=clang', '-DANDROID_STL=c++_shared', - '-DQT_CMAKE_PREFIX_PATH=' + HIFI_ANDROID_PRECOMPILED + '/qt/lib/cmake', - '-DHIFI_ANDROID_PRECOMPILED=' + HIFI_ANDROID_PRECOMPILED, '-DRELEASE_NUMBER=' + RELEASE_NUMBER, '-DRELEASE_TYPE=' + RELEASE_TYPE, '-DSTABLE_BUILD=' + STABLE_BUILD, '-DDISABLE_QML=OFF', '-DDISABLE_KTX_CACHE=OFF', '-DUSE_BREAKPAD=' + (System.getenv("CMAKE_BACKTRACE_URL") && System.getenv("CMAKE_BACKTRACE_TOKEN") ? 'ON' : 'OFF'); + targets = ['native-lib'] } } signingConfigs { @@ -72,7 +104,7 @@ android { externalNativeBuild { cmake { - path '../../CMakeLists.txt' + path '../../../CMakeLists.txt' } } @@ -82,6 +114,7 @@ android { variant.externalNativeBuildTasks.each { task -> variant.mergeResources.dependsOn(task) if (Os.isFamily(Os.FAMILY_UNIX)) { + // FIXME def uploadDumpSymsTask = rootProject.getTasksByName("uploadBreakpadDumpSyms${variant.name.capitalize()}", false).first() def runDumpSymsTask = rootProject.getTasksByName("runBreakpadDumpSyms${variant.name.capitalize()}", false).first() def renameHifiACTask = rootProject.getTasksByName("renameHifiACTask${variant.name.capitalize()}", false).first() @@ -97,7 +130,7 @@ android { // Copy the compiled resources generated by the external native build copy { - from new File(projectDir, "../../interface/compiledResources") + from new File(projectDir, "../../../interface/compiledResources") into outputDir duplicatesStrategy DuplicatesStrategy.INCLUDE eachFile { details -> @@ -108,7 +141,7 @@ android { // Copy the scripts directory copy { - from new File(projectDir, "../../scripts") + from new File(projectDir, "../../../scripts") into new File(outputDir, "scripts") duplicatesStrategy DuplicatesStrategy.INCLUDE eachFile { details-> @@ -123,12 +156,6 @@ android { assetList.each { file -> out.println(file) } } } - - variant.outputs.all { - if (RELEASE_NUMBER != '0') { - outputFileName = "app_" + RELEASE_NUMBER + "_" + RELEASE_TYPE + ".apk" - } - } } } @@ -157,5 +184,6 @@ dependencies { api 'com.sothree.slidinguppanel:library:3.4.0' - implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation fileTree(include: ['*.jar'], dir: '../../libraries/qt/libs') + implementation project(':qt') } diff --git a/android/app/proguard-rules.pro b/android/apps/interface/proguard-rules.pro similarity index 100% rename from android/app/proguard-rules.pro rename to android/apps/interface/proguard-rules.pro diff --git a/android/app/src/main/AndroidManifest.xml b/android/apps/interface/src/main/AndroidManifest.xml similarity index 100% rename from android/app/src/main/AndroidManifest.xml rename to android/apps/interface/src/main/AndroidManifest.xml diff --git a/android/app/src/main/assets/privacy_policy.html b/android/apps/interface/src/main/assets/privacy_policy.html similarity index 100% rename from android/app/src/main/assets/privacy_policy.html rename to android/apps/interface/src/main/assets/privacy_policy.html diff --git a/android/app/src/main/cpp/native.cpp b/android/apps/interface/src/main/cpp/native.cpp similarity index 99% rename from android/app/src/main/cpp/native.cpp rename to android/apps/interface/src/main/cpp/native.cpp index f9c7751a3e..2bb851bb85 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/apps/interface/src/main/cpp/native.cpp @@ -149,7 +149,7 @@ void unpackAndroidAssets() { extern "C" { -JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCreate(JNIEnv* env, jobject obj, jobject instance, jobject asset_mgr) { +JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCreate(JNIEnv* env, jobject instance, jobject asset_mgr) { g_assetManager = AAssetManager_fromJava(env, asset_mgr); qRegisterMetaType("QAndroidJniObject"); __interfaceActivity = QAndroidJniObject(instance); diff --git a/android/app/src/main/java/io/highfidelity/gvrinterface/InterfaceActivity.java b/android/apps/interface/src/main/java/io/highfidelity/gvrinterface/InterfaceActivity.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/gvrinterface/InterfaceActivity.java rename to android/apps/interface/src/main/java/io/highfidelity/gvrinterface/InterfaceActivity.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/BreakpadUploaderService.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/BreakpadUploaderService.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/BreakpadUploaderService.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/BreakpadUploaderService.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java similarity index 98% rename from android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java index 50aea59663..b7d2157737 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -61,7 +61,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW private HeadsetStateReceiver headsetStateReceiver; //public static native void handleHifiURL(String hifiURLString); - private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager); + private native void nativeOnCreate(AssetManager assetManager); private native void nativeOnDestroy(); private native void nativeGotoUrl(String url); private native void nativeGoToUser(String username); @@ -114,7 +114,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW assetManager = getResources().getAssets(); //nativeGvrApi = - nativeOnCreate(this, assetManager /*, gvrApi.getNativeGvrContext()*/); + nativeOnCreate(assetManager /*, gvrApi.getNativeGvrContext()*/); final View rootView = getWindow().getDecorView().findViewById(android.R.id.content); diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/LoginMenuActivity.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/LoginMenuActivity.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/LoginMenuActivity.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/LoginMenuActivity.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/MainActivity.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/MainActivity.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/WebViewActivity.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/WebViewActivity.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/WebViewActivity.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/WebViewActivity.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/LoginFragment.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/LoginFragment.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/fragment/LoginFragment.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/LoginFragment.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/OnBackPressedListener.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/OnBackPressedListener.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/fragment/OnBackPressedListener.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/OnBackPressedListener.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/PolicyFragment.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/PolicyFragment.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/fragment/PolicyFragment.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/PolicyFragment.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SignupFragment.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/SignupFragment.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SignupFragment.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/SignupFragment.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/StartMenuFragment.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/StartMenuFragment.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/fragment/StartMenuFragment.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/StartMenuFragment.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/WebViewFragment.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/WebViewFragment.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/fragment/WebViewFragment.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/WebViewFragment.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/Callback.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/Callback.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/provider/Callback.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/Callback.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/DomainProvider.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/DomainProvider.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/provider/DomainProvider.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/DomainProvider.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/task/DownloadProfileImageTask.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/task/DownloadProfileImageTask.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/task/DownloadProfileImageTask.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/task/DownloadProfileImageTask.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java diff --git a/android/app/src/main/res/drawable/default_profile_avatar.xml b/android/apps/interface/src/main/res/drawable/default_profile_avatar.xml similarity index 100% rename from android/app/src/main/res/drawable/default_profile_avatar.xml rename to android/apps/interface/src/main/res/drawable/default_profile_avatar.xml diff --git a/android/app/src/main/res/drawable/domain_placeholder.png b/android/apps/interface/src/main/res/drawable/domain_placeholder.png similarity index 100% rename from android/app/src/main/res/drawable/domain_placeholder.png rename to android/apps/interface/src/main/res/drawable/domain_placeholder.png diff --git a/android/app/src/main/res/drawable/encourage_login_background.jpg b/android/apps/interface/src/main/res/drawable/encourage_login_background.jpg similarity index 100% rename from android/app/src/main/res/drawable/encourage_login_background.jpg rename to android/apps/interface/src/main/res/drawable/encourage_login_background.jpg diff --git a/android/app/src/main/res/drawable/hifi_header.xml b/android/apps/interface/src/main/res/drawable/hifi_header.xml similarity index 100% rename from android/app/src/main/res/drawable/hifi_header.xml rename to android/apps/interface/src/main/res/drawable/hifi_header.xml diff --git a/android/app/src/main/res/drawable/hifi_logo_header.xml b/android/apps/interface/src/main/res/drawable/hifi_logo_header.xml similarity index 100% rename from android/app/src/main/res/drawable/hifi_logo_header.xml rename to android/apps/interface/src/main/res/drawable/hifi_logo_header.xml diff --git a/android/app/src/main/res/drawable/hifi_logo_splash.xml b/android/apps/interface/src/main/res/drawable/hifi_logo_splash.xml similarity index 100% rename from android/app/src/main/res/drawable/hifi_logo_splash.xml rename to android/apps/interface/src/main/res/drawable/hifi_logo_splash.xml diff --git a/android/app/src/main/res/drawable/ic_bookmark.xml b/android/apps/interface/src/main/res/drawable/ic_bookmark.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_bookmark.xml rename to android/apps/interface/src/main/res/drawable/ic_bookmark.xml diff --git a/android/app/src/main/res/drawable/ic_clear.xml b/android/apps/interface/src/main/res/drawable/ic_clear.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_clear.xml rename to android/apps/interface/src/main/res/drawable/ic_clear.xml diff --git a/android/app/src/main/res/drawable/ic_close.xml b/android/apps/interface/src/main/res/drawable/ic_close.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_close.xml rename to android/apps/interface/src/main/res/drawable/ic_close.xml diff --git a/android/app/src/main/res/drawable/ic_close_black_24dp.xml b/android/apps/interface/src/main/res/drawable/ic_close_black_24dp.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_close_black_24dp.xml rename to android/apps/interface/src/main/res/drawable/ic_close_black_24dp.xml diff --git a/android/app/src/main/res/drawable/ic_delete_black_24dp.xml b/android/apps/interface/src/main/res/drawable/ic_delete_black_24dp.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_delete_black_24dp.xml rename to android/apps/interface/src/main/res/drawable/ic_delete_black_24dp.xml diff --git a/android/app/src/main/res/drawable/ic_expand.xml b/android/apps/interface/src/main/res/drawable/ic_expand.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_expand.xml rename to android/apps/interface/src/main/res/drawable/ic_expand.xml diff --git a/android/app/src/main/res/drawable/ic_eye_noshow.xml b/android/apps/interface/src/main/res/drawable/ic_eye_noshow.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_eye_noshow.xml rename to android/apps/interface/src/main/res/drawable/ic_eye_noshow.xml diff --git a/android/app/src/main/res/drawable/ic_eye_show.xml b/android/apps/interface/src/main/res/drawable/ic_eye_show.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_eye_show.xml rename to android/apps/interface/src/main/res/drawable/ic_eye_show.xml diff --git a/android/app/src/main/res/drawable/ic_launcher.xml b/android/apps/interface/src/main/res/drawable/ic_launcher.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_launcher.xml rename to android/apps/interface/src/main/res/drawable/ic_launcher.xml diff --git a/android/app/src/main/res/drawable/ic_menu.xml b/android/apps/interface/src/main/res/drawable/ic_menu.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_menu.xml rename to android/apps/interface/src/main/res/drawable/ic_menu.xml diff --git a/android/app/src/main/res/drawable/ic_person.xml b/android/apps/interface/src/main/res/drawable/ic_person.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_person.xml rename to android/apps/interface/src/main/res/drawable/ic_person.xml diff --git a/android/app/src/main/res/drawable/ic_right_arrow.xml b/android/apps/interface/src/main/res/drawable/ic_right_arrow.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_right_arrow.xml rename to android/apps/interface/src/main/res/drawable/ic_right_arrow.xml diff --git a/android/app/src/main/res/drawable/ic_search.xml b/android/apps/interface/src/main/res/drawable/ic_search.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_search.xml rename to android/apps/interface/src/main/res/drawable/ic_search.xml diff --git a/android/app/src/main/res/drawable/ic_share.xml b/android/apps/interface/src/main/res/drawable/ic_share.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_share.xml rename to android/apps/interface/src/main/res/drawable/ic_share.xml diff --git a/android/app/src/main/res/drawable/ic_star.xml b/android/apps/interface/src/main/res/drawable/ic_star.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_star.xml rename to android/apps/interface/src/main/res/drawable/ic_star.xml diff --git a/android/app/src/main/res/drawable/ic_steam.xml b/android/apps/interface/src/main/res/drawable/ic_steam.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_steam.xml rename to android/apps/interface/src/main/res/drawable/ic_steam.xml diff --git a/android/app/src/main/res/drawable/ic_teleporticon.xml b/android/apps/interface/src/main/res/drawable/ic_teleporticon.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_teleporticon.xml rename to android/apps/interface/src/main/res/drawable/ic_teleporticon.xml diff --git a/android/app/src/main/res/drawable/launch_screen.xml b/android/apps/interface/src/main/res/drawable/launch_screen.xml similarity index 100% rename from android/app/src/main/res/drawable/launch_screen.xml rename to android/apps/interface/src/main/res/drawable/launch_screen.xml diff --git a/android/app/src/main/res/drawable/rounded_button_color1.xml b/android/apps/interface/src/main/res/drawable/rounded_button_color1.xml similarity index 100% rename from android/app/src/main/res/drawable/rounded_button_color1.xml rename to android/apps/interface/src/main/res/drawable/rounded_button_color1.xml diff --git a/android/app/src/main/res/drawable/rounded_button_color3.xml b/android/apps/interface/src/main/res/drawable/rounded_button_color3.xml similarity index 100% rename from android/app/src/main/res/drawable/rounded_button_color3.xml rename to android/apps/interface/src/main/res/drawable/rounded_button_color3.xml diff --git a/android/app/src/main/res/drawable/rounded_button_color4.xml b/android/apps/interface/src/main/res/drawable/rounded_button_color4.xml similarity index 100% rename from android/app/src/main/res/drawable/rounded_button_color4.xml rename to android/apps/interface/src/main/res/drawable/rounded_button_color4.xml diff --git a/android/app/src/main/res/drawable/rounded_secondary_button.xml b/android/apps/interface/src/main/res/drawable/rounded_secondary_button.xml similarity index 100% rename from android/app/src/main/res/drawable/rounded_secondary_button.xml rename to android/apps/interface/src/main/res/drawable/rounded_secondary_button.xml diff --git a/android/app/src/main/res/drawable/search_bg.xml b/android/apps/interface/src/main/res/drawable/search_bg.xml similarity index 100% rename from android/app/src/main/res/drawable/search_bg.xml rename to android/apps/interface/src/main/res/drawable/search_bg.xml diff --git a/android/app/src/main/res/drawable/selector_show_password.xml b/android/apps/interface/src/main/res/drawable/selector_show_password.xml similarity index 100% rename from android/app/src/main/res/drawable/selector_show_password.xml rename to android/apps/interface/src/main/res/drawable/selector_show_password.xml diff --git a/android/app/src/main/res/font/raleway.ttf b/android/apps/interface/src/main/res/font/raleway.ttf similarity index 100% rename from android/app/src/main/res/font/raleway.ttf rename to android/apps/interface/src/main/res/font/raleway.ttf diff --git a/android/app/src/main/res/font/raleway_bold.xml b/android/apps/interface/src/main/res/font/raleway_bold.xml similarity index 100% rename from android/app/src/main/res/font/raleway_bold.xml rename to android/apps/interface/src/main/res/font/raleway_bold.xml diff --git a/android/app/src/main/res/font/raleway_italic.xml b/android/apps/interface/src/main/res/font/raleway_italic.xml similarity index 100% rename from android/app/src/main/res/font/raleway_italic.xml rename to android/apps/interface/src/main/res/font/raleway_italic.xml diff --git a/android/app/src/main/res/font/raleway_light_italic.xml b/android/apps/interface/src/main/res/font/raleway_light_italic.xml similarity index 100% rename from android/app/src/main/res/font/raleway_light_italic.xml rename to android/apps/interface/src/main/res/font/raleway_light_italic.xml diff --git a/android/app/src/main/res/font/raleway_medium.xml b/android/apps/interface/src/main/res/font/raleway_medium.xml similarity index 100% rename from android/app/src/main/res/font/raleway_medium.xml rename to android/apps/interface/src/main/res/font/raleway_medium.xml diff --git a/android/app/src/main/res/font/raleway_semibold.xml b/android/apps/interface/src/main/res/font/raleway_semibold.xml similarity index 100% rename from android/app/src/main/res/font/raleway_semibold.xml rename to android/apps/interface/src/main/res/font/raleway_semibold.xml diff --git a/android/app/src/main/res/layout/activity_encourage_login.xml b/android/apps/interface/src/main/res/layout/activity_encourage_login.xml similarity index 100% rename from android/app/src/main/res/layout/activity_encourage_login.xml rename to android/apps/interface/src/main/res/layout/activity_encourage_login.xml diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/apps/interface/src/main/res/layout/activity_main.xml similarity index 100% rename from android/app/src/main/res/layout/activity_main.xml rename to android/apps/interface/src/main/res/layout/activity_main.xml diff --git a/android/app/src/main/res/layout/activity_splash.xml b/android/apps/interface/src/main/res/layout/activity_splash.xml similarity index 100% rename from android/app/src/main/res/layout/activity_splash.xml rename to android/apps/interface/src/main/res/layout/activity_splash.xml diff --git a/android/app/src/main/res/layout/activity_web_view.xml b/android/apps/interface/src/main/res/layout/activity_web_view.xml similarity index 100% rename from android/app/src/main/res/layout/activity_web_view.xml rename to android/apps/interface/src/main/res/layout/activity_web_view.xml diff --git a/android/app/src/main/res/layout/domain_view.xml b/android/apps/interface/src/main/res/layout/domain_view.xml similarity index 100% rename from android/app/src/main/res/layout/domain_view.xml rename to android/apps/interface/src/main/res/layout/domain_view.xml diff --git a/android/app/src/main/res/layout/fragment_friends.xml b/android/apps/interface/src/main/res/layout/fragment_friends.xml similarity index 100% rename from android/app/src/main/res/layout/fragment_friends.xml rename to android/apps/interface/src/main/res/layout/fragment_friends.xml diff --git a/android/app/src/main/res/layout/fragment_home.xml b/android/apps/interface/src/main/res/layout/fragment_home.xml similarity index 100% rename from android/app/src/main/res/layout/fragment_home.xml rename to android/apps/interface/src/main/res/layout/fragment_home.xml diff --git a/android/app/src/main/res/layout/fragment_login.xml b/android/apps/interface/src/main/res/layout/fragment_login.xml similarity index 100% rename from android/app/src/main/res/layout/fragment_login.xml rename to android/apps/interface/src/main/res/layout/fragment_login.xml diff --git a/android/app/src/main/res/layout/fragment_login_menu.xml b/android/apps/interface/src/main/res/layout/fragment_login_menu.xml similarity index 100% rename from android/app/src/main/res/layout/fragment_login_menu.xml rename to android/apps/interface/src/main/res/layout/fragment_login_menu.xml diff --git a/android/app/src/main/res/layout/fragment_policy.xml b/android/apps/interface/src/main/res/layout/fragment_policy.xml similarity index 100% rename from android/app/src/main/res/layout/fragment_policy.xml rename to android/apps/interface/src/main/res/layout/fragment_policy.xml diff --git a/android/app/src/main/res/layout/fragment_signup.xml b/android/apps/interface/src/main/res/layout/fragment_signup.xml similarity index 100% rename from android/app/src/main/res/layout/fragment_signup.xml rename to android/apps/interface/src/main/res/layout/fragment_signup.xml diff --git a/android/app/src/main/res/layout/fragment_web_view.xml b/android/apps/interface/src/main/res/layout/fragment_web_view.xml similarity index 100% rename from android/app/src/main/res/layout/fragment_web_view.xml rename to android/apps/interface/src/main/res/layout/fragment_web_view.xml diff --git a/android/app/src/main/res/layout/navigation_header.xml b/android/apps/interface/src/main/res/layout/navigation_header.xml similarity index 100% rename from android/app/src/main/res/layout/navigation_header.xml rename to android/apps/interface/src/main/res/layout/navigation_header.xml diff --git a/android/app/src/main/res/layout/user_item.xml b/android/apps/interface/src/main/res/layout/user_item.xml similarity index 100% rename from android/app/src/main/res/layout/user_item.xml rename to android/apps/interface/src/main/res/layout/user_item.xml diff --git a/android/app/src/main/res/layout/web_drawer.xml b/android/apps/interface/src/main/res/layout/web_drawer.xml similarity index 100% rename from android/app/src/main/res/layout/web_drawer.xml rename to android/apps/interface/src/main/res/layout/web_drawer.xml diff --git a/android/app/src/main/res/menu/menu_navigation.xml b/android/apps/interface/src/main/res/menu/menu_navigation.xml similarity index 100% rename from android/app/src/main/res/menu/menu_navigation.xml rename to android/apps/interface/src/main/res/menu/menu_navigation.xml diff --git a/android/app/src/main/res/menu/web_view_menu.xml b/android/apps/interface/src/main/res/menu/web_view_menu.xml similarity index 100% rename from android/app/src/main/res/menu/web_view_menu.xml rename to android/apps/interface/src/main/res/menu/web_view_menu.xml diff --git a/android/app/src/main/res/values-w385dp/dimens.xml b/android/apps/interface/src/main/res/values-w385dp/dimens.xml similarity index 100% rename from android/app/src/main/res/values-w385dp/dimens.xml rename to android/apps/interface/src/main/res/values-w385dp/dimens.xml diff --git a/android/app/src/main/res/values/colors.xml b/android/apps/interface/src/main/res/values/colors.xml similarity index 100% rename from android/app/src/main/res/values/colors.xml rename to android/apps/interface/src/main/res/values/colors.xml diff --git a/android/app/src/main/res/values/dimens.xml b/android/apps/interface/src/main/res/values/dimens.xml similarity index 100% rename from android/app/src/main/res/values/dimens.xml rename to android/apps/interface/src/main/res/values/dimens.xml diff --git a/android/app/src/main/res/values/font_certs.xml b/android/apps/interface/src/main/res/values/font_certs.xml similarity index 100% rename from android/app/src/main/res/values/font_certs.xml rename to android/apps/interface/src/main/res/values/font_certs.xml diff --git a/android/app/src/main/res/values/preloaded_fonts.xml b/android/apps/interface/src/main/res/values/preloaded_fonts.xml similarity index 100% rename from android/app/src/main/res/values/preloaded_fonts.xml rename to android/apps/interface/src/main/res/values/preloaded_fonts.xml diff --git a/android/app/src/main/res/values/strings.xml b/android/apps/interface/src/main/res/values/strings.xml similarity index 100% rename from android/app/src/main/res/values/strings.xml rename to android/apps/interface/src/main/res/values/strings.xml diff --git a/android/app/src/main/res/values/styles.xml b/android/apps/interface/src/main/res/values/styles.xml similarity index 100% rename from android/app/src/main/res/values/styles.xml rename to android/apps/interface/src/main/res/values/styles.xml diff --git a/android/app/src/main/res/xml/settings.xml b/android/apps/interface/src/main/res/xml/settings.xml similarity index 100% rename from android/app/src/main/res/xml/settings.xml rename to android/apps/interface/src/main/res/xml/settings.xml diff --git a/android/build.gradle b/android/build.gradle index 8d03b9f6b3..ed2ca1c47e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -10,8 +10,8 @@ import java.util.regex.Pattern buildscript { repositories { - jcenter() google() + jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.2.1' @@ -26,8 +26,8 @@ plugins { allprojects { repositories { - jcenter() google() + jcenter() mavenCentral() } } @@ -42,378 +42,13 @@ ext { RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV' STABLE_BUILD = project.hasProperty('STABLE_BUILD') ? project.getProperty('STABLE_BUILD') : '0' EXEC_SUFFIX = Os.isFamily(Os.FAMILY_WINDOWS) ? '.exe' : '' - QT5_DEPS = [ - 'Qt5Concurrent', - 'Qt5Core', - 'Qt5Gui', - 'Qt5Multimedia', - 'Qt5Network', - 'Qt5OpenGL', - 'Qt5Qml', - 'Qt5Quick', - 'Qt5QuickControls2', - 'Qt5QuickTemplates2', - 'Qt5Script', - 'Qt5ScriptTools', - 'Qt5Svg', - 'Qt5WebChannel', - 'Qt5WebSockets', - 'Qt5Widgets', - 'Qt5XmlPatterns', - // Android specific - 'Qt5AndroidExtras', - 'Qt5WebView', - ] } -def baseFolder = new File(HIFI_ANDROID_PRECOMPILED) -def appDir = new File(projectDir, 'app') +def appDir = new File(projectDir, 'apps/interface') def jniFolder = new File(appDir, 'src/main/jniLibs/arm64-v8a') def baseUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/android/' def breakpadDumpSymsDir = new File("${appDir}/build/tmp/breakpadDumpSyms") -def qtFile='qt-5.11.1_linux_armv8-libcpp_openssl_patched.tgz' -def qtChecksum='aa449d4bfa963f3bc9a9dfe558ba29df' -def qtVersionId='3S97HBM5G5Xw9EfE52sikmgdN3t6C2MN' -if (Os.isFamily(Os.FAMILY_MAC)) { - qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl_patched.tgz' - qtChecksum='c83cc477c08a892e00c71764dca051a0' - qtVersionId='OxBD7iKINv1HbyOXmAmDrBb8AF3N.Kup' -} else if (Os.isFamily(Os.FAMILY_WINDOWS)) { - qtFile = 'qt-5.11.1_win_armv8-libcpp_openssl_patched.tgz' - qtChecksum='0582191cc55431aa4f660848a542883e' - qtVersionId='JfWM0P_Mz5Qp0LwpzhrsRwN3fqlLSFeT' -} - -def packages = [ - qt: [ - file: qtFile, - versionId: qtVersionId, - checksum: qtChecksum, - ], - bullet: [ - file: 'bullet-2.88_armv8-libcpp.tgz', - versionId: 'S8YaoED0Cl8sSb8fSV7Q2G1lQJSNDxqg', - checksum: '81642779ccb110f8c7338e8739ac38a0', - ], - draco: [ - file: 'draco_armv8-libcpp.tgz', - versionId: '3.B.uBj31kWlgND3_R2xwQzT_TP6Dz_8', - checksum: '617a80d213a5ec69fbfa21a1f2f738cd', - ], - glad: [ - file: 'glad_armv8-libcpp.zip', - versionId: 'r5Zran.JSCtvrrB6Q4KaqfIoALPw3lYY', - checksum: 'a8ee8584cf1ccd34766c7ddd9d5e5449', - ], - gvr: [ - file: 'gvrsdk_v1.101.0.tgz', - versionId: 'nqBV_j81Uc31rC7bKIrlya_Hah4v3y5r', - checksum: '57fd02baa069176ba18597a29b6b4fc7', - ], - nvtt: [ - file: 'nvtt_armv8-libcpp.zip', - versionId: 'lmkBVR5t4UF1UUwMwEirnk9H_8Nt90IO', - checksum: 'eb46d0b683e66987190ed124aabf8910', - sharedLibFolder: 'lib', - includeLibs: ['libnvtt.so', 'libnvmath.so', 'libnvimage.so', 'libnvcore.so'], - ], - openssl: [ - file: 'openssl-1.1.0g_armv8.tgz', - versionId: 'AiiPjmgUZTgNj7YV1EEx2lL47aDvvvAW', - checksum: 'cabb681fbccd79594f65fcc266e02f32', - ], - polyvox: [ - file: 'polyvox_armv8-libcpp.tgz', - versionId: 'A2kbKiNhpIenGq23bKRRzg7IMAI5BI92', - checksum: 'dba88b3a098747af4bb169e9eb9af57e', - sharedLibFolder: 'lib', - includeLibs: ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so'], - ], - tbb: [ - file: 'tbb-2018_U1_armv8_libcpp.tgz', - versionId: 'mrRbWnv4O4evcM1quRH43RJqimlRtaKB', - checksum: '20768f298f53b195e71b414b0ae240c4', - sharedLibFolder: 'lib/release', - includeLibs: ['libtbb.so', 'libtbbmalloc.so'], - ], - hifiAC: [ - baseUrl: 'http://s3.amazonaws.com/hifi-public/dependencies/', - file: 'codecSDK-android_armv8-2.0.zip', - checksum: '1cbef929675818fc64c4101b72f84a6a' - ], - etc2comp: [ - file: 'etc2comp-patched-armv8-libcpp.tgz', - versionId: 'bHhGECRAQR1vkpshBcK6ByNc1BQIM8gU', - checksum: '14b02795d774457a33bbc60e00a786bc' - ], - breakpad: [ - file: 'breakpad.tgz', - versionId: '8VrYXz7oyc.QBxNia0BVJOUBvrFO61jI', - checksum: 'ddcb23df336b08017042ba4786db1d9e', - sharedLibFolder: 'lib', - includeLibs: ['libbreakpad_client.a'] - ] -] - -def options = [ - files: new TreeSet(), - features: new HashSet(), - permissions: new HashSet() -] - -def qmlRoot = new File(HIFI_ANDROID_PRECOMPILED, 'qt') - -def captureOutput = { String command, List commandArgs -> - def result - new ByteArrayOutputStream().withStream { os -> - def execResult = exec { - executable = command - args = commandArgs - standardOutput = os - errorOutput = new ByteArrayOutputStream() - } - result = os.toString() - } - return result; -} - -def relativize = { File root, File absolute -> - def relativeURI = root.toURI().relativize(absolute.toURI()) - return new File(relativeURI.toString()) -} - -def scanQmlImports = { File qmlRootPath -> - def qmlImportCommandFile = new File(qmlRoot, 'bin/qmlimportscanner' + EXEC_SUFFIX) - if (!qmlImportCommandFile.exists()) { - throw new GradleException('Unable to find required qmlimportscanner executable at ' + qmlImportCommandFile.parent.toString()) - } - - def command = qmlImportCommandFile.absolutePath - def args = [ - '-rootPath', qmlRootPath.absolutePath, - '-importPath', "${qmlRoot.absolutePath}/qml" - ] - - def commandResult = captureOutput(command, args) - new JsonSlurper().parseText(commandResult).each { - if (!it.containsKey('path')) { - println "Warning: QML import could not be resolved in any of the import paths: ${it.name}" - return - } - def file = new File(it.path) - // Ignore non-existent files - if (!file.exists()) { - return - } - // Ignore files in the import path - if (file.canonicalPath.startsWith(qmlRootPath.canonicalPath)) { - return - } - if (file.isFile()) { - options.files.add(file) - } else { - file.eachFileRecurse(FileType.FILES, { - options.files.add(it) - }) - } - } -} - -def parseQtDependencies = { List qtLibs -> - qtLibs.each({ - def libFile = new File(qmlRoot, "lib/lib${it}.so") - options.files.add(libFile) - - def androidDeps = new File(qmlRoot, "lib/${it}-android-dependencies.xml") - if (!libFile.exists()) return - if (!androidDeps.exists()) return - - new XmlSlurper().parse(androidDeps).dependencies.lib.depends.'*'.each{ node -> - switch (node.name()) { - case 'lib': - case 'bundled': - def relativeFilename = node.@file.toString() - - // Special case, since this is handled by qmlimportscanner instead - if (relativeFilename.startsWith('qml')) - return - - def file = new File(qmlRoot, relativeFilename) - - if (!file.exists()) - return - - if (file.isFile()) { - options.files.add(file) - } else { - file.eachFileRecurse(FileType.FILES, { options.files.add(it) }) - } - break - - - case 'jar': - if (node.@bundling == "1") { - def jar = new File(qmlRoot, node.@file.toString()) - if (!jar.exists()) { - throw new GradleException('Unable to find required JAR ' + jar.path) - } - options.files.add(jar) - } - break - - case 'permission': - options.permissions.add(node.@name) - break - - case 'feature': - options.features.add(node.@name) - break - - default: - throw new GradleException('Unhandled Android Dependency node ' + node.name()) - } - } - }) -} - -def generateLibsXml = { - def libDestinationDirectory = jniFolder - def jarDestinationDirectory = new File(appDir, 'libs') - def assetDestinationDirectory = new File(appDir, 'src/main/assets/--Added-by-androiddeployqt--'); - def libsXmlFile = new File(appDir, 'src/main/res/values/libs.xml') - def libPrefix = 'lib' + File.separator - def jarPrefix = 'jar' + File.separator - - def xmlParser = new XmlParser() - def libsXmlRoot = xmlParser.parseText('') - def qtLibsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'qt_libs']) - def bundledLibsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'bundled_in_lib']) - def bundledAssetsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'bundled_in_assets']) - - options.files.each { - def sourceFile = it - if (!sourceFile.exists()) { - throw new GradleException("Unable to find dependency file " + sourceFile.toString()) - } - - def relativePath = relativize( qmlRoot, sourceFile ).toString() - def destinationFile - if (relativePath.endsWith('.so')) { - def garbledFileName - if (relativePath.startsWith(libPrefix)) { - garbledFileName = relativePath.substring(libPrefix.size()) - Pattern p = ~/lib(Qt5.*).so/ - Matcher m = p.matcher(garbledFileName) - assert m.matches() - def libName = m.group(1) - xmlParser.createNode(qtLibsNode, 'item', [:]).setValue(libName) - } else { - garbledFileName = 'lib' + relativePath.replace(File.separator, '_'[0]) - xmlParser.createNode(bundledLibsNode, 'item', [:]).setValue("${garbledFileName}:${relativePath}".replace(File.separator, '/')) - } - destinationFile = new File(libDestinationDirectory, garbledFileName) - } else if (relativePath.startsWith('jar')) { - destinationFile = new File(jarDestinationDirectory, relativePath.substring(jarPrefix.size())) - } else { - xmlParser.createNode(bundledAssetsNode, 'item', [:]).setValue("--Added-by-androiddeployqt--/${relativePath}:${relativePath}".replace(File.separator, '/')) - destinationFile = new File(assetDestinationDirectory, relativePath) - } - - copy { from sourceFile; into destinationFile.parent; rename(sourceFile.name, destinationFile.name) } - assert destinationFile.exists() && destinationFile.isFile() - } - def xml = XmlUtil.serialize(libsXmlRoot) - new FileWriter(libsXmlFile).withPrintWriter { writer -> - writer.write(xml) - } -} - -task downloadDependencies { - doLast { - packages.each { entry -> - def filename = entry.value['file']; - def dependencyBaseUrl = entry.value['baseUrl'] - def url = (dependencyBaseUrl?.trim() ? dependencyBaseUrl : baseUrl) + filename; - if (entry.value.containsKey('versionId')) { - url = url + '?versionId=' + entry.value['versionId'] - } - download { - src url - dest new File(baseFolder, filename) - onlyIfNewer true - } - } - } -} - -task verifyQt(type: Verify) { def p = packages['qt']; src new File(baseFolder, p['file']); checksum p['checksum']; } -task verifyBullet(type: Verify) { def p = packages['bullet']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyDraco(type: Verify) { def p = packages['draco']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyGvr(type: Verify) { def p = packages['gvr']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyOpenSSL(type: Verify) { def p = packages['openssl']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyPolyvox(type: Verify) { def p = packages['polyvox']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyTBB(type: Verify) { def p = packages['tbb']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyHifiAC(type: Verify) { def p = packages['hifiAC']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyEtc2Comp(type: Verify) { def p = packages['etc2comp']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyBreakpad(type: Verify) { def p = packages['breakpad']; src new File(baseFolder, p['file']); checksum p['checksum'] } - -task verifyDependencyDownloads(dependsOn: downloadDependencies) { } -verifyDependencyDownloads.dependsOn verifyQt -verifyDependencyDownloads.dependsOn verifyBullet -verifyDependencyDownloads.dependsOn verifyDraco -verifyDependencyDownloads.dependsOn verifyGvr -verifyDependencyDownloads.dependsOn verifyOpenSSL -verifyDependencyDownloads.dependsOn verifyPolyvox -verifyDependencyDownloads.dependsOn verifyTBB -verifyDependencyDownloads.dependsOn verifyHifiAC -verifyDependencyDownloads.dependsOn verifyEtc2Comp -verifyDependencyDownloads.dependsOn verifyBreakpad - -task extractDependencies(dependsOn: verifyDependencyDownloads) { - doLast { - packages.each { entry -> - def folder = entry.key - def filename = entry.value['file'] - def localFile = new File(HIFI_ANDROID_PRECOMPILED, filename) - def localFolder = new File(HIFI_ANDROID_PRECOMPILED, folder) - def fileTree; - if (filename.endsWith('zip')) { - fileTree = zipTree(localFile) - } else { - fileTree = tarTree(resources.gzip(localFile)) - } - copy { - from fileTree - into localFolder - } - } - } -} - -// Copies the non Qt dependencies. Qt dependencies (primary libraries and plugins) are handled by the qtBundle task -task copyDependencies() { - doLast { - packages.each { entry -> - def packageName = entry.key - def currentPackage = entry.value; - if (currentPackage.containsKey('sharedLibFolder')) { - def localFolder = new File(baseFolder, packageName + '/' + currentPackage['sharedLibFolder']) - def tree = fileTree(localFolder); - if (currentPackage.containsKey('includeLibs')) { - currentPackage['includeLibs'].each { includeSpec -> tree.include includeSpec } - } - tree.visit { element -> - if (!element.file.isDirectory()) { - println "Copying " + element.file + " to " + jniFolder - copy { from element.file; into jniFolder } - } - } - } - } - } -} - task extractGvrBinaries() { doLast { def gvrLibFolder = new File(HIFI_ANDROID_PRECOMPILED, 'gvr/gvr-android-sdk-1.101.0/libraries'); @@ -500,13 +135,11 @@ task qtBundle { } } -task setupDependencies(dependsOn: [copyDependencies, extractGvrBinaries, qtBundle]) { } +task setupDependencies() { + // migrated to python +} task cleanDependencies(type: Delete) { - delete HIFI_ANDROID_PRECOMPILED - delete 'app/src/main/jniLibs/arm64-v8a' - delete 'app/src/main/assets/--Added-by-androiddeployqt--' - delete 'app/src/main/res/values/libs.xml' } def runBreakpadDumpSyms = { buildType -> diff --git a/android/build_android.sh b/android/build_android.sh index 189e6099a8..9c68b8969b 100755 --- a/android/build_android.sh +++ b/android/build_android.sh @@ -1,4 +1,11 @@ #!/usr/bin/env bash set -xeuo pipefail -./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} setupDependencies -./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} app:${ANDROID_BUILD_TARGET} \ No newline at end of file +./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} ${ANDROID_APP}:${ANDROID_BUILD_TARGET} + +# This is the actual output from gradle, which no longer attempts to muck with the naming of the APK +OUTPUT_APK=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_DIR}/${ANDROID_APP}-${ANDROID_BUILD_DIR}.apk +# This is the APK name requested by Jenkins +TARGET_APK=./${ANDROID_APK_NAME} +# Make sure this matches up with the new ARTIFACT_EXPRESSION for jenkins builds, which should be "android/*.apk" +cp ${OUTPUT_APK} ${TARGET_APK} + diff --git a/android/containerized_build.sh b/android/containerized_build.sh index e5ec895146..42118a8e38 100755 --- a/android/containerized_build.sh +++ b/android/containerized_build.sh @@ -5,12 +5,21 @@ DOCKER_IMAGE_NAME="hifi_androidbuild" docker build --build-arg BUILD_UID=`id -u` -t "${DOCKER_IMAGE_NAME}" -f docker/Dockerfile docker +# The Jenkins PR builds use VERSION_CODE, but the release builds use VERSION +# So make sure we use VERSION_CODE consistently +if [-z "$VERSION_CODE"]; then + export VERSION_CODE=$VERSION +fi + docker run \ --rm \ - --security-opt seccomp:unconfined \ + --security-opt seccomp:unconfined \ -v "${WORKSPACE}":/home/jenkins/hifi \ + -v /home/jenkins/.gradle:/home/jenkins/.gradle \ -e RELEASE_NUMBER \ -e RELEASE_TYPE \ + -e ANDROID_APP \ + -e ANDROID_APK_NAME \ -e ANDROID_BUILD_TARGET \ -e ANDROID_BUILD_DIR \ -e CMAKE_BACKTRACE_URL \ diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 2a6943cbc2..c37f73cb2a 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -26,10 +26,9 @@ RUN mkdir -p "$ANDROID_HOME" "$ANDROID_SDK_HOME" && \ curl -s -S -o sdk.zip -L "${SDK_URL}" && \ unzip sdk.zip && \ rm sdk.zip && \ - yes | $ANDROID_HOME/tools/bin/sdkmanager --licenses + yes | $ANDROID_HOME/tools/bin/sdkmanager --licenses && yes | $ANDROID_HOME/tools/bin/sdkmanager --update # Install Android Build Tool and Libraries -RUN $ANDROID_HOME/tools/bin/sdkmanager --update RUN $ANDROID_HOME/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" \ "platforms;android-${ANDROID_VERSION}" \ "platform-tools" @@ -52,11 +51,14 @@ ENV PATH ${PATH}:${ANDROID_NDK_HOME} RUN apt-get -y install \ g++ \ gcc \ + sudo \ + emacs-nox \ - # --- Gradle ARG BUILD_UID=1001 RUN useradd -ms /bin/bash -u $BUILD_UID jenkins +RUN echo "jenkins ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers USER jenkins WORKDIR /home/jenkins @@ -71,22 +73,21 @@ RUN mkdir "$HIFI_BASE" && \ RUN git clone https://github.com/jherico/hifi.git && \ cd ~/hifi && \ - git checkout feature/build/gradle-wrapper - + git checkout feature/quest_move_interface WORKDIR /home/jenkins/hifi -RUN touch .test4 && \ - git fetch && git reset origin/feature/build/gradle-wrapper --hard +RUN touch .test6 && \ + git fetch && git reset origin/feature/quest_move_interface --hard RUN mkdir build # Pre-cache the vcpkg managed dependencies WORKDIR /home/jenkins/hifi/build -RUN python3 ../prebuild.py --build-root `pwd` --android +RUN python3 ../prebuild.py --build-root `pwd` --android interface # Pre-cache the gradle dependencies WORKDIR /home/jenkins/hifi/android RUN ./gradlew -m tasks -PHIFI_ANDROID_PRECOMPILED=$HIFI_ANDROID_PRECOMPILED -RUN ./gradlew extractDependencies -PHIFI_ANDROID_PRECOMPILED=$HIFI_ANDROID_PRECOMPILED +#RUN ./gradlew extractDependencies -PHIFI_ANDROID_PRECOMPILED=$HIFI_ANDROID_PRECOMPILED diff --git a/android/docker/update.txt b/android/docker/update.txt new file mode 100644 index 0000000000..a12c215a06 --- /dev/null +++ b/android/docker/update.txt @@ -0,0 +1,13 @@ +git fetch +git checkout feature/quest_move_interface +export VERSION_CODE=1 +export RELEASE_NUMBER=1 +export RELEASE_TYPE=DEV +export ANDROID_APP=interface +touch ~/.gradle/gradle.properties +echo HIFI_ANDROID_KEYSTORE=/home/jenkins/keystore.jks > ~/.gradle/gradle.properties +echo HIFI_ANDROID_KEYSTORE_PASSWORD=password >> ~/.gradle/gradle.properties +echo HIFI_ANDROID_KEY_ALIAS=key0 >> ~/.gradle/gradle.properties +echo HIFI_ANDROID_KEY_PASSWORD=password >> ~/.gradle/gradle.properties +./build_android.sh +cp ./apps/${ANDROID_APP}/build/outputs/apk/release/${ANDROID_APP}-release.apk ${ANDROID_APP}.apk \ No newline at end of file diff --git a/android/libraries/qt/build.gradle b/android/libraries/qt/build.gradle new file mode 100644 index 0000000000..e6141f4cdf --- /dev/null +++ b/android/libraries/qt/build.gradle @@ -0,0 +1,22 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 28 + + defaultConfig { + minSdkVersion 24 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + api 'com.google.guava:guava:23.0' +} diff --git a/android/libraries/qt/src/main/AndroidManifest.xml b/android/libraries/qt/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..c6638c09e8 --- /dev/null +++ b/android/libraries/qt/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/android/libraries/qt/src/main/java/io/highfidelity/utils/HifiUtils.java b/android/libraries/qt/src/main/java/io/highfidelity/utils/HifiUtils.java new file mode 100644 index 0000000000..e8e9f04d9f --- /dev/null +++ b/android/libraries/qt/src/main/java/io/highfidelity/utils/HifiUtils.java @@ -0,0 +1,69 @@ + +package io.highfidelity.utils; + +import android.content.res.AssetManager; + +import com.google.common.io.ByteStreams; +import com.google.common.io.Files; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.LinkedList; + +public class HifiUtils { + + private static LinkedList readAssetLines(AssetManager assetManager, String asset) throws IOException { + LinkedList assets = new LinkedList<>(); + InputStream is = assetManager.open(asset); + BufferedReader in = new BufferedReader(new InputStreamReader(is, "UTF-8")); + String line; + while ((line=in.readLine()) != null) { + assets.add(line); + } + in.close(); + return assets; + } + + private static void copyAsset(AssetManager assetManager, String asset, String destFileName) throws IOException { + try (InputStream is = assetManager.open(asset)) { + try (OutputStream os = Files.asByteSink(new File(destFileName)).openStream()) { + ByteStreams.copy(is, os); + } + } + } + + public static void upackAssets(AssetManager assetManager, String destDir) { + try { + if (!destDir.endsWith("/")) + destDir = destDir + "/"; + LinkedList assets = readAssetLines(assetManager, "cache_assets.txt"); + String dateStamp = assets.poll(); + String dateStampFilename = destDir + dateStamp; + File dateStampFile = new File(dateStampFilename); + if (dateStampFile.exists()) { + return; + } + for (String fileToCopy : assets) { + String destFileName = destDir + fileToCopy; + { + File destFile = new File(destFileName); + File destFolder = destFile.getParentFile(); + if (!destFolder.exists()) { + destFolder.mkdirs(); + } + if (destFile.exists()) { + destFile.delete(); + } + } + copyAsset(assetManager, fileToCopy, destFileName); + } + Files.write("touch".getBytes(), dateStampFile); + } catch (IOException e){ + throw new RuntimeException(e); + } + } +} diff --git a/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java b/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java similarity index 100% rename from android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java rename to android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java diff --git a/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivityLoader.java b/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivityLoader.java similarity index 100% rename from android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivityLoader.java rename to android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivityLoader.java diff --git a/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtApplication.java b/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtApplication.java similarity index 100% rename from android/app/src/main/java/org/qtproject/qt5/android/bindings/QtApplication.java rename to android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtApplication.java diff --git a/android/settings.gradle b/android/settings.gradle index e7b4def49c..40b5eb44bf 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1 +1,5 @@ -include ':app' +include ':qt' +project(':qt').projectDir = new File(settingsDir, 'libraries/qt') + +include ':interface' +project(':interface').projectDir = new File(settingsDir, 'apps/interface') diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index cc2973f61d..5c644cb132 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -196,7 +196,8 @@ void Agent::run() { connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &Agent::nodeKilled); nodeList->addSetOfNodeTypesToNodeInterestSet({ - NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::EntityServer, NodeType::MessagesMixer, NodeType::AssetServer + NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::EntityServer, + NodeType::MessagesMixer, NodeType::AssetServer, NodeType::EntityScriptServer }); } @@ -505,16 +506,6 @@ void Agent::executeScript() { DependencyManager::set(_entityViewer.getTree()); - // Agents should run at 45hz - static const int AVATAR_DATA_HZ = 45; - static const int AVATAR_DATA_IN_MSECS = MSECS_PER_SECOND / AVATAR_DATA_HZ; - QTimer* avatarDataTimer = new QTimer(this); - connect(avatarDataTimer, &QTimer::timeout, this, &Agent::processAgentAvatar); - avatarDataTimer->setSingleShot(false); - avatarDataTimer->setInterval(AVATAR_DATA_IN_MSECS); - avatarDataTimer->setTimerType(Qt::PreciseTimer); - avatarDataTimer->start(); - _scriptEngine->run(); Frame::clearFrameHandler(AUDIO_FRAME_TYPE); @@ -528,8 +519,6 @@ void Agent::executeScript() { recordingInterface->stopRecording(); } - avatarDataTimer->stop(); - setIsAvatar(false); // will stop timers for sending identity packets } @@ -584,20 +573,16 @@ void Agent::setIsAvatar(bool isAvatar) { auto scriptableAvatar = DependencyManager::get(); if (_isAvatar) { - if (!_avatarIdentityTimer) { + if (!_avatarQueryTimer) { // set up the avatar timers - _avatarIdentityTimer = new QTimer(this); _avatarQueryTimer = new QTimer(this); // connect our slot - connect(_avatarIdentityTimer, &QTimer::timeout, this, &Agent::sendAvatarIdentityPacket); connect(_avatarQueryTimer, &QTimer::timeout, this, &Agent::queryAvatars); - static const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000; static const int AVATAR_VIEW_PACKET_SEND_INTERVAL_MSECS = 1000; - // start the timers - _avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); // FIXME - we shouldn't really need to constantly send identity packets + // start the timer _avatarQueryTimer->start(AVATAR_VIEW_PACKET_SEND_INTERVAL_MSECS); connect(_scriptEngine.data(), &ScriptEngine::update, @@ -609,11 +594,7 @@ void Agent::setIsAvatar(bool isAvatar) { _entityEditSender.setMyAvatar(scriptableAvatar.data()); } else { - if (_avatarIdentityTimer) { - _avatarIdentityTimer->stop(); - delete _avatarIdentityTimer; - _avatarIdentityTimer = nullptr; - + if (_avatarQueryTimer) { _avatarQueryTimer->stop(); delete _avatarQueryTimer; _avatarQueryTimer = nullptr; @@ -646,14 +627,6 @@ void Agent::setIsAvatar(bool isAvatar) { } } -void Agent::sendAvatarIdentityPacket() { - if (_isAvatar) { - auto scriptedAvatar = DependencyManager::get(); - scriptedAvatar->markIdentityDataChanged(); - scriptedAvatar->sendIdentityPacket(); - } -} - void Agent::queryAvatars() { auto scriptedAvatar = DependencyManager::get(); @@ -681,44 +654,6 @@ void Agent::queryAvatars() { { NodeType::AvatarMixer }); } -void Agent::processAgentAvatar() { - if (!_scriptEngine->isFinished() && _isAvatar) { - auto scriptedAvatar = DependencyManager::get(); - - AvatarData::AvatarDataDetail dataDetail = (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO) ? AvatarData::SendAllData : AvatarData::CullSmallData; - QByteArray avatarByteArray = scriptedAvatar->toByteArrayStateful(dataDetail); - - int maximumByteArraySize = NLPacket::maxPayloadSize(PacketType::AvatarData) - sizeof(AvatarDataSequenceNumber); - - if (avatarByteArray.size() > maximumByteArraySize) { - qWarning() << " scriptedAvatar->toByteArrayStateful() resulted in very large buffer:" << avatarByteArray.size() << "... attempt to drop facial data"; - avatarByteArray = scriptedAvatar->toByteArrayStateful(dataDetail, true); - - if (avatarByteArray.size() > maximumByteArraySize) { - qWarning() << " scriptedAvatar->toByteArrayStateful() without facial data resulted in very large buffer:" << avatarByteArray.size() << "... reduce to MinimumData"; - avatarByteArray = scriptedAvatar->toByteArrayStateful(AvatarData::MinimumData, true); - - if (avatarByteArray.size() > maximumByteArraySize) { - qWarning() << " scriptedAvatar->toByteArrayStateful() MinimumData resulted in very large buffer:" << avatarByteArray.size() << "... FAIL!!"; - return; - } - } - } - - scriptedAvatar->doneEncoding(true); - - static AvatarDataSequenceNumber sequenceNumber = 0; - auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size() + sizeof(sequenceNumber)); - avatarPacket->writePrimitive(sequenceNumber++); - - avatarPacket->write(avatarByteArray); - - auto nodeList = DependencyManager::get(); - - nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); - } -} - void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) { _flushEncoder = false; static const QByteArray zeros(AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL, 0); diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 244f72e624..b8e7652cca 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -81,7 +81,6 @@ private slots: void nodeActivated(SharedNodePointer activatedNode); void nodeKilled(SharedNodePointer killedNode); - void processAgentAvatar(); void processAgentAvatarAudio(); private: @@ -99,7 +98,6 @@ private: void setAvatarSound(SharedSoundPointer avatarSound) { _avatarSound = avatarSound; } - void sendAvatarIdentityPacket(); void queryAvatars(); QString _scriptContents; @@ -110,7 +108,6 @@ private: bool _shouldMuteRecordingAudio { false }; int _numAvatarSoundSentBytes = 0; bool _isAvatar = false; - QTimer* _avatarIdentityTimer = nullptr; QTimer* _avatarQueryTimer = nullptr; QHash _outgoingScriptAudioSequenceNumbers; diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 004e4ad2ea..bdec17bd8d 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -68,6 +68,13 @@ AudioMixer::AudioMixer(ReceivedMessage& message) : // hash the available codecs (on the mixer) _availableCodecs.clear(); // Make sure struct is clean auto pluginManager = DependencyManager::set(); + // Only load codec plugins; for now assume codec plugins have 'codec' in their name. + auto codecPluginFilter = [](const QJsonObject& metaData) { + QJsonValue nameValue = metaData["MetaData"]["name"]; + return nameValue.toString().contains("codec", Qt::CaseInsensitive); + }; + pluginManager->setPluginFilter(codecPluginFilter); + auto codecPlugins = pluginManager->getCodecPlugins(); for_each(codecPlugins.cbegin(), codecPlugins.cend(), [&](const CodecPluginPointer& codec) { diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index 7a6ab9c3e2..681d822e11 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -504,7 +504,7 @@ void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStre float distance = glm::max(glm::length(relativePosition), EPSILON); float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition); - float gain = 1.0f; + float gain = masterListenerGain; if (!isSoloing) { gain = computeGain(masterListenerGain, listeningNodeStream, *streamToAdd, relativePosition, distance, isEcho); } diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index cb2f0636b9..500772c1b5 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -54,8 +54,8 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket"); packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket"); packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket"); - packetReceiver.registerListener(PacketType::AvatarIdentityRequest, this, "handleAvatarIdentityRequestPacket"); packetReceiver.registerListener(PacketType::SetAvatarTraits, this, "queueIncomingPacket"); + packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket"); packetReceiver.registerListenerForTypes({ PacketType::ReplicatedAvatarIdentity, @@ -581,36 +581,6 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer mes _handleAvatarIdentityPacketElapsedTime += (end - start); } -void AvatarMixer::handleAvatarIdentityRequestPacket(QSharedPointer message, SharedNodePointer senderNode) { - if (message->getSize() < NUM_BYTES_RFC4122_UUID) { - qCDebug(avatars) << "Malformed AvatarIdentityRequest received from" << message->getSenderSockAddr().toString(); - return; - } - - QUuid avatarID(QUuid::fromRfc4122(message->getMessage()) ); - if (!avatarID.isNull()) { - auto nodeList = DependencyManager::get(); - auto requestedNode = nodeList->nodeWithUUID(avatarID); - - if (requestedNode) { - AvatarMixerClientData* avatarClientData = static_cast(requestedNode->getLinkedData()); - if (avatarClientData) { - const AvatarData& avatarData = avatarClientData->getAvatar(); - QByteArray serializedAvatar = avatarData.identityByteArray(); - auto identityPackets = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); - identityPackets->write(serializedAvatar); - nodeList->sendPacketList(std::move(identityPackets), *senderNode); - ++_sumIdentityPackets; - } - - AvatarMixerClientData* senderData = static_cast(senderNode->getLinkedData()); - if (senderData) { - senderData->resetSentTraitData(requestedNode->getLocalID()); - } - } - } -} - void AvatarMixer::handleKillAvatarPacket(QSharedPointer message, SharedNodePointer node) { auto start = usecTimestampNow(); handleAvatarKilled(node); @@ -767,6 +737,9 @@ void AvatarMixer::sendStatsPacket() { float averageOverBudgetAvatars = averageNodes ? aggregateStats.overBudgetAvatars / averageNodes : 0.0f; slavesAggregatObject["sent_3_averageOverBudgetAvatars"] = TIGHT_LOOP_STAT(averageOverBudgetAvatars); + slavesAggregatObject["sent_4_averageDataBytes"] = TIGHT_LOOP_STAT(aggregateStats.numDataBytesSent); + slavesAggregatObject["sent_5_averageTraitsBytes"] = TIGHT_LOOP_STAT(aggregateStats.numTraitsBytesSent); + slavesAggregatObject["sent_6_averageIdentityBytes"] = TIGHT_LOOP_STAT(aggregateStats.numIdentityBytesSent); slavesAggregatObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.processIncomingPacketsElapsedTime); slavesAggregatObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.ignoreCalculationElapsedTime); @@ -775,7 +748,7 @@ void AvatarMixer::sendStatsPacket() { slavesAggregatObject["timing_5_packetSending"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.packetSendingElapsedTime); slavesAggregatObject["timing_6_jobElapsedTime"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.jobElapsedTime); - statsObject["slaves_aggregate"] = slavesAggregatObject; + statsObject["slaves_aggregate (per frame)"] = slavesAggregatObject; _handleViewFrustumPacketElapsedTime = 0; _handleAvatarIdentityPacketElapsedTime = 0; @@ -800,7 +773,8 @@ void AvatarMixer::sendStatsPacket() { // add the key to ask the domain-server for a username replacement, if it has it avatarStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuidStringWithoutCurlyBraces(node->getUUID()); - avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY] = node->getOutboundKbps(); + float outboundAvatarDataKbps = node->getOutboundKbps(); + avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY] = outboundAvatarDataKbps; avatarStats[NODE_INBOUND_KBPS_STAT_KEY] = node->getInboundKbps(); AvatarMixerClientData* clientData = static_cast(node->getLinkedData()); @@ -811,7 +785,7 @@ void AvatarMixer::sendStatsPacket() { // add the diff between the full outbound bandwidth and the measured bandwidth for AvatarData send only avatarStats["delta_full_vs_avatar_data_kbps"] = - avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY].toDouble() - avatarStats[OUTBOUND_AVATAR_DATA_STATS_KEY].toDouble(); + (double)outboundAvatarDataKbps - avatarStats[OUTBOUND_AVATAR_DATA_STATS_KEY].toDouble(); } } diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 8ae7fc9931..764656a2d5 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -54,7 +54,6 @@ private slots: void handleRequestsDomainListDataPacket(QSharedPointer message, SharedNodePointer senderNode); void handleReplicatedPacket(QSharedPointer message); void handleReplicatedBulkAvatarPacket(QSharedPointer message); - void handleAvatarIdentityRequestPacket(QSharedPointer message, SharedNodePointer senderNode); void domainSettingsRequestComplete(); void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); void start(); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index f215fd4448..b7d2f5cdf8 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -19,9 +19,8 @@ #include "AvatarMixerSlave.h" -AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) : - NodeData(nodeID, nodeLocalID) -{ +AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) : + NodeData(nodeID, nodeLocalID) { // in case somebody calls getSessionUUID on the AvatarData instance, make sure it has the right ID _avatar->setID(nodeID); } @@ -68,6 +67,9 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData case PacketType::SetAvatarTraits: processSetTraitsMessage(*packet, slaveSharedData, *node); break; + case PacketType::BulkAvatarTraitsAck: + processBulkAvatarTraitsAckMessage(*packet); + break; default: Q_UNREACHABLE(); } @@ -79,12 +81,11 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData } int AvatarMixerClientData::parseData(ReceivedMessage& message) { - // pull the sequence number from the data first uint16_t sequenceNumber; message.readPrimitive(&sequenceNumber); - + if (sequenceNumber < _lastReceivedSequenceNumber && _lastReceivedSequenceNumber != UINT16_MAX) { incrementNumOutOfOrderSends(); } @@ -95,7 +96,8 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message) { } void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, - const SlaveSharedData& slaveSharedData, Node& sendingNode) { + const SlaveSharedData& slaveSharedData, + Node& sendingNode) { // pull the trait version from the message AvatarTraits::TraitVersion packetTraitVersion; message.readPrimitive(&packetTraitVersion); @@ -134,7 +136,7 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, AvatarTraits::TraitInstanceID instanceID = QUuid::fromRfc4122(message.readWithoutCopy(NUM_BYTES_RFC4122_UUID)); if (message.getBytesLeftToRead() == 0) { - qWarning () << "Received an instanced trait with no size from" << message.getSenderSockAddr(); + qWarning() << "Received an instanced trait with no size from" << message.getSenderSockAddr(); break; } @@ -142,7 +144,8 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, message.readPrimitive(&traitSize); if (traitSize < -1 || traitSize > message.getBytesLeftToRead()) { - qWarning() << "Refusing to process instanced trait of size" << traitSize << "from" << message.getSenderSockAddr(); + qWarning() << "Refusing to process instanced trait of size" << traitSize << "from" + << message.getSenderSockAddr(); break; } @@ -154,7 +157,7 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, if (traitSize == AvatarTraits::DELETED_TRAIT_SIZE) { _avatar->processDeletedTraitInstance(traitType, instanceID); // Mixer doesn't need deleted IDs. - _avatar->getAndClearRecentlyDetachedIDs(); + _avatar->getAndClearRecentlyRemovedIDs(); // to track a deleted instance but keep version information // the avatar mixer uses the negative value of the sent version @@ -169,7 +172,8 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, message.seek(message.getPosition() + traitSize); } } else { - qWarning() << "Refusing to process traits packet with instanced trait of unprocessable type from" << message.getSenderSockAddr(); + qWarning() << "Refusing to process traits packet with instanced trait of unprocessable type from" + << message.getSenderSockAddr(); break; } } @@ -180,7 +184,63 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, } } -void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedData &slaveSharedData, Node& sendingNode, +void AvatarMixerClientData::processBulkAvatarTraitsAckMessage(ReceivedMessage& message) { + // Avatar Traits flow control marks each outgoing avatar traits packet with a + // sequence number. The mixer caches the traits sent in the traits packet. + // Until an ack with the sequence number comes back, all updates to _traits + // in that packet_ are ignored. Updates to traits not in that packet will + // be sent. + + // Look up the avatar/trait data associated with this ack and update the 'last ack' list + // with it. + AvatarTraits::TraitMessageSequence seq; + message.readPrimitive(&seq); + auto sentAvatarTraitVersions = _perNodePendingTraitVersions.find(seq); + if (sentAvatarTraitVersions != _perNodePendingTraitVersions.end()) { + for (auto& perNodeTraitVersions : sentAvatarTraitVersions->second) { + auto& nodeId = perNodeTraitVersions.first; + auto& traitVersions = perNodeTraitVersions.second; + // For each trait that was sent in the traits packet, + // update the 'acked' trait version. Traits not + // sent in the traits packet keep their version. + + // process simple traits + auto simpleReceivedIt = traitVersions.simpleCBegin(); + while (simpleReceivedIt != traitVersions.simpleCEnd()) { + if (*simpleReceivedIt != AvatarTraits::DEFAULT_TRAIT_VERSION) { + auto traitType = static_cast(std::distance(traitVersions.simpleCBegin(), simpleReceivedIt)); + _perNodeAckedTraitVersions[nodeId][traitType] = *simpleReceivedIt; + } + simpleReceivedIt++; + } + + // process instanced traits + auto instancedSentIt = traitVersions.instancedCBegin(); + while (instancedSentIt != traitVersions.instancedCEnd()) { + auto traitType = instancedSentIt->traitType; + + for (auto& sentInstance : instancedSentIt->instances) { + auto instanceID = sentInstance.id; + const auto sentVersion = sentInstance.value; + _perNodeAckedTraitVersions[nodeId].instanceInsert(traitType, instanceID, sentVersion); + } + instancedSentIt++; + } + } + _perNodePendingTraitVersions.erase(sentAvatarTraitVersions); + } else { + // This can happen either the BulkAvatarTraits was sent with no simple traits, + // or if the avatar mixer restarts while there are pending + // BulkAvatarTraits messages in-flight. + if (seq > getTraitsMessageSequence()) { + qWarning() << "Received BulkAvatarTraitsAck with future seq (potential avatar mixer restart) " << seq << " from " + << message.getSenderSockAddr(); + } + } +} + +void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedData& slaveSharedData, + Node& sendingNode, AvatarTraits::TraitVersion traitVersion) { const auto& whitelist = slaveSharedData.skeletonURLWhitelist; @@ -282,14 +342,18 @@ void AvatarMixerClientData::removeFromRadiusIgnoringSet(const QUuid& other) { void AvatarMixerClientData::resetSentTraitData(Node::LocalID nodeLocalID) { _lastSentTraitsTimestamps[nodeLocalID] = TraitsCheckTimestamp(); - _sentTraitVersions[nodeLocalID].reset(); + _perNodeSentTraitVersions[nodeLocalID].reset(); + _perNodeAckedTraitVersions[nodeLocalID].reset(); + for (auto && pendingTraitVersions : _perNodePendingTraitVersions) { + pendingTraitVersions.second[nodeLocalID].reset(); + } } void AvatarMixerClientData::readViewFrustumPacket(const QByteArray& message) { _currentViewFrustums.clear(); auto sourceBuffer = reinterpret_cast(message.constData()); - + uint8_t numFrustums = 0; memcpy(&numFrustums, sourceBuffer, sizeof(numFrustums)); sourceBuffer += sizeof(numFrustums); @@ -317,7 +381,8 @@ void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const { jsonObject["total_num_out_of_order_sends"] = _numOutOfOrderSends; jsonObject[OUTBOUND_AVATAR_DATA_STATS_KEY] = getOutboundAvatarDataKbps(); - jsonObject[INBOUND_AVATAR_DATA_STATS_KEY] = _avatar->getAverageBytesReceivedPerSecond() / (float) BYTES_PER_KILOBIT; + jsonObject[OUTBOUND_AVATAR_TRAITS_STATS_KEY] = getOutboundAvatarTraitsKbps(); + jsonObject[INBOUND_AVATAR_DATA_STATS_KEY] = _avatar->getAverageBytesReceivedPerSecond() / (float)BYTES_PER_KILOBIT; jsonObject["av_data_receive_rate"] = _avatar->getReceiveRate(); jsonObject["recent_other_av_in_view"] = _recentOtherAvatarsInView; @@ -338,5 +403,5 @@ void AvatarMixerClientData::cleanupKilledNode(const QUuid&, Node::LocalID nodeLo removeLastBroadcastSequenceNumber(nodeLocalID); removeLastBroadcastTime(nodeLocalID); _lastSentTraitsTimestamps.erase(nodeLocalID); - _sentTraitVersions.erase(nodeLocalID); + _perNodeSentTraitVersions.erase(nodeLocalID); } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 8a86af384a..843f19cf22 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -32,6 +32,7 @@ #include const QString OUTBOUND_AVATAR_DATA_STATS_KEY = "outbound_av_data_kbps"; +const QString OUTBOUND_AVATAR_TRAITS_STATS_KEY = "outbound_av_traits_kbps"; const QString INBOUND_AVATAR_DATA_STATS_KEY = "inbound_av_data_kbps"; struct SlaveSharedData; @@ -42,6 +43,7 @@ public: AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID); virtual ~AvatarMixerClientData() {} using HRCTime = p_high_resolution_clock::time_point; + using PerNodeTraitVersions = std::unordered_map; int parseData(ReceivedMessage& message) override; AvatarData& getAvatar() { return *_avatar; } @@ -85,10 +87,15 @@ public: void incrementNumFramesSinceFRDAdjustment() { ++_numFramesSinceAdjustment; } void resetNumFramesSinceFRDAdjustment() { _numFramesSinceAdjustment = 0; } - void recordSentAvatarData(int numBytes) { _avgOtherAvatarDataRate.updateAverage((float) numBytes); } + void recordSentAvatarData(int numDataBytes, int numTraitsBytes = 0) { + _avgOtherAvatarDataRate.updateAverage(numDataBytes); + _avgOtherAvatarTraitsRate.updateAverage(numTraitsBytes); + } float getOutboundAvatarDataKbps() const { return _avgOtherAvatarDataRate.getAverageSampleValuePerSecond() / (float) BYTES_PER_KILOBIT; } + float getOutboundAvatarTraitsKbps() const + { return _avgOtherAvatarTraitsRate.getAverageSampleValuePerSecond() / BYTES_PER_KILOBIT; } void loadJSONStats(QJsonObject& jsonObject) const; @@ -124,6 +131,7 @@ public: int processPackets(const SlaveSharedData& slaveSharedData); // returns number of packets processed void processSetTraitsMessage(ReceivedMessage& message, const SlaveSharedData& slaveSharedData, Node& sendingNode); + void processBulkAvatarTraitsAckMessage(ReceivedMessage& message); void checkSkeletonURLAgainstWhitelist(const SlaveSharedData& slaveSharedData, Node& sendingNode, AvatarTraits::TraitVersion traitVersion); @@ -138,7 +146,14 @@ public: void setLastOtherAvatarTraitsSendPoint(Node::LocalID otherAvatar, TraitsCheckTimestamp sendPoint) { _lastSentTraitsTimestamps[otherAvatar] = sendPoint; } - AvatarTraits::TraitVersions& getLastSentTraitVersions(Node::LocalID otherAvatar) { return _sentTraitVersions[otherAvatar]; } + AvatarTraits::TraitMessageSequence getTraitsMessageSequence() const { return _currentTraitsMessageSequence; } + AvatarTraits::TraitMessageSequence nextTraitsMessageSequence() { return ++_currentTraitsMessageSequence; } + AvatarTraits::TraitVersions& getPendingTraitVersions(AvatarTraits::TraitMessageSequence seq, Node::LocalID otherId) { + return _perNodePendingTraitVersions[seq][otherId]; + } + + AvatarTraits::TraitVersions& getLastSentTraitVersions(Node::LocalID otherAvatar) { return _perNodeSentTraitVersions[otherAvatar]; } + AvatarTraits::TraitVersions& getLastAckedTraitVersions(Node::LocalID otherAvatar) { return _perNodeAckedTraitVersions[otherAvatar]; } void resetSentTraitData(Node::LocalID nodeID); @@ -171,6 +186,7 @@ private: int _numOutOfOrderSends = 0; SimpleMovingAverage _avgOtherAvatarDataRate; + SimpleMovingAverage _avgOtherAvatarTraitsRate; std::vector _radiusIgnoredOthers; ConicalViewFrustums _currentViewFrustums; @@ -183,8 +199,27 @@ private: AvatarTraits::TraitVersions _lastReceivedTraitVersions; TraitsCheckTimestamp _lastReceivedTraitsChange; + AvatarTraits::TraitMessageSequence _currentTraitsMessageSequence{ 0 }; + + // Cache of trait versions sent in a given packet (indexed by sequence number) + // When an ack is received, the sequence number in the ack is used to look up + // the sent trait versions and they are copied to _perNodeAckedTraitVersions. + // We remember the data in _perNodePendingTraitVersions instead of requiring + // the client to return all of the versions for each trait it received in a given packet, + // reducing the size of the ack packet. + std::unordered_map _perNodePendingTraitVersions; + + // Versions of traits that have been acked, which will be compared to incoming + // trait updates. Incoming updates going to a given node will be ignored if + // the ack for the previous packet (containing those versions) has not been + // received. + PerNodeTraitVersions _perNodeAckedTraitVersions; + std::unordered_map _lastSentTraitsTimestamps; - std::unordered_map _sentTraitVersions; + + // cache of traits sent to a node which are compared to incoming traits to + // prevent sending traits that have already been sent. + PerNodeTraitVersions _perNodeSentTraitVersions; std::atomic_bool _isIgnoreRadiusEnabled { false }; }; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index cd9d164ef7..6b039e2c03 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -73,52 +73,82 @@ int AvatarMixerSlave::sendIdentityPacket(NLPacketList& packetList, const AvatarM QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(); individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122()); // FIXME, this looks suspicious packetList.write(individualData); - _stats.numIdentityPackets++; + _stats.numIdentityPacketsSent++; + _stats.numIdentityBytesSent += individualData.size(); return individualData.size(); } else { return 0; } } +qint64 AvatarMixerSlave::addTraitsNodeHeader(AvatarMixerClientData* listeningNodeData, + const AvatarMixerClientData* sendingNodeData, + NLPacketList& traitsPacketList, + qint64 bytesWritten) { + if (bytesWritten == 0) { + if (traitsPacketList.getNumPackets() == 0) { + // This is the beginning of the traits packet, write out the sequence number. + bytesWritten += traitsPacketList.writePrimitive(listeningNodeData->nextTraitsMessageSequence()); + } + // This is the beginning of the traits for a node, write out the node id + bytesWritten += traitsPacketList.write(sendingNodeData->getNodeID().toRfc4122()); + } + return bytesWritten; +} + qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* listeningNodeData, const AvatarMixerClientData* sendingNodeData, NLPacketList& traitsPacketList) { - auto otherNodeLocalID = sendingNodeData->getNodeLocalID(); + // Avatar Traits flow control marks each outgoing avatar traits packet with a + // sequence number. The mixer caches the traits sent in the traits packet. + // Until an ack with the sequence number comes back, all updates to _traits + // in that packet_ are ignored. Updates to traits not in that packet will + // be sent. + + auto sendingNodeLocalID = sendingNodeData->getNodeLocalID(); // Perform a simple check with two server clock time points // to see if there is any new traits data for this avatar that we need to send - auto timeOfLastTraitsSent = listeningNodeData->getLastOtherAvatarTraitsSendPoint(otherNodeLocalID); + auto timeOfLastTraitsSent = listeningNodeData->getLastOtherAvatarTraitsSendPoint(sendingNodeLocalID); auto timeOfLastTraitsChange = sendingNodeData->getLastReceivedTraitsChange(); + bool allTraitsUpdated = true; qint64 bytesWritten = 0; if (timeOfLastTraitsChange > timeOfLastTraitsSent) { // there is definitely new traits data to send - // add the avatar ID to mark the beginning of traits for this avatar - bytesWritten += traitsPacketList.write(sendingNodeData->getNodeID().toRfc4122()); - auto sendingAvatar = sendingNodeData->getAvatarSharedPointer(); // compare trait versions so we can see what exactly needs to go out - auto& lastSentVersions = listeningNodeData->getLastSentTraitVersions(otherNodeLocalID); + auto& lastSentVersions = listeningNodeData->getLastSentTraitVersions(sendingNodeLocalID); + auto& lastAckedVersions = listeningNodeData->getLastAckedTraitVersions(sendingNodeLocalID); const auto& lastReceivedVersions = sendingNodeData->getLastReceivedTraitVersions(); auto simpleReceivedIt = lastReceivedVersions.simpleCBegin(); while (simpleReceivedIt != lastReceivedVersions.simpleCEnd()) { auto traitType = static_cast(std::distance(lastReceivedVersions.simpleCBegin(), simpleReceivedIt)); - auto lastReceivedVersion = *simpleReceivedIt; auto& lastSentVersionRef = lastSentVersions[traitType]; + auto& lastAckedVersionRef = lastAckedVersions[traitType]; - if (lastReceivedVersions[traitType] > lastSentVersionRef) { - // there is an update to this trait, add it to the traits packet - bytesWritten += sendingAvatar->packTrait(traitType, traitsPacketList, lastReceivedVersion); - - // update the last sent version - lastSentVersionRef = lastReceivedVersion; + // hold sending more traits until we've been acked that the last one we sent was received + if (lastSentVersionRef == lastAckedVersionRef) { + if (lastReceivedVersion > lastSentVersionRef) { + bytesWritten += addTraitsNodeHeader(listeningNodeData, sendingNodeData, traitsPacketList, bytesWritten); + // there is an update to this trait, add it to the traits packet + bytesWritten += sendingAvatar->packTrait(traitType, traitsPacketList, lastReceivedVersion); + // update the last sent version + lastSentVersionRef = lastReceivedVersion; + // Remember which versions we sent in this particular packet + // so we can verify when it's acked. + auto& pendingTraitVersions = listeningNodeData->getPendingTraitVersions(listeningNodeData->getTraitsMessageSequence(), sendingNodeLocalID); + pendingTraitVersions[traitType] = lastReceivedVersion; + } + } else { + allTraitsUpdated = false; } ++simpleReceivedIt; @@ -131,6 +161,7 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis // get or create the sent trait versions for this trait type auto& sentIDValuePairs = lastSentVersions.getInstanceIDValuePairs(traitType); + auto& ackIDValuePairs = lastAckedVersions.getInstanceIDValuePairs(traitType); // enumerate each received instance for (auto& receivedInstance : instancedReceivedIt->instances) { @@ -148,8 +179,19 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis { return sentInstance.id == instanceID; }); - + // look for existing acked version for this instance + auto ackedInstanceIt = std::find_if(ackIDValuePairs.begin(), ackIDValuePairs.end(), + [instanceID](auto& ackInstance) { return ackInstance.id == instanceID; }); + + // if we have a sent version, then we must have an acked instance of the same trait with the same + // version to go on, otherwise we drop the received trait + if (sentInstanceIt != sentIDValuePairs.end() && + (ackedInstanceIt == ackIDValuePairs.end() || sentInstanceIt->value != ackedInstanceIt->value)) { + allTraitsUpdated = false; + continue; + } if (!isDeleted && (sentInstanceIt == sentIDValuePairs.end() || receivedVersion > sentInstanceIt->value)) { + bytesWritten += addTraitsNodeHeader(listeningNodeData, sendingNodeData, traitsPacketList, bytesWritten); // this instance version exists and has never been sent or is newer so we need to send it bytesWritten += sendingAvatar->packTraitInstance(traitType, instanceID, traitsPacketList, receivedVersion); @@ -159,25 +201,40 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis } else { sentIDValuePairs.emplace_back(instanceID, receivedVersion); } + + auto& pendingTraitVersions = + listeningNodeData->getPendingTraitVersions(listeningNodeData->getTraitsMessageSequence(), + sendingNodeLocalID); + pendingTraitVersions.instanceInsert(traitType, instanceID, receivedVersion); + } else if (isDeleted && sentInstanceIt != sentIDValuePairs.end() && absoluteReceivedVersion > sentInstanceIt->value) { + bytesWritten += addTraitsNodeHeader(listeningNodeData, sendingNodeData, traitsPacketList, bytesWritten); // this instance version was deleted and we haven't sent the delete to this client yet bytesWritten += AvatarTraits::packInstancedTraitDelete(traitType, instanceID, traitsPacketList, absoluteReceivedVersion); // update the last sent version for this trait instance to the absolute value of the deleted version sentInstanceIt->value = absoluteReceivedVersion; + + auto& pendingTraitVersions = + listeningNodeData->getPendingTraitVersions(listeningNodeData->getTraitsMessageSequence(), + sendingNodeLocalID); + pendingTraitVersions.instanceInsert(traitType, instanceID, absoluteReceivedVersion); + } } ++instancedReceivedIt; } - - // write a null trait type to mark the end of trait data for this avatar - bytesWritten += traitsPacketList.writePrimitive(AvatarTraits::NullTrait); - - // since we send all traits for this other avatar, update the time of last traits sent - // to match the time of last traits change - listeningNodeData->setLastOtherAvatarTraitsSendPoint(otherNodeLocalID, timeOfLastTraitsChange); + if (bytesWritten) { + // write a null trait type to mark the end of trait data for this avatar + bytesWritten += traitsPacketList.writePrimitive(AvatarTraits::NullTrait); + // since we send all traits for this other avatar, update the time of last traits sent + // to match the time of last traits change + if (allTraitsUpdated) { + listeningNodeData->setLastOtherAvatarTraitsSendPoint(sendingNodeLocalID, timeOfLastTraitsChange); + } + } } @@ -191,7 +248,8 @@ int AvatarMixerSlave::sendReplicatedIdentityPacket(const Node& agentNode, const auto identityPacket = NLPacketList::create(PacketType::ReplicatedAvatarIdentity, QByteArray(), true, true); identityPacket->write(individualData); DependencyManager::get()->sendPacketList(std::move(identityPacket), destinationNode); - _stats.numIdentityPackets++; + _stats.numIdentityPacketsSent++; + _stats.numIdentityBytesSent += individualData.size(); return individualData.size(); } else { return 0; @@ -419,6 +477,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int remainingAvatars = (int)sortedAvatars.size(); auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); + auto avatarPacket = NLPacket::create(PacketType::BulkAvatarData); const int avatarPacketCapacity = avatarPacket->getPayloadCapacity(); int avatarSpaceAvailable = avatarPacketCapacity; @@ -539,17 +598,16 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) ++numPacketsSent; } - _stats.numPacketsSent += numPacketsSent; - _stats.numBytesSent += numAvatarDataBytes; - - // record the bytes sent for other avatar data in the AvatarMixerClientData - nodeData->recordSentAvatarData(numAvatarDataBytes); + _stats.numDataPacketsSent += numPacketsSent; + _stats.numDataBytesSent += numAvatarDataBytes; // close the current traits packet list traitsPacketList->closeCurrentPacket(); if (traitsPacketList->getNumPackets() >= 1) { // send the traits packet list + _stats.numTraitsBytesSent += traitBytesSent; + _stats.numTraitsPacketsSent += (int) traitsPacketList->getNumPackets(); nodeList->sendPacketList(std::move(traitsPacketList), *destinationNode); } @@ -559,6 +617,10 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) nodeList->sendPacketList(std::move(identityPacketList), *destinationNode); } + // record the bytes sent for other avatar data in the AvatarMixerClientData + nodeData->recordSentAvatarData(numAvatarDataBytes, traitBytesSent); + + // record the number of avatars held back this frame nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack); nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames); @@ -685,8 +747,8 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin // close the current packet so that we're always sending something avatarPacketList->closeCurrentPacket(true); - _stats.numPacketsSent += (int)avatarPacketList->getNumPackets(); - _stats.numBytesSent += numAvatarDataBytes; + _stats.numDataPacketsSent += (int)avatarPacketList->getNumPackets(); + _stats.numDataBytesSent += numAvatarDataBytes; // send the replicated bulk avatar data auto nodeList = DependencyManager::get(); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index 2ef90af38e..91bb02fd55 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -24,9 +24,12 @@ public: int nodesBroadcastedTo { 0 }; int downstreamMixersBroadcastedTo { 0 }; - int numPacketsSent { 0 }; - int numBytesSent { 0 }; - int numIdentityPackets { 0 }; + int numDataBytesSent { 0 }; + int numTraitsBytesSent { 0 }; + int numIdentityBytesSent { 0 }; + int numDataPacketsSent { 0 }; + int numTraitsPacketsSent { 0 }; + int numIdentityPacketsSent { 0 }; int numOthersIncluded { 0 }; int overBudgetAvatars { 0 }; @@ -45,9 +48,13 @@ public: // sending job stats nodesBroadcastedTo = 0; downstreamMixersBroadcastedTo = 0; - numPacketsSent = 0; - numBytesSent = 0; - numIdentityPackets = 0; + + numDataBytesSent = 0; + numTraitsBytesSent = 0; + numIdentityBytesSent = 0; + numDataPacketsSent = 0; + numTraitsPacketsSent = 0; + numIdentityPacketsSent = 0; numOthersIncluded = 0; overBudgetAvatars = 0; @@ -65,9 +72,12 @@ public: nodesBroadcastedTo += rhs.nodesBroadcastedTo; downstreamMixersBroadcastedTo += rhs.downstreamMixersBroadcastedTo; - numPacketsSent += rhs.numPacketsSent; - numBytesSent += rhs.numBytesSent; - numIdentityPackets += rhs.numIdentityPackets; + numDataBytesSent += rhs.numDataBytesSent; + numTraitsBytesSent += rhs.numTraitsBytesSent; + numIdentityBytesSent += rhs.numIdentityBytesSent; + numDataPacketsSent += rhs.numDataPacketsSent; + numTraitsPacketsSent += rhs.numTraitsPacketsSent; + numIdentityPacketsSent += rhs.numIdentityPacketsSent; numOthersIncluded += rhs.numOthersIncluded; overBudgetAvatars += rhs.overBudgetAvatars; @@ -104,6 +114,11 @@ private: int sendIdentityPacket(NLPacketList& packet, const AvatarMixerClientData* nodeData, const Node& destinationNode); int sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode); + qint64 addTraitsNodeHeader(AvatarMixerClientData* listeningNodeData, + const AvatarMixerClientData* sendingNodeData, + NLPacketList& traitsPacketList, + qint64 bytesWritten); + qint64 addChangedTraitsToBulkPacket(AvatarMixerClientData* listeningNodeData, const AvatarMixerClientData* sendingNodeData, NLPacketList& traitsPacketList); diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index a2e13a03be..044ab86942 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -21,6 +21,8 @@ #include #include #include +#include +#include ScriptableAvatar::ScriptableAvatar() { @@ -89,6 +91,39 @@ void ScriptableAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { updateJointMappings(); } +int ScriptableAvatar::sendAvatarDataPacket(bool sendAll) { + using namespace std::chrono; + auto now = Clock::now(); + + int MAX_DATA_RATE_MBPS = 3; + int maxDataRateBytesPerSeconds = MAX_DATA_RATE_MBPS * BYTES_PER_KILOBYTE * KILO_PER_MEGA / BITS_IN_BYTE; + int maxDataRateBytesPerMilliseconds = maxDataRateBytesPerSeconds / MSECS_PER_SECOND; + + auto bytesSent = 0; + + if (now > _nextTraitsSendWindow) { + if (getIdentityDataChanged()) { + bytesSent += sendIdentityPacket(); + } + + bytesSent += _clientTraitsHandler->sendChangedTraitsToMixer(); + + // Compute the next send window based on how much data we sent and what + // data rate we're trying to max at. + milliseconds timeUntilNextSend { bytesSent / maxDataRateBytesPerMilliseconds }; + _nextTraitsSendWindow += timeUntilNextSend; + + // Don't let the next send window lag behind if we're not sending a lot of data. + if (_nextTraitsSendWindow < now) { + _nextTraitsSendWindow = now; + } + } + + bytesSent += AvatarData::sendAvatarDataPacket(sendAll); + + return bytesSent; +} + static AnimPose composeAnimPose(const HFMJoint& joint, const glm::quat rotation, const glm::vec3 translation) { glm::mat4 translationMat = glm::translate(translation); glm::mat4 rotationMat = glm::mat4_cast(joint.preRotation * rotation * joint.postRotation); @@ -159,7 +194,13 @@ void ScriptableAvatar::update(float deltatime) { } } - _clientTraitsHandler->sendChangedTraitsToMixer(); + quint64 now = usecTimestampNow(); + quint64 dt = now - _lastSendAvatarDataTime; + + if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS) { + sendAvatarDataPacket(); + _lastSendAvatarDataTime = now; + } } void ScriptableAvatar::updateJointMappings() { @@ -249,3 +290,157 @@ void ScriptableAvatar::setHasProceduralEyeFaceMovement(bool hasProceduralEyeFace void ScriptableAvatar::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) { _headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement); } + +AvatarEntityMap ScriptableAvatar::getAvatarEntityData() const { + // DANGER: Now that we store the AvatarEntityData in packed format this call is potentially Very Expensive! + // Avoid calling this method if possible. + AvatarEntityMap data; + QUuid sessionID = getID(); + _avatarEntitiesLock.withReadLock([&] { + for (const auto& itr : _entities) { + QUuid id = itr.first; + EntityItemPointer entity = itr.second; + EntityItemProperties properties = entity->getProperties(); + QByteArray blob; + EntityItemProperties::propertiesToBlob(_scriptEngine, sessionID, properties, blob); + data[id] = blob; + } + }); + return data; +} + +void ScriptableAvatar::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) { + // Note: this is an invokable Script call + // avatarEntityData is expected to be a map of QByteArrays that represent EntityItemProperties objects from JavaScript + // + if (avatarEntityData.size() > MAX_NUM_AVATAR_ENTITIES) { + // the data is suspect + qCDebug(avatars) << "discard suspect avatarEntityData with size =" << avatarEntityData.size(); + return; + } + + // convert binary data to EntityItemProperties + // NOTE: this operation is NOT efficient + std::map newProperties; + AvatarEntityMap::const_iterator dataItr = avatarEntityData.begin(); + while (dataItr != avatarEntityData.end()) { + EntityItemProperties properties; + const QByteArray& blob = dataItr.value(); + if (!blob.isNull() && EntityItemProperties::blobToProperties(_scriptEngine, blob, properties)) { + newProperties[dataItr.key()] = properties; + } + ++dataItr; + } + + // delete existing entities not found in avatarEntityData + std::vector idsToClear; + _avatarEntitiesLock.withWriteLock([&] { + std::map::iterator entityItr = _entities.begin(); + while (entityItr != _entities.end()) { + QUuid id = entityItr->first; + std::map::const_iterator propertiesItr = newProperties.find(id); + if (propertiesItr == newProperties.end()) { + idsToClear.push_back(id); + entityItr = _entities.erase(entityItr); + } else { + ++entityItr; + } + } + }); + + // add or update entities + _avatarEntitiesLock.withWriteLock([&] { + std::map::const_iterator propertiesItr = newProperties.begin(); + while (propertiesItr != newProperties.end()) { + QUuid id = propertiesItr->first; + const EntityItemProperties& properties = propertiesItr->second; + std::map::iterator entityItr = _entities.find(id); + EntityItemPointer entity; + if (entityItr != _entities.end()) { + entity = entityItr->second; + entity->setProperties(properties); + } else { + entity = EntityTypes::constructEntityItem(id, properties); + } + if (entity) { + // build outgoing payload + OctreePacketData packetData(false, AvatarTraits::MAXIMUM_TRAIT_SIZE); + EncodeBitstreamParams params; + EntityTreeElementExtraEncodeDataPointer extra { nullptr }; + OctreeElement::AppendState appendState = entity->appendEntityData(&packetData, params, extra); + + if (appendState == OctreeElement::COMPLETED) { + _entities[id] = entity; + QByteArray tempArray((const char*)packetData.getUncompressedData(), packetData.getUncompressedSize()); + storeAvatarEntityDataPayload(id, tempArray); + } else { + // payload doesn't fit + entityItr = _entities.find(id); + if (entityItr != _entities.end()) { + _entities.erase(entityItr); + idsToClear.push_back(id); + } + + } + } + ++propertiesItr; + } + }); + + // clear deleted traits + for (const auto& id : idsToClear) { + clearAvatarEntity(id); + } +} + +void ScriptableAvatar::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) { + if (entityData.isNull()) { + // interpret this as a DELETE + std::map::iterator itr = _entities.find(entityID); + if (itr != _entities.end()) { + _entities.erase(itr); + clearAvatarEntity(entityID); + } + return; + } + + EntityItemPointer entity; + EntityItemProperties properties; + if (!EntityItemProperties::blobToProperties(_scriptEngine, entityData, properties)) { + // entityData is corrupt + return; + } + + std::map::iterator itr = _entities.find(entityID); + if (itr == _entities.end()) { + // this is an ADD + entity = EntityTypes::constructEntityItem(entityID, properties); + if (entity) { + OctreePacketData packetData(false, AvatarTraits::MAXIMUM_TRAIT_SIZE); + EncodeBitstreamParams params; + EntityTreeElementExtraEncodeDataPointer extra { nullptr }; + OctreeElement::AppendState appendState = entity->appendEntityData(&packetData, params, extra); + + if (appendState == OctreeElement::COMPLETED) { + _entities[entityID] = entity; + QByteArray tempArray((const char*)packetData.getUncompressedData(), packetData.getUncompressedSize()); + storeAvatarEntityDataPayload(entityID, tempArray); + } + } + } else { + // this is an UPDATE + entity = itr->second; + bool somethingChanged = entity->setProperties(properties); + if (somethingChanged) { + OctreePacketData packetData(false, AvatarTraits::MAXIMUM_TRAIT_SIZE); + EncodeBitstreamParams params; + EntityTreeElementExtraEncodeDataPointer extra { nullptr }; + OctreeElement::AppendState appendState = entity->appendEntityData(&packetData, params, extra); + + if (appendState == OctreeElement::COMPLETED) { + QByteArray tempArray((const char*)packetData.getUncompressedData(), packetData.getUncompressedSize()); + storeAvatarEntityDataPayload(entityID, tempArray); + } + } + } +} diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index 66b0b5ae3f..e93be897d5 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -16,6 +16,7 @@ #include #include #include +#include /**jsdoc * The Avatar API is used to manipulate scriptable avatars on the domain. This API is a subset of the @@ -122,6 +123,10 @@ class ScriptableAvatar : public AvatarData, public Dependency { Q_OBJECT + + using Clock = std::chrono::system_clock; + using TimePoint = Clock::time_point; + public: ScriptableAvatar(); @@ -176,6 +181,8 @@ public: virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; + int sendAvatarDataPacket(bool sendAll = false) override; + virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override; void setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement); @@ -185,6 +192,26 @@ public: void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement); bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); } + /**jsdoc + * Potentially Very Expensive. Do not use. + * @function Avatar.getAvatarEntityData + * @returns {object} + */ + Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const override; + + /**jsdoc + * @function MyAvatar.setAvatarEntityData + * @param {object} avatarEntityData + */ + Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData) override; + + /**jsdoc + * @function MyAvatar.updateAvatarEntity + * @param {Uuid} entityID + * @param {string} entityData + */ + Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) override; + public slots: void update(float deltatime); @@ -202,9 +229,15 @@ private: QHash _fstJointIndices; ///< 1-based, since zero is returned for missing keys QStringList _fstJointNames; ///< in order of depth-first traversal QUrl _skeletonFBXURL; + mutable QScriptEngine _scriptEngine; + std::map _entities; /// Loads the joint indices, names from the FST file (if any) void updateJointMappings(); + + quint64 _lastSendAvatarDataTime { 0 }; + + TimePoint _nextTraitsSendWindow; }; #endif // hifi_ScriptableAvatar_h diff --git a/hifi_android.py b/hifi_android.py index e3944cda9a..13c9cdccf2 100644 --- a/hifi_android.py +++ b/hifi_android.py @@ -6,6 +6,7 @@ import re import shutil import xml.etree.ElementTree as ET import functools +import zipfile print = functools.partial(print, flush=True) @@ -163,6 +164,31 @@ def copyAndroidLibs(packagePath, appPath): print("Copying {}".format(lib)) shutil.copy(sourceFile, destFile) + gvrLibFolder = os.path.join(packagePath, 'gvr/gvr-android-sdk-1.101.0/libraries') + audioSoOut = os.path.join(gvrLibFolder, 'libgvr_audio.so') + if not os.path.isfile(audioSoOut): + audioAar = os.path.join(gvrLibFolder, 'sdk-audio-1.101.0.aar') + with zipfile.ZipFile(audioAar) as z: + with z.open('jni/arm64-v8a/libgvr_audio.so') as f: + with open(audioSoOut, 'wb') as of: + shutil.copyfileobj(f, of) + + audioSoOut2 = os.path.join(jniPath, 'libgvr_audio.so') + if not os.path.isfile(audioSoOut2): + shutil.copy(audioSoOut, audioSoOut2) + + baseSoOut = os.path.join(gvrLibFolder, 'libgvr.so') + if not os.path.isfile(baseSoOut): + baseAar = os.path.join(gvrLibFolder, 'sdk-base-1.101.0.aar') + with zipfile.ZipFile(baseAar) as z: + with z.open('jni/arm64-v8a/libgvr.so') as f: + with open(baseSoOut, 'wb') as of: + shutil.copyfileobj(f, of) + + baseSoOut2 = os.path.join(jniPath, 'libgvr.so') + if not os.path.isfile(baseSoOut2): + shutil.copy(baseSoOut, baseSoOut2) + class QtPackager: def __init__(self, appPath, qtRootPath): self.appPath = appPath @@ -170,6 +196,7 @@ class QtPackager: self.jniPath = os.path.join(self.appPath, 'src/main/jniLibs/arm64-v8a') self.assetPath = os.path.join(self.appPath, 'src/main/assets') self.qtAssetPath = os.path.join(self.assetPath, '--Added-by-androiddeployqt--') + self.qtAssetCacheList = os.path.join(self.qtAssetPath, 'qt_cache_pregenerated_file_list') # Jars go into the qt library self.jarPath = os.path.realpath(os.path.join(self.appPath, '../../libraries/qt/libs')) self.xmlFile = os.path.join(self.appPath, 'src/main/res/values/libs.xml') @@ -195,7 +222,7 @@ class QtPackager: if (relativeFilename.startswith('qml')): continue filename = os.path.join(self.qtRootPath, relativeFilename) - self.files.extend(hifi_utils.recursiveFileList(filename)) + self.files.extend(hifi_utils.recursiveFileList(filename, excludeNamePattern=r"^\.")) elif item.tag == 'jar' and 'bundling' in item.attrib and item.attrib['bundling'] == "1": self.files.append(os.path.join(self.qtRootPath, item.attrib['file'])) elif item.tag == 'permission': @@ -220,7 +247,6 @@ class QtPackager: qmlImportResults = json.loads(commandResult) for item in qmlImportResults: if 'path' not in item: - print("Warning: QML import could not be resolved in any of the import paths: {}".format(item['name'])) continue path = os.path.realpath(item['path']) if not os.path.exists(path): @@ -231,7 +257,7 @@ class QtPackager: basePath = os.path.normcase(basePath) if basePath.startswith(qmlRootPath): continue - self.files.extend(hifi_utils.recursiveFileList(path)) + self.files.extend(hifi_utils.recursiveFileList(path, excludeNamePattern=r"^\.")) def processFiles(self): self.files = list(set(self.files)) @@ -244,7 +270,7 @@ class QtPackager: for sourceFile in self.files: if not os.path.isfile(sourceFile): raise RuntimeError("Unable to find dependency file " + sourceFile) - relativePath = os.path.relpath(sourceFile, self.qtRootPath) + relativePath = os.path.relpath(sourceFile, self.qtRootPath).replace('\\', '/') destinationFile = None if relativePath.endswith('.so'): garbledFileName = None @@ -257,7 +283,7 @@ class QtPackager: libName = m.group(1) ET.SubElement(qtLibsNode, 'item').text = libName else: - garbledFileName = 'lib' + relativePath.replace('\\', '_'[0]) + garbledFileName = 'lib' + relativePath.replace('/', '_'[0]) value = "{}:{}".format(garbledFileName, relativePath).replace('\\', '/') ET.SubElement(bundledLibsNode, 'item').text = value destinationFile = os.path.join(self.jniPath, garbledFileName) @@ -277,10 +303,44 @@ class QtPackager: tree = ET.ElementTree(libsXmlRoot) tree.write(self.xmlFile, 'UTF-8', True) + def generateAssetsFileList(self): + print("Implement asset file list") + # outputFilename = os.path.join(self.qtAssetPath, "qt_cache_pregenerated_file_list") + # fileList = hifi_utils.recursiveFileList(self.qtAssetPath) + # fileMap = {} + # for fileName in fileList: + # relativeFileName = os.path.relpath(fileName, self.assetPath) + # dirName, localFileName = os.path.split(relativeFileName) + # if not dirName in fileMap: + # fileMap[dirName] = [] + # fileMap[dirName].append(localFileName) + + # for dirName in fileMap: + # for localFileName in fileMap[dirName]: + # ???? + + # + # Gradle version + # + # DataOutputStream fos = new DataOutputStream(new FileOutputStream(outputFile)); + # for (Map.Entry> e: directoryContents.entrySet()) { + # def entryList = e.getValue() + # fos.writeInt(e.key.length()*2); // 2 bytes per char + # fos.writeChars(e.key); + # fos.writeInt(entryList.size()); + # for (String entry: entryList) { + # fos.writeInt(entry.length()*2); + # fos.writeChars(entry); + # } + # } + def bundle(self): - if not os.path.isfile(self.xmlFile) or True: + if not os.path.isfile(self.xmlFile): + print("Bundling Qt info into {}".format(self.xmlFile)) self.copyQtDeps() self.scanQmlImports() self.processFiles() + # if not os.path.isfile(self.qtAssetCacheList): + # self.generateAssetsFileList() diff --git a/hifi_utils.py b/hifi_utils.py index f53258d4f6..24e43dc83c 100644 --- a/hifi_utils.py +++ b/hifi_utils.py @@ -6,6 +6,7 @@ import ssl import subprocess import sys import tarfile +import re import urllib import urllib.request import zipfile @@ -23,13 +24,15 @@ def scriptRelative(*paths): return result -def recursiveFileList(startPath): +def recursiveFileList(startPath, excludeNamePattern=None ): result = [] if os.path.isfile(startPath): result.append(startPath) elif os.path.isdir(startPath): for dirName, subdirList, fileList in os.walk(startPath): for fname in fileList: + if excludeNamePattern and re.match(excludeNamePattern, fname): + continue result.append(os.path.realpath(os.path.join(startPath, dirName, fname))) result.sort() return result @@ -97,16 +100,12 @@ def downloadFile(url, hash=None, hasher=hashlib.sha512(), retries=3): else: tempFileName, headers = urllib.request.urlretrieve(url) - # for some reason the hash we get back from the downloaded file is sometimes wrong if we check it right away - # but if we examine the file later, it is correct. - time.sleep(3) downloadHash = hashFile(tempFileName, hasher) # Verify the hash if hash is not None and hash != downloadHash: print("Try {}: Downloaded file {} hash {} does not match expected hash {} for url {}".format(i + 1, tempFileName, downloadHash, hash, url)) os.remove(tempFileName) continue - return tempFileName raise RuntimeError("Downloaded file hash {} does not match expected hash {} for\n{}".format(downloadHash, hash, url)) diff --git a/hifi_vcpkg.py b/hifi_vcpkg.py index 5492109864..e062b40d86 100644 --- a/hifi_vcpkg.py +++ b/hifi_vcpkg.py @@ -85,7 +85,7 @@ endif() if self.args.android: self.triplet = 'arm64-android' - self.androidPackagePath = os.path.join(self.path, 'android') + self.androidPackagePath = os.getenv('HIFI_ANDROID_PRECOMPILED', os.path.join(self.path, 'android')) else: self.triplet = self.hostTriplet @@ -189,6 +189,18 @@ endif() #hifi_utils.downloadAndExtract(url, dest, hash) hifi_utils.downloadAndExtract(url, dest) + print("Installing additional android archives") + androidPackages = hifi_android.getPlatformPackages() + for packageName in androidPackages: + package = androidPackages[packageName] + dest = os.path.join(self.androidPackagePath, packageName) + if os.path.isdir(dest): + continue + url = hifi_android.getPackageUrl(package) + zipFile = package['file'].endswith('.zip') + print("Android archive {}".format(package['file'])) + hifi_utils.downloadAndExtract(url, dest, isZip=zipFile, hash=package['checksum'], hasher=hashlib.md5()) + def writeTag(self): print("Writing tag {} to {}".format(self.tagContents, self.tagFile)) with open(self.tagFile, 'w') as f: @@ -203,6 +215,12 @@ endif() cmakeTemplate = VcpkgRepo.CMAKE_TEMPLATE if not self.args.android: cmakeTemplate += VcpkgRepo.CMAKE_TEMPLATE_NON_ANDROID + else: + precompiled = os.path.realpath(self.androidPackagePath) + qtCmakePrefix = os.path.realpath(os.path.join(precompiled, 'qt/lib/cmake')) + cmakeTemplate += 'set(HIFI_ANDROID_PRECOMPILED "{}")\n'.format(precompiled) + cmakeTemplate += 'set(QT_CMAKE_PREFIX_PATH "{}")\n'.format(qtCmakePrefix) + cmakeConfig = cmakeTemplate.format(cmakeScript, cmakeScript, installPath, toolsPath).replace('\\', '/') with open(self.configFilePath, 'w') as f: f.write(cmakeConfig) diff --git a/interface/icon/interface-beta.icns b/interface/icon/interface-beta.icns index 1e2a4baeaa..5c1eddd8d2 100644 Binary files a/interface/icon/interface-beta.icns and b/interface/icon/interface-beta.icns differ diff --git a/interface/icon/interface-beta.ico b/interface/icon/interface-beta.ico index 1ed1ebddb9..d934d2fe0c 100644 Binary files a/interface/icon/interface-beta.ico and b/interface/icon/interface-beta.ico differ diff --git a/interface/icon/interface.icns b/interface/icon/interface.icns index 332539af2a..1035942e09 100644 Binary files a/interface/icon/interface.icns and b/interface/icon/interface.icns differ diff --git a/interface/icon/interface.ico b/interface/icon/interface.ico index e3d852cb41..077cfdeb63 100644 Binary files a/interface/icon/interface.ico and b/interface/icon/interface.ico differ diff --git a/interface/resources/controllers/keyboardMouse.json b/interface/resources/controllers/keyboardMouse.json index 2ad07911c6..74c11203ef 100644 --- a/interface/resources/controllers/keyboardMouse.json +++ b/interface/resources/controllers/keyboardMouse.json @@ -123,7 +123,16 @@ { "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] }, "when": "Keyboard.RightMouseButton", - "to": "Actions.Yaw", + "to": "Actions.DeltaYaw", + "filters": + [ + { "type": "scale", "scale": 0.6 } + ] + }, + + { "from": { "makeAxis" : ["Keyboard.MouseMoveUp", "Keyboard.MouseMoveDown"] }, + "when": "Keyboard.RightMouseButton", + "to": "Actions.DeltaPitch", "filters": [ { "type": "scale", "scale": 0.6 } @@ -144,20 +153,6 @@ { "from": "Keyboard.PgDown", "to": "Actions.VERTICAL_DOWN" }, { "from": "Keyboard.PgUp", "to": "Actions.VERTICAL_UP" }, - { "from": "Keyboard.MouseMoveUp", "when": "Keyboard.RightMouseButton", "to": "Actions.PITCH_UP", - "filters": - [ - { "type": "scale", "scale": 0.6 } - ] - - }, - { "from": "Keyboard.MouseMoveDown", "when": "Keyboard.RightMouseButton", "to": "Actions.PITCH_DOWN", - "filters": - [ - { "type": "scale", "scale": 0.6 } - ] - }, - { "from": "Keyboard.TouchpadDown", "to": "Actions.PITCH_DOWN" }, { "from": "Keyboard.TouchpadUp", "to": "Actions.PITCH_UP" }, diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index 0f3db48925..1c205b5f5e 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -54,7 +54,8 @@ FocusScope { Image { z: -10 id: loginDialogBackground - source: "LoginDialog/images/background.jpg" + fillMode: Image.PreserveAspectCrop + source: "LoginDialog/images/background.png" anchors.fill: parent } diff --git a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml index 6859b7ab3d..144b91063f 100644 --- a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml +++ b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml @@ -29,6 +29,8 @@ Item { readonly property bool withSteam: withSteam property string errorString: errorString + readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() + QtObject { id: d readonly property int minWidth: 480 @@ -123,6 +125,13 @@ Item { fontSize: completeProfileBody.fontSize fontBold: completeProfileBody.fontBold onClicked: { + if (completeProfileBody.loginDialogPoppedUp) { + var data = { + "action": "user clicked cancel on the complete profile screen" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader }); } } @@ -142,6 +151,12 @@ Item { fontSize: completeProfileBody.fontSize fontBold: completeProfileBody.fontBold onClicked: { + if (completeProfileBody.loginDialogPoppedUp) { + var data = { + "action": "user clicked create profile" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } loginErrorMessage.visible = false; loginDialog.createAccountFromSteam(); } @@ -253,13 +268,29 @@ Item { onHandleCreateCompleted: { console.log("Create Succeeded") - loginDialog.loginThroughSteam(); - bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": true, "linkSteam": false }); + if (completeProfileBody.withSteam) { + if (completeProfileBody.loginDialogPoppedUp) { + var data = { + "action": "user created a profile with Steam successfully from the complete profile screen" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + loginDialog.loginThroughSteam(); + } + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, "linkSteam": false }); } onHandleCreateFailed: { console.log("Create Failed: " + error); + if (completeProfileBody.withSteam) { + if (completeProfileBody.loginDialogPoppedUp) { + var data = { + "action": "user failed to create a profile with Steam from the complete profile screen" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + } - bodyLoader.setSource("UsernameCollisionBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader }); + bodyLoader.setSource("UsernameCollisionBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam }); } } diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 987c5b08e4..5048bf0278 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -42,6 +42,8 @@ Item { property string errorString: errorString property bool lostFocus: false + readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() + QtObject { id: d readonly property int minWidth: 480 @@ -68,6 +70,20 @@ Item { function login() { loginDialog.login(emailField.text, passwordField.text); + if (linkAccountBody.loginDialogPoppedUp) { + var data; + if (linkAccountBody.linkSteam) { + data = { + "action": "user linking hifi account with Steam" + }; + } else { + data = { + "action": "user logging in" + }; + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam }); } @@ -294,6 +310,14 @@ Item { topMargin: hifi.dimensions.contentSpacing.y } onClicked: { + if (linkAccountBody.loginDialogPoppedUp) { + var data = { + "action": "user clicked cancel at link account screen" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + loginDialog.dismissLoginDialog(); + } + bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "errorString": "" }); } } @@ -310,7 +334,7 @@ Item { topMargin: hifi.dimensions.contentSpacing.y } onClicked: { - linkAccountBody.login() + linkAccountBody.login(); } } TextMetrics { @@ -373,7 +397,12 @@ Item { lightboxPopup.visible = false; } lightboxPopup.visible = true; - // bodyLoader.setSource("CantAccessBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader }); + if (linkAccountBody.loginDialogPoppedUp) { + var data = { + "action": "user clicked can't access account" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } } } HifiControlsUit.Button { @@ -402,6 +431,19 @@ Item { linkAccountBody.withSteam = true; loginDialog.loginThroughSteam(); } + if (linkAccountBody.loginDialogPoppedUp) { + var data; + if (linkAccountBody.withOculus) { + data = { + "action": "user clicked login through Oculus" + }; + } else if (linkAccountBody.withSteam) { + data = { + "action": "user clicked login through Steam" + }; + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam }); @@ -470,6 +512,12 @@ Item { linkColor: hifi.colors.blueAccent onLinkActivated: { Tablet.playSound(TabletEnums.ButtonClick); + if (linkAccountBody.loginDialogPoppedUp) { + var data = { + "action": "user clicked sign up button" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } bodyLoader.setSource("SignUpBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "", "linkSteam": linkAccountBody.linkSteam }); } @@ -495,10 +543,9 @@ Item { fontFamily: linkAccountBody.fontFamily fontSize: linkAccountBody.fontSize fontBold: linkAccountBody.fontBold - visible: loginDialog.getLoginDialogPoppedUp() && !linkAccountBody.linkSteam; + visible: linkAccountBody.loginDialogPoppedUp && !linkAccountBody.linkSteam; onClicked: { - if (loginDialog.getLoginDialogPoppedUp()) { - console.log("[ENCOURAGELOGINDIALOG]: user dismissed login screen") + if (linkAccountBody.loginDialogPoppedUp) { var data = { "action": "user dismissed login screen" }; diff --git a/interface/resources/qml/LoginDialog/LoggingInBody.qml b/interface/resources/qml/LoginDialog/LoggingInBody.qml index 355f89667b..5e4a6c4cb3 100644 --- a/interface/resources/qml/LoginDialog/LoggingInBody.qml +++ b/interface/resources/qml/LoginDialog/LoggingInBody.qml @@ -30,6 +30,8 @@ Item { property bool withOculus: withOculus property bool linkSteam: linkSteam + readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() + QtObject { id: d readonly property int minWidth: 480 @@ -62,8 +64,12 @@ Item { running: false; repeat: false; onTriggered: { - if (loginDialog.getLoginDialogPoppedUp()) { + if (loggingInBody.loginDialogPoppedUp) { loginDialog.dismissLoginDialog(); + var data = { + "action": "user logged in successfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); } root.tryDestroy(); } @@ -107,6 +113,12 @@ Item { loggingInText.x = 0; loggingInText.anchors.centerIn = loggingInHeader; loggedInGlyph.visible = true; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user logged in with Steam successfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } } else if (loggingInBody.withOculus) { // reset the flag. loggingInGlyph.visible = false; @@ -115,8 +127,21 @@ Item { loggingInText.anchors.centerIn = loggingInHeader; loggedInGlyph.text = hifi.glyphs.oculus; loggedInGlyph.visible = true; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user logged in with Oculus successfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } } else { loggingInText.text = "You are now logged in!"; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user logged in successfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } + } successTimer.start(); } @@ -234,11 +259,28 @@ Item { target: loginDialog onHandleLinkCompleted: { console.log("Link Succeeded"); - loggingInBody.linkSteam = false; - loggingInBody.loadingSuccess(); + if (loggingInBody.linkSteam) { + loggingInBody.linkSteam = false; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user linked Steam with their hifi account credentials successfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } + + loggingInBody.loadingSuccess(); + } } onHandleLinkFailed: { console.log("Link Failed: " + error); + if (loggingInBody.linkSteam) { + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user linked Steam unsuccessfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } + } bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": true, "errorString": error }); } @@ -253,18 +295,43 @@ Item { var errorString = ""; if (loggingInBody.linkSteam && loggingInBody.withSteam) { errorString = "Username or password is incorrect."; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user failed to link Steam with their hifi account credentials" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, "linkSteam": loggingInBody.linkSteam, "errorString": errorString }); } else if (loggingInBody.withSteam) { loggingInGlyph.visible = false; errorString = "Your Steam authentication has failed. Please make sure you are logged into Steam and try again."; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user failed to authenticate with Steam to log in" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, "errorString": errorString }); } else if (loggingInBody.withOculus) { loggingInGlyph.visible = false; errorString = "Your Oculus authentication has failed. Please make sure you are logged into Oculus and try again." + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user failed to authenticate with Oculus to log in" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": errorString }); } else { errorString = "Username or password is incorrect."; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user failed at logging in" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } + bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": errorString }); } } diff --git a/interface/resources/qml/LoginDialog/SignUpBody.qml b/interface/resources/qml/LoginDialog/SignUpBody.qml index 5e0e955330..3ba66391e6 100644 --- a/interface/resources/qml/LoginDialog/SignUpBody.qml +++ b/interface/resources/qml/LoginDialog/SignUpBody.qml @@ -40,6 +40,8 @@ Item { property bool linkSteam: linkSteam property bool lostFocus: false + readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() + QtObject { id: d readonly property int minWidth: 480 @@ -345,6 +347,12 @@ Item { fontSize: signUpBody.fontSize fontBold: signUpBody.fontBold onClicked: { + if (signUpBody.loginDialogPoppedUp) { + var data = { + "action": "user clicked cancel button at sign up screen" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": signUpBody.linkSteam }); } } @@ -363,23 +371,18 @@ Item { } onClicked: { + if (signUpBody.loginDialogPoppedUp) { + var data = { + "action": "user clicked sign up button" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } signUpBody.signup(); } } } } - MouseArea { - z: -2 - anchors.fill: parent - acceptedButtons: Qt.LeftButton - onClicked: { - if (!usernameField.focus && !emailField.focus && !passwordField.focus) { - usernameField.focus = true; - } - } - } - Component.onCompleted: { //but rise Tablet's one instead for Tablet interface root.keyboardEnabled = HMD.active; @@ -408,12 +411,26 @@ Item { onHandleSignupCompleted: { console.log("Sign Up Completed"); + if (signUpBody.loginDialogPoppedUp) { + var data = { + "action": "user signed up successfully" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + loginDialog.login(usernameField.text, passwordField.text); bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": false, "linkSteam": false }); } onHandleSignupFailed: { console.log("Sign Up Failed") + if (signUpBody.loginDialogPoppedUp) { + var data = { + "action": "user signed up unsuccessfully" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + if (errorString !== "") { loginErrorMessage.visible = true; var errorStringEdited = errorString.replace(/[\n\r]+/g, "\n"); diff --git a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml index d44f5b733c..af46fc0223 100644 --- a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml +++ b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml @@ -26,6 +26,10 @@ Item { readonly property int textFieldFontSize: 18 readonly property bool fontBold: true + readonly property bool withSteam: withSteam + + readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() + function create() { mainTextContainer.visible = false loginDialog.createAccountFromSteam(textField.text); @@ -197,18 +201,40 @@ Item { target: loginDialog onHandleCreateCompleted: { console.log("Create Succeeded"); - loginDialog.loginThroughSteam(); - bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": true, "linkSteam": false }) + if (usernameCollisionBody.withSteam) { + if (usernameCollisionBody.loginDialogPoppedUp) { + var data = { + "action": "user created a profile with Steam successfully in the username collision screen" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + + loginDialog.loginThroughSteam(); + } + + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam, "linkSteam": false }) } onHandleCreateFailed: { console.log("Create Failed: " + error) + if (usernameCollisionBody.loginDialogPoppedUp) { + var data = { + "action": "user failed to create account from the username collision screen" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + mainTextContainer.visible = true mainTextContainer.text = "\"" + textField.text + qsTr("\" is invalid or already taken."); } onHandleLoginCompleted: { console.log("Login Succeeded"); - if (loginDialog.getLoginDialogPoppedUp()) { + if (usernameCollisionBody.loginDialogPoppedUp) { + var data = { + "action": "user logged in successfully from the username collision screen" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + loginDialog.dismissLoginDialog(); } root.tryDestroy(); @@ -216,6 +242,13 @@ Item { onHandleLoginFailed: { console.log("Login Failed") + if (usernameCollisionBody.loginDialogPoppedUp) { + var data = { + "action": "user failed to log in from the username collision screen" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + mainTextContainer.text = "Login Failed"; } diff --git a/interface/resources/qml/LoginDialog/images/background.jpg b/interface/resources/qml/LoginDialog/images/background.jpg deleted file mode 100644 index db2798a4a1..0000000000 Binary files a/interface/resources/qml/LoginDialog/images/background.jpg and /dev/null differ diff --git a/interface/resources/qml/LoginDialog/images/background.png b/interface/resources/qml/LoginDialog/images/background.png new file mode 100644 index 0000000000..cd107b13eb Binary files /dev/null and b/interface/resources/qml/LoginDialog/images/background.png differ diff --git a/interface/resources/qml/LoginDialog/images/background_tablet.jpg b/interface/resources/qml/LoginDialog/images/background_tablet.jpg deleted file mode 100644 index a46c052c04..0000000000 Binary files a/interface/resources/qml/LoginDialog/images/background_tablet.jpg and /dev/null differ diff --git a/interface/resources/qml/LoginDialog/images/background_tablet.png b/interface/resources/qml/LoginDialog/images/background_tablet.png new file mode 100644 index 0000000000..5c288590ab Binary files /dev/null and b/interface/resources/qml/LoginDialog/images/background_tablet.png differ diff --git a/interface/resources/qml/OverlayLoginDialog.qml b/interface/resources/qml/OverlayLoginDialog.qml index 3de3f68942..8f709af2ab 100644 --- a/interface/resources/qml/OverlayLoginDialog.qml +++ b/interface/resources/qml/OverlayLoginDialog.qml @@ -55,7 +55,8 @@ FocusScope { Image { z: -10 id: loginDialogBackground - source: "LoginDialog/images/background.jpg" + fillMode: Image.PreserveAspectCrop + source: "LoginDialog/images/background.png" anchors.fill: parent } diff --git a/interface/resources/qml/dialogs/TabletLoginDialog.qml b/interface/resources/qml/dialogs/TabletLoginDialog.qml index 7bffcbe75c..3ad17430cd 100644 --- a/interface/resources/qml/dialogs/TabletLoginDialog.qml +++ b/interface/resources/qml/dialogs/TabletLoginDialog.qml @@ -95,7 +95,8 @@ FocusScope { Image { z: -10 id: loginDialogBackground - source: "../LoginDialog/images/background_tablet.jpg" + fillMode: Image.PreserveAspectCrop + source: "../LoginDialog/images/background_tablet.png" anchors.fill: parent } diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index 247a42428a..1abd4f45ff 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -148,7 +148,7 @@ Windows.ScrollingWindow { } function canAddToWorld(path) { - var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i]; + var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i, /\.gltf\b/i]; if (selectedItemCount > 1) { return false; diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index bfa37385a5..171ea4fd15 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -10,7 +10,7 @@ import "avatarapp" Rectangle { id: root width: 480 - height: 706 + height: 706 property bool keyboardEnabled: true property bool keyboardRaised: false @@ -416,7 +416,7 @@ Rectangle { width: 21.2 height: 19.3 source: isAvatarInFavorites ? '../../images/FavoriteIconActive.svg' : '../../images/FavoriteIconInActive.svg' - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter } // TextStyle5 @@ -425,7 +425,7 @@ Rectangle { Layout.fillWidth: true text: isAvatarInFavorites ? avatarName : "Add to Favorites" elide: Qt.ElideRight - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter } } diff --git a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml index 493bfa2a30..136d535b3f 100644 --- a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml +++ b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml @@ -157,7 +157,7 @@ Rectangle { visible = false; adjustWearablesClosed(status, avatarName); } - + HifiConstants { id: hifi } @@ -177,8 +177,9 @@ Rectangle { repeat: true onTriggered: { var currentWearable = getCurrentWearable(); - var soft = currentWearable ? currentWearable.relayParentJoints : false; - var softEnabled = currentWearable ? entityHasAvatarJoints(currentWearable.id) : false; + var hasSoft = currentWearable && currentWearable.relayParentJoints !== undefined; + var soft = hasSoft ? currentWearable.relayParentJoints : false; + var softEnabled = hasSoft ? entityHasAvatarJoints(currentWearable.id) : false; isSoft.set(soft); isSoft.enabled = softEnabled; } @@ -230,7 +231,7 @@ Rectangle { lineHeightMode: Text.FixedHeight lineHeight: 18; text: "Wearable" - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter } spacing: 10 @@ -241,7 +242,7 @@ Rectangle { lineHeight: 18; text: "Get more" linkColor: hifi.colors.blueHighlight - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter onLinkActivated: { popup.showGetWearables(function() { emitSendToScript({'method' : 'navigate', 'url' : 'hifi://AvatarIsland/11.5848,-8.10862,-2.80195'}) @@ -511,7 +512,7 @@ Rectangle { function set(value) { notify = false; - checked = value + checked = value; notify = true; } diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index d212186c5e..b6d0167ba5 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -35,8 +35,8 @@ Rectangle { property real scaleValue: scaleSlider.value / 10 property alias dominantHandIsLeft: leftHandRadioButton.checked - property alias otherAvatarsCollisionsOn: otherAvatarsCollisionsEnabledCheckBox.checked - property alias environmentCollisionsOn: environmentCollisionsEnabledCheckBox.checked + property alias otherAvatarsCollisionsOn: otherAvatarsCollisionsEnabledRadiobutton.checked + property alias environmentCollisionsOn: environmentCollisionsEnabledRadiobutton.checked property alias avatarAnimationOverrideJSON: avatarAnimationUrlInputText.text property alias avatarAnimationJSON: avatarAnimationUrlInputText.placeholderText property alias avatarCollisionSoundUrl: avatarCollisionSoundUrlInputText.text @@ -56,10 +56,14 @@ Rectangle { rightHandRadioButton.checked = true; } if (settings.otherAvatarsCollisionsEnabled) { - otherAvatarsCollisionsEnabledCheckBox.checked = true; + otherAvatarsCollisionsEnabledRadiobutton.checked = true; + } else { + otherAvatarsCollisionsDisabledRadiobutton.checked = true; } if (settings.collisionsEnabled) { - environmentCollisionsEnabledCheckBox.checked = true; + environmentCollisionsEnabledRadiobutton.checked = true; + } else { + environmentCollisionsDisabledRadiobutton.checked = true; } avatarAnimationJSON = settings.animGraphUrl; @@ -104,11 +108,11 @@ Rectangle { size: 17; text: "Avatar Scale" verticalAlignment: Text.AlignVCenter - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter } RowLayout { - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true spacing: 0 @@ -118,7 +122,7 @@ Rectangle { text: 'T' verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter } HifiControlsUit.Slider { @@ -136,7 +140,7 @@ Rectangle { } } - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true // TextStyle9 @@ -165,7 +169,7 @@ Rectangle { text: 'T' verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter } } @@ -229,7 +233,7 @@ Rectangle { Layout.row: 0 Layout.column: 1 - Layout.leftMargin: -40 + Layout.leftMargin: -20 ButtonGroup.group: leftRight checked: true @@ -245,8 +249,8 @@ Rectangle { id: rightHandRadioButton Layout.row: 0 - Layout.column: 2 - Layout.rightMargin: 20 + Layout.column: 3 + Layout.rightMargin: -15 ButtonGroup.group: leftRight @@ -266,16 +270,43 @@ Rectangle { size: 17; Layout.row: 1 Layout.column: 0 - text: "Avatar collides with other avatars" + text: "Avatar to avatar collision" + } + + ButtonGroup { + id: otherAvatarsOnOff + } + + HifiControlsUit.RadioButton { + id: otherAvatarsCollisionsEnabledRadiobutton + + Layout.row: 1 + Layout.column: 1 + Layout.leftMargin: -20 + + ButtonGroup.group: otherAvatarsOnOff + + colorScheme: hifi.colorSchemes.light + fontSize: 17 + letterSpacing: 1.4 + text: "On" + boxSize: 20 } - HifiControlsUit.CheckBox { - id: otherAvatarsCollisionsEnabledCheckBox; - boxSize: 20; + HifiControlsUit.RadioButton { + id: otherAvatarsCollisionsDisabledRadiobutton + Layout.row: 1 - Layout.column: 2 - Layout.leftMargin: 60 + Layout.column: 3 + Layout.rightMargin: -15 + + ButtonGroup.group: otherAvatarsOnOff + colorScheme: hifi.colorSchemes.light + fontSize: 17 + letterSpacing: 1.4 + text: "Off" + boxSize: 20 } // TextStyle9 @@ -283,16 +314,43 @@ Rectangle { size: 17; Layout.row: 2 Layout.column: 0 - text: "Avatar collides with environment" + text: "Avatar to environment collision" } - HifiControlsUit.CheckBox { - id: environmentCollisionsEnabledCheckBox; - boxSize: 20; + ButtonGroup { + id: worldOnOff + } + + HifiControlsUit.RadioButton { + id: environmentCollisionsEnabledRadiobutton + Layout.row: 2 - Layout.column: 2 - Layout.leftMargin: 60 + Layout.column: 1 + Layout.leftMargin: -20 + + ButtonGroup.group: worldOnOff + colorScheme: hifi.colorSchemes.light + fontSize: 17 + letterSpacing: 1.4 + text: "On" + boxSize: 20 + } + + HifiControlsUit.RadioButton { + id: environmentCollisionsDisabledRadiobutton + + Layout.row: 2 + Layout.column: 3 + Layout.rightMargin: -15 + + ButtonGroup.group: worldOnOff + + colorScheme: hifi.colorSchemes.light + fontSize: 17 + letterSpacing: 1.4 + text: "Off" + boxSize: 20 } } @@ -316,8 +374,7 @@ Rectangle { InputTextStyle4 { id: avatarAnimationUrlInputText font.pixelSize: 17 - anchors.left: parent.left - anchors.right: parent.right + Layout.fillWidth: true placeholderText: 'user\\file\\dir' onFocusChanged: { @@ -346,8 +403,7 @@ Rectangle { InputTextStyle4 { id: avatarCollisionSoundUrlInputText font.pixelSize: 17 - anchors.left: parent.left - anchors.right: parent.right + Layout.fillWidth: true placeholderText: 'https://hifi-public.s3.amazonaws.com/sounds/Collisions-' onFocusChanged: { diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index b5374b2fe0..62ec264fc9 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -148,7 +148,7 @@ Rectangle { } function canAddToWorld(path) { - var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i]; + var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i, /\.jpg\b/i, /\.png\b/i, /\.gltf\b/i]; if (selectedItemCount > 1) { return false; diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index a01d978b2f..b19dcbb919 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -134,8 +134,7 @@ Item { if (isWebPage) { var webUrl = tabletApps.get(currentApp).appWebUrl; var scriptUrl = tabletApps.get(currentApp).scriptUrl; - loadSource("hifi/tablet/TabletWebView.qml"); - loadWebUrl(webUrl, scriptUrl); + loadWebBase(webUrl, scriptUrl); } else { loader.load(tabletApps.get(currentApp).appUrl); } @@ -150,16 +149,6 @@ Item { tabletRoot.openBrowser = newWindow; } - function loadWebUrl(url, injectedJavaScriptUrl) { - tabletApps.clear(); - loader.item.url = url; - loader.item.scriptURL = injectedJavaScriptUrl; - tabletApps.append({"appUrl": "TabletWebView.qml", "isWebUrl": true, "scriptUrl": injectedJavaScriptUrl, "appWebUrl": url}); - if (loader.item.hasOwnProperty("closeButtonVisible")) { - loader.item.closeButtonVisible = false; - } - } - // used to send a message from qml to interface script. signal sendToScript(var message); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e016b4afc7..68ac05ef18 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -725,6 +725,8 @@ const QString TEST_RESULTS_LOCATION_COMMAND{ "--testResultsLocation" }; bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { const char** constArgv = const_cast(argv); + qInstallMessageHandler(messageHandler); + // HRS: I could not figure out how to move these any earlier in startup, so when using this option, be sure to also supply // --allowMultipleInstances auto reportAndQuit = [&](const char* commandSwitch, std::function report) { @@ -918,6 +920,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -975,6 +978,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo QApplication(argc, argv), _window(new MainWindow(desktop())), _sessionRunTimer(startupTimer), + _logger(new FileLogger(this)), _previousSessionCrashed(setupEssentials(argc, argv, runningMarkerExisted)), _entitySimulation(new PhysicalEntitySimulation()), _physicsEngine(new PhysicsEngine(Vectors::ZERO)), @@ -1064,9 +1068,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo installNativeEventFilter(&MyNativeEventFilter::getInstance()); #endif - _logger = new FileLogger(this); - qInstallMessageHandler(messageHandler); - QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "styles/Inconsolata.otf"); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/fontawesome-webfont.ttf"); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/hifi-glyphs.ttf"); @@ -1201,6 +1202,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(&domainHandler, SIGNAL(connectedToDomain(QUrl)), SLOT(updateWindowTitle())); connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle())); connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this]() { + auto tabletScriptingInterface = DependencyManager::get(); + if (tabletScriptingInterface) { + tabletScriptingInterface->setQmlTabletRoot(SYSTEM_TABLET, nullptr); + } getOverlays().deleteOverlay(getTabletScreenID()); getOverlays().deleteOverlay(getTabletHomeButtonID()); getOverlays().deleteOverlay(getTabletFrameID()); @@ -2061,7 +2066,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo properties["avatar_ping"] = avatarMixerNode ? avatarMixerNode->getPingMs() : -1; properties["asset_ping"] = assetServerNode ? assetServerNode->getPingMs() : -1; properties["messages_ping"] = messagesMixerNode ? messagesMixerNode->getPingMs() : -1; - properties["atp_in_kbps"] = messagesMixerNode ? assetServerNode->getInboundKbps() : 0.0f; + properties["atp_in_kbps"] = assetServerNode ? assetServerNode->getInboundKbps() : 0.0f; auto loadingRequests = ResourceCache::getLoadingRequests(); @@ -2202,7 +2207,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo || ((rightHandPose.valid || lastRightHandPose.valid) && (rightHandPose != lastRightHandPose)); lastLeftHandPose = leftHandPose; lastRightHandPose = rightHandPose; - properties["avatar_identity_requests_sent"] = DependencyManager::get()->getIdentityRequestsSent(); UserActivityLogger::getInstance().logAction("stats", properties); }); @@ -2356,12 +2360,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo }); render::entities::WebEntityRenderer::setReleaseWebSurfaceOperator([this](QSharedPointer& webSurface, bool& cachedWebSurface, std::vector& connections) { QQuickItem* rootItem = webSurface->getRootItem(); - if (rootItem && rootItem->objectName() == "tabletRoot") { - auto tabletScriptingInterface = DependencyManager::get(); - if (tabletScriptingInterface) { - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr); - } - } // Fix for crash in QtWebEngineCore when rapidly switching domains // Call stop on the QWebEngineView before destroying OffscreenQMLSurface. @@ -2536,6 +2534,9 @@ void Application::updateHeartbeat() const { } void Application::onAboutToQuit() { + // quickly save AvatarEntityData before the EntityTree is dismantled + getMyAvatar()->saveAvatarEntityDataToSettings(); + emit beforeAboutToQuit(); if (getLoginDialogPoppedUp() && _firstRun.get()) { @@ -2673,6 +2674,7 @@ void Application::cleanupBeforeQuit() { DependencyManager::destroy(); // Must be destroyed before TabletScriptingInterface // stop QML + DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); @@ -2941,7 +2943,7 @@ void Application::showLoginScreen() { dialogsManager->showLoginDialog(); emit loginDialogFocusEnabled(); QJsonObject loginData = {}; - loginData["action"] = "login dialog shown"; + loginData["action"] = "login dialog popped up"; UserActivityLogger::getInstance().logAction("encourageLoginDialog", loginData); _window->setWindowTitle("High Fidelity Interface"); } else { @@ -2961,7 +2963,7 @@ void Application::initializeUi() { Tooltip::registerType(); UpdateDialog::registerType(); QmlContextCallback commerceCallback = [](QQmlContext* context) { - context->setContextProperty("Commerce", new QmlCommerce()); + context->setContextProperty("Commerce", DependencyManager::get().data()); }; OffscreenQmlSurface::addWhitelistContextHandler({ QUrl{ "hifi/commerce/checkout/Checkout.qml" }, @@ -2986,6 +2988,7 @@ void Application::initializeUi() { QUrl{ "hifi/dialogs/security/SecurityImageChange.qml" }, QUrl{ "hifi/dialogs/security/SecurityImageModel.qml" }, QUrl{ "hifi/dialogs/security/SecurityImageSelection.qml" }, + QUrl{ "hifi/tablet/TabletMenu.qml" }, }, commerceCallback); QmlContextCallback ttsCallback = [](QQmlContext* context) { context->setContextProperty("TextToSpeech", DependencyManager::get().data()); @@ -6096,6 +6099,8 @@ void Application::update(float deltaTime) { if (deltaTime > FLT_EPSILON) { myAvatar->setDriveKey(MyAvatar::PITCH, -1.0f * userInputMapper->getActionState(controller::Action::PITCH)); myAvatar->setDriveKey(MyAvatar::YAW, -1.0f * userInputMapper->getActionState(controller::Action::YAW)); + myAvatar->setDriveKey(MyAvatar::DELTA_PITCH, -1.0f * userInputMapper->getActionState(controller::Action::DELTA_PITCH)); + myAvatar->setDriveKey(MyAvatar::DELTA_YAW, -1.0f * userInputMapper->getActionState(controller::Action::DELTA_YAW)); myAvatar->setDriveKey(MyAvatar::STEP_YAW, -1.0f * userInputMapper->getActionState(controller::Action::STEP_YAW)); } } @@ -6401,7 +6406,7 @@ void Application::update(float deltaTime) { // TODO: Fix this by modeling the way the secondary camera works on how the main camera works // ie. Use a camera object stored in the game logic and informs the Engine on where the secondary // camera should be. - // updateSecondaryCameraViewFrustum(); + updateSecondaryCameraViewFrustum(); } quint64 now = usecTimestampNow(); @@ -6884,9 +6889,11 @@ void Application::updateWindowTitle() const { DependencyManager::get< MessagesClient >()->sendLocalMessage("Toolbar-DomainChanged", ""); } -void Application::clearDomainOctreeDetails() { +void Application::clearDomainOctreeDetails(bool clearAll) { + // before we delete all entities get MyAvatar's AvatarEntityData ready + getMyAvatar()->prepareAvatarEntityDataForReload(); - // if we're about to quit, we really don't need to do any of these things... + // if we're about to quit, we really don't need to do the rest of these things... if (_aboutToQuit) { return; } @@ -6901,7 +6908,7 @@ void Application::clearDomainOctreeDetails() { }); // reset the model renderer - getEntities()->clear(); + clearAll ? getEntities()->clear() : getEntities()->clearNonLocalEntities(); auto skyStage = DependencyManager::get()->getSkyStage(); @@ -6914,8 +6921,6 @@ void Application::clearDomainOctreeDetails() { ShaderCache::instance().clearUnusedResources(); DependencyManager::get()->clearUnusedResources(); DependencyManager::get()->clearUnusedResources(); - - getMyAvatar()->setAvatarEntityDataChanged(true); } void Application::domainURLChanged(QUrl domainURL) { @@ -6941,7 +6946,7 @@ void Application::goToErrorDomainURL(QUrl errorDomainURL) { void Application::resettingDomain() { _notifiedPacketVersionMismatchThisDomain = false; - clearDomainOctreeDetails(); + clearDomainOctreeDetails(false); } void Application::nodeAdded(SharedNodePointer node) const { @@ -7027,7 +7032,7 @@ void Application::nodeKilled(SharedNodePointer node) { QMetaObject::invokeMethod(DependencyManager::get().data(), "audioMixerKilled"); } else if (node->getType() == NodeType::EntityServer) { // we lost an entity server, clear all of the domain octree details - clearDomainOctreeDetails(); + clearDomainOctreeDetails(false); } else if (node->getType() == NodeType::AssetServer) { // asset server going away - check if we have the asset browser showing @@ -8142,8 +8147,7 @@ void Application::openUrl(const QUrl& url) const { if (url.scheme() == URL_SCHEME_HIFI) { DependencyManager::get()->handleLookupString(url.toString()); } else if (url.scheme() == URL_SCHEME_HIFIAPP) { - QmlCommerce commerce; - commerce.openSystemApp(url.path()); + DependencyManager::get()->openSystemApp(url.path()); } else { // address manager did not handle - ask QDesktopServices to handle QDesktopServices::openUrl(url); diff --git a/interface/src/Application.h b/interface/src/Application.h index 2f5ef38412..fbf6e8bc9c 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -469,7 +469,7 @@ private slots: void onDesktopRootItemCreated(QQuickItem* qmlContext); void onDesktopRootContextCreated(QQmlContext* qmlContext); void showDesktop(); - void clearDomainOctreeDetails(); + void clearDomainOctreeDetails(bool clearAll = true); void onAboutToQuit(); void onPresent(quint32 frameCount); @@ -596,6 +596,8 @@ private: bool _aboutToQuit { false }; + FileLogger* _logger { nullptr }; + bool _previousSessionCrashed; DisplayPluginPointer _displayPlugin; @@ -676,8 +678,6 @@ private: QPointer _entityScriptServerLogDialog; QDir _defaultScriptsLocation; - FileLogger* _logger; - TouchEvent _lastTouchEvent; quint64 _lastNackTime; diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index ee639f602d..5fe35bd23f 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -55,12 +55,11 @@ void addAvatarEntities(const QVariantList& avatarEntities) { QVariantMap asMap = variantProperties.toMap(); QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine); EntityItemProperties entityProperties; - EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, entityProperties); + EntityItemPropertiesFromScriptValueIgnoreReadOnly(scriptProperties, entityProperties); entityProperties.setParentID(myNodeID); entityProperties.setEntityHostType(entity::HostType::AVATAR); entityProperties.setOwningAvatarID(myNodeID); - entityProperties.setSimulationOwner(myNodeID, AVATAR_ENTITY_SIMULATION_PRIORITY); entityProperties.markAllChanged(); EntityItemID id = EntityItemID(QUuid::createUuid()); @@ -152,8 +151,29 @@ void AvatarBookmarks::removeBookmark(const QString& bookmarkName) { void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) { auto myAvatar = DependencyManager::get()->getMyAvatar(); - myAvatar->removeWearableAvatarEntities(); - addAvatarEntities(avatarEntities); + auto currentAvatarEntities = myAvatar->getAvatarEntityData(); + std::set newAvatarEntities; + + // Update or add all the new avatar entities + for (auto& avatarEntityVariant : avatarEntities) { + auto avatarEntityVariantMap = avatarEntityVariant.toMap(); + auto idItr = avatarEntityVariantMap.find("id"); + if (idItr != avatarEntityVariantMap.end()) { + auto propertiesItr = avatarEntityVariantMap.find("properties"); + if (propertiesItr != avatarEntityVariantMap.end()) { + EntityItemID id = idItr.value().toUuid(); + newAvatarEntities.insert(id); + myAvatar->updateAvatarEntity(id, QJsonDocument::fromVariant(propertiesItr.value()).toBinaryData()); + } + } + } + + // Remove any old entities not in the new list + for (auto& avatarEntityID : currentAvatarEntities.keys()) { + if (newAvatarEntities.find(avatarEntityID) == newAvatarEntities.end()) { + myAvatar->removeWornAvatarEntity(avatarEntityID); + } + } } void AvatarBookmarks::loadBookmark(const QString& bookmarkName) { @@ -177,7 +197,7 @@ void AvatarBookmarks::loadBookmark(const QString& bookmarkName) { auto myAvatar = DependencyManager::get()->getMyAvatar(); auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; - myAvatar->removeWearableAvatarEntities(); + myAvatar->clearWornAvatarEntities(); const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString(); myAvatar->useFullAvatarURL(avatarUrl); qCDebug(interfaceapp) << "Avatar On"; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp old mode 100644 new mode 100755 index 53c16c8a61..5e4f02742e --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -48,8 +48,6 @@ // 50 times per second - target is 45hz, but this helps account for any small deviations // in the update loop - this also results in ~30hz when in desktop mode which is essentially // what we want -const int CLIENT_TO_AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 50; -static const quint64 MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS = USECS_PER_SECOND / CLIENT_TO_AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND; // We add _myAvatar into the hash with all the other AvatarData, and we use the default NULL QUid as the key. const QUuid MY_AVATAR_KEY; // NULL key @@ -67,6 +65,11 @@ AvatarManager::AvatarManager(QObject* parent) : connect(nodeList.data(), &NodeList::ignoredNode, this, [this](const QUuid& nodeID, bool enabled) { if (enabled) { removeAvatar(nodeID, KillAvatarReason::AvatarIgnored); + } else { + auto avatar = std::static_pointer_cast(getAvatarBySessionID(nodeID)); + if (avatar) { + avatar->createOrb(); + } } }); @@ -267,7 +270,6 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { if (avatar->getSkeletonModel()->isLoaded()) { // remove the orb if it is there avatar->removeOrb(); - avatar->updateCollisionGroup(_myAvatar->getOtherAvatarsCollisionsEnabled()); if (avatar->needsPhysicsUpdate()) { _avatarsToChangeInPhysics.insert(avatar); } @@ -347,25 +349,6 @@ void AvatarManager::postUpdate(float deltaTime, const render::ScenePointer& scen } } -void AvatarManager::sendIdentityRequest(const QUuid& avatarID) const { - auto nodeList = DependencyManager::get(); - QWeakPointer nodeListWeak = nodeList; - nodeList->eachMatchingNode( - [](const SharedNodePointer& node)->bool { - return node->getType() == NodeType::AvatarMixer && node->getActiveSocket(); - }, - [this, avatarID, nodeListWeak](const SharedNodePointer& node) { - auto nodeList = nodeListWeak.lock(); - if (nodeList) { - auto packet = NLPacket::create(PacketType::AvatarIdentityRequest, NUM_BYTES_RFC4122_UUID, true); - packet->write(avatarID.toRfc4122()); - nodeList->sendPacket(std::move(packet), *node); - ++_identityRequestsSent; - } - } - ); -} - void AvatarManager::simulateAvatarFades(float deltaTime) { if (_avatarsToFadeOut.empty()) { return; @@ -391,8 +374,14 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { scene->enqueueTransaction(transaction); } -AvatarSharedPointer AvatarManager::newSharedAvatar() { - return AvatarSharedPointer(new OtherAvatar(qApp->thread()), [](OtherAvatar* ptr) { ptr->deleteLater(); }); +AvatarSharedPointer AvatarManager::newSharedAvatar(const QUuid& sessionUUID) { + auto otherAvatar = new OtherAvatar(qApp->thread()); + otherAvatar->setSessionUUID(sessionUUID); + auto nodeList = DependencyManager::get(); + if (!nodeList || !nodeList->isIgnoringNode(sessionUUID)) { + otherAvatar->createOrb(); + } + return AvatarSharedPointer(otherAvatar, [](OtherAvatar* ptr) { ptr->deleteLater(); }); } void AvatarManager::queuePhysicsChange(const OtherAvatarPointer& avatar) { diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 359af8e361..7bd4e8236a 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -92,7 +92,6 @@ public: void updateMyAvatar(float deltaTime); void updateOtherAvatars(float deltaTime); - void sendIdentityRequest(const QUuid& avatarID) const; void setMyAvatarDataPacketsPaused(bool puase); @@ -191,7 +190,6 @@ public: Q_INVOKABLE QVariantMap getPalData(const QStringList& specificAvatarIdentifiers = QStringList()); float getMyAvatarSendRate() const { return _myAvatarSendRate.rate(); } - int getIdentityRequestsSent() const { return _identityRequestsSent; } void queuePhysicsChange(const OtherAvatarPointer& avatar); void buildPhysicsTransaction(PhysicsEngine::Transaction& transaction); @@ -216,7 +214,7 @@ private: void simulateAvatarFades(float deltaTime); - AvatarSharedPointer newSharedAvatar() override; + AvatarSharedPointer newSharedAvatar(const QUuid& sessionUUID) override; // called only from the AvatarHashMap thread - cannot be called while this thread holds the // hash lock, since handleRemovedAvatar needs a write lock on the entity tree and the entity tree @@ -241,7 +239,6 @@ private: float _avatarSimulationTime { 0.0f }; bool _shouldRender { true }; bool _myAvatarDataPacketsPaused { false }; - mutable int _identityRequestsSent { 0 }; mutable std::mutex _spaceLock; workload::SpacePointer _space; diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp old mode 100644 new mode 100755 index 3fa59ea967..77fc81fa04 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -15,7 +15,6 @@ #include #include - AvatarMotionState::AvatarMotionState(OtherAvatarPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) { assert(_avatar); _type = MOTIONSTATE_TYPE_AVATAR; @@ -172,7 +171,10 @@ QUuid AvatarMotionState::getSimulatorID() const { // virtual void AvatarMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const { group = _collisionGroup; - mask = _collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS ? 0 : Physics::getDefaultCollisionMask(group); + mask = Physics::getDefaultCollisionMask(group); + if (!_avatar->getCollideWithOtherAvatars()) { + mask &= ~(BULLET_COLLISION_GROUP_MY_AVATAR | BULLET_COLLISION_GROUP_OTHER_AVATAR); + } } // virtual diff --git a/interface/src/avatar/GrabManager.cpp b/interface/src/avatar/GrabManager.cpp index c41435d67e..db1337b64d 100644 --- a/interface/src/avatar/GrabManager.cpp +++ b/interface/src/avatar/GrabManager.cpp @@ -18,6 +18,8 @@ void GrabManager::simulateGrabs() { // Update grabbed objects auto entityTreeRenderer = DependencyManager::get(); auto entityTree = entityTreeRenderer->getTree(); + auto sessionID = DependencyManager::get()->getSessionUUID(); + EntityEditPacketSender* packetSender = entityTreeRenderer ? entityTreeRenderer->getPacketSender() : nullptr; entityTree->withReadLock([&] { PROFILE_RANGE(simulation, "Grabs"); @@ -33,6 +35,8 @@ void GrabManager::simulateGrabs() { glm::vec3 finalPosition = acc.finalizePosition(); glm::quat finalOrientation = acc.finalizeOrientation(); grabbedThing->setTransform(createMatFromQuatAndPos(finalOrientation, finalPosition)); + bool iShouldTellServer = grabbedThing->getEditSenderID() == sessionID; + entityTree->updateEntityQueryAACube(grabbedThing, packetSender, false, iShouldTellServer); } } }); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index e40fc7f9dd..fec988b29f 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -50,6 +50,7 @@ #include #include #include +#include #include "MyHead.h" #include "MySkeletonModel.h" @@ -204,12 +205,12 @@ MyAvatar::MyAvatar(QThread* thread) : if (recordingInterface->getPlayFromCurrentLocation()) { setRecordingBasis(); } - _previousCollisionGroup = _characterController.computeCollisionGroup(); + _previousCollisionMask = _characterController.computeCollisionMask(); _characterController.setCollisionless(true); } else { clearRecordingBasis(); useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName); - if (_previousCollisionGroup != BULLET_COLLISION_GROUP_COLLISIONLESS) { + if (_previousCollisionMask != BULLET_COLLISION_MASK_COLLISIONLESS) { _characterController.setCollisionless(false); } } @@ -281,6 +282,8 @@ MyAvatar::MyAvatar(QThread* thread) : MyAvatar::~MyAvatar() { _lookAtTargetAvatar.reset(); + delete _myScriptEngine; + _myScriptEngine = nullptr; } void MyAvatar::setDominantHand(const QString& hand) { @@ -665,13 +668,7 @@ void MyAvatar::update(float deltaTime) { Q_ARG(glm::vec3, (getWorldPosition() - halfBoundingBoxDimensions)), Q_ARG(glm::vec3, (halfBoundingBoxDimensions*2.0f))); - if (getIdentityDataChanged()) { - sendIdentityPacket(); - } - - _clientTraitsHandler->sendChangedTraitsToMixer(); - - simulate(deltaTime); + simulate(deltaTime, true); currentEnergy += energyChargeRate; currentEnergy -= getAccelerationEnergy(); @@ -743,7 +740,7 @@ void MyAvatar::updateChildCauterization(SpatiallyNestablePointer object, bool ca } } -void MyAvatar::simulate(float deltaTime) { +void MyAvatar::simulate(float deltaTime, bool inView) { PerformanceTimer perfTimer("simulate"); animateScaleChanges(deltaTime); @@ -770,7 +767,7 @@ void MyAvatar::simulate(float deltaTime) { auto headBoneSet = _skeletonModel->getCauterizeBoneSet(); forEachChild([&](SpatiallyNestablePointer object) { bool isChildOfHead = headBoneSet.find(object->getParentJointIndex()) != headBoneSet.end(); - if (isChildOfHead) { + if (isChildOfHead && !object->hasGrabs()) { // Cauterize or display children of head per head drawing state. updateChildCauterization(object, !_prevShouldDrawHead); object->forEachDescendant([&](SpatiallyNestablePointer descendant) { @@ -820,7 +817,9 @@ void MyAvatar::simulate(float deltaTime) { // and all of its joints, now update our attachements. Avatar::simulateAttachments(deltaTime); relayJointDataToChildren(); - updateGrabs(); + if (updateGrabs()) { + _cauterizationNeedsUpdate = true; + } if (!_skeletonModel->hasSkeleton()) { // All the simulation that can be done has been done @@ -876,9 +875,13 @@ void MyAvatar::simulate(float deltaTime) { collisionlessAllowed = zone->getGhostingAllowed(); } EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); - bool force = false; - bool iShouldTellServer = true; forEachDescendant([&](SpatiallyNestablePointer object) { + // we need to update attached queryAACubes in our own local tree so point-select always works + // however we don't want to flood the update pipeline with AvatarEntity updates, so we assume + // others have all info required to properly update queryAACube of AvatarEntities on their end + EntityItemPointer entity = std::dynamic_pointer_cast(object); + bool iShouldTellServer = !(entity && entity->isAvatarEntity()); + const bool force = false; entityTree->updateEntityQueryAACube(object, packetSender, force, iShouldTellServer); }); }); @@ -887,7 +890,7 @@ void MyAvatar::simulate(float deltaTime) { _characterController.setCollisionlessAllowed(collisionlessAllowed); } - updateAvatarEntities(); + handleChangedAvatarEntityData(); updateFadingStatus(); } @@ -1251,7 +1254,7 @@ void MyAvatar::saveAvatarUrl() { } } -void MyAvatar::resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex) { +void MyAvatar::resizeAvatarEntitySettingHandles(uint32_t maxIndex) { // The original Settings interface saved avatar-entity array data like this: // Avatar/avatarEntityData/size: 5 // Avatar/avatarEntityData/1/id: ... @@ -1261,14 +1264,15 @@ void MyAvatar::resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex) // Avatar/avatarEntityData/5/properties: ... // // Create Setting::Handles to mimic this. - - while (_avatarEntityIDSettings.size() <= avatarEntityIndex) { + uint32_t settingsIndex = (uint32_t)_avatarEntityIDSettings.size() + 1; + while (settingsIndex <= maxIndex) { Setting::Handle idHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" - << QString::number(avatarEntityIndex + 1) << "id", QUuid()); + << QString::number(settingsIndex) << "id", QUuid()); _avatarEntityIDSettings.push_back(idHandle); Setting::Handle dataHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" - << QString::number(avatarEntityIndex + 1) << "properties", QByteArray()); + << QString::number(settingsIndex) << "properties", QByteArray()); _avatarEntityDataSettings.push_back(dataHandle); + settingsIndex++; } } @@ -1298,26 +1302,54 @@ void MyAvatar::saveData() { _flyingHMDSetting.set(getFlyingHMDPref()); auto hmdInterface = DependencyManager::get(); - _avatarEntitiesLock.withReadLock([&] { - QList avatarEntityIDs = _avatarEntityData.keys(); - unsigned int avatarEntityCount = avatarEntityIDs.size(); - unsigned int previousAvatarEntityCount = _avatarEntityCountSetting.get(0); - resizeAvatarEntitySettingHandles(std::max(avatarEntityCount, previousAvatarEntityCount)); - _avatarEntityCountSetting.set(avatarEntityCount); + saveAvatarEntityDataToSettings(); +} - unsigned int avatarEntityIndex = 0; - for (auto entityID : avatarEntityIDs) { - _avatarEntityIDSettings[avatarEntityIndex].set(entityID); - _avatarEntityDataSettings[avatarEntityIndex].set(_avatarEntityData.value(entityID)); - avatarEntityIndex++; - } +void MyAvatar::saveAvatarEntityDataToSettings() { + if (!_needToSaveAvatarEntitySettings) { + return; + } + bool success = updateStaleAvatarEntityBlobs(); + if (!success) { + return; + } + _needToSaveAvatarEntitySettings = false; - // clean up any left-over (due to the list shrinking) slots - for (; avatarEntityIndex < previousAvatarEntityCount; avatarEntityIndex++) { - _avatarEntityIDSettings[avatarEntityIndex].remove(); - _avatarEntityDataSettings[avatarEntityIndex].remove(); + uint32_t numEntities = (uint32_t)_cachedAvatarEntityBlobs.size(); + uint32_t prevNumEntities = _avatarEntityCountSetting.get(0); + resizeAvatarEntitySettingHandles(std::max(numEntities, prevNumEntities)); + + // save new Settings + if (numEntities > 0) { + // save all unfortunately-formatted-binary-blobs to Settings + _avatarEntitiesLock.withWriteLock([&] { + uint32_t i = 0; + AvatarEntityMap::const_iterator itr = _cachedAvatarEntityBlobs.begin(); + while (itr != _cachedAvatarEntityBlobs.end()) { + _avatarEntityIDSettings[i].set(itr.key()); + _avatarEntityDataSettings[i].set(itr.value()); + ++itr; + ++i; + } + numEntities = i; + }); + } + _avatarEntityCountSetting.set(numEntities); + + // remove old Settings if any + if (numEntities < prevNumEntities) { + uint32_t numEntitiesToRemove = prevNumEntities - numEntities; + for (uint32_t i = 0; i < numEntitiesToRemove; ++i) { + if (_avatarEntityIDSettings.size() > numEntities) { + _avatarEntityIDSettings.back().remove(); + _avatarEntityIDSettings.pop_back(); + } + if (_avatarEntityDataSettings.size() > numEntities) { + _avatarEntityDataSettings.back().remove(); + _avatarEntityDataSettings.pop_back(); + } } - }); + } } float loadSetting(Settings& settings, const QString& name, float defaultValue) { @@ -1414,7 +1446,420 @@ void MyAvatar::setEnableInverseKinematics(bool isEnabled) { _skeletonModel->getRig().setEnableInverseKinematics(isEnabled); } +void MyAvatar::storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& payload) { + AvatarData::storeAvatarEntityDataPayload(entityID, payload); + _avatarEntitiesLock.withWriteLock([&] { + _cachedAvatarEntityBlobsToAddOrUpdate.push_back(entityID); + }); +} + +void MyAvatar::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree) { + AvatarData::clearAvatarEntity(entityID, requiresRemovalFromTree); + _avatarEntitiesLock.withWriteLock([&] { + _cachedAvatarEntityBlobsToDelete.push_back(entityID); + }); +} + +void MyAvatar::sanitizeAvatarEntityProperties(EntityItemProperties& properties) const { + properties.setEntityHostType(entity::HostType::AVATAR); + properties.setOwningAvatarID(getID()); + + // there's no entity-server to tell us we're the simulation owner, so always set the + // simulationOwner to the owningAvatarID and a high priority. + properties.setSimulationOwner(getID(), AVATAR_ENTITY_SIMULATION_PRIORITY); + + if (properties.getParentID() == AVATAR_SELF_ID) { + properties.setParentID(getID()); + } + + // When grabbing avatar entities, they are parented to the joint moving them, then when un-grabbed + // they go back to the default parent (null uuid). When un-gripped, others saw the entity disappear. + // The thinking here is the local position was noticed as changing, but not the parentID (since it is now + // back to the default), and the entity flew off somewhere. Marking all changed definitely fixes this, + // and seems safe (per Seth). + properties.markAllChanged(); +} + +void MyAvatar::handleChangedAvatarEntityData() { + // NOTE: this is a per-frame update + if (getID().isNull() || + getID() == AVATAR_SELF_ID || + DependencyManager::get()->getSessionUUID() == QUuid()) { + // wait until MyAvatar and this Node gets an ID before doing this. Otherwise, various things go wrong: + // things get their parent fixed up from AVATAR_SELF_ID to a null uuid which means "no parent". + return; + } + if (_reloadAvatarEntityDataFromSettings) { + loadAvatarEntityDataFromSettings(); + } + + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + if (!entityTree) { + return; + } + + // We collect changes to AvatarEntities and then handle them all in one spot per frame: handleChangedAvatarEntityData(). + // Basically this is a "transaction pattern" with an extra complication: these changes can come from two + // "directions" and the "authoritative source" of each direction is different, so we maintain two distinct sets + // of transaction lists: + // + // The _entitiesToDelete/Add/Update lists are for changes whose "authoritative sources" are already + // correctly stored in _cachedAvatarEntityBlobs. These come from loadAvatarEntityDataFromSettings() and + // setAvatarEntityData(). These changes need to be extracted from _cachedAvatarEntityBlobs and applied to + // real EntityItems. + // + // The _cachedAvatarEntityBlobsToDelete/Add/Update lists are for changes whose "authoritative sources" are + // already reflected in real EntityItems. These changes need to be propagated to _cachedAvatarEntityBlobs + // and eventually to Settings. + // + // The DELETEs also need to be propagated to the traits, which will eventually propagate to + // AvatarData::_packedAvatarEntityData via deeper logic. + + // move the lists to minimize lock time + std::vector cachedBlobsToDelete; + std::vector cachedBlobsToUpdate; + std::vector entitiesToDelete; + std::vector entitiesToAdd; + std::vector entitiesToUpdate; + _avatarEntitiesLock.withWriteLock([&] { + cachedBlobsToDelete = std::move(_cachedAvatarEntityBlobsToDelete); + cachedBlobsToUpdate = std::move(_cachedAvatarEntityBlobsToAddOrUpdate); + entitiesToDelete = std::move(_entitiesToDelete); + entitiesToAdd = std::move(_entitiesToAdd); + entitiesToUpdate = std::move(_entitiesToUpdate); + }); + + auto removeAllInstancesHelper = [] (const QUuid& id, std::vector& v) { + uint32_t i = 0; + while (i < v.size()) { + if (id == v[i]) { + v[i] = v.back(); + v.pop_back(); + } else { + ++i; + } + } + }; + + // remove delete-add and delete-update overlap + for (const auto& id : entitiesToDelete) { + removeAllInstancesHelper(id, cachedBlobsToUpdate); + removeAllInstancesHelper(id, entitiesToAdd); + removeAllInstancesHelper(id, entitiesToUpdate); + } + for (const auto& id : cachedBlobsToDelete) { + removeAllInstancesHelper(id, entitiesToUpdate); + removeAllInstancesHelper(id, cachedBlobsToUpdate); + } + for (const auto& id : entitiesToAdd) { + removeAllInstancesHelper(id, entitiesToUpdate); + } + + // DELETE real entities + for (const auto& id : entitiesToDelete) { + entityTree->withWriteLock([&] { + entityTree->deleteEntity(id); + }); + } + + // ADD real entities + EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); + for (const auto& id : entitiesToAdd) { + bool blobFailed = false; + EntityItemProperties properties; + _avatarEntitiesLock.withReadLock([&] { + AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.find(id); + if (itr == _cachedAvatarEntityBlobs.end()) { + blobFailed = true; // blob doesn't exist + return; + } + if (!EntityItemProperties::blobToProperties(*_myScriptEngine, itr.value(), properties)) { + blobFailed = true; // blob is corrupt + } + }); + if (blobFailed) { + // remove from _cachedAvatarEntityBlobUpdatesToSkip just in case: + // avoids a resource leak when blob updates to be skipped are never actually skipped + // when the blob fails to result in a real EntityItem + _avatarEntitiesLock.withWriteLock([&] { + removeAllInstancesHelper(id, _cachedAvatarEntityBlobUpdatesToSkip); + }); + continue; + } + sanitizeAvatarEntityProperties(properties); + entityTree->withWriteLock([&] { + EntityItemPointer entity = entityTree->addEntity(id, properties); + if (entity) { + packetSender->queueEditEntityMessage(PacketType::EntityAdd, entityTree, id, properties); + } + }); + } + + // CHANGE real entities + for (const auto& id : entitiesToUpdate) { + EntityItemProperties properties; + bool skip = false; + _avatarEntitiesLock.withReadLock([&] { + AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.find(id); + if (itr == _cachedAvatarEntityBlobs.end()) { + skip = true; + return; + } + if (!EntityItemProperties::blobToProperties(*_myScriptEngine, itr.value(), properties)) { + skip = true; + } + }); + if (!skip) { + sanitizeAvatarEntityProperties(properties); + entityTree->withWriteLock([&] { + entityTree->updateEntity(id, properties); + }); + } + } + + // DELETE cached blobs + _avatarEntitiesLock.withWriteLock([&] { + for (const auto& id : cachedBlobsToDelete) { + AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.find(id); + // remove blob and remember to remove from settings + if (itr != _cachedAvatarEntityBlobs.end()) { + _cachedAvatarEntityBlobs.erase(itr); + _needToSaveAvatarEntitySettings = true; + } + // also remove from list of stale blobs to avoid failed entity lookup later + std::set::iterator blobItr = _staleCachedAvatarEntityBlobs.find(id); + if (blobItr != _staleCachedAvatarEntityBlobs.end()) { + _staleCachedAvatarEntityBlobs.erase(blobItr); + } + // also remove from _cachedAvatarEntityBlobUpdatesToSkip just in case: + // avoids a resource leak when things are deleted before they could be skipped + removeAllInstancesHelper(id, _cachedAvatarEntityBlobUpdatesToSkip); + } + }); + + // ADD/UPDATE cached blobs + for (const auto& id : cachedBlobsToUpdate) { + // computing the blobs is expensive and we want to avoid it when possible + // so we add these ids to _staleCachedAvatarEntityBlobs for later + // and only build the blobs when absolutely necessary + bool skip = false; + uint32_t i = 0; + _avatarEntitiesLock.withWriteLock([&] { + while (i < _cachedAvatarEntityBlobUpdatesToSkip.size()) { + if (id == _cachedAvatarEntityBlobUpdatesToSkip[i]) { + _cachedAvatarEntityBlobUpdatesToSkip[i] = _cachedAvatarEntityBlobUpdatesToSkip.back(); + _cachedAvatarEntityBlobUpdatesToSkip.pop_back(); + skip = true; + break; // assume no duplicates + } else { + ++i; + } + } + }); + if (!skip) { + _staleCachedAvatarEntityBlobs.insert(id); + _needToSaveAvatarEntitySettings = true; + } + } + + // DELETE traits + // (no need to worry about the ADDs and UPDATEs: each will be handled when the interface + // tries to send a real update packet (via AvatarData::storeAvatarEntityDataPayload())) + if (_clientTraitsHandler) { + // we have a client traits handler + // flag removed entities as deleted so that changes are sent next frame + _avatarEntitiesLock.withWriteLock([&] { + for (const auto& id : entitiesToDelete) { + if (_packedAvatarEntityData.find(id) != _packedAvatarEntityData.end()) { + _clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, id); + } + } + for (const auto& id : cachedBlobsToDelete) { + if (_packedAvatarEntityData.find(id) != _packedAvatarEntityData.end()) { + _clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, id); + } + } + }); + } +} + +bool MyAvatar::updateStaleAvatarEntityBlobs() const { + // call this right before you actually need to use the blobs + // + // Note: this method is const (and modifies mutable data members) + // so we can call it at the Last Minute inside other const methods + // + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + if (!entityTree) { + return false; + } + + std::set staleBlobs = std::move(_staleCachedAvatarEntityBlobs); + int32_t numFound = 0; + for (const auto& id : staleBlobs) { + bool found = false; + EntityItemProperties properties; + entityTree->withReadLock([&] { + EntityItemPointer entity = entityTree->findEntityByID(id); + if (entity) { + properties = entity->getProperties(); + found = true; + } + }); + if (found) { + ++numFound; + QByteArray blob; + EntityItemProperties::propertiesToBlob(*_myScriptEngine, getID(), properties, blob); + _avatarEntitiesLock.withWriteLock([&] { + _cachedAvatarEntityBlobs[id] = blob; + }); + } + } + return true; +} + +void MyAvatar::prepareAvatarEntityDataForReload() { + saveAvatarEntityDataToSettings(); + + _avatarEntitiesLock.withWriteLock([&] { + _packedAvatarEntityData.clear(); + _entitiesToDelete.clear(); + _entitiesToAdd.clear(); + _entitiesToUpdate.clear(); + _cachedAvatarEntityBlobs.clear(); + _cachedAvatarEntityBlobsToDelete.clear(); + _cachedAvatarEntityBlobsToAddOrUpdate.clear(); + _cachedAvatarEntityBlobUpdatesToSkip.clear(); + }); + + _reloadAvatarEntityDataFromSettings = true; +} + +AvatarEntityMap MyAvatar::getAvatarEntityData() const { + // NOTE: the return value is expected to be a map of unfortunately-formatted-binary-blobs + updateStaleAvatarEntityBlobs(); + AvatarEntityMap result; + _avatarEntitiesLock.withReadLock([&] { + result = _cachedAvatarEntityBlobs; + }); + return result; +} + +void MyAvatar::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) { + // Note: this is an invokable Script call + // avatarEntityData is expected to be a map of QByteArrays that represent EntityItemProperties objects from JavaScript, + // aka: unfortunately-formatted-binary-blobs because we store them in non-human-readable format in Settings. + // + if (avatarEntityData.size() > MAX_NUM_AVATAR_ENTITIES) { + // the data is suspect + qCDebug(interfaceapp) << "discard suspect AvatarEntityData with size =" << avatarEntityData.size(); + return; + } + + // this overwrites ALL AvatarEntityData so we clear pending operations + _avatarEntitiesLock.withWriteLock([&] { + _packedAvatarEntityData.clear(); + _entitiesToDelete.clear(); + _entitiesToAdd.clear(); + _entitiesToUpdate.clear(); + }); + _needToSaveAvatarEntitySettings = true; + + _avatarEntitiesLock.withWriteLock([&] { + // find new and updated IDs + AvatarEntityMap::const_iterator constItr = avatarEntityData.begin(); + while (constItr != avatarEntityData.end()) { + QUuid id = constItr.key(); + if (_cachedAvatarEntityBlobs.find(id) == _cachedAvatarEntityBlobs.end()) { + _entitiesToAdd.push_back(id); + } else { + _entitiesToUpdate.push_back(id); + } + ++constItr; + } + // find and erase deleted IDs from _cachedAvatarEntityBlobs + std::vector deletedIDs; + AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.begin(); + while (itr != _cachedAvatarEntityBlobs.end()) { + QUuid id = itr.key(); + if (std::find(_entitiesToUpdate.begin(), _entitiesToUpdate.end(), id) == _entitiesToUpdate.end()) { + deletedIDs.push_back(id); + itr = _cachedAvatarEntityBlobs.erase(itr); + } else { + ++itr; + } + } + // copy new data + constItr = avatarEntityData.begin(); + while (constItr != avatarEntityData.end()) { + _cachedAvatarEntityBlobs.insert(constItr.key(), constItr.value()); + ++constItr; + } + // erase deleted IDs from _packedAvatarEntityData + for (const auto& id : deletedIDs) { + itr = _packedAvatarEntityData.find(id); + if (itr != _packedAvatarEntityData.end()) { + _packedAvatarEntityData.erase(itr); + } else { + ++itr; + } + _entitiesToDelete.push_back(id); + } + }); +} + +void MyAvatar::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) { + // NOTE: this is an invokable Script call + bool changed = false; + _avatarEntitiesLock.withWriteLock([&] { + auto data = QJsonDocument::fromBinaryData(entityData); + if (data.isEmpty() || data.isNull() || !data.isObject() || data.object().isEmpty()) { + qDebug() << "ERROR! Trying to update with invalid avatar entity data. Skipping." << data; + } else { + auto itr = _cachedAvatarEntityBlobs.find(entityID); + if (itr == _cachedAvatarEntityBlobs.end()) { + _entitiesToAdd.push_back(entityID); + _cachedAvatarEntityBlobs.insert(entityID, entityData); + changed = true; + } else { + _entitiesToUpdate.push_back(entityID); + itr.value() = entityData; + changed = true; + } + } + }); + if (changed) { + _needToSaveAvatarEntitySettings = true; + } +} + +void MyAvatar::avatarEntityDataToJson(QJsonObject& root) const { + updateStaleAvatarEntityBlobs(); + _avatarEntitiesLock.withReadLock([&] { + if (!_cachedAvatarEntityBlobs.empty()) { + QJsonArray avatarEntityJson; + int entityCount = 0; + AvatarEntityMap::const_iterator itr = _cachedAvatarEntityBlobs.begin(); + while (itr != _cachedAvatarEntityBlobs.end()) { + QVariantMap entityData; + QUuid id = _avatarEntityForRecording.size() == _cachedAvatarEntityBlobs.size() ? _avatarEntityForRecording.values()[entityCount++] : itr.key(); + entityData.insert("id", id); + entityData.insert("properties", itr.value().toBase64()); + avatarEntityJson.push_back(QVariant(entityData).toJsonObject()); + ++itr; + } + const QString JSON_AVATAR_ENTITIES = QStringLiteral("attachedEntities"); + root[JSON_AVATAR_ENTITIES] = avatarEntityJson; + } + }); +} + void MyAvatar::loadData() { + if (!_myScriptEngine) { + _myScriptEngine = new QScriptEngine(); + } getHead()->setBasePitch(_headPitchSetting.get()); _yawSpeed = _yawSpeedSetting.get(_yawSpeed); @@ -1426,14 +1871,7 @@ void MyAvatar::loadData() { useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName); - int avatarEntityCount = _avatarEntityCountSetting.get(0); - for (int i = 0; i < avatarEntityCount; i++) { - resizeAvatarEntitySettingHandles(i); - // QUuid entityID = QUuid::createUuid(); // generate a new ID - QUuid entityID = _avatarEntityIDSettings[i].get(QUuid()); - QByteArray properties = _avatarEntityDataSettings[i].get(); - updateAvatarEntity(entityID, properties); - } + loadAvatarEntityDataFromSettings(); // Flying preferences must be loaded before calling setFlyingEnabled() Setting::Handle firstRunVal { Settings::firstRun, true }; @@ -1455,6 +1893,38 @@ void MyAvatar::loadData() { setEnableDebugDrawPosition(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawPosition)); } +void MyAvatar::loadAvatarEntityDataFromSettings() { + // this overwrites ALL AvatarEntityData so we clear pending operations + _avatarEntitiesLock.withWriteLock([&] { + _packedAvatarEntityData.clear(); + _entitiesToDelete.clear(); + _entitiesToAdd.clear(); + _entitiesToUpdate.clear(); + }); + _reloadAvatarEntityDataFromSettings = false; + _needToSaveAvatarEntitySettings = false; + + int numEntities = _avatarEntityCountSetting.get(0); + if (numEntities == 0) { + return; + } + resizeAvatarEntitySettingHandles(numEntities); + + _avatarEntitiesLock.withWriteLock([&] { + _entitiesToAdd.reserve(numEntities); + // TODO: build map between old and new IDs so we can restitch parent-child relationships + for (int i = 0; i < numEntities; i++) { + QUuid id = QUuid::createUuid(); // generate a new ID + _cachedAvatarEntityBlobs[id] = _avatarEntityDataSettings[i].get(); + _entitiesToAdd.push_back(id); + // this blob is the "authoritative source" for this AvatarEntity and we want to avoid overwriting it + // (the outgoing update packet will flag it for save-back into the blob) + // which is why we remember its id: to skip its save-back later + _cachedAvatarEntityBlobUpdatesToSkip.push_back(id); + } + }); +} + void MyAvatar::saveAttachmentData(const AttachmentData& attachment) const { Settings settings; settings.beginGroup("savedAttachmentData"); @@ -1902,53 +2372,47 @@ bool isWearableEntity(const EntityItemPointer& entity) { || entity->getParentID() == AVATAR_SELF_ID); } -void MyAvatar::clearAvatarEntities() { +void MyAvatar::removeWornAvatarEntity(const EntityItemID& entityID) { auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; - AvatarEntityMap avatarEntities = getAvatarEntityData(); - for (auto entityID : avatarEntities.keys()) { - entityTree->withWriteLock([&entityID, &entityTree] { - // remove this entity first from the entity tree - entityTree->deleteEntity(entityID, true, true); - }); - - // remove the avatar entity from our internal list - // (but indicate it doesn't need to be pulled from the tree) - clearAvatarEntity(entityID, false); - } -} - -void MyAvatar::removeWearableAvatarEntities() { - auto treeRenderer = DependencyManager::get(); - EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; - if (entityTree) { - AvatarEntityMap avatarEntities = getAvatarEntityData(); - for (auto entityID : avatarEntities.keys()) { - auto entity = entityTree->findEntityByID(entityID); - if (entity && isWearableEntity(entity)) { - entityTree->withWriteLock([&entityID, &entityTree] { - // remove this entity first from the entity tree - entityTree->deleteEntity(entityID, true, true); - }); + auto entity = entityTree->findEntityByID(entityID); + if (entity && isWearableEntity(entity)) { + entityTree->withWriteLock([&entityID, &entityTree] { + // remove this entity first from the entity tree + entityTree->deleteEntity(entityID, true, true); + }); - // remove the avatar entity from our internal list - // (but indicate it doesn't need to be pulled from the tree) - clearAvatarEntity(entityID, false); - } + // remove the avatar entity from our internal list + // (but indicate it doesn't need to be pulled from the tree) + clearAvatarEntity(entityID, false); } } } +void MyAvatar::clearWornAvatarEntities() { + QList avatarEntityIDs; + _avatarEntitiesLock.withReadLock([&] { + avatarEntityIDs = _packedAvatarEntityData.keys(); + }); + for (auto entityID : avatarEntityIDs) { + removeWornAvatarEntity(entityID); + } +} + + QVariantList MyAvatar::getAvatarEntitiesVariant() { + // NOTE: this method is NOT efficient QVariantList avatarEntitiesData; - QScriptEngine scriptEngine; auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; if (entityTree) { - AvatarEntityMap avatarEntities = getAvatarEntityData(); - for (auto entityID : avatarEntities.keys()) { + QList avatarEntityIDs; + _avatarEntitiesLock.withReadLock([&] { + avatarEntityIDs = _packedAvatarEntityData.keys(); + }); + for (const auto& entityID : avatarEntityIDs) { auto entity = entityTree->findEntityByID(entityID); if (!entity) { continue; @@ -1959,7 +2423,8 @@ QVariantList MyAvatar::getAvatarEntitiesVariant() { desiredProperties += PROP_LOCAL_POSITION; desiredProperties += PROP_LOCAL_ROTATION; EntityItemProperties entityProperties = entity->getProperties(desiredProperties); - QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties); + QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(_myScriptEngine, entityProperties); + avatarEntityData["id"] = entityID; avatarEntityData["properties"] = scriptProperties.toVariant(); avatarEntitiesData.append(QVariant(avatarEntityData)); } @@ -2069,7 +2534,7 @@ void MyAvatar::updateMotors() { float verticalMotorTimescale; if (_characterController.getState() == CharacterController::State::Hover || - _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { + _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) { horizontalMotorTimescale = FLYING_MOTOR_TIMESCALE; verticalMotorTimescale = FLYING_MOTOR_TIMESCALE; } else { @@ -2079,7 +2544,7 @@ void MyAvatar::updateMotors() { if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { if (_characterController.getState() == CharacterController::State::Hover || - _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { + _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) { motorRotation = getMyHead()->getHeadOrientation(); } else { // non-hovering = walking: follow camera twist about vertical but not lift @@ -2134,7 +2599,7 @@ void MyAvatar::prepareForPhysicsSimulation() { qDebug() << "Warning: getParentVelocity failed" << getID(); parentVelocity = glm::vec3(); } - _characterController.handleChangedCollisionGroup(); + _characterController.handleChangedCollisionMask(); _characterController.setParentVelocity(parentVelocity); _characterController.setScaleFactor(getSensorToWorldScale()); @@ -2338,8 +2803,8 @@ void MyAvatar::setAttachmentData(const QVector& attachmentData) newEntitiesProperties.push_back(properties); } - // clear any existing avatar entities - clearAvatarEntities(); + // clear any existing wearables + clearWornAvatarEntities(); for (auto& properties : newEntitiesProperties) { DependencyManager::get()->addEntity(properties, true); @@ -2348,17 +2813,17 @@ void MyAvatar::setAttachmentData(const QVector& attachmentData) } QVector MyAvatar::getAttachmentData() const { - QVector avatarData; - auto avatarEntities = getAvatarEntityData(); - AvatarEntityMap::const_iterator dataItr = avatarEntities.begin(); - while (dataItr != avatarEntities.end()) { - QUuid entityID = dataItr.key(); + QVector attachmentData; + QList avatarEntityIDs; + _avatarEntitiesLock.withReadLock([&] { + avatarEntityIDs = _packedAvatarEntityData.keys(); + }); + for (const auto& entityID : avatarEntityIDs) { auto properties = DependencyManager::get()->getEntityProperties(entityID); AttachmentData data = entityPropertiesToAttachmentData(properties); - avatarData.append(data); - dataItr++; + attachmentData.append(data); } - return avatarData; + return attachmentData; } QVariantList MyAvatar::getAttachmentsVariant() const { @@ -2387,16 +2852,16 @@ void MyAvatar::setAttachmentsVariant(const QVariantList& variant) { } bool MyAvatar::findAvatarEntity(const QString& modelURL, const QString& jointName, QUuid& entityID) { - auto avatarEntities = getAvatarEntityData(); - AvatarEntityMap::const_iterator dataItr = avatarEntities.begin(); - while (dataItr != avatarEntities.end()) { - entityID = dataItr.key(); + QList avatarEntityIDs; + _avatarEntitiesLock.withReadLock([&] { + avatarEntityIDs = _packedAvatarEntityData.keys(); + }); + for (const auto& entityID : avatarEntityIDs) { auto props = DependencyManager::get()->getEntityProperties(entityID); if (props.getModelURL() == modelURL && (jointName.isEmpty() || props.getParentJointIndex() == getJointIndex(jointName))) { return true; } - dataItr++; } return false; } @@ -2638,6 +3103,39 @@ void MyAvatar::preDisplaySide(const RenderArgs* renderArgs) { _prevShouldDrawHead = shouldDrawHead; } +int MyAvatar::sendAvatarDataPacket(bool sendAll) { + using namespace std::chrono; + auto now = Clock::now(); + + int MAX_DATA_RATE_MBPS = 3; + int maxDataRateBytesPerSeconds = MAX_DATA_RATE_MBPS * BYTES_PER_KILOBYTE * KILO_PER_MEGA / BITS_IN_BYTE; + int maxDataRateBytesPerMilliseconds = maxDataRateBytesPerSeconds / MSECS_PER_SECOND; + + auto bytesSent = 0; + + if (now > _nextTraitsSendWindow) { + if (getIdentityDataChanged()) { + bytesSent += sendIdentityPacket(); + } + + bytesSent += _clientTraitsHandler->sendChangedTraitsToMixer(); + + // Compute the next send window based on how much data we sent and what + // data rate we're trying to max at. + milliseconds timeUntilNextSend { bytesSent / maxDataRateBytesPerMilliseconds }; + _nextTraitsSendWindow += timeUntilNextSend; + + // Don't let the next send window lag behind if we're not sending a lot of data. + if (_nextTraitsSendWindow < now) { + _nextTraitsSendWindow = now; + } + } + + bytesSent += Avatar::sendAvatarDataPacket(sendAll); + + return bytesSent; +} + const float RENDER_HEAD_CUTOFF_DISTANCE = 0.47f; bool MyAvatar::cameraInsideHead(const glm::vec3& cameraPosition) const { @@ -2710,9 +3208,10 @@ void MyAvatar::updateOrientation(float deltaTime) { _bodyYawDelta = 0.0f; } } - float totalBodyYaw = _bodyYawDelta * deltaTime; + // Rotate directly proportional to delta yaw and delta pitch from right-click mouse movement. + totalBodyYaw += getDriveKey(DELTA_YAW) * _yawSpeed / YAW_SPEED_DEFAULT; // Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll // get an instantaneous 15 degree turn. If you keep holding the key down you'll get another @@ -2780,7 +3279,8 @@ void MyAvatar::updateOrientation(float deltaTime) { head->setBaseRoll(ROLL(euler)); } else { head->setBaseYaw(0.0f); - head->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime); + head->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime + + getDriveKey(DELTA_PITCH) * _pitchSpeed / PITCH_SPEED_DEFAULT); head->setBaseRoll(0.0f); } } @@ -2825,7 +3325,7 @@ void MyAvatar::updateActionMotor(float deltaTime) { glm::vec3 direction = forward + right; if (state == CharacterController::State::Hover || - _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { + _characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) { glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP; direction += up; } @@ -3381,7 +3881,7 @@ void MyAvatar::setCollisionsEnabled(bool enabled) { bool MyAvatar::getCollisionsEnabled() { // may return 'false' even though the collisionless option was requested // because the zone may disallow collisionless avatars - return _characterController.computeCollisionGroup() != BULLET_COLLISION_GROUP_COLLISIONLESS; + return _characterController.computeCollisionMask() != BULLET_COLLISION_MASK_COLLISIONLESS; } void MyAvatar::setOtherAvatarsCollisionsEnabled(bool enabled) { @@ -3390,7 +3890,11 @@ void MyAvatar::setOtherAvatarsCollisionsEnabled(bool enabled) { QMetaObject::invokeMethod(this, "setOtherAvatarsCollisionsEnabled", Q_ARG(bool, enabled)); return; } + bool change = _collideWithOtherAvatars != enabled; _collideWithOtherAvatars = enabled; + if (change) { + setCollisionWithOtherAvatarsFlags(); + } emit otherAvatarsCollisionsEnabledChanged(enabled); } @@ -3398,6 +3902,11 @@ bool MyAvatar::getOtherAvatarsCollisionsEnabled() { return _collideWithOtherAvatars; } +void MyAvatar::setCollisionWithOtherAvatarsFlags() { + _characterController.setCollideWithOtherAvatars(_collideWithOtherAvatars); + _characterController.setPendingFlagsUpdateCollisionMask(); +} + void MyAvatar::updateCollisionCapsuleCache() { glm::vec3 start, end; float radius; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h old mode 100644 new mode 100755 index 17b71153ea..af08955ca0 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -253,6 +253,9 @@ class MyAvatar : public Avatar { const QString DOMINANT_LEFT_HAND = "left"; const QString DOMINANT_RIGHT_HAND = "right"; + using Clock = std::chrono::system_clock; + using TimePoint = Clock::time_point; + public: enum DriveKeys { TRANSLATE_X = 0, @@ -265,6 +268,8 @@ public: STEP_YAW, PITCH, ZOOM, + DELTA_YAW, + DELTA_PITCH, MAX_DRIVE_KEYS }; Q_ENUM(DriveKeys) @@ -292,6 +297,8 @@ public: void reset(bool andRecenter = false, bool andReload = true, bool andHead = true); + void setCollisionWithOtherAvatarsFlags() override; + /**jsdoc * @function MyAvatar.resetSensorsAndBody */ @@ -575,9 +582,11 @@ public: float getHMDRollControlRate() const { return _hmdRollControlRate; } // get/set avatar data - void resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex); + void resizeAvatarEntitySettingHandles(uint32_t maxIndex); void saveData(); + void saveAvatarEntityDataToSettings(); void loadData(); + void loadAvatarEntityDataFromSettings(); void saveAttachmentData(const AttachmentData& attachment) const; AttachmentData loadAttachmentData(const QUrl& modelURL, const QString& jointName = QString()) const; @@ -966,8 +975,8 @@ public: * @returns {object[]} */ Q_INVOKABLE QVariantList getAvatarEntitiesVariant(); - void clearAvatarEntities(); - void removeWearableAvatarEntities(); + void removeWornAvatarEntity(const EntityItemID& entityID); + void clearWornAvatarEntities(); /**jsdoc * Check whether your avatar is flying or not. @@ -1184,6 +1193,7 @@ public: virtual void setAttachmentsVariant(const QVariantList& variant) override; glm::vec3 getNextPosition() { return _goToPending ? _goToPosition : getWorldPosition(); } + void prepareAvatarEntityDataForReload(); /**jsdoc * Create a new grab. @@ -1204,6 +1214,12 @@ public: */ Q_INVOKABLE void releaseGrab(const QUuid& grabID); + AvatarEntityMap getAvatarEntityData() const override; + void setAvatarEntityData(const AvatarEntityMap& avatarEntityData) override; + void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) override; + void avatarEntityDataToJson(QJsonObject& root) const override; + int sendAvatarDataPacket(bool sendAll = false) override; + public slots: /**jsdoc @@ -1402,6 +1418,10 @@ public slots: */ bool getEnableMeshVisible() const override; + void storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& payload) override; + void clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree = true) override; + void sanitizeAvatarEntityProperties(EntityItemProperties& properties) const; + /**jsdoc * Set whether or not your avatar mesh is visible. * @function MyAvatar.setEnableMeshVisible @@ -1601,23 +1621,24 @@ signals: */ void disableHandTouchForIDChanged(const QUuid& entityID, bool disable); - private slots: void leaveDomain(); void updateCollisionCapsuleCache(); protected: + void handleChangedAvatarEntityData(); virtual void beParentOfChild(SpatiallyNestablePointer newChild) const override; virtual void forgetChild(SpatiallyNestablePointer newChild) const override; virtual void recalculateChildCauterization() const override; private: + bool updateStaleAvatarEntityBlobs() const; bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut); virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) override; - void simulate(float deltaTime); + void simulate(float deltaTime, bool inView) override; void updateFromTrackers(float deltaTime); void saveAvatarUrl(); virtual void render(RenderArgs* renderArgs) override; @@ -1713,7 +1734,7 @@ private: SharedSoundPointer _collisionSound; MyCharacterController _characterController; - int32_t _previousCollisionGroup { BULLET_COLLISION_GROUP_MY_AVATAR }; + int32_t _previousCollisionMask { BULLET_COLLISION_MASK_MY_AVATAR }; AvatarWeakPointer _lookAtTargetAvatar; glm::vec3 _targetAvatarPosition; @@ -1920,6 +1941,9 @@ private: bool _haveReceivedHeightLimitsFromDomain { false }; int _disableHandTouchCount { 0 }; bool _skeletonModelLoaded { false }; + bool _reloadAvatarEntityDataFromSettings { true }; + + TimePoint _nextTraitsSendWindow; Setting::Handle _dominantHandSetting; Setting::Handle _headPitchSetting; @@ -1938,6 +1962,38 @@ private: Setting::Handle _allowTeleportingSetting { "allowTeleporting", true }; std::vector> _avatarEntityIDSettings; std::vector> _avatarEntityDataSettings; + + // AvatarEntities stuff: + // We cache the "map of unfortunately-formatted-binary-blobs" because they are expensive to compute + // Do not confuse these with AvatarData::_packedAvatarEntityData which are in wire-format. + mutable AvatarEntityMap _cachedAvatarEntityBlobs; + + // We collect changes to AvatarEntities and then handle them all in one spot per frame: updateAvatarEntities(). + // Basically this is a "transaction pattern" with an extra complication: these changes can come from two + // "directions" and the "authoritative source" of each direction is different, so maintain two distinct sets of + // transaction lists; + // + // The _entitiesToDelete/Add/Update lists are for changes whose "authoritative sources" are already + // correctly stored in _cachedAvatarEntityBlobs. These come from loadAvatarEntityDataFromSettings() and + // setAvatarEntityData(). These changes need to be extracted from _cachedAvatarEntityBlobs and applied to + // real EntityItems. + std::vector _entitiesToDelete; + std::vector _entitiesToAdd; + std::vector _entitiesToUpdate; + // + // The _cachedAvatarEntityBlobsToDelete/Add/Update lists are for changes whose "authoritative sources" are + // already reflected in real EntityItems. These changes need to be propagated to _cachedAvatarEntityBlobs + // and eventually to settings. + std::vector _cachedAvatarEntityBlobsToDelete; + std::vector _cachedAvatarEntityBlobsToAddOrUpdate; + std::vector _cachedAvatarEntityBlobUpdatesToSkip; + // + // Also these lists for tracking delayed changes to blobs and Settings + mutable std::set _staleCachedAvatarEntityBlobs; + // + // keep a ScriptEngine around so we don't have to instantiate on the fly (these are very slow to create/delete) + QScriptEngine* _myScriptEngine { nullptr }; + bool _needToSaveAvatarEntitySettings { false }; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); diff --git a/interface/src/avatar/MyCharacterController.cpp b/interface/src/avatar/MyCharacterController.cpp index 798dbc91ed..821b01c2c6 100755 --- a/interface/src/avatar/MyCharacterController.cpp +++ b/interface/src/avatar/MyCharacterController.cpp @@ -202,6 +202,29 @@ bool MyCharacterController::testRayShotgun(const glm::vec3& position, const glm: return result.hitFraction < 1.0f; } +int32_t MyCharacterController::computeCollisionMask() const { + int32_t collisionMask = BULLET_COLLISION_MASK_MY_AVATAR; + if (_collisionless && _collisionlessAllowed) { + collisionMask = BULLET_COLLISION_MASK_COLLISIONLESS; + } else if (!_collideWithOtherAvatars) { + collisionMask &= ~BULLET_COLLISION_GROUP_OTHER_AVATAR; + } + return collisionMask; +} + +void MyCharacterController::handleChangedCollisionMask() { + if (_pendingFlags & PENDING_FLAG_UPDATE_COLLISION_MASK) { + // ATM the easiest way to update collision groups/masks is to remove/re-add the RigidBody + if (_dynamicsWorld) { + _dynamicsWorld->removeRigidBody(_rigidBody); + int32_t collisionMask = computeCollisionMask(); + _dynamicsWorld->addRigidBody(_rigidBody, BULLET_COLLISION_GROUP_MY_AVATAR, collisionMask); + } + _pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_MASK; + updateCurrentGravity(); + } +} + btConvexHullShape* MyCharacterController::computeShape() const { // HACK: the avatar collides using convex hull with a collision margin equal to // the old capsule radius. Two points define a capsule and additional points are diff --git a/interface/src/avatar/MyCharacterController.h b/interface/src/avatar/MyCharacterController.h old mode 100644 new mode 100755 index fd9caface2..76fe588e71 --- a/interface/src/avatar/MyCharacterController.h +++ b/interface/src/avatar/MyCharacterController.h @@ -42,6 +42,12 @@ public: void setDensity(btScalar density) { _density = density; } + int32_t computeCollisionMask() const override; + void handleChangedCollisionMask() override; + + bool _collideWithOtherAvatars{ true }; + void setCollideWithOtherAvatars(bool collideWithOtherAvatars) { _collideWithOtherAvatars = collideWithOtherAvatars; } + protected: void initRayShotgun(const btCollisionWorld* world); void updateMassProperties() override; diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp old mode 100644 new mode 100755 index 356b365f93..26d69841d0 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -187,7 +187,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { } } - bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS); + bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS); if (isFlying != _prevIsFlying) { const float FLY_TO_IDLE_HIPS_TRANSITION_TIME = 0.5f; _flyIdleTimer = FLY_TO_IDLE_HIPS_TRANSITION_TIME; @@ -198,7 +198,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { // if hips are not under direct control, estimate the hips position. if (avatarHeadPose.isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] & (uint8_t)Rig::ControllerFlags::Enabled)) { - bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS); + bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS); // timescale in seconds const float TRANS_HORIZ_TIMESCALE = 0.15f; diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp old mode 100644 new mode 100755 index a71d2478ad..754d914135 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -7,10 +7,18 @@ // #include "OtherAvatar.h" -#include "Application.h" +#include +#include + +#include + +#include "Application.h" #include "AvatarMotionState.h" +const float DISPLAYNAME_FADE_TIME = 0.5f; +const float DISPLAYNAME_FADE_FACTOR = pow(0.01f, 1.0f / DISPLAYNAME_FADE_TIME); + static glm::u8vec3 getLoadingOrbColor(Avatar::LoadingStatus loadingStatus) { const glm::u8vec3 NO_MODEL_COLOR(0xe3, 0xe3, 0xe3); @@ -38,9 +46,6 @@ OtherAvatar::OtherAvatar(QThread* thread) : Avatar(thread) { connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset); - - // add the purple orb - createOrb(); } OtherAvatar::~OtherAvatar() { @@ -130,16 +135,297 @@ void OtherAvatar::rebuildCollisionShape() { } } -void OtherAvatar::updateCollisionGroup(bool myAvatarCollide) { +void OtherAvatar::setCollisionWithOtherAvatarsFlags() { if (_motionState) { - bool collides = _motionState->getCollisionGroup() == BULLET_COLLISION_GROUP_OTHER_AVATAR && myAvatarCollide; - if (_collideWithOtherAvatars != collides) { - if (!myAvatarCollide) { - _collideWithOtherAvatars = false; + _motionState->addDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); + } +} + +void OtherAvatar::simulate(float deltaTime, bool inView) { + PROFILE_RANGE(simulation, "simulate"); + + _globalPosition = _transit.isActive() ? _transit.getCurrentPosition() : _serverPosition; + if (!hasParent()) { + setLocalPosition(_globalPosition); + } + + _simulationRate.increment(); + if (inView) { + _simulationInViewRate.increment(); + } + + PerformanceTimer perfTimer("simulate"); + { + PROFILE_RANGE(simulation, "updateJoints"); + if (inView) { + Head* head = getHead(); + if (_hasNewJointData || _transit.isActive()) { + _skeletonModel->getRig().copyJointsFromJointData(_jointData); + glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset()); + _skeletonModel->getRig().computeExternalPoses(rootTransform); + _jointDataSimulationRate.increment(); + + _skeletonModel->simulate(deltaTime, true); + + locationChanged(); // joints changed, so if there are any children, update them. + _hasNewJointData = false; + + glm::vec3 headPosition = getWorldPosition(); + if (!_skeletonModel->getHeadPosition(headPosition)) { + headPosition = getWorldPosition(); + } + head->setPosition(headPosition); } - auto newCollisionGroup = _collideWithOtherAvatars ? BULLET_COLLISION_GROUP_OTHER_AVATAR : BULLET_COLLISION_GROUP_COLLISIONLESS; - _motionState->setCollisionGroup(newCollisionGroup); - _motionState->addDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); + head->setScale(getModelScale()); + head->simulate(deltaTime); + relayJointDataToChildren(); + } else { + // a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated. + _skeletonModel->simulate(deltaTime, false); + } + _skeletonModelSimulationRate.increment(); + } + + // update animation for display name fade in/out + if ( _displayNameTargetAlpha != _displayNameAlpha) { + // the alpha function is + // Fade out => alpha(t) = factor ^ t => alpha(t+dt) = alpha(t) * factor^(dt) + // Fade in => alpha(t) = 1 - factor^t => alpha(t+dt) = 1-(1-alpha(t))*coef^(dt) + // factor^(dt) = coef + float coef = pow(DISPLAYNAME_FADE_FACTOR, deltaTime); + if (_displayNameTargetAlpha < _displayNameAlpha) { + // Fading out + _displayNameAlpha *= coef; + } else { + // Fading in + _displayNameAlpha = 1.0f - (1.0f - _displayNameAlpha) * coef; + } + _displayNameAlpha = glm::abs(_displayNameAlpha - _displayNameTargetAlpha) < 0.01f ? _displayNameTargetAlpha : _displayNameAlpha; + } + + { + PROFILE_RANGE(simulation, "misc"); + measureMotionDerivatives(deltaTime); + simulateAttachments(deltaTime); + updatePalms(); + } + { + PROFILE_RANGE(simulation, "entities"); + handleChangedAvatarEntityData(); + updateAttachedAvatarEntities(); + } + + { + PROFILE_RANGE(simulation, "grabs"); + updateGrabs(); + } + + updateFadingStatus(); +} + +void OtherAvatar::handleChangedAvatarEntityData() { + PerformanceTimer perfTimer("attachments"); + + // AVATAR ENTITY UPDATE FLOW + // - if queueEditEntityMessage() sees "AvatarEntity" HostType it calls _myAvatar->storeAvatarEntityDataPayload() + // - storeAvatarEntityDataPayload() saves the payload and flags the trait instance for the entity as updated, + // - ClientTraitsHandler::sendChangedTraitsToMixea() sends the entity bytes to the mixer which relays them to other interfaces + // - AvatarHashMap::processBulkAvatarTraits() on other interfaces calls avatar->processTraitInstance() + // - AvatarData::processTraitInstance() calls storeAvatarEntityDataPayload(), which sets _avatarEntityDataChanged = true + // - (My)Avatar::simulate() calls handleChangedAvatarEntityData() every frame which checks _avatarEntityDataChanged + // and here we are... + + // AVATAR ENTITY DELETE FLOW + // - EntityScriptingInterface::deleteEntity() calls _myAvatar->clearAvatarEntity() for deleted avatar entities + // - clearAvatarEntity() removes the avatar entity and flags the trait instance for the entity as deleted + // - ClientTraitsHandler::sendChangedTraitsToMixer() sends a deletion to the mixer which relays to other interfaces + // - AvatarHashMap::processBulkAvatarTraits() on other interfaces calls avatar->processDeletedTraitInstace() + // - AvatarData::processDeletedTraitInstance() calls clearAvatarEntity() + // - AvatarData::clearAvatarEntity() sets _avatarEntityDataChanged = true and adds the ID to the detached list + // - (My)Avatar::simulate() calls handleChangedAvatarEntityData() every frame which checks _avatarEntityDataChanged + // and here we are... + + if (!_avatarEntityDataChanged) { + return; + } + + auto treeRenderer = DependencyManager::get(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + if (!entityTree) { + return; + } + + PackedAvatarEntityMap packedAvatarEntityData; + _avatarEntitiesLock.withReadLock([&] { + packedAvatarEntityData = _packedAvatarEntityData; + }); + entityTree->withWriteLock([&] { + AvatarEntityMap::const_iterator dataItr = packedAvatarEntityData.begin(); + while (dataItr != packedAvatarEntityData.end()) { + // compute hash of data. TODO? cache this? + QByteArray data = dataItr.value(); + uint32_t newHash = qHash(data); + + // check to see if we recognize this hash and whether it was already successfully processed + QUuid entityID = dataItr.key(); + MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID); + if (stateItr != _avatarEntityDataHashes.end()) { + if (stateItr.value().success) { + if (newHash == stateItr.value().hash) { + // data hasn't changed --> nothing to do + ++dataItr; + continue; + } + } else { + // NOTE: if the data was unsuccessful in producing an entity in the past + // we will try again just in case something changed (unlikely). + // Unfortunately constantly trying to build the entity for this data costs + // CPU cycles that we'd rather not spend. + // TODO? put a maximum number of tries on this? + } + } else { + // sanity check data + QUuid id; + EntityTypes::EntityType type; + EntityTypes::extractEntityTypeAndID((unsigned char*)(data.data()), data.size(), type, id); + if (id != entityID || !EntityTypes::typeIsValid(type)) { + // skip for corrupt + ++dataItr; + continue; + } + // remember this hash for the future + stateItr = _avatarEntityDataHashes.insert(entityID, AvatarEntityDataHash(newHash)); + } + ++dataItr; + + EntityItemProperties properties; + int32_t bytesLeftToRead = data.size(); + unsigned char* dataAt = (unsigned char*)(data.data()); + if (!properties.constructFromBuffer(dataAt, bytesLeftToRead)) { + // properties are corrupt + continue; + } + + properties.setEntityHostType(entity::HostType::AVATAR); + properties.setOwningAvatarID(getID()); + + // there's no entity-server to tell us we're the simulation owner, so always set the + // simulationOwner to the owningAvatarID and a high priority. + properties.setSimulationOwner(getID(), AVATAR_ENTITY_SIMULATION_PRIORITY); + + if (properties.getParentID() == AVATAR_SELF_ID) { + properties.setParentID(getID()); + } + + // NOTE: if this avatar entity is not attached to us, strip its entity script completely... + auto attachedScript = properties.getScript(); + if (!isMyAvatar() && !attachedScript.isEmpty()) { + QString noScript; + properties.setScript(noScript); + } + + auto specifiedHref = properties.getHref(); + if (!isMyAvatar() && !specifiedHref.isEmpty()) { + qCDebug(avatars) << "removing entity href from avatar attached entity:" << entityID << "old href:" << specifiedHref; + QString noHref; + properties.setHref(noHref); + } + + // When grabbing avatar entities, they are parented to the joint moving them, then when un-grabbed + // they go back to the default parent (null uuid). When un-gripped, others saw the entity disappear. + // The thinking here is the local position was noticed as changing, but not the parentID (since it is now + // back to the default), and the entity flew off somewhere. Marking all changed definitely fixes this, + // and seems safe (per Seth). + properties.markAllChanged(); + + // try to build the entity + EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID)); + bool success = true; + if (entity) { + QUuid oldParentID = entity->getParentID(); + if (entityTree->updateEntity(entityID, properties)) { + entity->updateLastEditedFromRemote(); + } else { + success = false; + } + if (oldParentID != entity->getParentID()) { + if (entity->getParentID() == getID()) { + onAddAttachedAvatarEntity(entityID); + } else if (oldParentID == getID()) { + onRemoveAttachedAvatarEntity(entityID); + } + } + } else { + entity = entityTree->addEntity(entityID, properties); + if (!entity) { + success = false; + } else if (entity->getParentID() == getID()) { + onAddAttachedAvatarEntity(entityID); + } + } + stateItr.value().success = success; + } + + AvatarEntityIDs recentlyRemovedAvatarEntities = getAndClearRecentlyRemovedIDs(); + if (!recentlyRemovedAvatarEntities.empty()) { + // only lock this thread when absolutely necessary + AvatarEntityMap packedAvatarEntityData; + _avatarEntitiesLock.withReadLock([&] { + packedAvatarEntityData = _packedAvatarEntityData; + }); + foreach (auto entityID, recentlyRemovedAvatarEntities) { + if (!packedAvatarEntityData.contains(entityID)) { + entityTree->deleteEntity(entityID, true, true); + } + } + + // TODO: move this outside of tree lock + // remove stale data hashes + foreach (auto entityID, recentlyRemovedAvatarEntities) { + MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID); + if (stateItr != _avatarEntityDataHashes.end()) { + _avatarEntityDataHashes.erase(stateItr); + } + onRemoveAttachedAvatarEntity(entityID); + } + } + if (packedAvatarEntityData.size() != _avatarEntityForRecording.size()) { + createRecordingIDs(); + } + }); + + setAvatarEntityDataChanged(false); +} + +void OtherAvatar::onAddAttachedAvatarEntity(const QUuid& id) { + for (uint32_t i = 0; i < _attachedAvatarEntities.size(); ++i) { + if (_attachedAvatarEntities[i] == id) { + return; } } -} \ No newline at end of file + _attachedAvatarEntities.push_back(id); +} + +void OtherAvatar::onRemoveAttachedAvatarEntity(const QUuid& id) { + for (uint32_t i = 0; i < _attachedAvatarEntities.size(); ++i) { + if (_attachedAvatarEntities[i] == id) { + if (i != _attachedAvatarEntities.size() - 1) { + _attachedAvatarEntities[i] = _attachedAvatarEntities.back(); + } + _attachedAvatarEntities.pop_back(); + break; + } + } +} + +void OtherAvatar::updateAttachedAvatarEntities() { + if (!_attachedAvatarEntities.empty()) { + auto treeRenderer = DependencyManager::get(); + if (!treeRenderer) { + return; + } + for (const QUuid& id : _attachedAvatarEntities) { + treeRenderer->onEntityChanged(id); + } + } +} diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h old mode 100644 new mode 100755 index 48402fe55c..969f551783 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -10,6 +10,7 @@ #define hifi_OtherAvatar_h #include +#include #include #include @@ -45,11 +46,21 @@ public: bool shouldBeInPhysicsSimulation() const; bool needsPhysicsUpdate() const; - void updateCollisionGroup(bool myAvatarCollide); + bool getCollideWithOtherAvatars() const { return _collideWithOtherAvatars; } + + void setCollisionWithOtherAvatarsFlags() override; + + void simulate(float deltaTime, bool inView) override; friend AvatarManager; protected: + void handleChangedAvatarEntityData(); + void updateAttachedAvatarEntities(); + void onAddAttachedAvatarEntity(const QUuid& id); + void onRemoveAttachedAvatarEntity(const QUuid& id); + + std::vector _attachedAvatarEntities; std::shared_ptr _otherAvatarOrbMeshPlaceholder { nullptr }; OverlayID _otherAvatarOrbMeshPlaceholderID { UNKNOWN_OVERLAY_ID }; AvatarMotionState* _motionState { nullptr }; diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 00acd40e70..5236c5a7fb 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -22,7 +22,9 @@ #include #include "scripting/HMDScriptingInterface.h" -QmlCommerce::QmlCommerce() { +QmlCommerce::QmlCommerce() : + _appsPath(PathUtils::getAppDataPath() + "Apps/") +{ auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); connect(ledger.data(), &Ledger::buyResult, this, &QmlCommerce::buyResult); @@ -44,22 +46,18 @@ QmlCommerce::QmlCommerce() { auto accountManager = DependencyManager::get(); connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { setPassphrase(""); }); - - _appsPath = PathUtils::getAppDataPath() + "Apps/"; } - - void QmlCommerce::openSystemApp(const QString& appName) { - static QMap systemApps { + static const QMap systemApps { {"GOTO", "hifi/tablet/TabletAddressDialog.qml"}, {"PEOPLE", "hifi/Pal.qml"}, {"WALLET", "hifi/commerce/wallet/Wallet.qml"}, {"MARKET", "/marketplace.html"} }; - static QMap systemInject{ + static const QMap systemInject{ {"MARKET", "/scripts/system/html/js/marketplacesInject.js"} }; diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index ad21899ebf..3217b8a1f9 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -19,7 +19,9 @@ #include -class QmlCommerce : public QObject { +#include + +class QmlCommerce : public QObject, public Dependency { Q_OBJECT public: @@ -98,7 +100,7 @@ protected: Q_INVOKABLE void updateItem(const QString& certificateId); private: - QString _appsPath; + const QString _appsPath; }; #endif // hifi_QmlCommerce_h diff --git a/interface/src/raypick/PathPointer.cpp b/interface/src/raypick/PathPointer.cpp index 8cee6134c8..ae0ce4671b 100644 --- a/interface/src/raypick/PathPointer.cpp +++ b/interface/src/raypick/PathPointer.cpp @@ -223,7 +223,7 @@ Pointer::Buttons PathPointer::getPressedButtons(const PickResultPointer& pickRes std::string button = trigger.getButton(); TriggerState& state = _states[button]; // TODO: right now, LaserPointers don't support axes, only on/off buttons - if (trigger.getEndpoint()->peek() >= 1.0f) { + if (trigger.getEndpoint()->peek().value >= 1.0f) { toReturn.insert(button); if (_previousButtons.find(button) == _previousButtons.end()) { diff --git a/interface/src/scripting/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h index 051a372aad..b063e98992 100644 --- a/interface/src/scripting/ControllerScriptingInterface.h +++ b/interface/src/scripting/ControllerScriptingInterface.h @@ -104,7 +104,7 @@ class ScriptEngine; *
    *
  • {@link Controller.getValue|getValue}
  • *
  • {@link Controller.getAxisValue|getAxisValue}
  • - *
  • {@link Controller.getPoseValue|getgetPoseValue}
  • + *
  • {@link Controller.getPoseValue|getPoseValue}
  • *
  • {@link Controller.getActionValue|getActionValue}
  • *
* diff --git a/interface/src/scripting/MenuScriptingInterface.cpp b/interface/src/scripting/MenuScriptingInterface.cpp index d6dc2fa703..ae6a7c7d67 100644 --- a/interface/src/scripting/MenuScriptingInterface.cpp +++ b/interface/src/scripting/MenuScriptingInterface.cpp @@ -43,7 +43,7 @@ bool MenuScriptingInterface::menuExists(const QString& menu) { if (QThread::currentThread() == qApp->thread()) { return Menu::getInstance()->menuExists(menu); } - bool result; + bool result { false }; BLOCKING_INVOKE_METHOD(Menu::getInstance(), "menuExists", Q_RETURN_ARG(bool, result), Q_ARG(const QString&, menu)); @@ -86,7 +86,7 @@ bool MenuScriptingInterface::menuItemExists(const QString& menu, const QString& if (QThread::currentThread() == qApp->thread()) { return Menu::getInstance()->menuItemExists(menu, menuitem); } - bool result; + bool result { false }; BLOCKING_INVOKE_METHOD(Menu::getInstance(), "menuItemExists", Q_RETURN_ARG(bool, result), Q_ARG(const QString&, menu), @@ -98,7 +98,7 @@ bool MenuScriptingInterface::isOptionChecked(const QString& menuOption) { if (QThread::currentThread() == qApp->thread()) { return Menu::getInstance()->isOptionChecked(menuOption); } - bool result; + bool result { false }; BLOCKING_INVOKE_METHOD(Menu::getInstance(), "isOptionChecked", Q_RETURN_ARG(bool, result), Q_ARG(const QString&, menuOption)); @@ -115,7 +115,7 @@ bool MenuScriptingInterface::isMenuEnabled(const QString& menuOption) { if (QThread::currentThread() == qApp->thread()) { return Menu::getInstance()->isOptionChecked(menuOption); } - bool result; + bool result { false }; BLOCKING_INVOKE_METHOD(Menu::getInstance(), "isMenuEnabled", Q_RETURN_ARG(bool, result), Q_ARG(const QString&, menuOption)); diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index eab7a96a4b..eb43e8cf45 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -27,6 +27,9 @@ Base3DOverlay::Base3DOverlay() : _drawInFront(false), _drawHUDLayer(false) { + // HACK: queryAACube stuff not actually relevant for 3DOverlays, and by setting _queryAACubeSet true here + // we can avoid incorrect evaluation for sending updates for entities with 3DOverlays children. + _queryAACubeSet = true; } Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) : @@ -41,6 +44,9 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) : _isVisibleInSecondaryCamera(base3DOverlay->_isVisibleInSecondaryCamera) { setTransform(base3DOverlay->getTransform()); + // HACK: queryAACube stuff not actually relevant for 3DOverlays, and by setting _queryAACubeSet true here + // we can avoid incorrect evaluation for sending updates for entities with 3DOverlays children. + _queryAACubeSet = true; } QVariantMap convertOverlayLocationFromScriptSemantics(const QVariantMap& properties, bool scalesWithParent) { @@ -209,6 +215,7 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { transaction.updateItem(itemID); scene->enqueueTransaction(transaction); } + _queryAACubeSet = true; // HACK: just in case some SpatiallyNestable code accidentally set it false } } diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 6cc5182b56..daf15e676f 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -25,6 +25,7 @@ public: Base3DOverlay(const Base3DOverlay* base3DOverlay); void setVisible(bool visible) override; + bool queryAACubeNeedsUpdate() const override { return false; } // HACK: queryAACube not relevant for Overlays virtual OverlayID getOverlayID() const override { return OverlayID(getID().toString()); } void setOverlayID(OverlayID overlayID) override { setID(overlayID); } diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 9dcf5822cd..1adc04ee1b 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -109,9 +109,6 @@ void AnimClip::copyFromNetworkAnim() { jointMap.reserve(animJointCount); for (int i = 0; i < animJointCount; i++) { int skeletonJoint = _skeleton->nameToJointIndex(animSkeleton.getJointName(i)); - if (skeletonJoint == -1) { - qCWarning(animation) << "animation contains joint =" << animSkeleton.getJointName(i) << " which is not in the skeleton"; - } jointMap.push_back(skeletonJoint); } diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 16c2c1cc7e..cc48308f17 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -237,8 +237,17 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints, _relativeDefaultPoses = _absoluteDefaultPoses; convertAbsolutePosesToRelative(_relativeDefaultPoses); + // build _jointIndicesByName hash for (int i = 0; i < _jointsSize; i++) { - _jointIndicesByName[_joints[i].name] = i; + auto iter = _jointIndicesByName.find(_joints[i].name); + if (iter != _jointIndicesByName.end()) { + // prefer joints over meshes if there is a name collision. + if (_joints[i].isSkeletonJoint && !_joints[iter.value()].isSkeletonJoint) { + iter.value() = i; + } + } else { + _jointIndicesByName.insert(_joints[i].name, i); + } } // build mirror map. diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 44fdd8797f..bc4dca54f2 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -425,7 +425,6 @@ int Rig::indexOfJoint(const QString& jointName) const { // This is a content error, so we should issue a warning. if (result < 0 && _jointNameWarningCount < MAX_JOINT_NAME_WARNING_COUNT) { - qCWarning(animation) << "Rig: Missing joint" << jointName << "in avatar model"; _jointNameWarningCount++; } return result; @@ -1985,11 +1984,10 @@ void Rig::copyJointsIntoJointData(QVector& jointDataVec) const { data.rotation = !_sendNetworkNode ? _internalPoseSet._absolutePoses[i].rot() : _networkPoseSet._absolutePoses[i].rot(); data.rotationIsDefaultPose = isEqual(data.rotation, defaultAbsRot); - // translations are in relative frame but scaled so that they are in meters, - // instead of model units. + // translations are in relative frame. glm::vec3 defaultRelTrans = _animSkeleton->getRelativeDefaultPose(i).trans(); glm::vec3 currentRelTrans = _sendNetworkNode ? _networkPoseSet._relativePoses[i].trans() : _internalPoseSet._relativePoses[i].trans(); - data.translation = geometryToRigScale * currentRelTrans; + data.translation = currentRelTrans; data.translationIsDefaultPose = isEqual(currentRelTrans, defaultRelTrans); } else { data.translationIsDefaultPose = true; @@ -2016,7 +2014,6 @@ void Rig::copyJointsFromJointData(const QVector& jointDataVec) { std::vector rotations; rotations.reserve(numJoints); const glm::quat rigToGeometryRot(glmExtractRotation(_rigToGeometryTransform)); - const glm::vec3 rigToGeometryScale(extractScale(_rigToGeometryTransform)); for (int i = 0; i < numJoints; i++) { const JointData& data = jointDataVec.at(i); @@ -2042,8 +2039,8 @@ void Rig::copyJointsFromJointData(const QVector& jointDataVec) { if (data.translationIsDefaultPose) { _internalPoseSet._relativePoses[i].trans() = relativeDefaultPoses[i].trans(); } else { - // JointData translations are in scaled relative-frame so we scale back to regular relative-frame - _internalPoseSet._relativePoses[i].trans() = rigToGeometryScale * data.translation; + // JointData translations are in relative-frame + _internalPoseSet._relativePoses[i].trans() = data.translation; } } } diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 9bad7e2f45..36fb701168 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1333,8 +1333,12 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { } else if (injector->isStereo()) { + // calculate distance, gain + glm::vec3 relativePosition = injector->getPosition() - _positionGetter(); + float distance = glm::max(glm::length(relativePosition), EPSILON); + float gain = gainForSource(distance, injector->getVolume()); + // stereo gets directly mixed into mixBuffer - float gain = injector->getVolume(); for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i++) { mixBuffer[i] += convertToFloat(_localScratchBuffer[i]) * gain; } diff --git a/libraries/audio/src/AudioFOA.cpp b/libraries/audio/src/AudioFOA.cpp index 16c0721047..30d29b72b7 100644 --- a/libraries/audio/src/AudioFOA.cpp +++ b/libraries/audio/src/AudioFOA.cpp @@ -695,7 +695,7 @@ static void ifft_radix8_first(complex_t* x, complex_t* y, int n, int p) { // n >= 4 static void rfft_post(complex_t* x, const complex_t* w, int n) { - size_t t = n/4; + int t = n/4; assert(t >= 1); // NOTE: x[n/2].re is packed into x[0].im @@ -707,7 +707,7 @@ static void rfft_post(complex_t* x, const complex_t* w, int n) { complex_t* xp0 = &x[1]; complex_t* xp1 = &x[n/2 - 1]; - for (size_t i = 0; i < t; i++) { + for (int i = 0; i < t; i++) { float ar = xp0[i].re; float ai = xp0[i].im; @@ -743,7 +743,7 @@ static void rfft_post(complex_t* x, const complex_t* w, int n) { // n >= 4 static void rifft_pre(complex_t* x, const complex_t* w, int n) { - size_t t = n/4; + int t = n/4; assert(t >= 1); // NOTE: x[n/2].re is packed into x[0].im @@ -755,7 +755,7 @@ static void rifft_pre(complex_t* x, const complex_t* w, int n) { complex_t* xp0 = &x[1]; complex_t* xp1 = &x[n/2 - 1]; - for (size_t i = 0; i < t; i++) { + for (int i = 0; i < t; i++) { float ar = xp0[i].re; float ai = xp0[i].im; diff --git a/libraries/audio/src/avx2/AudioFOA_avx2.cpp b/libraries/audio/src/avx2/AudioFOA_avx2.cpp index 880f40b185..70f9b0e5f6 100644 --- a/libraries/audio/src/avx2/AudioFOA_avx2.cpp +++ b/libraries/audio/src/avx2/AudioFOA_avx2.cpp @@ -973,8 +973,8 @@ FORCEINLINE static void ifft_radix8_first(complex_t* x, complex_t* y, int n, int // n >= 32 static void rfft_post(complex_t* x, const complex_t* w, int n) { - size_t t = n/4; - assert(n/4 >= 8); // SIMD8 + int t = n/4; + assert(t >= 8); // SIMD8 // NOTE: x[n/2].re is packed into x[0].im float tr = x[0].re; @@ -985,7 +985,7 @@ static void rfft_post(complex_t* x, const complex_t* w, int n) { complex_t* xp0 = &x[1]; complex_t* xp1 = &x[n/2 - 8]; - for (size_t i = 0; i < t; i += 8) { + for (int i = 0; i < t; i += 8) { __m256 z0 = _mm256_loadu_ps(&xp0[i+0].re); __m256 z1 = _mm256_loadu_ps(&xp0[i+4].re); @@ -1033,8 +1033,8 @@ static void rfft_post(complex_t* x, const complex_t* w, int n) { // n >= 32 static void rifft_pre(complex_t* x, const complex_t* w, int n) { - size_t t = n/4; - assert(n/4 >= 8); // SIMD8 + int t = n/4; + assert(t >= 8); // SIMD8 // NOTE: x[n/2].re is packed into x[0].im float tr = x[0].re; @@ -1045,7 +1045,7 @@ static void rifft_pre(complex_t* x, const complex_t* w, int n) { complex_t* xp0 = &x[1]; complex_t* xp1 = &x[n/2 - 8]; - for (size_t i = 0; i < t; i += 8) { + for (int i = 0; i < t; i += 8) { __m256 z0 = _mm256_loadu_ps(&xp0[i+0].re); __m256 z1 = _mm256_loadu_ps(&xp0[i+4].re); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 0676454b29..17d10cdf49 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -308,183 +308,24 @@ void Avatar::setAvatarEntityDataChanged(bool value) { _avatarEntityDataHashes.clear(); } -void Avatar::updateAvatarEntities() { - PerformanceTimer perfTimer("attachments"); - - // AVATAR ENTITY UPDATE FLOW - // - if queueEditEntityMessage sees avatarEntity flag it does _myAvatar->updateAvatarEntity() - // - updateAvatarEntity saves the bytes and flags the trait instance for the entity as updated - // - ClientTraitsHandler::sendChangedTraitsToMixer sends the entity bytes to the mixer which relays them to other interfaces - // - AvatarHashMap::processBulkAvatarTraits on other interfaces calls avatar->processTraitInstace - // - AvatarData::processTraitInstance calls updateAvatarEntity, which sets _avatarEntityDataChanged = true - // - (My)Avatar::simulate notices _avatarEntityDataChanged and here we are... - - // AVATAR ENTITY DELETE FLOW - // - EntityScriptingInterface::deleteEntity calls _myAvatar->clearAvatarEntity() for deleted avatar entities - // - clearAvatarEntity removes the avatar entity and flags the trait instance for the entity as deleted - // - ClientTraitsHandler::sendChangedTraitsToMixer sends a deletion to the mixer which relays to other interfaces - // - AvatarHashMap::processBulkAvatarTraits on other interfaces calls avatar->processDeletedTraitInstace - // - AvatarData::processDeletedTraitInstance calls clearAvatarEntity - // - AvatarData::clearAvatarEntity sets _avatarEntityDataChanged = true and adds the ID to the detached list - // - Avatar::simulate notices _avatarEntityDataChanged and here we are... - - if (!_avatarEntityDataChanged) { - return; - } - - if (getID().isNull() || - getID() == AVATAR_SELF_ID || - DependencyManager::get()->getSessionUUID() == QUuid()) { - // wait until MyAvatar and this Node gets an ID before doing this. Otherwise, various things go wrong -- - // things get their parent fixed up from AVATAR_SELF_ID to a null uuid which means "no parent". - return; - } - - auto treeRenderer = DependencyManager::get(); - EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; - if (!entityTree) { - return; - } - - QScriptEngine scriptEngine; - entityTree->withWriteLock([&] { - AvatarEntityMap avatarEntities = getAvatarEntityData(); - AvatarEntityMap::const_iterator dataItr = avatarEntities.begin(); - while (dataItr != avatarEntities.end()) { - // compute hash of data. TODO? cache this? - QByteArray data = dataItr.value(); - uint32_t newHash = qHash(data); - - // check to see if we recognize this hash and whether it was already successfully processed - QUuid entityID = dataItr.key(); - MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID); - if (stateItr != _avatarEntityDataHashes.end()) { - if (stateItr.value().success) { - if (newHash == stateItr.value().hash) { - // data hasn't changed --> nothing to do - ++dataItr; - continue; - } - } else { - // NOTE: if the data was unsuccessful in producing an entity in the past - // we will try again just in case something changed (unlikely). - // Unfortunately constantly trying to build the entity for this data costs - // CPU cycles that we'd rather not spend. - // TODO? put a maximum number of tries on this? - } - } else { - // remember this hash for the future - stateItr = _avatarEntityDataHashes.insert(entityID, AvatarEntityDataHash(newHash)); - } - ++dataItr; - - // see EntityEditPacketSender::queueEditEntityMessage for the other end of this. unpack properties - // and either add or update the entity. - QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(data); - if (!jsonProperties.isObject()) { - qCDebug(avatars_renderer) << "got bad avatarEntity json" << QString(data.toHex()); - continue; - } - - QVariant variantProperties = jsonProperties.toVariant(); - QVariantMap asMap = variantProperties.toMap(); - QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine); - EntityItemProperties properties; - EntityItemPropertiesFromScriptValueIgnoreReadOnly(scriptProperties, properties); - properties.setEntityHostType(entity::HostType::AVATAR); - properties.setOwningAvatarID(getID()); - - // there's no entity-server to tell us we're the simulation owner, so always set the - // simulationOwner to the owningAvatarID and a high priority. - properties.setSimulationOwner(getID(), AVATAR_ENTITY_SIMULATION_PRIORITY); - - if (properties.getParentID() == AVATAR_SELF_ID) { - properties.setParentID(getID()); - } - - // NOTE: if this avatar entity is not attached to us, strip its entity script completely... - auto attachedScript = properties.getScript(); - if (!isMyAvatar() && !attachedScript.isEmpty()) { - QString noScript; - properties.setScript(noScript); - } - - auto specifiedHref = properties.getHref(); - if (!isMyAvatar() && !specifiedHref.isEmpty()) { - qCDebug(avatars_renderer) << "removing entity href from avatar attached entity:" << entityID << "old href:" << specifiedHref; - QString noHref; - properties.setHref(noHref); - } - - // When grabbing avatar entities, they are parented to the joint moving them, then when un-grabbed - // they go back to the default parent (null uuid). When un-gripped, others saw the entity disappear. - // The thinking here is the local position was noticed as changing, but not the parentID (since it is now - // back to the default), and the entity flew off somewhere. Marking all changed definitely fixes this, - // and seems safe (per Seth). - properties.markAllChanged(); - - // try to build the entity - EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID)); - bool success = true; - if (entity) { - if (entityTree->updateEntity(entityID, properties)) { - entity->updateLastEditedFromRemote(); - } else { - success = false; - } - } else { - entity = entityTree->addEntity(entityID, properties); - if (!entity) { - success = false; - } - } - stateItr.value().success = success; - } - - AvatarEntityIDs recentlyDetachedAvatarEntities = getAndClearRecentlyDetachedIDs(); - if (!recentlyDetachedAvatarEntities.empty()) { - // only lock this thread when absolutely necessary - AvatarEntityMap avatarEntityData; - _avatarEntitiesLock.withReadLock([&] { - avatarEntityData = _avatarEntityData; - }); - foreach (auto entityID, recentlyDetachedAvatarEntities) { - if (!avatarEntityData.contains(entityID)) { - entityTree->deleteEntity(entityID, true, true); - } - } - - // remove stale data hashes - foreach (auto entityID, recentlyDetachedAvatarEntities) { - MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID); - if (stateItr != _avatarEntityDataHashes.end()) { - _avatarEntityDataHashes.erase(stateItr); - } - } - } - if (avatarEntities.size() != _avatarEntityForRecording.size()) { - createRecordingIDs(); - } - }); - - setAvatarEntityDataChanged(false); -} - void Avatar::removeAvatarEntitiesFromTree() { auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; if (entityTree) { + QList avatarEntityIDs; + _avatarEntitiesLock.withReadLock([&] { + avatarEntityIDs = _packedAvatarEntityData.keys(); + }); entityTree->withWriteLock([&] { - AvatarEntityMap avatarEntities = getAvatarEntityData(); - for (auto entityID : avatarEntities.keys()) { + for (const auto& entityID : avatarEntityIDs) { entityTree->deleteEntity(entityID, true, true); } }); } } -void Avatar::updateGrabs() { - +bool Avatar::updateGrabs() { + bool grabAddedOrRemoved = false; // update the Grabs according to any changes in _avatarGrabData _avatarGrabsLock.withWriteLock([&] { if (_avatarGrabDataChanged) { @@ -544,6 +385,7 @@ void Avatar::updateGrabs() { entityTree->updateEntityQueryAACube(target, packetSender, force, iShouldTellServer); }); } + grabAddedOrRemoved = true; } _avatarGrabs.remove(grabID); _changedAvatarGrabs.remove(grabID); @@ -561,9 +403,11 @@ void Avatar::updateGrabs() { target->addGrab(grab); // only clear this entry from the _changedAvatarGrabs if we found the entity. changeItr.remove(); + grabAddedOrRemoved = true; } } }); + return grabAddedOrRemoved; } void Avatar::accumulateGrabPositions(std::map& grabAccumulators) { @@ -651,87 +495,6 @@ void Avatar::relayJointDataToChildren() { _reconstructSoftEntitiesJointMap = false; } -void Avatar::simulate(float deltaTime, bool inView) { - PROFILE_RANGE(simulation, "simulate"); - - _globalPosition = _transit.isActive() ? _transit.getCurrentPosition() : _serverPosition; - if (!hasParent()) { - setLocalPosition(_globalPosition); - } - - _simulationRate.increment(); - if (inView) { - _simulationInViewRate.increment(); - } - - PerformanceTimer perfTimer("simulate"); - { - PROFILE_RANGE(simulation, "updateJoints"); - if (inView) { - Head* head = getHead(); - if (_hasNewJointData || _transit.isActive()) { - _skeletonModel->getRig().copyJointsFromJointData(_jointData); - glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset()); - _skeletonModel->getRig().computeExternalPoses(rootTransform); - _jointDataSimulationRate.increment(); - - _skeletonModel->simulate(deltaTime, true); - - locationChanged(); // joints changed, so if there are any children, update them. - _hasNewJointData = false; - - glm::vec3 headPosition = getWorldPosition(); - if (!_skeletonModel->getHeadPosition(headPosition)) { - headPosition = getWorldPosition(); - } - head->setPosition(headPosition); - } - head->setScale(getModelScale()); - head->simulate(deltaTime); - relayJointDataToChildren(); - } else { - // a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated. - _skeletonModel->simulate(deltaTime, false); - } - _skeletonModelSimulationRate.increment(); - } - - // update animation for display name fade in/out - if ( _displayNameTargetAlpha != _displayNameAlpha) { - // the alpha function is - // Fade out => alpha(t) = factor ^ t => alpha(t+dt) = alpha(t) * factor^(dt) - // Fade in => alpha(t) = 1 - factor^t => alpha(t+dt) = 1-(1-alpha(t))*coef^(dt) - // factor^(dt) = coef - float coef = pow(DISPLAYNAME_FADE_FACTOR, deltaTime); - if (_displayNameTargetAlpha < _displayNameAlpha) { - // Fading out - _displayNameAlpha *= coef; - } else { - // Fading in - _displayNameAlpha = 1 - (1 - _displayNameAlpha) * coef; - } - _displayNameAlpha = abs(_displayNameAlpha - _displayNameTargetAlpha) < 0.01f ? _displayNameTargetAlpha : _displayNameAlpha; - } - - { - PROFILE_RANGE(simulation, "misc"); - measureMotionDerivatives(deltaTime); - simulateAttachments(deltaTime); - updatePalms(); - } - { - PROFILE_RANGE(simulation, "entities"); - updateAvatarEntities(); - } - - { - PROFILE_RANGE(simulation, "grabs"); - updateGrabs(); - } - - updateFadingStatus(); -} - float Avatar::getSimulationRate(const QString& rateName) const { if (rateName == "") { return _simulationRate.rate(); @@ -1046,7 +809,6 @@ void Avatar::render(RenderArgs* renderArgs) { } } - void Avatar::setEnableMeshVisible(bool isEnabled) { if (_isMeshVisible != isEnabled) { _isMeshVisible = isEnabled; @@ -1616,7 +1378,7 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { AvatarData::setSkeletonModelURL(skeletonModelURL); if (QThread::currentThread() == thread()) { - if (!isMyAvatar()) { + if (!isMyAvatar() && !DependencyManager::get()->isIgnoringNode(getSessionUUID())) { createOrb(); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index fddd52a6dd..d5431ad2d2 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -139,9 +139,8 @@ public: typedef render::Payload Payload; void init(); - void updateAvatarEntities(); void removeAvatarEntitiesFromTree(); - void simulate(float deltaTime, bool inView); + virtual void simulate(float deltaTime, bool inView) = 0; virtual void simulateAttachments(float deltaTime); virtual void render(RenderArgs* renderArgs); @@ -240,8 +239,6 @@ public: static void renderJointConnectingCone(gpu::Batch& batch, glm::vec3 position1, glm::vec3 position2, float radius1, float radius2, const glm::vec4& color); - virtual void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) { } - /**jsdoc * Set the offset applied to the current avatar. The offset adjusts the position that the avatar is rendered. For example, * with an offset of { x: 0, y: 0.1, z: 0 }, your avatar will appear to be raised off the ground slightly. @@ -541,7 +538,7 @@ protected: // protected methods... bool isLookingAtMe(AvatarSharedPointer avatar) const; - void updateGrabs(); + bool updateGrabs(); void relayJointDataToChildren(); void fade(render::Transaction& transaction, render::Transition::Type type); diff --git a/libraries/avatars/src/AssociatedTraitValues.h b/libraries/avatars/src/AssociatedTraitValues.h index cf1edef7f7..e3060a8097 100644 --- a/libraries/avatars/src/AssociatedTraitValues.h +++ b/libraries/avatars/src/AssociatedTraitValues.h @@ -14,16 +14,33 @@ #include "AvatarTraits.h" +// This templated class is admittedly fairly confusing to look at. It is used +// to hold some associated value of type T for both simple (non-instanced) and instanced traits. +// Most of the complexity comes from the fact that simple and instanced trait types are +// handled differently. For each simple trait type there can be a value T, but for +// each instance of each instanced trait +// (keyed by a TraitInstanceID, which at the time of this writing is a UUID) there can be a value T. +// There are separate methods in most cases for simple traits and instanced traits +// because of this different behaviour. This class is not used to hold the values +// of the traits themselves, but instead an associated value like the latest version +// of each trait (see TraitVersions) or a state associated with each trait (like added/changed/deleted). + namespace AvatarTraits { template class AssociatedTraitValues { public: + // constructor that pre-fills _simpleTypes with the default value specified by the template AssociatedTraitValues() : _simpleTypes(FirstInstancedTrait, defaultValue) {} + /// inserts the given value for the given simple trait type void insert(TraitType type, T value) { _simpleTypes[type] = value; } + /// resets the simple trait type value to the default void erase(TraitType type) { _simpleTypes[type] = defaultValue; } + /// returns a reference to the value for a given instance for a given instanced trait type T& getInstanceValueRef(TraitType traitType, TraitInstanceID instanceID); + + /// inserts the passed value for the given instance for the given instanced trait type void instanceInsert(TraitType traitType, TraitInstanceID instanceID, T value); struct InstanceIDValuePair { @@ -34,24 +51,30 @@ namespace AvatarTraits { }; using InstanceIDValuePairs = std::vector; - + + /// returns a vector of InstanceIDValuePair objects for the given instanced trait type InstanceIDValuePairs& getInstanceIDValuePairs(TraitType traitType); + /// erases the a given instance for a given instanced trait type void instanceErase(TraitType traitType, TraitInstanceID instanceID); + /// erases the value for all instances for a given instanced trait type void eraseAllInstances(TraitType traitType); - // will return defaultValue for instanced traits + /// value getters for simple trait types, will be default value if value has been erased or not set T operator[](TraitType traitType) const { return _simpleTypes[traitType]; } T& operator[](TraitType traitType) { return _simpleTypes[traitType]; } + /// resets all simple trait types to the default value and erases all values for instanced trait types void reset() { std::fill(_simpleTypes.begin(), _simpleTypes.end(), defaultValue); _instancedTypes.clear(); } + /// const iterators for the vector of simple type values typename std::vector::const_iterator simpleCBegin() const { return _simpleTypes.cbegin(); } typename std::vector::const_iterator simpleCEnd() const { return _simpleTypes.cend(); } + /// non-const iterators for the vector of simple type values typename std::vector::iterator simpleBegin() { return _simpleTypes.begin(); } typename std::vector::iterator simpleEnd() { return _simpleTypes.end(); } @@ -64,15 +87,18 @@ namespace AvatarTraits { traitType(traitType), instances({{ instanceID, value }}) {}; }; + /// const iterators for the vector of TraitWithInstances objects typename std::vector::const_iterator instancedCBegin() const { return _instancedTypes.cbegin(); } typename std::vector::const_iterator instancedCEnd() const { return _instancedTypes.cend(); } + /// non-const iterators for the vector of TraitWithInstances objects typename std::vector::iterator instancedBegin() { return _instancedTypes.begin(); } typename std::vector::iterator instancedEnd() { return _instancedTypes.end(); } private: std::vector _simpleTypes; + /// return the iterator to the matching TraitWithInstances object for a given instanced trait type typename std::vector::iterator instancesForTrait(TraitType traitType) { return std::find_if(_instancedTypes.begin(), _instancedTypes.end(), [traitType](TraitWithInstances& traitWithInstances){ @@ -83,25 +109,34 @@ namespace AvatarTraits { std::vector _instancedTypes; }; + /// returns a reference to the InstanceIDValuePairs object for a given instanced trait type template inline typename AssociatedTraitValues::InstanceIDValuePairs& AssociatedTraitValues::getInstanceIDValuePairs(TraitType traitType) { + // first check if we already have some values for instances of this trait type auto it = instancesForTrait(traitType); if (it != _instancedTypes.end()) { return it->instances; } else { + // if we didn't have any values for instances of the instanced trait type + // add an empty InstanceIDValuePairs object first and then return the reference to it _instancedTypes.emplace_back(traitType); return _instancedTypes.back().instances; } } + // returns a reference to value for the given instance of the given instanced trait type template inline T& AssociatedTraitValues::getInstanceValueRef(TraitType traitType, TraitInstanceID instanceID) { + // first check if we already have some values for instances of this trait type auto it = instancesForTrait(traitType); if (it != _instancedTypes.end()) { + // grab the matching vector of instances auto& instancesVector = it->instances; + + // check if we have a value for this specific instance ID auto instanceIt = std::find_if(instancesVector.begin(), instancesVector.end(), [instanceID](InstanceIDValuePair& idValuePair){ return idValuePair.id == instanceID; @@ -109,40 +144,53 @@ namespace AvatarTraits { if (instanceIt != instancesVector.end()) { return instanceIt->value; } else { + // no value for this specific instance ID, insert the default value and return it instancesVector.emplace_back(instanceID, defaultValue); return instancesVector.back().value; } } else { + // no values for any instances of this trait type + // insert the default value for the specific instance for the instanced trait type _instancedTypes.emplace_back(traitType, instanceID, defaultValue); return _instancedTypes.back().instances.back().value; } } + /// inserts the passed value for the specific instance of the given instanced trait type template inline void AssociatedTraitValues::instanceInsert(TraitType traitType, TraitInstanceID instanceID, T value) { + // first check if we already have some instances for this trait type auto it = instancesForTrait(traitType); if (it != _instancedTypes.end()) { + // found some instances for the instanced trait type, check if our specific instance is one of them auto& instancesVector = it->instances; auto instanceIt = std::find_if(instancesVector.begin(), instancesVector.end(), [instanceID](InstanceIDValuePair& idValuePair){ return idValuePair.id == instanceID; }); if (instanceIt != instancesVector.end()) { + // the instance already existed, update the value instanceIt->value = value; } else { + // the instance was not present, emplace the new value instancesVector.emplace_back(instanceID, value); } } else { + // there were no existing instances for the given trait type + // setup the container for instances and insert the passed value for this instance ID _instancedTypes.emplace_back(traitType, instanceID, value); } } + /// erases the value for a specific instance of the given instanced trait type template inline void AssociatedTraitValues::instanceErase(TraitType traitType, TraitInstanceID instanceID) { + // check if we have any instances at all for this instanced trait type auto it = instancesForTrait(traitType); if (it != _instancedTypes.end()) { + // we have some instances, erase the value for the passed instance ID if it is present auto& instancesVector = it->instances; instancesVector.erase(std::remove_if(instancesVector.begin(), instancesVector.end(), diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp old mode 100644 new mode 100755 index e72fa3a6eb..17dad715f9 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -54,7 +54,8 @@ using namespace std; const QString AvatarData::FRAME_NAME = "com.highfidelity.recording.AvatarData"; -static const int TRANSLATION_COMPRESSION_RADIX = 12; +static const int TRANSLATION_COMPRESSION_RADIX = 14; +static const int FAUX_JOINT_COMPRESSION_RADIX = 12; static const int SENSOR_TO_WORLD_SCALE_RADIX = 10; static const float AUDIO_LOUDNESS_SCALE = 1024.0f; static const float DEFAULT_AVATAR_DENSITY = 1000.0f; // density of water @@ -73,6 +74,7 @@ size_t AvatarDataPacket::maxJointDataSize(size_t numJoints, bool hasGrabJoints) totalSize += validityBitsSize; // Orientations mask totalSize += numJoints * sizeof(SixByteQuat); // Orientations totalSize += validityBitsSize; // Translations mask + totalSize += sizeof(float); // maxTranslationDimension totalSize += numJoints * sizeof(SixByteTrans); // Translations size_t NUM_FAUX_JOINT = 2; @@ -85,6 +87,23 @@ size_t AvatarDataPacket::maxJointDataSize(size_t numJoints, bool hasGrabJoints) return totalSize; } +size_t AvatarDataPacket::minJointDataSize(size_t numJoints) { + const size_t validityBitsSize = calcBitVectorSize((int)numJoints); + + size_t totalSize = sizeof(uint8_t); // numJoints + + totalSize += validityBitsSize; // Orientations mask + // assume no valid rotations + totalSize += validityBitsSize; // Translations mask + totalSize += sizeof(float); // maxTranslationDimension + // assume no valid translations + + size_t NUM_FAUX_JOINT = 2; + totalSize += NUM_FAUX_JOINT * (sizeof(SixByteQuat) + sizeof(SixByteTrans)); // faux joints + + return totalSize; +} + size_t AvatarDataPacket::maxJointDefaultPoseFlagsSize(size_t numJoints) { const size_t bitVectorSize = calcBitVectorSize((int)numJoints); size_t totalSize = sizeof(uint8_t); // numJoints @@ -611,13 +630,24 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent assert(numJoints <= 255); const int jointBitVectorSize = calcBitVectorSize(numJoints); - // Start joints if room for at least the faux joints. - IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, 1 + 2 * jointBitVectorSize + AvatarDataPacket::FAUX_JOINTS_SIZE) { + // include jointData if there is room for the most minimal section. i.e. no translations or rotations. + IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, AvatarDataPacket::minJointDataSize(numJoints)) { // Allow for faux joints + translation bit-vector: const ptrdiff_t minSizeForJoint = sizeof(AvatarDataPacket::SixByteQuat) + jointBitVectorSize + AvatarDataPacket::FAUX_JOINTS_SIZE; auto startSection = destinationBuffer; + // compute maxTranslationDimension before we send any joint data. + float maxTranslationDimension = 0.001f; + for (int i = sendStatus.translationsSent; i < numJoints; ++i) { + const JointData& data = jointData[i]; + if (!data.translationIsDefaultPose) { + maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); + maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); + } + } + // joint rotation data *destinationBuffer++ = (uint8_t)numJoints; @@ -684,9 +714,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent memset(destinationBuffer, 0, jointBitVectorSize); destinationBuffer += jointBitVectorSize; // Move pointer past the validity bytes + // write maxTranslationDimension + AVATAR_MEMCPY(maxTranslationDimension); + float minTranslation = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinTranslationDistance(viewerPosition) : AVATAR_MIN_TRANSLATION; - float maxTranslationDimension = 0.0; i = sendStatus.translationsSent; for (; i < numJoints; ++i) { const JointData& data = joints[i]; @@ -700,12 +732,8 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent #ifdef WANT_DEBUG translationSentCount++; #endif - maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension); - maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension); - maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension); - - destinationBuffer += - packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); + destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation / maxTranslationDimension, + TRANSLATION_COMPRESSION_RADIX); if (sentJoints) { sentJoints[i].translation = data.translation; @@ -727,12 +755,12 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix()); destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerLeftHandTransform.getRotation()); destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerLeftHandTransform.getTranslation(), - TRANSLATION_COMPRESSION_RADIX); + FAUX_JOINT_COMPRESSION_RADIX); Transform controllerRightHandTransform = Transform(getControllerRightHandMatrix()); destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, controllerRightHandTransform.getRotation()); destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(), - TRANSLATION_COMPRESSION_RADIX); + FAUX_JOINT_COMPRESSION_RADIX); IF_AVATAR_SPACE(PACKET_HAS_GRAB_JOINTS, sizeof (AvatarDataPacket::FarGrabJoints)) { // the far-grab joints may range further than 3 meters, so we can't use packFloatVec3ToSignedTwoByteFixed etc @@ -785,7 +813,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent outboundDataRateOut->jointDataRate.increment(numBytes); } } - + IF_AVATAR_SPACE(PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS, 1 + 2 * jointBitVectorSize) { auto startSection = destinationBuffer; @@ -871,7 +899,7 @@ const unsigned char* unpackFauxJoint(const unsigned char* sourceBuffer, ThreadSa glm::vec3 position; Transform transform; sourceBuffer += unpackOrientationQuatFromSixBytes(sourceBuffer, orientation); - sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, position, TRANSLATION_COMPRESSION_RADIX); + sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, position, FAUX_JOINT_COMPRESSION_RADIX); transform.setTranslation(position); transform.setRotation(orientation); matrixCache.set(transform.getMatrix()); @@ -1144,6 +1172,9 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { sourceBuffer += sizeof(AvatarDataPacket::AdditionalFlags); + if (collideWithOtherAvatarsChanged) { + setCollisionWithOtherAvatarsFlags(); + } if (somethingChanged) { _additionalFlagsChanged = now; } @@ -1280,6 +1311,12 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { } } // 1 + bytesOfValidity bytes + // read maxTranslationDimension + float maxTranslationDimension; + PACKET_READ_CHECK(JointMaxTranslationDimension, sizeof(float)); + memcpy(&maxTranslationDimension, sourceBuffer, sizeof(float)); + sourceBuffer += sizeof(float); + // each joint translation component is stored in 6 bytes. const int COMPRESSED_TRANSLATION_SIZE = 6; PACKET_READ_CHECK(JointTranslation, numValidJointTranslations * COMPRESSED_TRANSLATION_SIZE); @@ -1288,6 +1325,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { JointData& data = _jointData[i]; if (validTranslations[i]) { sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX); + data.translation *= maxTranslationDimension; _hasNewJointData = true; data.translationIsDefaultPose = false; } @@ -1908,10 +1946,9 @@ qint64 AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitType traitTy // grab a read lock on the avatar entities and check for entity data for the given ID QByteArray entityBinaryData; - _avatarEntitiesLock.withReadLock([this, &entityBinaryData, &traitInstanceID] { - if (_avatarEntityData.contains(traitInstanceID)) { - entityBinaryData = _avatarEntityData[traitInstanceID]; + if (_packedAvatarEntityData.contains(traitInstanceID)) { + entityBinaryData = _packedAvatarEntityData[traitInstanceID]; } }); @@ -1987,9 +2024,9 @@ qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTr qint64 bytesWritten = 0; if (traitType == AvatarTraits::AvatarEntity) { - packAvatarEntityTraitInstance(traitType, traitInstanceID, destination, traitVersion); + bytesWritten += packAvatarEntityTraitInstance(traitType, traitInstanceID, destination, traitVersion); } else if (traitType == AvatarTraits::Grab) { - packGrabTraitInstance(traitType, traitInstanceID, destination, traitVersion); + bytesWritten += packGrabTraitInstance(traitType, traitInstanceID, destination, traitVersion); } return bytesWritten; @@ -1998,7 +2035,7 @@ qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTr void AvatarData::prepareResetTraitInstances() { if (_clientTraitsHandler) { _avatarEntitiesLock.withReadLock([this]{ - foreach (auto entityID, _avatarEntityData.keys()) { + foreach (auto entityID, _packedAvatarEntityData.keys()) { _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); } foreach (auto grabID, _avatarGrabData.keys()) { @@ -2019,7 +2056,7 @@ void AvatarData::processTrait(AvatarTraits::TraitType traitType, QByteArray trai void AvatarData::processTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData) { if (traitType == AvatarTraits::AvatarEntity) { - updateAvatarEntity(instanceID, traitBinaryData); + storeAvatarEntityDataPayload(instanceID, traitBinaryData); } else if (traitType == AvatarTraits::Grab) { updateAvatarGrabData(instanceID, traitBinaryData); } @@ -2158,7 +2195,7 @@ void AvatarData::detachAll(const QString& modelURL, const QString& jointName) { setAttachmentData(attachmentData); } -void AvatarData::sendAvatarDataPacket(bool sendAll) { +int AvatarData::sendAvatarDataPacket(bool sendAll) { auto nodeList = DependencyManager::get(); // about 2% of the time, we send a full update (meaning, we transmit all the joint data), even if nothing has changed. @@ -2171,16 +2208,14 @@ void AvatarData::sendAvatarDataPacket(bool sendAll) { int maximumByteArraySize = NLPacket::maxPayloadSize(PacketType::AvatarData) - sizeof(AvatarDataSequenceNumber); if (avatarByteArray.size() > maximumByteArraySize) { - qCWarning(avatars) << "toByteArrayStateful() resulted in very large buffer:" << avatarByteArray.size() << "... attempt to drop facial data"; avatarByteArray = toByteArrayStateful(dataDetail, true); if (avatarByteArray.size() > maximumByteArraySize) { - qCWarning(avatars) << "toByteArrayStateful() without facial data resulted in very large buffer:" << avatarByteArray.size() << "... reduce to MinimumData"; avatarByteArray = toByteArrayStateful(MinimumData, true); if (avatarByteArray.size() > maximumByteArraySize) { qCWarning(avatars) << "toByteArrayStateful() MinimumData resulted in very large buffer:" << avatarByteArray.size() << "... FAIL!!"; - return; + return 0; } } } @@ -2192,18 +2227,20 @@ void AvatarData::sendAvatarDataPacket(bool sendAll) { auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size() + sizeof(sequenceNumber)); avatarPacket->writePrimitive(sequenceNumber++); avatarPacket->write(avatarByteArray); + auto packetSize = avatarPacket->getWireSize(); nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); + + return packetSize; } -void AvatarData::sendIdentityPacket() { +int AvatarData::sendIdentityPacket() { auto nodeList = DependencyManager::get(); if (_identityDataChanged) { // if the identity data has changed, push the sequence number forwards ++_identitySequenceNumber; } - QByteArray identityData = identityByteArray(); auto packetList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); @@ -2217,6 +2254,7 @@ void AvatarData::sendIdentityPacket() { }); _identityDataChanged = false; + return identityData.size(); } static const QString JSON_ATTACHMENT_URL = QStringLiteral("modelUrl"); @@ -2367,7 +2405,7 @@ void AvatarData::setRecordingBasis(std::shared_ptr recordingBasis) { void AvatarData::createRecordingIDs() { _avatarEntitiesLock.withReadLock([&] { _avatarEntityForRecording.clear(); - for (int i = 0; i < _avatarEntityData.size(); i++) { + for (int i = 0; i < _packedAvatarEntityData.size(); i++) { _avatarEntityForRecording.insert(QUuid::createUuid()); } }); @@ -2393,7 +2431,8 @@ static const QString JSON_AVATAR_VERSION = QStringLiteral("version"); enum class JsonAvatarFrameVersion : int { JointRotationsInRelativeFrame = 0, JointRotationsInAbsoluteFrame, - JointDefaultPoseBits + JointDefaultPoseBits, + JointUnscaledTranslations, }; QJsonValue toJsonValue(const JointData& joint) { @@ -2410,7 +2449,16 @@ JointData jointDataFromJsonValue(int version, const QJsonValue& json) { if (json.isArray()) { QJsonArray array = json.toArray(); result.rotation = quatFromJsonValue(array[0]); + result.translation = vec3FromJsonValue(array[1]); + + // In old recordings, translations are scaled by _geometryOffset. Undo that scaling. + if (version < (int)JsonAvatarFrameVersion::JointUnscaledTranslations) { + // because we don't have access to the actual _geometryOffset used. we have to guess. + // most avatar FBX files were authored in centimeters. + const float METERS_TO_CENTIMETERS = 100.0f; + result.translation *= METERS_TO_CENTIMETERS; + } if (version >= (int)JsonAvatarFrameVersion::JointDefaultPoseBits) { result.rotationIsDefaultPose = array[2].toBool(); result.translationIsDefaultPose = array[3].toBool(); @@ -2422,10 +2470,14 @@ JointData jointDataFromJsonValue(int version, const QJsonValue& json) { return result; } +void AvatarData::avatarEntityDataToJson(QJsonObject& root) const { + // overridden where needed +} + QJsonObject AvatarData::toJson() const { QJsonObject root; - root[JSON_AVATAR_VERSION] = (int)JsonAvatarFrameVersion::JointDefaultPoseBits; + root[JSON_AVATAR_VERSION] = (int)JsonAvatarFrameVersion::JointUnscaledTranslations; if (!getSkeletonModelURL().isEmpty()) { root[JSON_AVATAR_BODY_MODEL] = getSkeletonModelURL().toString(); @@ -2433,20 +2485,8 @@ QJsonObject AvatarData::toJson() const { if (!getDisplayName().isEmpty()) { root[JSON_AVATAR_DISPLAY_NAME] = getDisplayName(); } - _avatarEntitiesLock.withReadLock([&] { - if (!_avatarEntityData.empty()) { - QJsonArray avatarEntityJson; - int entityCount = 0; - for (auto entityID : _avatarEntityData.keys()) { - QVariantMap entityData; - QUuid newId = _avatarEntityForRecording.size() == _avatarEntityData.size() ? _avatarEntityForRecording.values()[entityCount++] : entityID; - entityData.insert("id", newId); - entityData.insert("properties", _avatarEntityData.value(entityID).toBase64()); - avatarEntityJson.push_back(QVariant(entityData).toJsonObject()); - } - root[JSON_AVATAR_ENTITIES] = avatarEntityJson; - } - }); + + avatarEntityDataToJson(root); auto recordingBasis = getRecordingBasis(); bool success; @@ -2568,9 +2608,9 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { for (auto attachmentJson : attachmentsJson) { if (attachmentJson.isObject()) { QVariantMap entityData = attachmentJson.toObject().toVariantMap(); - QUuid entityID = entityData.value("id").toUuid(); - QByteArray properties = QByteArray::fromBase64(entityData.value("properties").toByteArray()); - updateAvatarEntity(entityID, properties); + QUuid id = entityData.value("id").toUuid(); + QByteArray data = QByteArray::fromBase64(entityData.value("properties").toByteArray()); + updateAvatarEntity(id, data); } } } @@ -2752,38 +2792,46 @@ void AvatarData::setAttachmentsVariant(const QVariantList& variant) { setAttachmentData(newAttachments); } -const int MAX_NUM_AVATAR_ENTITIES = 42; - -void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) { +void AvatarData::storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& data) { + bool changed = false; _avatarEntitiesLock.withWriteLock([&] { - AvatarEntityMap::iterator itr = _avatarEntityData.find(entityID); - if (itr == _avatarEntityData.end()) { - if (_avatarEntityData.size() < MAX_NUM_AVATAR_ENTITIES) { - _avatarEntityData.insert(entityID, entityData); + auto itr = _packedAvatarEntityData.find(entityID); + if (itr == _packedAvatarEntityData.end()) { + if (_packedAvatarEntityData.size() < MAX_NUM_AVATAR_ENTITIES) { + _packedAvatarEntityData.insert(entityID, data); + changed = true; } } else { - itr.value() = entityData; + itr.value() = data; + changed = true; } }); - _avatarEntityDataChanged = true; + if (changed) { + _avatarEntityDataChanged = true; - if (_clientTraitsHandler) { - // we have a client traits handler, so we need to mark this instanced trait as changed - // so that changes will be sent next frame - _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); + if (_clientTraitsHandler) { + // we have a client traits handler, so we need to mark this instanced trait as changed + // so that changes will be sent next frame + _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); + } } } +void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) { + // overridden where needed + // expects 'entityData' to be a JavaScript EntityItemProperties Object in QByteArray form +} + void AvatarData::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree) { bool removedEntity = false; _avatarEntitiesLock.withWriteLock([this, &removedEntity, &entityID] { - removedEntity = _avatarEntityData.remove(entityID); + removedEntity = _packedAvatarEntityData.remove(entityID); }); - insertDetachedEntityID(entityID); + insertRemovedEntityID(entityID); if (removedEntity && _clientTraitsHandler) { // we have a client traits handler, so we need to mark this removed instance trait as deleted @@ -2793,75 +2841,29 @@ void AvatarData::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFr } AvatarEntityMap AvatarData::getAvatarEntityData() const { - AvatarEntityMap result; - _avatarEntitiesLock.withReadLock([&] { - result = _avatarEntityData; - }); - return result; -} - -void AvatarData::insertDetachedEntityID(const QUuid entityID) { - _avatarEntitiesLock.withWriteLock([&] { - _avatarEntityDetached.insert(entityID); - }); - - _avatarEntityDataChanged = true; + // overridden where needed + // NOTE: the return value is expected to be a map of unfortunately-formatted-binary-blobs + return AvatarEntityMap(); } void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) { - if (avatarEntityData.size() > MAX_NUM_AVATAR_ENTITIES) { - // the data is suspect - qCDebug(avatars) << "discard suspect AvatarEntityData with size =" << avatarEntityData.size(); - return; - } - - std::vector deletedEntityIDs; - QList updatedEntityIDs; - - _avatarEntitiesLock.withWriteLock([&] { - if (_avatarEntityData != avatarEntityData) { - - // keep track of entities that were attached to this avatar but no longer are - AvatarEntityIDs previousAvatarEntityIDs = QSet::fromList(_avatarEntityData.keys()); - - _avatarEntityData = avatarEntityData; - setAvatarEntityDataChanged(true); - - deletedEntityIDs.reserve(previousAvatarEntityIDs.size()); - - foreach (auto entityID, previousAvatarEntityIDs) { - if (!_avatarEntityData.contains(entityID)) { - _avatarEntityDetached.insert(entityID); - deletedEntityIDs.push_back(entityID); - } - } - - updatedEntityIDs = _avatarEntityData.keys(); - } - }); - - if (_clientTraitsHandler) { - // we have a client traits handler - - // flag removed entities as deleted so that changes are sent next frame - for (auto& deletedEntityID : deletedEntityIDs) { - _clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, deletedEntityID); - } - - // flag any updated or created entities so that we send changes for them next frame - for (auto& entityID : updatedEntityIDs) { - _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); - } - } - - + // overridden where needed + // avatarEntityData is expected to be a map of QByteArrays + // each QByteArray represents an EntityItemProperties object from JavaScript } -AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() { +void AvatarData::insertRemovedEntityID(const QUuid entityID) { + _avatarEntitiesLock.withWriteLock([&] { + _avatarEntityRemoved.insert(entityID); + }); + _avatarEntityDataChanged = true; +} + +AvatarEntityIDs AvatarData::getAndClearRecentlyRemovedIDs() { AvatarEntityIDs result; _avatarEntitiesLock.withWriteLock([&] { - result = _avatarEntityDetached; - _avatarEntityDetached.clear(); + result = _avatarEntityRemoved; + _avatarEntityRemoved.clear(); }); return result; } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h old mode 100644 new mode 100755 index b42c387f61..9128c2dbf9 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -63,6 +63,7 @@ using AvatarWeakPointer = std::weak_ptr; using AvatarHash = QHash; using AvatarEntityMap = QMap; +using PackedAvatarEntityMap = QMap; // similar to AvatarEntityMap, but different internal format using AvatarEntityIDs = QSet; using AvatarGrabDataMap = QMap; @@ -71,6 +72,8 @@ using AvatarGrabMap = QMap; using AvatarDataSequenceNumber = uint16_t; +const int MAX_NUM_AVATAR_ENTITIES = 42; + // avatar motion behaviors const quint32 AVATAR_MOTION_ACTION_MOTOR_ENABLED = 1U << 0; const quint32 AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED = 1U << 1; @@ -274,8 +277,8 @@ namespace AvatarDataPacket { uint8_t rotationValidityBits[ceil(numJoints / 8)]; // one bit per joint, if true then a compressed rotation follows. SixByteQuat rotation[numValidRotations]; // encodeded and compressed by packOrientationQuatToSixBytes() uint8_t translationValidityBits[ceil(numJoints / 8)]; // one bit per joint, if true then a compressed translation follows. - SixByteTrans translation[numValidTranslations]; // encodeded and compressed by packFloatVec3ToSignedTwoByteFixed() - + float maxTranslationDimension; // used to normalize fixed point translation values. + SixByteTrans translation[numValidTranslations]; // normalized and compressed by packFloatVec3ToSignedTwoByteFixed() SixByteQuat leftHandControllerRotation; SixByteTrans leftHandControllerTranslation; SixByteQuat rightHandControllerRotation; @@ -283,6 +286,7 @@ namespace AvatarDataPacket { }; */ size_t maxJointDataSize(size_t numJoints, bool hasGrabJoints); + size_t minJointDataSize(size_t numJoints); /* struct JointDefaultPoseFlags { @@ -462,8 +466,6 @@ public: static const QUrl& defaultFullAvatarModelUrl(); - virtual bool isMyAvatar() const { return false; } - const QUuid getSessionUUID() const { return getID(); } glm::vec3 getHandPosition() const; @@ -494,6 +496,8 @@ public: /// \return number of bytes parsed virtual int parseDataFromBuffer(const QByteArray& buffer); + virtual void setCollisionWithOtherAvatarsFlags() {}; + // Body Rotation (degrees) float getBodyYaw() const; void setBodyYaw(float bodyYaw); @@ -952,19 +956,20 @@ public: // FIXME: Can this name be improved? Can it be deprecated? Q_INVOKABLE virtual void setAttachmentsVariant(const QVariantList& variant); + virtual void storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& payload); /**jsdoc * @function MyAvatar.updateAvatarEntity * @param {Uuid} entityID * @param {string} entityData */ - Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData); + Q_INVOKABLE virtual void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData); /**jsdoc * @function MyAvatar.clearAvatarEntity * @param {Uuid} entityID */ - Q_INVOKABLE void clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree = true); + Q_INVOKABLE virtual void clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree = true); /**jsdoc @@ -1125,6 +1130,7 @@ public: TransformPointer getRecordingBasis() const; void setRecordingBasis(TransformPointer recordingBasis = TransformPointer()); void createRecordingIDs(); + virtual void avatarEntityDataToJson(QJsonObject& root) const; QJsonObject toJson() const; void fromJson(const QJsonObject& json, bool useFrameSkeleton = true); @@ -1136,17 +1142,16 @@ public: * @function MyAvatar.getAvatarEntityData * @returns {object} */ - Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const; + Q_INVOKABLE virtual AvatarEntityMap getAvatarEntityData() const; /**jsdoc * @function MyAvatar.setAvatarEntityData * @param {object} avatarEntityData */ - Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData); + Q_INVOKABLE virtual void setAvatarEntityData(const AvatarEntityMap& avatarEntityData); virtual void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; } - void insertDetachedEntityID(const QUuid entityID); - AvatarEntityIDs getAndClearRecentlyDetachedIDs(); + AvatarEntityIDs getAndClearRecentlyRemovedIDs(); /**jsdoc * @function MyAvatar.getSensorToWorldMatrix @@ -1269,12 +1274,12 @@ public slots: * @function MyAvatar.sendAvatarDataPacket * @param {boolean} [sendAll=false] */ - void sendAvatarDataPacket(bool sendAll = false); + virtual int sendAvatarDataPacket(bool sendAll = false); /**jsdoc * @function MyAvatar.sendIdentityPacket */ - void sendIdentityPacket(); + int sendIdentityPacket(); /**jsdoc * @function MyAvatar.setSessionUUID @@ -1333,6 +1338,7 @@ public slots: void resetLastSent() { _lastToByteArray = 0; } protected: + void insertRemovedEntityID(const QUuid entityID); void lazyInitHeadData() const; float getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition) const; @@ -1461,9 +1467,9 @@ protected: AABox _defaultBubbleBox; mutable ReadWriteLockable _avatarEntitiesLock; - AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar + AvatarEntityIDs _avatarEntityRemoved; // recently removed AvatarEntity ids AvatarEntityIDs _avatarEntityForRecording; // create new entities id for avatar recording - AvatarEntityMap _avatarEntityData; + PackedAvatarEntityMap _packedAvatarEntityData; bool _avatarEntityDataChanged { false }; mutable ReadWriteLockable _avatarGrabsLock; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 41ca950b3b..5f30d98ed6 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -195,21 +195,22 @@ int AvatarHashMap::numberOfAvatarsInRange(const glm::vec3& position, float range return count; } -AvatarSharedPointer AvatarHashMap::newSharedAvatar() { - return std::make_shared(); +AvatarSharedPointer AvatarHashMap::newSharedAvatar(const QUuid& sessionUUID) { + auto avatarData = std::make_shared(); + avatarData->setSessionUUID(sessionUUID); + return avatarData; } AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { qCDebug(avatars) << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarHashMap."; - auto avatar = newSharedAvatar(); + auto avatar = newSharedAvatar(sessionUUID); avatar->setSessionUUID(sessionUUID); avatar->setOwningAvatarMixer(mixerWeakPointer); // addAvatar is only called from newOrExistingAvatar, which already locks _hashLock _avatarHash.insert(sessionUUID, avatar); emit avatarAddedEvent(sessionUUID); - return avatar; } @@ -328,6 +329,19 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer } void AvatarHashMap::processBulkAvatarTraits(QSharedPointer message, SharedNodePointer sendingNode) { + AvatarTraits::TraitMessageSequence seq; + + message->readPrimitive(&seq); + + auto traitsAckPacket = NLPacket::create(PacketType::BulkAvatarTraitsAck, sizeof(AvatarTraits::TraitMessageSequence), true); + traitsAckPacket->writePrimitive(seq); + auto nodeList = DependencyManager::get(); + SharedNodePointer avatarMixer = nodeList->soloNodeOfType(NodeType::AvatarMixer); + if (!avatarMixer.isNull()) { + // we have a mixer to send to, acknowledge that we received these + // traits. + nodeList->sendPacket(std::move(traitsAckPacket), *avatarMixer); + } while (message->getBytesLeftToRead()) { // read the avatar ID to figure out which avatar this is for diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 3bb38dd081..8395651d6b 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -32,6 +32,9 @@ #include "AvatarData.h" #include "AssociatedTraitValues.h" +const int CLIENT_TO_AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 50; +const quint64 MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS = USECS_PER_SECOND / CLIENT_TO_AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND; + /**jsdoc * Note: An AvatarList API is also provided for Interface and client entity scripts: it is a * synonym for the {@link AvatarManager} API. @@ -179,7 +182,7 @@ protected: AvatarHashMap(); virtual AvatarSharedPointer parseAvatarData(QSharedPointer message, SharedNodePointer sendingNode); - virtual AvatarSharedPointer newSharedAvatar(); + virtual AvatarSharedPointer newSharedAvatar(const QUuid& sessionUUID); virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer); AvatarSharedPointer newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer, bool& isNew); diff --git a/libraries/avatars/src/AvatarTraits.h b/libraries/avatars/src/AvatarTraits.h index 0f4ad0b90f..4516572e42 100644 --- a/libraries/avatars/src/AvatarTraits.h +++ b/libraries/avatars/src/AvatarTraits.h @@ -42,6 +42,10 @@ namespace AvatarTraits { const TraitWireSize DELETED_TRAIT_SIZE = -1; const TraitWireSize MAXIMUM_TRAIT_SIZE = INT16_MAX; + using TraitMessageSequence = int64_t; + const TraitMessageSequence FIRST_TRAIT_SEQUENCE = 0; + const TraitMessageSequence MAX_TRAIT_SEQUENCE = INT64_MAX; + inline qint64 packInstancedTraitDelete(TraitType traitType, TraitInstanceID instanceID, ExtendedIODevice& destination, TraitVersion traitVersion = NULL_TRAIT_VERSION) { qint64 bytesWritten = 0; diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp index 3e24c1f9ad..bcbe5308c7 100644 --- a/libraries/avatars/src/ClientTraitsHandler.cpp +++ b/libraries/avatars/src/ClientTraitsHandler.cpp @@ -65,8 +65,9 @@ void ClientTraitsHandler::resetForNewMixer() { _owningAvatar->prepareResetTraitInstances(); } -void ClientTraitsHandler::sendChangedTraitsToMixer() { +int ClientTraitsHandler::sendChangedTraitsToMixer() { std::unique_lock lock(_traitLock); + int bytesWritten = 0; if (hasChangedTraits() || _shouldPerformInitialSend) { // we have at least one changed trait to send @@ -75,7 +76,7 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { auto avatarMixer = nodeList->soloNodeOfType(NodeType::AvatarMixer); if (!avatarMixer || !avatarMixer->getActiveSocket()) { // we don't have an avatar mixer with an active socket, we can't send changed traits at this time - return; + return 0; } // we have a mixer to send to, setup our set traits packet @@ -106,7 +107,7 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { if (initialSend || *simpleIt == Updated) { if (traitType == AvatarTraits::SkeletonModelURL) { - _owningAvatar->packTrait(traitType, *traitsPacketList); + bytesWritten += _owningAvatar->packTrait(traitType, *traitsPacketList); // keep track of our skeleton version in case we get an override back _currentSkeletonVersion = _currentTraitVersion; @@ -123,10 +124,10 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { || instanceIDValuePair.value == Updated) { // this is a changed trait we need to send or we haven't send out trait information yet // ask the owning avatar to pack it - _owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList); + bytesWritten += _owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList); } else if (!initialSend && instanceIDValuePair.value == Deleted) { // pack delete for this trait instance - AvatarTraits::packInstancedTraitDelete(instancedIt->traitType, instanceIDValuePair.id, + bytesWritten += AvatarTraits::packInstancedTraitDelete(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList); } } @@ -136,6 +137,8 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { nodeList->sendPacketList(std::move(traitsPacketList), *avatarMixer); } + + return bytesWritten; } void ClientTraitsHandler::processTraitOverride(QSharedPointer message, SharedNodePointer sendingNode) { diff --git a/libraries/avatars/src/ClientTraitsHandler.h b/libraries/avatars/src/ClientTraitsHandler.h index 3900268101..35499fd2cf 100644 --- a/libraries/avatars/src/ClientTraitsHandler.h +++ b/libraries/avatars/src/ClientTraitsHandler.h @@ -24,7 +24,7 @@ class ClientTraitsHandler : public QObject { public: ClientTraitsHandler(AvatarData* owningAvatar); - void sendChangedTraitsToMixer(); + int sendChangedTraitsToMixer(); bool hasChangedTraits() const { return _hasChangedTraits; } diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index 6923ef4b98..5a396231b6 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -52,11 +52,17 @@ namespace controller { * TranslateZnumbernumberMove the user's avatar in the direction of its * z-axis, if the camera isn't in independent or mirror modes. * PitchnumbernumberRotate the user's avatar head and attached camera - * about its negative x-axis (i.e., positive values pitch down), if the camera isn't in HMD, independent, or mirror - * modes. - * YawnumbernumberRotate the user's avatar about its y-axis, if the - * camera isn't in independent or mirror modes. + * about its negative x-axis (i.e., positive values pitch down) at a rate proportional to the control value, if the + * camera isn't in HMD, independent, or mirror modes. + * YawnumbernumberRotate the user's avatar about its y-axis at a rate + * proportional to the control value, if the camera isn't in independent or mirror modes. * RollnumbernumberNo action. + * DeltaPitchnumbernumberRotate the user's avatar head and attached + * camera about its negative x-axis (i.e., positive values pitch down) by an amount proportional to the control value, + * if the camera isn't in HMD, independent, or mirror modes. + * DeltaYawnumbernumberRotate the user's avatar about its y-axis by an + * amount proportional to the control value, if the camera isn't in independent or mirror modes. + * DeltaRollnumbernumberNo action. * StepTranslateXnumbernumberNo action. * StepTranslateYnumbernumberNo action. * StepTranslateZnumbernumberNo action. @@ -318,6 +324,9 @@ namespace controller { makeAxisPair(Action::ROLL, "Roll"), makeAxisPair(Action::PITCH, "Pitch"), makeAxisPair(Action::YAW, "Yaw"), + makeAxisPair(Action::DELTA_YAW, "DeltaYaw"), + makeAxisPair(Action::DELTA_PITCH, "DeltaPitch"), + makeAxisPair(Action::DELTA_ROLL, "DeltaRoll"), makeAxisPair(Action::STEP_YAW, "StepYaw"), makeAxisPair(Action::STEP_PITCH, "StepPitch"), makeAxisPair(Action::STEP_ROLL, "StepRoll"), diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index 0c77d63863..a12a3d60a9 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -27,6 +27,10 @@ enum class Action { ROTATE_Y, YAW = ROTATE_Y, ROTATE_Z, ROLL = ROTATE_Z, + DELTA_PITCH, + DELTA_YAW, + DELTA_ROLL, + STEP_YAW, // FIXME does this have a use case? STEP_PITCH, diff --git a/libraries/controllers/src/controllers/AxisValue.cpp b/libraries/controllers/src/controllers/AxisValue.cpp new file mode 100644 index 0000000000..4b7913754c --- /dev/null +++ b/libraries/controllers/src/controllers/AxisValue.cpp @@ -0,0 +1,21 @@ +// +// AxisValue.cpp +// +// Created by David Rowe on 14 Dec 2018. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "AxisValue.h" + +namespace controller { + + AxisValue::AxisValue(const float value, const quint64 timestamp) : + value(value), timestamp(timestamp) { } + + bool AxisValue::operator==(const AxisValue& right) const { + return value == right.value && timestamp == right.timestamp; + } +} diff --git a/libraries/controllers/src/controllers/AxisValue.h b/libraries/controllers/src/controllers/AxisValue.h new file mode 100644 index 0000000000..e4bc20f7d2 --- /dev/null +++ b/libraries/controllers/src/controllers/AxisValue.h @@ -0,0 +1,34 @@ +// +// AxisValue.h +// +// Created by David Rowe on 13 Dec 2018. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once +#ifndef hifi_controllers_AxisValue_h +#define hifi_controllers_AxisValue_h + +#include + +namespace controller { + + struct AxisValue { + public: + float value { 0.0f }; + // The value can be timestamped to determine if consecutive identical values should be output (e.g., mouse movement). + quint64 timestamp { 0 }; + + AxisValue() {} + AxisValue(const float value, const quint64 timestamp); + + bool operator ==(const AxisValue& right) const; + bool operator !=(const AxisValue& right) const { return !(*this == right); } + }; + +} + +#endif // hifi_controllers_AxisValue_h diff --git a/libraries/controllers/src/controllers/InputDevice.cpp b/libraries/controllers/src/controllers/InputDevice.cpp index a907842a17..dd430263fa 100644 --- a/libraries/controllers/src/controllers/InputDevice.cpp +++ b/libraries/controllers/src/controllers/InputDevice.cpp @@ -26,12 +26,12 @@ namespace controller { return 0.0f; } - float InputDevice::getAxis(int channel) const { + AxisValue InputDevice::getAxis(int channel) const { auto axis = _axisStateMap.find(channel); if (axis != _axisStateMap.end()) { return (*axis).second; } else { - return 0.0f; + return AxisValue(); } } @@ -68,26 +68,25 @@ namespace controller { return Input::NamedPair(makeInput(pose), name); } - float InputDevice::getValue(ChannelType channelType, uint16_t channel) const { + AxisValue InputDevice::getValue(ChannelType channelType, uint16_t channel) const { switch (channelType) { case ChannelType::AXIS: return getAxis(channel); case ChannelType::BUTTON: - return getButton(channel); + return { getButton(channel), 0 }; case ChannelType::POSE: - return getPose(channel).valid ? 1.0f : 0.0f; + return { getPose(channel).valid ? 1.0f : 0.0f, 0 }; default: break; } - return 0.0f; + return { 0.0f, 0 }; } - - float InputDevice::getValue(const Input& input) const { + AxisValue InputDevice::getValue(const Input& input) const { return getValue(input.getType(), input.channel); } diff --git a/libraries/controllers/src/controllers/InputDevice.h b/libraries/controllers/src/controllers/InputDevice.h index 7479ef7b75..7c3c31cb38 100644 --- a/libraries/controllers/src/controllers/InputDevice.h +++ b/libraries/controllers/src/controllers/InputDevice.h @@ -16,6 +16,7 @@ #include +#include "AxisValue.h" #include "Pose.h" #include "Input.h" #include "StandardControls.h" @@ -103,16 +104,16 @@ public: using Pointer = std::shared_ptr; typedef std::unordered_set ButtonPressedMap; - typedef std::map AxisStateMap; + typedef std::map AxisStateMap; typedef std::map PoseStateMap; // Get current state for each channel float getButton(int channel) const; - float getAxis(int channel) const; + AxisValue getAxis(int channel) const; Pose getPose(int channel) const; - float getValue(const Input& input) const; - float getValue(ChannelType channelType, uint16_t channel) const; + AxisValue getValue(const Input& input) const; + AxisValue getValue(ChannelType channelType, uint16_t channel) const; Pose getPoseValue(uint16_t channel) const; const QString& getName() const { return _name; } diff --git a/libraries/controllers/src/controllers/InputRecorder.cpp b/libraries/controllers/src/controllers/InputRecorder.cpp index 54d1aaf131..928dbf0521 100644 --- a/libraries/controllers/src/controllers/InputRecorder.cpp +++ b/libraries/controllers/src/controllers/InputRecorder.cpp @@ -297,6 +297,13 @@ namespace controller { return 0.0f; } + InputRecorder::ActionStates InputRecorder::getActionstates() { + if (_actionStateList.size() > 0) { + return _actionStateList[_playCount]; + } + return {}; + } + controller::Pose InputRecorder::getPoseState(const QString& action) { if (_poseStateList.size() > 0) { return _poseStateList[_playCount][action]; diff --git a/libraries/controllers/src/controllers/InputRecorder.h b/libraries/controllers/src/controllers/InputRecorder.h index 9adb8e386f..a0fcb2e87e 100644 --- a/libraries/controllers/src/controllers/InputRecorder.h +++ b/libraries/controllers/src/controllers/InputRecorder.h @@ -45,6 +45,7 @@ namespace controller { void setActionState(const QString& action, float value); void setActionState(const QString& action, const controller::Pose& pose); float getActionState(const QString& action); + ActionStates getActionstates(); controller::Pose getPoseState(const QString& action); QString getSaveDirectory(); void frameTick(); diff --git a/libraries/controllers/src/controllers/ScriptingInterface.cpp b/libraries/controllers/src/controllers/ScriptingInterface.cpp index ce730bffa8..07c59e1aaa 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.cpp +++ b/libraries/controllers/src/controllers/ScriptingInterface.cpp @@ -89,17 +89,17 @@ namespace controller { float ScriptingInterface::getValue(const int& source) const { auto userInputMapper = DependencyManager::get(); - return userInputMapper->getValue(Input((uint32_t)source)); + return userInputMapper->getValue(Input((uint32_t)source)).value; } float ScriptingInterface::getAxisValue(int source) const { auto userInputMapper = DependencyManager::get(); - return userInputMapper->getValue(Input((uint32_t)source)); + return userInputMapper->getValue(Input((uint32_t)source)).value; } Pose ScriptingInterface::getPoseValue(const int& source) const { auto userInputMapper = DependencyManager::get(); - return userInputMapper->getPose(Input((uint32_t)source)); + return userInputMapper->getPose(Input((uint32_t)source)); } QVector ScriptingInterface::getAllActions() { diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 307064c073..33dc37312e 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -290,17 +290,17 @@ void UserInputMapper::update(float deltaTime) { if ((int)_lastStandardStates.size() != standardInputs.size()) { _lastStandardStates.resize(standardInputs.size()); for (auto& lastValue : _lastStandardStates) { - lastValue = 0; + lastValue = AxisValue(); } } for (int i = 0; i < standardInputs.size(); ++i) { const auto& input = standardInputs[i].first; - float value = getValue(input); - float& oldValue = _lastStandardStates[i]; + AxisValue value = getValue(input); + AxisValue& oldValue = _lastStandardStates[i]; if (value != oldValue) { oldValue = value; - emit inputEvent(input.id, value); + emit inputEvent(input.id, value.value); } } inputRecorder->frameTick(); @@ -489,6 +489,21 @@ void UserInputMapper::runMappings() { } applyRoutes(_standardRoutes); + InputRecorder* inputRecorder = InputRecorder::getInstance(); + if (inputRecorder->isPlayingback()) { + if (debugRoutes) { + qCDebug(controllers) << "Playing back recording actions"; + } + + // Play back each numeric action even if there is no current route active for the action. + auto actionStates = inputRecorder->getActionstates(); + for (InputRecorder::ActionStates::iterator it = actionStates.begin(); it != actionStates.end(); ++it) { + setActionState((Action)findAction(it->first), it->second); + } + + // Poses are played back in StandardEndpoint. + } + if (debugRoutes) { qCDebug(controllers) << "Done with mappings"; } @@ -604,10 +619,10 @@ bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) { destination->apply(value, source); } else { // Fetch the value, may have been overriden by previous loopback routes - float value = getValue(source, route->peek); + auto value = getValue(source, route->peek); if (debugRoutes && route->debug) { - qCDebug(controllers) << "Value was " << value; + qCDebug(controllers) << "Value was " << value.value << value.timestamp; } // Apply each of the filters. for (const auto& filter : route->filters) { @@ -615,7 +630,7 @@ bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) { } if (debugRoutes && route->debug) { - qCDebug(controllers) << "Filtered value was " << value; + qCDebug(controllers) << "Filtered value was " << value.value << value.timestamp; } destination->apply(value, source); @@ -741,15 +756,15 @@ void UserInputMapper::enableMapping(const QString& mappingName, bool enable) { } } -float UserInputMapper::getValue(const Endpoint::Pointer& endpoint, bool peek) { +AxisValue UserInputMapper::getValue(const Endpoint::Pointer& endpoint, bool peek) { return peek ? endpoint->peek() : endpoint->value(); } -float UserInputMapper::getValue(const Input& input) const { +AxisValue UserInputMapper::getValue(const Input& input) const { Locker locker(_lock); auto endpoint = endpointFor(input); if (!endpoint) { - return 0; + return AxisValue(); } return endpoint->value(); } diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index 5fd21e6299..2b3c947491 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -121,7 +121,7 @@ namespace controller { void unloadMappings(const QStringList& jsonFiles); void unloadMapping(const QString& jsonFile); - float getValue(const Input& input) const; + AxisValue getValue(const Input& input) const; Pose getPose(const Input& input) const; // perform an action when the UserInputMapper mutex is acquired. @@ -147,9 +147,9 @@ namespace controller { std::vector _actionScales = std::vector(toInt(Action::NUM_ACTIONS), 1.0f); std::vector _lastActionStates = std::vector(toInt(Action::NUM_ACTIONS), 0.0f); std::vector _poseStates = std::vector(toInt(Action::NUM_ACTIONS)); - std::vector _lastStandardStates = std::vector(); + std::vector _lastStandardStates = std::vector(); - static float getValue(const EndpointPointer& endpoint, bool peek = false); + static AxisValue getValue(const EndpointPointer& endpoint, bool peek = false); static Pose getPose(const EndpointPointer& endpoint, bool peek = false); friend class RouteBuilderProxy; diff --git a/libraries/controllers/src/controllers/impl/Endpoint.h b/libraries/controllers/src/controllers/impl/Endpoint.h index a938dd30b6..bcf71f3094 100644 --- a/libraries/controllers/src/controllers/impl/Endpoint.h +++ b/libraries/controllers/src/controllers/impl/Endpoint.h @@ -16,6 +16,7 @@ #include +#include "../AxisValue.h" #include "../Input.h" #include "../Pose.h" @@ -36,9 +37,9 @@ namespace controller { using WriteLambda = std::function; Endpoint(const Input& input) : _input(input) {} - virtual float value() { return peek(); } - virtual float peek() const = 0; - virtual void apply(float value, const Pointer& source) = 0; + virtual AxisValue value() { return peek(); } + virtual AxisValue peek() const = 0; + virtual void apply(AxisValue value, const Pointer& source) = 0; virtual Pose peekPose() const { return Pose(); }; virtual Pose pose() { return peekPose(); } virtual void apply(const Pose& value, const Pointer& source) {} @@ -59,8 +60,8 @@ namespace controller { LambdaEndpoint(ReadLambda readLambda, WriteLambda writeLambda = [](float) {}) : Endpoint(Input::INVALID_INPUT), _readLambda(readLambda), _writeLambda(writeLambda) { } - virtual float peek() const override { return _readLambda(); } - virtual void apply(float value, const Pointer& source) override { _writeLambda(value); } + virtual AxisValue peek() const override { return AxisValue(_readLambda(), 0); } + virtual void apply(AxisValue value, const Pointer& source) override { _writeLambda(value.value); } private: ReadLambda _readLambda; @@ -76,8 +77,8 @@ namespace controller { : Endpoint(Input::INVALID_INPUT), _readLambda(readLambda), _writeLambda(writeLambda) { } - virtual float peek() const override { return _readLambda(); } - virtual void apply(float value, const Pointer& source) override { _writeLambda(value); } + virtual AxisValue peek() const override { return AxisValue(_readLambda(), 0); } + virtual void apply(AxisValue value, const Pointer& source) override { _writeLambda(value.value); } private: const ReadLambda& _readLambda; @@ -91,15 +92,15 @@ namespace controller { : Endpoint(id) { } - virtual float peek() const override { return _currentValue; } - virtual void apply(float value, const Pointer& source) override { _currentValue = value; } + virtual AxisValue peek() const override { return _currentValue; } + virtual void apply(AxisValue value, const Pointer& source) override { _currentValue = value; } virtual Pose peekPose() const override { return _currentPose; } virtual void apply(const Pose& value, const Pointer& source) override { _currentPose = value; } protected: - float _currentValue { 0.0f }; + AxisValue _currentValue { 0.0f, 0 }; Pose _currentPose {}; }; diff --git a/libraries/controllers/src/controllers/impl/Filter.h b/libraries/controllers/src/controllers/impl/Filter.h index 7afabb4bcb..5c88a5be58 100644 --- a/libraries/controllers/src/controllers/impl/Filter.h +++ b/libraries/controllers/src/controllers/impl/Filter.h @@ -21,6 +21,7 @@ #include +#include "../AxisValue.h" #include "../Pose.h" class QJsonValue; @@ -37,7 +38,7 @@ namespace controller { virtual ~Filter() = default; - virtual float apply(float value) const = 0; + virtual AxisValue apply(AxisValue value) const = 0; virtual Pose apply(Pose value) const = 0; // Factory features diff --git a/libraries/controllers/src/controllers/impl/conditionals/EndpointConditional.h b/libraries/controllers/src/controllers/impl/conditionals/EndpointConditional.h index 0ba1347087..50a33471e7 100644 --- a/libraries/controllers/src/controllers/impl/conditionals/EndpointConditional.h +++ b/libraries/controllers/src/controllers/impl/conditionals/EndpointConditional.h @@ -18,7 +18,7 @@ namespace controller { class EndpointConditional : public Conditional { public: EndpointConditional(Endpoint::Pointer endpoint) : _endpoint(endpoint) {} - virtual bool satisfied() override { return _endpoint && _endpoint->peek() != 0.0f; } + virtual bool satisfied() override { return _endpoint && _endpoint->peek().value != 0.0f; } private: Endpoint::Pointer _endpoint; }; diff --git a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp index f73268b41f..58744c468c 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp @@ -15,22 +15,19 @@ using namespace controller; -void ActionEndpoint::apply(float newValue, const Pointer& source) { +void ActionEndpoint::apply(AxisValue newValue, const Pointer& source) { auto userInputMapper = DependencyManager::get(); InputRecorder* inputRecorder = InputRecorder::getInstance(); - QString actionName; if (inputRecorder->isPlayingback() || inputRecorder->isRecording()) { - actionName = userInputMapper->getActionName(Action(_input.getChannel())); - if (inputRecorder->isPlayingback()) { - newValue = inputRecorder->getActionState(actionName); - } + QString actionName = userInputMapper->getActionName(Action(_input.getChannel())); + inputRecorder->setActionState(actionName, newValue.value); } - _currentValue += newValue; + _currentValue.value += newValue.value; + if (_input != Input::INVALID_INPUT) { - userInputMapper->deltaActionState(Action(_input.getChannel()), newValue); + userInputMapper->deltaActionState(Action(_input.getChannel()), newValue.value); } - inputRecorder->setActionState(actionName, newValue); } void ActionEndpoint::apply(const Pose& value, const Pointer& source) { @@ -51,7 +48,7 @@ void ActionEndpoint::apply(const Pose& value, const Pointer& source) { } void ActionEndpoint::reset() { - _currentValue = 0.0f; + _currentValue = AxisValue(); _currentPose = Pose(); } diff --git a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.h index 1073dc6593..94da4663aa 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.h @@ -23,8 +23,8 @@ class ActionEndpoint : public Endpoint { public: ActionEndpoint(const Input& id = Input::INVALID_INPUT) : Endpoint(id) { } - virtual float peek() const override { return _currentValue; } - virtual void apply(float newValue, const Pointer& source) override; + virtual AxisValue peek() const override { return _currentValue; } + virtual void apply(AxisValue newValue, const Pointer& source) override; virtual Pose peekPose() const override { return _currentPose; } virtual void apply(const Pose& value, const Pointer& source) override; @@ -32,7 +32,7 @@ public: virtual void reset() override; private: - float _currentValue{ 0.0f }; + AxisValue _currentValue { 0.0f, 0 }; Pose _currentPose{}; }; diff --git a/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.cpp index 598dfb6ee8..7f0f0fec48 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.cpp @@ -27,13 +27,13 @@ AnyEndpoint::AnyEndpoint(Endpoint::List children) : Endpoint(Input::INVALID_INPU } } -// The value of an any-point is considered to be the maxiumum absolute value, +// The value of an any-point is considered to be the maximum absolute value, // this handles any's of multiple axis values as well as single values as well -float AnyEndpoint::peek() const { - float result = 0.0f; +AxisValue AnyEndpoint::peek() const { + auto result = AxisValue(); for (auto& child : _children) { auto childValue = child->peek(); - if (std::abs(childValue) > std::abs(result)) { + if (std::abs(childValue.value) > std::abs(result.value)) { result = childValue; } } @@ -41,18 +41,18 @@ float AnyEndpoint::peek() const { } // Fetching the value must trigger any necessary side effects of value() on ALL the children. -float AnyEndpoint::value() { - float result = 0.0f; +AxisValue AnyEndpoint::value() { + auto result = AxisValue(); for (auto& child : _children) { auto childValue = child->value(); - if (std::abs(childValue) > std::abs(result)) { + if (std::abs(childValue.value) > std::abs(result.value)) { result = childValue; } } return result; } -void AnyEndpoint::apply(float newValue, const Endpoint::Pointer& source) { +void AnyEndpoint::apply(AxisValue newValue, const Endpoint::Pointer& source) { qFatal("AnyEndpoint is read only"); } diff --git a/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.h index 8947eb675f..a9634f10d8 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.h @@ -19,9 +19,9 @@ class AnyEndpoint : public Endpoint { public: using Endpoint::apply; AnyEndpoint(Endpoint::List children); - virtual float peek() const override; - virtual float value() override; - virtual void apply(float newValue, const Endpoint::Pointer& source) override; + virtual AxisValue peek() const override; + virtual AxisValue value() override; + virtual void apply(AxisValue newValue, const Endpoint::Pointer& source) override; virtual bool writeable() const override; virtual bool readable() const override; diff --git a/libraries/controllers/src/controllers/impl/endpoints/ArrayEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/ArrayEndpoint.h index 34d30a2e97..f928a4f568 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ArrayEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/ArrayEndpoint.h @@ -21,9 +21,9 @@ public: using Pointer = std::shared_ptr; ArrayEndpoint() : Endpoint(Input::INVALID_INPUT) { } - virtual float peek() const override { return 0.0f; } + virtual AxisValue peek() const override { return AxisValue(); } - virtual void apply(float value, const Endpoint::Pointer& source) override { + virtual void apply(AxisValue value, const Endpoint::Pointer& source) override { for (auto& child : _children) { if (child->writeable()) { child->apply(value, source); diff --git a/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.cpp index 1bd27489f8..f54c786a33 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.cpp @@ -24,18 +24,22 @@ bool CompositeEndpoint::readable() const { return first->readable() && second->readable(); } -float CompositeEndpoint::peek() const { - float result = first->peek() * -1.0f + second->peek(); +AxisValue CompositeEndpoint::peek() const { + auto negative = first->peek(); + auto positive = second->peek(); + auto result = AxisValue(positive.value - negative.value, std::max(positive.timestamp, negative.timestamp)); return result; } // Fetching via value() must trigger any side effects of value() on the children -float CompositeEndpoint::value() { - float result = first->value() * -1.0f + second->value(); +AxisValue CompositeEndpoint::value() { + auto negative = first->value(); + auto positive = second->value(); + auto result = AxisValue(positive.value - negative.value, std::max(positive.timestamp, negative.timestamp)); return result; } -void CompositeEndpoint::apply(float newValue, const Pointer& source) { +void CompositeEndpoint::apply(AxisValue newValue, const Pointer& source) { // Composites are read only } diff --git a/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.h index 3249aa1d37..004df36273 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.h @@ -18,9 +18,9 @@ namespace controller { using Endpoint::apply; CompositeEndpoint(Endpoint::Pointer first, Endpoint::Pointer second); - virtual float peek() const override; - virtual float value() override; - virtual void apply(float newValue, const Pointer& source) override; + virtual AxisValue peek() const override; + virtual AxisValue value() override; + virtual void apply(AxisValue newValue, const Pointer& source) override; virtual bool readable() const override; }; diff --git a/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.cpp index ce58c948d1..3755d860b6 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.cpp @@ -14,19 +14,19 @@ using namespace controller; -float InputEndpoint::peek() const { +AxisValue InputEndpoint::peek() const { if (isPose()) { - return peekPose().valid ? 1.0f : 0.0f; + return peekPose().valid ? AxisValue(1.0f, 0) : AxisValue(0.0f, 0); } auto userInputMapper = DependencyManager::get(); auto deviceProxy = userInputMapper->getDevice(_input); if (!deviceProxy) { - return 0.0f; + return AxisValue(); } return deviceProxy->getValue(_input); } -float InputEndpoint::value(){ +AxisValue InputEndpoint::value() { _read = true; return peek(); } diff --git a/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.h index 7e4560dcf9..9581228cef 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.h @@ -20,10 +20,11 @@ public: : Endpoint(id) { } - virtual float peek() const override; - virtual float value() override; + virtual AxisValue peek() const override; + virtual AxisValue value() override; // FIXME need support for writing back to vibration / force feedback effects - virtual void apply(float newValue, const Pointer& source) override {} + virtual void apply(AxisValue newValue, const Pointer& source) override {} + virtual Pose peekPose() const override; virtual Pose pose() override; virtual void apply(const Pose& value, const Pointer& source) override { } diff --git a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp index 2f20cd82c6..c01b67f1bc 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp @@ -30,18 +30,18 @@ QString formatException(const QJSValue& exception) { return result; } -float JSEndpoint::peek() const { +AxisValue JSEndpoint::peek() const { QJSValue result = _callable.call(); if (result.isError()) { qCDebug(controllers).noquote() << formatException(result); - return 0.0f; + return AxisValue(); } else { - return (float)result.toNumber(); + return AxisValue((float)result.toNumber(), 0); } } -void JSEndpoint::apply(float newValue, const Pointer& source) { - QJSValue result = _callable.call(QJSValueList({ QJSValue(newValue) })); +void JSEndpoint::apply(AxisValue newValue, const Pointer& source) { + QJSValue result = _callable.call(QJSValueList({ QJSValue(newValue.value) })); if (result.isError()) { qCDebug(controllers).noquote() << formatException(result); } diff --git a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.h index 4d179da8e6..6c953399dd 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.h @@ -24,8 +24,8 @@ public: : Endpoint(Input::INVALID_INPUT), _callable(callable) { } - virtual float peek() const override; - virtual void apply(float newValue, const Pointer& source) override; + virtual AxisValue peek() const override; + virtual void apply(AxisValue newValue, const Pointer& source) override; private: mutable QJSValue _callable; diff --git a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp index e2c48d776f..9f971d2f04 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp @@ -34,9 +34,9 @@ QString formatException(const QScriptValue& exception) { return result; } -float ScriptEndpoint::peek() const { +AxisValue ScriptEndpoint::peek() const { const_cast(this)->updateValue(); - return _lastValueRead; + return AxisValue(_lastValueRead, 0); } void ScriptEndpoint::updateValue() { @@ -58,15 +58,15 @@ void ScriptEndpoint::updateValue() { } } -void ScriptEndpoint::apply(float value, const Pointer& source) { +void ScriptEndpoint::apply(AxisValue value, const Pointer& source) { if (value == _lastValueWritten) { return; } - internalApply(value, source->getInput().getID()); + _lastValueWritten = value; + internalApply(value.value, source->getInput().getID()); } void ScriptEndpoint::internalApply(float value, int sourceID) { - _lastValueWritten = value; if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "internalApply", Qt::QueuedConnection, Q_ARG(float, value), diff --git a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h index 00439f5560..e739ab0b01 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h @@ -24,9 +24,8 @@ public: : Endpoint(Input::INVALID_INPUT), _callable(callable) { } - virtual float peek() const override; - virtual void apply(float newValue, const Pointer& source) override; - + virtual AxisValue peek() const override; + virtual void apply(AxisValue newValue, const Pointer& source) override; virtual Pose peekPose() const override; virtual void apply(const Pose& newValue, const Pointer& source) override; @@ -42,7 +41,7 @@ protected: private: QScriptValue _callable; float _lastValueRead { 0.0f }; - float _lastValueWritten { 0.0f }; + AxisValue _lastValueWritten { 0.0f, 0 }; bool _returnPose { false }; Pose _lastPoseRead; diff --git a/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.h index 2006809fed..26a8063cdc 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.h @@ -25,19 +25,19 @@ public: virtual bool writeable() const override { return !_written; } virtual bool readable() const override { return !_read; } virtual void reset() override { - apply(0.0f, Endpoint::Pointer()); + apply(AxisValue(), Endpoint::Pointer()); apply(Pose(), Endpoint::Pointer()); _written = _read = false; } - virtual float value() override { + virtual AxisValue value() override { _read = true; return VirtualEndpoint::value(); } - virtual void apply(float value, const Pointer& source) override { + virtual void apply(AxisValue value, const Pointer& source) override { // For standard endpoints, the first NON-ZERO write counts. - if (value != 0.0f) { + if (value != AxisValue()) { _written = true; } VirtualEndpoint::apply(value, source); diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h index 269fd54102..15653a7df6 100644 --- a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h @@ -19,7 +19,7 @@ namespace controller { public: AccelerationLimiterFilter() {} - float apply(float value) const override { return value; } + AxisValue apply(AxisValue value) const override { return value; } Pose apply(Pose value) const override; bool parseParameters(const QJsonValue& parameters) override; diff --git a/libraries/controllers/src/controllers/impl/filters/ClampFilter.h b/libraries/controllers/src/controllers/impl/filters/ClampFilter.h index b06a43515f..04684655c9 100644 --- a/libraries/controllers/src/controllers/impl/filters/ClampFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/ClampFilter.h @@ -18,8 +18,8 @@ class ClampFilter : public Filter { REGISTER_FILTER_CLASS(ClampFilter); public: ClampFilter(float min = 0.0, float max = 1.0) : _min(min), _max(max) {}; - virtual float apply(float value) const override { - return glm::clamp(value, _min, _max); + virtual AxisValue apply(AxisValue value) const override { + return { glm::clamp(value.value, _min, _max), value.timestamp }; } virtual Pose apply(Pose value) const override { return value; } diff --git a/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h b/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h index 129e08741d..2cce5f828d 100644 --- a/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h @@ -19,8 +19,8 @@ class ConstrainToIntegerFilter : public Filter { public: ConstrainToIntegerFilter() = default; - virtual float apply(float value) const override { - return glm::sign(value); + virtual AxisValue apply(AxisValue value) const override { + return { glm::sign(value.value), value.timestamp }; } virtual Pose apply(Pose value) const override { return value; } diff --git a/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h b/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h index 8f2140721a..07dd6654f1 100644 --- a/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h @@ -19,8 +19,8 @@ class ConstrainToPositiveIntegerFilter : public Filter { public: ConstrainToPositiveIntegerFilter() = default; - virtual float apply(float value) const override { - return (value <= 0.0f) ? 0.0f : 1.0f; + virtual AxisValue apply(AxisValue value) const override { + return { (value.value <= 0.0f) ? 0.0f : 1.0f, value.timestamp }; } virtual Pose apply(Pose value) const override { return value; } diff --git a/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.cpp b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.cpp index f07ef25976..84d3b9de60 100644 --- a/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.cpp @@ -12,13 +12,13 @@ #include using namespace controller; -float DeadZoneFilter::apply(float value) const { - float scale = ((value < 0.0f) ? -1.0f : 1.0f) / (1.0f - _min); - float magnitude = std::abs(value); +AxisValue DeadZoneFilter::apply(AxisValue value) const { + float scale = ((value.value < 0.0f) ? -1.0f : 1.0f) / (1.0f - _min); + float magnitude = std::abs(value.value); if (magnitude < _min) { - return 0.0f; + return { 0.0f, value.timestamp }; } - return (magnitude - _min) * scale; + return { (magnitude - _min) * scale, value.timestamp }; } bool DeadZoneFilter::parseParameters(const QJsonValue& parameters) { diff --git a/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.h b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.h index d898647126..7ac1e8a51d 100644 --- a/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.h @@ -19,7 +19,7 @@ class DeadZoneFilter : public Filter { public: DeadZoneFilter(float min = 0.0) : _min(min) {}; - virtual float apply(float value) const override; + virtual AxisValue apply(AxisValue value) const override; virtual Pose apply(Pose value) const override { return value; } diff --git a/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.h b/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.h index 134f57243e..e0135a8f68 100644 --- a/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.h @@ -21,7 +21,7 @@ namespace controller { ExponentialSmoothingFilter(float rotationConstant, float translationConstant) : _translationConstant(translationConstant), _rotationConstant(rotationConstant) {} - float apply(float value) const override { return value; } + AxisValue apply(AxisValue value) const override { return value; } Pose apply(Pose value) const override; bool parseParameters(const QJsonValue& parameters) override; diff --git a/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.cpp b/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.cpp index a7f22e1de4..91e59a39b9 100644 --- a/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.cpp @@ -20,17 +20,17 @@ HysteresisFilter::HysteresisFilter(float min, float max) : _min(min), _max(max) }; -float HysteresisFilter::apply(float value) const { +AxisValue HysteresisFilter::apply(AxisValue value) const { if (_signaled) { - if (value <= _min) { + if (value.value <= _min) { _signaled = false; } } else { - if (value >= _max) { + if (value.value >= _max) { _signaled = true; } } - return _signaled ? 1.0f : 0.0f; + return { _signaled ? 1.0f : 0.0f, value.timestamp }; } bool HysteresisFilter::parseParameters(const QJsonValue& parameters) { diff --git a/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.h b/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.h index 4eb563754f..c7c817ecfa 100644 --- a/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.h @@ -18,7 +18,7 @@ class HysteresisFilter : public Filter { REGISTER_FILTER_CLASS(HysteresisFilter); public: HysteresisFilter(float min = 0.25, float max = 0.75); - virtual float apply(float value) const override; + virtual AxisValue apply(AxisValue value) const override; virtual Pose apply(Pose value) const override { return value; } diff --git a/libraries/controllers/src/controllers/impl/filters/LowVelocityFilter.h b/libraries/controllers/src/controllers/impl/filters/LowVelocityFilter.h index ac5299dc6f..eb4f53797f 100644 --- a/libraries/controllers/src/controllers/impl/filters/LowVelocityFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/LowVelocityFilter.h @@ -21,7 +21,7 @@ namespace controller { LowVelocityFilter(float rotationConstant, float translationConstant) : _translationConstant(translationConstant), _rotationConstant(rotationConstant) {} - float apply(float value) const override { return value; } + AxisValue apply(AxisValue value) const override { return value; } Pose apply(Pose newPose) const override; bool parseParameters(const QJsonValue& parameters) override; diff --git a/libraries/controllers/src/controllers/impl/filters/NotFilter.cpp b/libraries/controllers/src/controllers/impl/filters/NotFilter.cpp index 73460aed91..c0396857e5 100644 --- a/libraries/controllers/src/controllers/impl/filters/NotFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/NotFilter.cpp @@ -5,6 +5,6 @@ using namespace controller; NotFilter::NotFilter() { } -float NotFilter::apply(float value) const { - return (value == 0.0f) ? 1.0f : 0.0f; +AxisValue NotFilter::apply(AxisValue value) const { + return { (value.value == 0.0f) ? 1.0f : 0.0f, value.timestamp }; } diff --git a/libraries/controllers/src/controllers/impl/filters/NotFilter.h b/libraries/controllers/src/controllers/impl/filters/NotFilter.h index ceb7d29de3..d68a9feff7 100644 --- a/libraries/controllers/src/controllers/impl/filters/NotFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/NotFilter.h @@ -11,7 +11,7 @@ class NotFilter : public Filter { public: NotFilter(); - virtual float apply(float value) const override; + virtual AxisValue apply(AxisValue value) const override; virtual Pose apply(Pose value) const override { return value; } }; diff --git a/libraries/controllers/src/controllers/impl/filters/PostTransformFilter.h b/libraries/controllers/src/controllers/impl/filters/PostTransformFilter.h index 3c1cb4f80c..aeb24cc3f8 100644 --- a/libraries/controllers/src/controllers/impl/filters/PostTransformFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/PostTransformFilter.h @@ -21,7 +21,7 @@ class PostTransformFilter : public Filter { public: PostTransformFilter() = default; PostTransformFilter(glm::mat4 transform) : _transform(transform) {} - virtual float apply(float value) const override { return value; } + virtual AxisValue apply(AxisValue value) const override { return value; } virtual Pose apply(Pose value) const override { return value.postTransform(_transform); } virtual bool parseParameters(const QJsonValue& parameters) override { return parseMat4Parameter(parameters, _transform); } private: diff --git a/libraries/controllers/src/controllers/impl/filters/PulseFilter.cpp b/libraries/controllers/src/controllers/impl/filters/PulseFilter.cpp index 6ef9d40802..d37eb99ca9 100644 --- a/libraries/controllers/src/controllers/impl/filters/PulseFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/PulseFilter.cpp @@ -15,21 +15,21 @@ using namespace controller; const float PulseFilter::DEFAULT_LAST_EMIT_TIME = -::std::numeric_limits::max(); -float PulseFilter::apply(float value) const { +AxisValue PulseFilter::apply(AxisValue value) const { float result = 0.0f; - if (0.0f != value) { + if (0.0f != value.value) { float now = secTimestampNow(); float delta = now - _lastEmitTime; if (delta >= _interval) { _lastEmitTime = now; - result = value; + result = value.value; } } else if (_resetOnZero) { _lastEmitTime = DEFAULT_LAST_EMIT_TIME; } - return result; + return { result, value.timestamp }; } bool PulseFilter::parseParameters(const QJsonValue& parameters) { diff --git a/libraries/controllers/src/controllers/impl/filters/PulseFilter.h b/libraries/controllers/src/controllers/impl/filters/PulseFilter.h index 2e0da0efa9..dc56b3c6c9 100644 --- a/libraries/controllers/src/controllers/impl/filters/PulseFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/PulseFilter.h @@ -21,7 +21,7 @@ public: PulseFilter() = default; PulseFilter(float interval) : _interval(interval) {} - virtual float apply(float value) const override; + virtual AxisValue apply(AxisValue value) const override; virtual Pose apply(Pose value) const override { return value; } diff --git a/libraries/controllers/src/controllers/impl/filters/RotateFilter.h b/libraries/controllers/src/controllers/impl/filters/RotateFilter.h index c6735f6aa9..d1f1f2d269 100644 --- a/libraries/controllers/src/controllers/impl/filters/RotateFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/RotateFilter.h @@ -22,7 +22,7 @@ public: RotateFilter() = default; RotateFilter(glm::quat rotation) : _rotation(rotation) {} - virtual float apply(float value) const override { return value; } + virtual AxisValue apply(AxisValue value) const override { return value; } virtual Pose apply(Pose value) const override { return value.transform(glm::mat4(glm::quat(_rotation))); diff --git a/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h b/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h index 936498a7a1..3eb58e7f47 100644 --- a/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h @@ -22,8 +22,8 @@ public: ScaleFilter() = default; ScaleFilter(float scale) : _scale(scale) {} - virtual float apply(float value) const override { - return value * _scale; + virtual AxisValue apply(AxisValue value) const override { + return { value.value * _scale, value.timestamp }; } virtual Pose apply(Pose value) const override { diff --git a/libraries/controllers/src/controllers/impl/filters/TransformFilter.h b/libraries/controllers/src/controllers/impl/filters/TransformFilter.h index a34edaa337..c81af2ef17 100644 --- a/libraries/controllers/src/controllers/impl/filters/TransformFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/TransformFilter.h @@ -22,7 +22,7 @@ public: TransformFilter() = default; TransformFilter(glm::mat4 transform) : _transform(transform) {} - virtual float apply(float value) const override { return value; } + virtual AxisValue apply(AxisValue value) const override { return value; } virtual Pose apply(Pose value) const override { return value.transform(_transform); } virtual bool parseParameters(const QJsonValue& parameters) override { return parseMat4Parameter(parameters, _transform); } diff --git a/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h b/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h index ced9cbc689..476481807d 100644 --- a/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h @@ -22,7 +22,7 @@ public: TranslateFilter() = default; TranslateFilter(glm::vec3 translate) : _translate(translate) {} - virtual float apply(float value) const override { return value; } + virtual AxisValue apply(AxisValue value) const override { return value; } virtual Pose apply(Pose value) const override { return value.transform(glm::translate(_translate)); } virtual bool parseParameters(const QJsonValue& parameters) override { return parseVec3Parameter(parameters, _translate); } diff --git a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h index 4234a8731b..c1253f825f 100644 --- a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h +++ b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h @@ -15,7 +15,7 @@ // These properties have JSDoc documentation in HMDScriptingInterface.h. class AbstractHMDScriptingInterface : public QObject { Q_OBJECT - Q_PROPERTY(bool active READ isHMDMode) + Q_PROPERTY(bool active READ isHMDMode NOTIFY mountedChanged) Q_PROPERTY(float ipd READ getIPD) Q_PROPERTY(float eyeHeight READ getEyeHeight) Q_PROPERTY(float playerHeight READ getPlayerHeight) @@ -43,7 +43,7 @@ signals: /**jsdoc * Triggered when Interface's display mode changes and when the user puts on or takes off their HMD. * @function HMD.displayModeChanged - * @param {boolean} isHMDMode - true if the display mode is HMD, otherwise false. This is the + * @param {boolean} isHMDMode - true if the display mode is HMD, otherwise false. This is the * same value as provided by HMD.active. * @returns {Signal} * @example Report when the display mode changes. diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 380998321f..c71b296a74 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -197,9 +197,57 @@ void EntityTreeRenderer::resetEntitiesScriptEngine() { }); } +void EntityTreeRenderer::stopNonLocalEntityScripts() { + leaveNonLocalEntities(); + // unload and stop the engine + if (_entitiesScriptEngine) { + QList entitiesWithEntityScripts = _entitiesScriptEngine->getListOfEntityScriptIDs(); + + foreach (const EntityItemID& entityID, entitiesWithEntityScripts) { + EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID); + + if (entityItem) { + if (!entityItem->isLocalEntity()) { + _entitiesScriptEngine->unloadEntityScript(entityID, true); + } + } + } + } +} + +void EntityTreeRenderer::clearNonLocalEntities() { + stopNonLocalEntityScripts(); + + std::unordered_map savedEntities; + // remove all entities from the scene + _space->clear(); + auto scene = _viewState->getMain3DScene(); + if (scene) { + render::Transaction transaction; + for (const auto& entry : _entitiesInScene) { + const auto& renderer = entry.second; + const EntityItemPointer& entityItem = renderer->getEntity(); + if (!entityItem->isLocalEntity()) { + renderer->removeFromScene(scene, transaction); + } else { + savedEntities[entry.first] = entry.second; + } + } + scene->enqueueTransaction(transaction); + } else { + qCWarning(entitiesrenderer) << "EntitityTreeRenderer::clear(), Unexpected null scene, possibly during application shutdown"; + } + + _renderablesToUpdate = savedEntities; + _entitiesInScene = savedEntities; + + _layeredZones.clearNonLocalLayeredZones(); + + OctreeProcessor::clearNonLocalEntities(); +} + void EntityTreeRenderer::clear() { leaveAllEntities(); - // unload and stop the engine if (_entitiesScriptEngine) { // do this here (instead of in deleter) to avoid marshalling unload signals back to this thread @@ -211,8 +259,8 @@ void EntityTreeRenderer::clear() { if (_wantScripts && !_shuttingDown) { resetEntitiesScriptEngine(); } - // remove all entities from the scene + _space->clear(); auto scene = _viewState->getMain3DScene(); if (scene) { @@ -507,8 +555,7 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QVector(_tree); // FIXME - if EntityTree had a findEntitiesContainingPoint() this could theoretically be a little faster - entityTree->evalEntitiesInSphere(_avatarPosition, radius, - PickFilter(PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES) | PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES)), entityIDs); + entityTree->evalEntitiesInSphere(_avatarPosition, radius, PickFilter(), entityIDs); LayeredZones oldLayeredZones(std::move(_layeredZones)); _layeredZones.clear(); @@ -614,6 +661,26 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { return didUpdate; } +void EntityTreeRenderer::leaveNonLocalEntities() { + if (_tree && !_shuttingDown) { + QVector currentLocalEntitiesInside; + foreach (const EntityItemID& entityID, _currentEntitiesInside) { + EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID); + if (!entityItem->isLocalEntity()) { + emit leaveEntity(entityID); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); + } + } else { + currentLocalEntitiesInside.push_back(entityID); + } + } + + _currentEntitiesInside = currentLocalEntitiesInside; + forceRecheckEntities(); + } +} + void EntityTreeRenderer::leaveAllEntities() { if (_tree && !_shuttingDown) { @@ -1136,6 +1203,29 @@ EntityTreeRenderer::LayeredZones::LayeredZones(LayeredZones&& other) { } } +void EntityTreeRenderer::LayeredZones::clearNonLocalLayeredZones() { + std::set localLayeredZones; + std::map newMap; + + for (auto iter = begin(); iter != end(); iter++) { + LayeredZone layeredZone = *iter; + + if (layeredZone.zone->isLocalEntity()) { + bool success; + iterator it; + std::tie(it, success) = localLayeredZones.insert(layeredZone); + + if (success) { + newMap.emplace(layeredZone.id, it); + } + } + } + + std::set::operator=(localLayeredZones); + _map = newMap; + _skyboxLayer = empty() ? end() : begin(); +} + void EntityTreeRenderer::LayeredZones::clear() { std::set::clear(); _map.clear(); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index b4f0bda703..d9f594a20b 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -86,6 +86,7 @@ public: virtual void init() override; /// clears the tree + virtual void clearNonLocalEntities() override; virtual void clear() override; /// reloads the entity scripts, calling unload and preload @@ -161,6 +162,7 @@ private: bool findBestZoneAndMaybeContainingEntities(QVector* entitiesContainingAvatar = nullptr); bool applyLayeredZones(); + void stopNonLocalEntityScripts(); void checkAndCallPreload(const EntityItemID& entityID, bool reload = false, bool unloadFirst = false); @@ -169,6 +171,7 @@ private: QScriptValueList createEntityArgs(const EntityItemID& entityID); bool checkEnterLeaveEntities(); + void leaveNonLocalEntities(); void leaveAllEntities(); void forceRecheckEntities(); @@ -219,6 +222,7 @@ private: LayeredZones& operator=(LayeredZones&&) = delete; void clear(); + void clearNonLocalLayeredZones(); std::pair insert(const LayeredZone& layer); void update(std::shared_ptr zone); bool contains(const LayeredZones& other); diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index a959131f7d..192c6cfd49 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -42,10 +42,23 @@ ShapeEntityRenderer::ShapeEntityRenderer(const EntityItemPointer& entity) : Pare // TODO: move into Procedural.cpp PrepareStencil::testMaskDrawShape(*_procedural._opaqueState); PrepareStencil::testMask(*_procedural._transparentState); + + addMaterial(graphics::MaterialLayer(_material, 0), "0"); } bool ShapeEntityRenderer::needsRenderUpdate() const { - if (_procedural.isEnabled() && _procedural.isFading()) { + if (resultWithReadLock([&] { + if (_procedural.isEnabled() && _procedural.isFading()) { + return true; + } + + auto mat = _materials.find("0"); + if (mat != _materials.end() && mat->second.needsUpdate()) { + return true; + } + + return false; + })) { return true; } @@ -56,7 +69,11 @@ bool ShapeEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin if (_lastUserData != entity->getUserData()) { return true; } - if (_material != entity->getMaterial()) { + + if (_color != entity->getColor()) { + return true; + } + if (_alpha != entity->getAlpha()) { return true; } @@ -83,10 +100,6 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce _procedural.setProceduralData(ProceduralData::parse(_lastUserData)); } - removeMaterial(_material, "0"); - _material = entity->getMaterial(); - addMaterial(graphics::MaterialLayer(_material, 0), "0"); - _shape = entity->getShape(); _pulseProperties = entity->getPulseProperties(); }); @@ -116,6 +129,20 @@ void ShapeEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint _procedural.setIsFading(isFading); } }); + + glm::u8vec3 color = entity->getColor(); + float alpha = entity->getAlpha(); + if (_color != color || _alpha != alpha) { + _color = color; + _alpha = alpha; + _material->setAlbedo(toGlm(_color)); + _material->setOpacity(_alpha); + + auto materials = _materials.find("0"); + if (materials != _materials.end()) { + materials->second.setNeedsUpdate(true); + } + } } bool ShapeEntityRenderer::isTransparent() const { @@ -129,18 +156,15 @@ bool ShapeEntityRenderer::isTransparent() const { auto mat = _materials.find("0"); if (mat != _materials.end()) { - if (mat->second.top().material) { - auto matKey = mat->second.top().material->getKey(); - if (matKey.isTranslucent()) { - return true; - } + if (mat->second.getMaterialKey().isTranslucent()) { + return true; } } return Parent::isTransparent(); } -bool ShapeEntityRenderer::useMaterialPipeline() const { +bool ShapeEntityRenderer::useMaterialPipeline(const graphics::MultiMaterial& materials) const { bool proceduralReady = resultWithReadLock([&] { return _procedural.isReady(); }); @@ -148,12 +172,7 @@ bool ShapeEntityRenderer::useMaterialPipeline() const { return false; } - graphics::MaterialKey drawMaterialKey; - auto mat = _materials.find("0"); - if (mat != _materials.end() && mat->second.top().material) { - drawMaterialKey = mat->second.top().material->getKey(); - } - + graphics::MaterialKey drawMaterialKey = materials.getMaterialKey(); if (drawMaterialKey.isEmissive() || drawMaterialKey.isUnlit() || drawMaterialKey.isMetallic() || drawMaterialKey.isScattering()) { return true; } @@ -168,11 +187,13 @@ bool ShapeEntityRenderer::useMaterialPipeline() const { } ShapeKey ShapeEntityRenderer::getShapeKey() { - if (useMaterialPipeline()) { - graphics::MaterialKey drawMaterialKey; - if (_materials["0"].top().material) { - drawMaterialKey = _materials["0"].top().material->getKey(); - } + auto mat = _materials.find("0"); + if (mat != _materials.end() && mat->second.needsUpdate()) { + RenderPipelines::updateMultiMaterial(mat->second); + } + + if (mat != _materials.end() && useMaterialPipeline(mat->second)) { + graphics::MaterialKey drawMaterialKey = mat->second.getMaterialKey(); bool isTranslucent = drawMaterialKey.isTranslucent(); bool hasTangents = drawMaterialKey.isNormalMap(); @@ -225,7 +246,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { gpu::Batch& batch = *args->_batch; - std::shared_ptr mat; + graphics::MultiMaterial materials; auto geometryCache = DependencyManager::get(); GeometryCache::Shape geometryShape; bool proceduralRender = false; @@ -233,30 +254,25 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { withReadLock([&] { geometryShape = geometryCache->getShapeForEntityShape(_shape); batch.setModelTransform(_renderTransform); // use a transform with scale, rotation, registration point and translation - mat = _materials["0"].top().material; - if (mat) { - outColor = glm::vec4(mat->getAlbedo(), mat->getOpacity()); - outColor = EntityRenderer::calculatePulseColor(outColor, _pulseProperties, _created); - if (_procedural.isReady()) { - outColor = _procedural.getColor(outColor); - outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f; - _procedural.prepare(batch, _position, _dimensions, _orientation, ProceduralProgramKey(outColor.a < 1.0f)); - proceduralRender = true; - } + materials = _materials["0"]; + auto& schema = materials.getSchemaBuffer().get(); + outColor = glm::vec4(schema._albedo, schema._opacity); + outColor = EntityRenderer::calculatePulseColor(outColor, _pulseProperties, _created); + if (_procedural.isReady()) { + outColor = _procedural.getColor(outColor); + outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f; + _procedural.prepare(batch, _position, _dimensions, _orientation, ProceduralProgramKey(outColor.a < 1.0f)); + proceduralRender = true; } }); - if (!mat) { - return; - } - if (proceduralRender) { if (render::ShapeKey(args->_globalShapeKey).isWireframe()) { geometryCache->renderWireShape(batch, geometryShape, outColor); } else { geometryCache->renderShape(batch, geometryShape, outColor); } - } else if (!useMaterialPipeline()) { + } else if (!useMaterialPipeline(materials)) { // FIXME, support instanced multi-shape rendering using multidraw indirect outColor.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; render::ShapePipelinePointer pipeline; @@ -272,7 +288,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { } } else { if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) { - RenderPipelines::bindMaterial(mat, batch, args->_enableTexturing); + RenderPipelines::bindMaterials(materials, batch, args->_enableTexturing); args->_details._materialSwitches++; } @@ -291,8 +307,9 @@ scriptable::ScriptableModelBase ShapeEntityRenderer::getScriptableModel() { { std::lock_guard lock(_materialsLock); result.appendMaterials(_materials); - if (_materials["0"].top().material) { - vertexColor = _materials["0"].top().material->getAlbedo(); + auto materials = _materials.find("0"); + if (materials != _materials.end()) { + vertexColor = materials->second.getSchemaBuffer().get()._albedo; } } if (auto mesh = geometryCache->meshFromShape(geometryShape, vertexColor)) { diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h index 0efcdbb360..c9fd4801d5 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.h +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h @@ -35,13 +35,17 @@ private: virtual void doRender(RenderArgs* args) override; virtual bool isTransparent() const override; - bool useMaterialPipeline() const; + bool useMaterialPipeline(const graphics::MultiMaterial& materials) const; Procedural _procedural; QString _lastUserData; entity::Shape _shape { entity::Sphere }; + PulsePropertyGroup _pulseProperties; - std::shared_ptr _material; + std::shared_ptr _material { std::make_shared() }; + glm::u8vec3 _color; + float _alpha; + glm::vec3 _position; glm::vec3 _dimensions; glm::quat _orientation; diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index c414a7a4ac..af0e34303b 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -39,13 +39,12 @@ void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByte } } -void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type, - EntityTreePointer entityTree, +void EntityEditPacketSender::queueEditAvatarEntityMessage(EntityTreePointer entityTree, EntityItemID entityItemID, const EntityItemProperties& properties) { assert(_myAvatar); if (!entityTree) { - qCDebug(entities) << "EntityEditPacketSender::queueEditEntityMessage null entityTree."; + qCDebug(entities) << "EntityEditPacketSender::queueEditAvatarEntityMessage null entityTree."; return; } EntityItemPointer entity = entityTree->findEntityByEntityItemID(entityItemID); @@ -53,33 +52,27 @@ void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type, qCDebug(entities) << "EntityEditPacketSender::queueEditAvatarEntityMessage can't find entity: " << entityItemID; return; } + entity->setLastBroadcast(usecTimestampNow()); - // the properties that get serialized into the avatar identity packet should be the entire set + // serialize ALL properties in an "AvatarEntity" packet // rather than just the ones being edited. EntityItemProperties entityProperties = entity->getProperties(); entityProperties.merge(properties); - std::lock_guard lock(_mutex); - QScriptValue scriptProperties = EntityItemNonDefaultPropertiesToScriptValue(&_scriptEngine, entityProperties); - QVariant variantProperties = scriptProperties.toVariant(); - QJsonDocument jsonProperties = QJsonDocument::fromVariant(variantProperties); + OctreePacketData packetData(false, AvatarTraits::MAXIMUM_TRAIT_SIZE); + EncodeBitstreamParams params; + EntityTreeElementExtraEncodeDataPointer extra { nullptr }; + OctreeElement::AppendState appendState = entity->appendEntityData(&packetData, params, extra); - // the ID of the parent/avatar changes from session to session. use a special UUID to indicate the avatar - QJsonObject jsonObject = jsonProperties.object(); - if (jsonObject.contains("parentID")) { - if (QUuid(jsonObject["parentID"].toString()) == _myAvatar->getID()) { - jsonObject["parentID"] = AVATAR_SELF_ID.toString(); - } + if (appendState != OctreeElement::COMPLETED) { + // this entity's payload is too big + return; } - jsonProperties = QJsonDocument(jsonObject); - QByteArray binaryProperties = jsonProperties.toBinaryData(); - _myAvatar->updateAvatarEntity(entityItemID, binaryProperties); - - entity->setLastBroadcast(usecTimestampNow()); + QByteArray tempArray((const char*)packetData.getUncompressedData(), packetData.getUncompressedSize()); + _myAvatar->storeAvatarEntityDataPayload(entityItemID, tempArray); } - void EntityEditPacketSender::queueEditEntityMessage(PacketType type, EntityTreePointer entityTree, EntityItemID entityItemID, @@ -89,7 +82,7 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, qCWarning(entities) << "Suppressing entity edit message: cannot send avatar entity edit with no myAvatar"; } else if (properties.getOwningAvatarID() == _myAvatar->getID()) { // this is an avatar-based entity --> update our avatar-data rather than sending to the entity-server - queueEditAvatarEntityMessage(type, entityTree, entityItemID, properties); + queueEditAvatarEntityMessage(entityTree, entityItemID, properties); } else { qCWarning(entities) << "Suppressing entity edit message: cannot send avatar entity edit for another avatar"; } @@ -127,7 +120,13 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, while (encodeResult == OctreeElement::PARTIAL) { encodeResult = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut, requestedProperties, didntFitProperties); - if (encodeResult != OctreeElement::NONE) { + if (encodeResult == OctreeElement::NONE) { + // This can happen for two reasons: + // 1. One of the properties is too large to fit in a single packet. + // 2. The requested properties don't exist in this entity type (e.g., 'modelUrl' in a Zone Entity). + // Since case #1 is more likely (and more critical), that's the one we warn about. + qCWarning(entities).nospace() << "queueEditEntityMessage: some of the properties don't fit and can't be sent. entityID=" << uuidStringWithoutCurlyBraces(entityItemID); + } else { #ifdef WANT_DEBUG qCDebug(entities) << "calling queueOctreeEditMessage()..."; qCDebug(entities) << " id:" << entityItemID; diff --git a/libraries/entities/src/EntityEditPacketSender.h b/libraries/entities/src/EntityEditPacketSender.h index 9bf9095f7f..a4ec2c45f9 100644 --- a/libraries/entities/src/EntityEditPacketSender.h +++ b/libraries/entities/src/EntityEditPacketSender.h @@ -50,7 +50,7 @@ public slots: void processEntityEditNackPacket(QSharedPointer message, SharedNodePointer sendingNode); private: - void queueEditAvatarEntityMessage(PacketType type, EntityTreePointer entityTree, + void queueEditAvatarEntityMessage(EntityTreePointer entityTree, EntityItemID entityItemID, const EntityItemProperties& properties); private: diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 70b6191852..32c7eb29dd 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -774,7 +774,10 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef auto lastEdited = lastEditedFromBufferAdjusted; bool otherOverwrites = overwriteLocalData && !weOwnSimulation; - auto shouldUpdate = [lastEdited, otherOverwrites, filterRejection](quint64 updatedTimestamp, bool valueChanged) { + auto shouldUpdate = [this, lastEdited, otherOverwrites, filterRejection](quint64 updatedTimestamp, bool valueChanged) { + if (stillHasGrabActions()) { + return false; + } bool simulationChanged = lastEdited > updatedTimestamp; return otherOverwrites && simulationChanged && (valueChanged || filterRejection); }; @@ -3414,7 +3417,8 @@ void EntityItem::prepareForSimulationOwnershipBid(EntityItemProperties& properti } bool EntityItem::isWearable() const { - return isVisible() && (getParentID() == DependencyManager::get()->getSessionUUID() || getParentID() == AVATAR_SELF_ID); + return isVisible() && + (getParentID() == DependencyManager::get()->getSessionUUID() || getParentID() == AVATAR_SELF_ID); } void EntityItem::addGrab(GrabPointer grab) { @@ -3433,7 +3437,8 @@ void EntityItem::addGrab(GrabPointer grab) { EntityDynamicType dynamicType; QVariantMap arguments; int grabParentJointIndex =grab->getParentJointIndex(); - if (grabParentJointIndex == FARGRAB_RIGHTHAND_INDEX || grabParentJointIndex == FARGRAB_LEFTHAND_INDEX) { + if (grabParentJointIndex == FARGRAB_RIGHTHAND_INDEX || grabParentJointIndex == FARGRAB_LEFTHAND_INDEX || + grabParentJointIndex == FARGRAB_MOUSE_INDEX) { // add a far-grab action dynamicType = DYNAMIC_TYPE_FAR_GRAB; arguments["otherID"] = grab->getOwnerID(); diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index a6cbae4bf1..4e3dd21a4c 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include "EntitiesLogging.h" #include "EntityItem.h" @@ -91,6 +92,16 @@ void EntityItemProperties::setLastEdited(quint64 usecTime) { _lastEdited = usecTime > _created ? usecTime : _created; } +bool EntityItemProperties::constructFromBuffer(const unsigned char* data, int dataLength) { + ReadBitstreamToTreeParams args; + EntityItemPointer tempEntity = EntityTypes::constructEntityItem(data, dataLength); + if (!tempEntity) { + return false; + } + tempEntity->readEntityDataFromBuffer(data, dataLength, args); + (*this) = tempEntity->getProperties(); + return true; +} QHash stringToShapeTypeLookup; @@ -921,10 +932,14 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {number} priority=0 - The priority for applying the material to its parent. Only the highest priority material is * applied, with materials of the same priority randomly assigned. Materials that come with the model have a priority of * 0. - * @property {string|number} parentMaterialName="0" - Selects the submesh or submeshes within the parent to apply the material - * to. If in the format "mat::string", all submeshes with material name "string" are replaced. - * Otherwise the property value is parsed as an unsigned integer, specifying the mesh index to modify. Invalid values are - * parsed to 0. + * @property {string} parentMaterialName="0" - Selects the mesh part or parts within the parent to which to apply the material. + * If in the format "mat::string", all mesh parts with material name "string" are replaced. + * Otherwise the property value is parsed as an unsigned integer, specifying the mesh part index to modify. If "all", + * all mesh parts will be replaced. If an array (starts with "[" and ends with "]"), the string will be + * split at each "," and each element will be parsed as either a number or a string if it starts with + * "mat::". In other words, "[0,1,mat::string,mat::string2]" will replace mesh parts 0 and 1, and any + * mesh parts with material "string" or "string2". Do not put spaces around the commas. Invalid values + * are parsed to 0. * @property {string} materialMappingMode="uv" - How the material is mapped to the entity. Either "uv" or * "projected". In "uv" mode, the material will be evaluated within the UV space of the mesh it is applied to. In * "projected" mode, the 3D transform of the Material Entity will be used to evaluate the texture coordinates for the material. @@ -980,7 +995,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * parse the JSON string into a JavaScript object of name, URL pairs. Read-only. * * @property {ShapeType} shapeType="none" - The shape of the collision hull used if collisions are enabled. - * @property {string} compoundShapeURL="" - The OBJ file to use for the compound shape if shapeType is + * @property {string} compoundShapeURL="" - The model file to use for the compound shape if shapeType is * "compound". * * @property {Entities.AnimationProperties} animation - An animation to play on the model. @@ -1330,7 +1345,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {ShapeType} shapeType="box" - The shape of the volume in which the zone's lighting effects and avatar * permissions have effect. Reverts to the default value if set to "none", or set to "compound" * and compoundShapeURL is "". - * @property {string} compoundShapeURL="" - The OBJ file to use for the compound shape if shapeType is + * @property {string} compoundShapeURL="" - The model file to use for the compound shape if shapeType is * "compound". * * @property {string} keyLightMode="inherit" - Configures the key light in the zone. Possible values:
@@ -2153,6 +2168,18 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool _lastEdited = usecTimestampNow(); } +void EntityItemProperties::copyFromJSONString(QScriptEngine& scriptEngine, const QString& jsonString) { + // DANGER: this method is expensive + QJsonDocument propertiesDoc = QJsonDocument::fromJson(jsonString.toUtf8()); + QJsonObject propertiesObj = propertiesDoc.object(); + QVariant propertiesVariant(propertiesObj); + QVariantMap propertiesMap = propertiesVariant.toMap(); + QScriptValue propertiesScriptValue = variantMapToScriptValue(propertiesMap, scriptEngine); + bool honorReadOnly = true; + copyFromScriptValue(propertiesScriptValue, honorReadOnly); +} + + void EntityItemProperties::merge(const EntityItemProperties& other) { // Core COPY_PROPERTY_IF_CHANGED(simulationOwner); @@ -2390,7 +2417,6 @@ void EntityItemPropertiesFromScriptValueHonorReadOnly(const QScriptValue &object properties.copyFromScriptValue(object, true); } - QScriptValue EntityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags) { return EntityItemProperties::entityPropertyFlagsToScriptValue(engine, flags); } @@ -4819,6 +4845,40 @@ void EntityItemProperties::convertToCloneProperties(const EntityItemID& entityID setCloneAvatarEntity(ENTITY_ITEM_DEFAULT_CLONE_AVATAR_ENTITY); } +bool EntityItemProperties::blobToProperties(QScriptEngine& scriptEngine, const QByteArray& blob, EntityItemProperties& properties) { + // DANGER: this method is NOT efficient. + // begin recipe for converting unfortunately-formatted-binary-blob to EntityItemProperties + QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(blob); + if (jsonProperties.isEmpty() || jsonProperties.isNull() || !jsonProperties.isObject() || jsonProperties.object().isEmpty()) { + qCDebug(entities) << "bad avatarEntityData json" << QString(blob.toHex()); + return false; + } + QVariant variant = jsonProperties.toVariant(); + QVariantMap variantMap = variant.toMap(); + QScriptValue scriptValue = variantMapToScriptValue(variantMap, scriptEngine); + EntityItemPropertiesFromScriptValueIgnoreReadOnly(scriptValue, properties); + // end recipe + return true; +} + +void EntityItemProperties::propertiesToBlob(QScriptEngine& scriptEngine, const QUuid& myAvatarID, const EntityItemProperties& properties, QByteArray& blob) { + // DANGER: this method is NOT efficient. + // begin recipe for extracting unfortunately-formatted-binary-blob from EntityItem + QScriptValue scriptValue = EntityItemNonDefaultPropertiesToScriptValue(&scriptEngine, properties); + QVariant variantProperties = scriptValue.toVariant(); + QJsonDocument jsonProperties = QJsonDocument::fromVariant(variantProperties); + // the ID of the parent/avatar changes from session to session. use a special UUID to indicate the avatar + QJsonObject jsonObject = jsonProperties.object(); + if (jsonObject.contains("parentID")) { + if (QUuid(jsonObject["parentID"].toString()) == myAvatarID) { + jsonObject["parentID"] = AVATAR_SELF_ID.toString(); + } + } + jsonProperties = QJsonDocument(jsonObject); + blob = jsonProperties.toBinaryData(); + // end recipe +} + QDebug& operator<<(QDebug& dbg, const EntityPropertyFlags& f) { QString result = "[ "; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index d2df8bebe0..1a3cd205ce 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -105,6 +105,9 @@ class EntityItemProperties { friend class ZoneEntityItem; friend class MaterialEntityItem; public: + static bool blobToProperties(QScriptEngine& scriptEngine, const QByteArray& blob, EntityItemProperties& properties); + static void propertiesToBlob(QScriptEngine& scriptEngine, const QUuid& myAvatarID, const EntityItemProperties& properties, QByteArray& blob); + EntityItemProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()); virtual ~EntityItemProperties() = default; @@ -116,6 +119,7 @@ public: virtual QScriptValue copyToScriptValue(QScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime = false, bool strictSemantics = false, EntityPsuedoPropertyFlags psueudoPropertyFlags = EntityPsuedoPropertyFlags()) const; virtual void copyFromScriptValue(const QScriptValue& object, bool honorReadOnly); + void copyFromJSONString(QScriptEngine& scriptEngine, const QString& jsonString); static QScriptValue entityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags); static void entityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags); @@ -142,6 +146,8 @@ public: EntityPropertyFlags getDesiredProperties() { return _desiredProperties; } void setDesiredProperties(EntityPropertyFlags properties) { _desiredProperties = properties; } + bool constructFromBuffer(const unsigned char* data, int dataLength); + // Note: DEFINE_PROPERTY(PROP_FOO, Foo, foo, type, value) creates the following methods and variables: // type getFoo() const; // void setFoo(type); @@ -164,7 +170,7 @@ public: DEFINE_PROPERTY(PROP_CREATED, Created, created, quint64, UNKNOWN_CREATED_TIME); DEFINE_PROPERTY_REF(PROP_LAST_EDITED_BY, LastEditedBy, lastEditedBy, QUuid, ENTITY_ITEM_DEFAULT_LAST_EDITED_BY); DEFINE_PROPERTY_REF_ENUM(PROP_ENTITY_HOST_TYPE, EntityHostType, entityHostType, entity::HostType, entity::HostType::DOMAIN); - DEFINE_PROPERTY_REF(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid, UNKNOWN_ENTITY_ID); + DEFINE_PROPERTY_REF_WITH_SETTER(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid, UNKNOWN_ENTITY_ID); DEFINE_PROPERTY_REF(PROP_PARENT_ID, ParentID, parentID, QUuid, UNKNOWN_ENTITY_ID); DEFINE_PROPERTY_REF(PROP_PARENT_JOINT_INDEX, ParentJointIndex, parentJointIndex, quint16, -1); DEFINE_PROPERTY_REF(PROP_QUERY_AA_CUBE, QueryAACube, queryAACube, AACube, AACube()); @@ -514,6 +520,16 @@ void EntityPropertyInfoFromScriptValue(const QScriptValue& object, EntityPropert inline void EntityItemProperties::setPosition(const glm::vec3& value) { _position = glm::clamp(value, (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); _positionChanged = true; } +inline void EntityItemProperties::setOwningAvatarID(const QUuid& id) { + _owningAvatarID = id; + if (!_owningAvatarID.isNull()) { + // for AvatarEntities there's no entity-server to tell us we're the simulation owner, + // so always set the simulationOwner to the owningAvatarID and a high priority. + setSimulationOwner(_owningAvatarID, AVATAR_ENTITY_SIMULATION_PRIORITY); + } + _owningAvatarIDChanged = true; +} + QDebug& operator<<(QDebug& dbg, const EntityPropertyFlags& f); inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 697d583de7..286f0dd650 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -986,7 +986,7 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { shouldSendDeleteToServer = false; } else { // only delete local entities, server entities will round trip through the server filters - if (entity->isAvatarEntity() || _entityTree->isServerlessMode()) { + if (!entity->isDomainEntity() || _entityTree->isServerlessMode()) { shouldSendDeleteToServer = false; _entityTree->deleteEntity(entityID); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 3f04850710..954462a9f2 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -70,6 +70,49 @@ OctreeElementPointer EntityTree::createNewElement(unsigned char* octalCode) { return std::static_pointer_cast(newElement); } +void EntityTree::eraseNonLocalEntities() { + emit clearingEntities(); + + if (_simulation) { + // This will clear all entities host types including local entities, because local entities + // are not in the physics simulation + _simulation->clearEntities(); + } + _staleProxies.clear(); + QHash localMap; + localMap.swap(_entityMap); + QHash savedEntities; + this->withWriteLock([&] { + foreach(EntityItemPointer entity, localMap) { + EntityTreeElementPointer element = entity->getElement(); + if (element) { + element->cleanupNonLocalEntities(); + } + + if (entity->isLocalEntity()) { + savedEntities[entity->getEntityItemID()] = entity; + } + } + }); + localMap.clear(); + _entityMap = savedEntities; + + resetClientEditStats(); + clearDeletedEntities(); + + { + QWriteLocker locker(&_needsParentFixupLock); + QVector localEntitiesNeedsParentFixup; + + foreach (EntityItemWeakPointer entityItem, _needsParentFixup) { + if (!entityItem.expired() && entityItem.lock()->isLocalEntity()) { + localEntitiesNeedsParentFixup.push_back(entityItem); + } + } + + _needsParentFixup = localEntitiesNeedsParentFixup; + } +} void EntityTree::eraseAllOctreeElements(bool createNewRoot) { emit clearingEntities(); @@ -174,7 +217,7 @@ int EntityTree::readEntityDataFromBuffer(const unsigned char* data, int bytesLef addToNeedsParentFixupList(entity); } } else { - entity = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead, args); + entity = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead); if (entity) { bytesForThisEntity = entity->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args); @@ -490,7 +533,6 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti } EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool isClone) { - EntityItemPointer result = NULL; EntityItemProperties props = properties; auto nodeList = DependencyManager::get(); @@ -517,12 +559,12 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti if (containingElement) { qCWarning(entities) << "EntityTree::addEntity() on existing entity item with entityID=" << entityID << "containingElement=" << containingElement.get(); - return result; + return nullptr; } // construct the instance of the entity EntityTypes::EntityType type = props.getType(); - result = EntityTypes::constructEntityItem(type, entityID, props); + EntityItemPointer result = EntityTypes::constructEntityItem(type, entityID, props); if (result) { if (recordCreationTime) { @@ -531,10 +573,6 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti // Recurse the tree and store the entity in the correct tree element AddEntityOperator theOperator(getThisPointer(), result); recurseTreeWithOperator(&theOperator); - if (!result->getParentID().isNull()) { - addToNeedsParentFixupList(result); - } - postAddEntity(result); } return result; @@ -2968,27 +3006,31 @@ void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, MovingEntitiesOperator& moveOperator, bool force, bool tellServer) { // if the queryBox has changed, tell the entity-server EntityItemPointer entity = std::dynamic_pointer_cast(object); - if (entity && (entity->updateQueryAACube() || force)) { - bool success; - AACube newCube = entity->getQueryAACube(success); - if (success) { - moveOperator.addEntityToMoveList(entity, newCube); - } - // send an edit packet to update the entity-server about the queryAABox. We do this for domain-hosted - // entities as well as for avatar-entities; the packet-sender will route the update accordingly - if (tellServer && packetSender && (entity->isDomainEntity() || entity->isAvatarEntity())) { - quint64 now = usecTimestampNow(); - EntityItemProperties properties = entity->getProperties(); - properties.setQueryAACubeDirty(); - properties.setLocationDirty(); - properties.setLastEdited(now); + if (entity) { + // NOTE: we rely on side-effects of the entity->updateQueryAACube() call in the following if() conditional: + if (entity->updateQueryAACube() || force) { + bool success; + AACube newCube = entity->getQueryAACube(success); + if (success) { + moveOperator.addEntityToMoveList(entity, newCube); + } + // send an edit packet to update the entity-server about the queryAABox. We do this for domain-hosted + // entities as well as for avatar-entities; the packet-sender will route the update accordingly + if (tellServer && packetSender && (entity->isDomainEntity() || entity->isAvatarEntity())) { + quint64 now = usecTimestampNow(); + EntityItemProperties properties = entity->getProperties(); + properties.setQueryAACubeDirty(); + properties.setLocationDirty(); + properties.setLastEdited(now); - packetSender->queueEditEntityMessage(PacketType::EntityEdit, getThisPointer(), entity->getID(), properties); - entity->setLastBroadcast(now); // for debug/physics status icons - } + packetSender->queueEditEntityMessage(PacketType::EntityEdit, getThisPointer(), entity->getID(), properties); + entity->setLastEdited(now); // so we ignore the echo from the server + entity->setLastBroadcast(now); // for debug/physics status icons + } - entity->markDirtyFlags(Simulation::DIRTY_POSITION); - entityChanged(entity); + entity->markDirtyFlags(Simulation::DIRTY_POSITION); + entityChanged(entity); + } } object->forEachDescendant([&](SpatiallyNestablePointer descendant) { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 9181a4851c..f9b7b8d67f 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -74,6 +74,8 @@ public: return std::static_pointer_cast(_rootElement); } + + virtual void eraseNonLocalEntities() override; virtual void eraseAllOctreeElements(bool createNewRoot = true) override; virtual void readBitstreamToTree(const unsigned char* bitstream, diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 22bddaa1c6..3b085457d1 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -691,6 +691,23 @@ EntityItemPointer EntityTreeElement::getEntityWithEntityItemID(const EntityItemI return foundEntity; } +void EntityTreeElement::cleanupNonLocalEntities() { + withWriteLock([&] { + EntityItems savedEntities; + foreach(EntityItemPointer entity, _entityItems) { + if (!entity->isLocalEntity()) { + entity->preDelete(); + entity->_element = NULL; + } else { + savedEntities.push_back(entity); + } + } + + _entityItems = savedEntities; + }); + bumpChangedContent(); +} + void EntityTreeElement::cleanupEntities() { withWriteLock([&] { foreach(EntityItemPointer entity, _entityItems) { diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index aed19eed15..3f1fda57bd 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -189,6 +189,7 @@ public: EntityItemPointer getEntityWithEntityItemID(const EntityItemID& id) const; void getEntitiesInside(const AACube& box, QVector& foundEntities); + void cleanupNonLocalEntities(); void cleanupEntities(); /// called by EntityTree on cleanup this will free all entities bool removeEntityItem(EntityItemPointer entity, bool deletion = false); diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index e511af83b0..ad078190dd 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -58,6 +58,10 @@ REGISTER_ENTITY_TYPE(Light) REGISTER_ENTITY_TYPE(Zone) REGISTER_ENTITY_TYPE(Material) +bool EntityTypes::typeIsValid(EntityType type) { + return type > EntityType::Unknown && type <= EntityType::NUM_TYPES; +} + const QString& EntityTypes::getEntityTypeName(EntityType entityType) { QMap::iterator matchedTypeName = _typeToNameMap.find(entityType); if (matchedTypeName != _typeToNameMap.end()) { @@ -107,8 +111,7 @@ EntityItemPointer EntityTypes::constructEntityItem(EntityType entityType, const return newEntityItem; } -EntityItemPointer EntityTypes::constructEntityItem(const unsigned char* data, int bytesToRead, - ReadBitstreamToTreeParams& args) { +void EntityTypes::extractEntityTypeAndID(const unsigned char* data, int dataLength, EntityTypes::EntityType& typeOut, QUuid& idOut) { // Header bytes // object ID [16 bytes] @@ -119,28 +122,36 @@ EntityItemPointer EntityTypes::constructEntityItem(const unsigned char* data, in // ~27-35 bytes... const int MINIMUM_HEADER_BYTES = 27; - int bytesRead = 0; - if (bytesToRead >= MINIMUM_HEADER_BYTES) { - int originalLength = bytesToRead; - QByteArray originalDataBuffer((const char*)data, originalLength); + if (dataLength >= MINIMUM_HEADER_BYTES) { + int bytesRead = 0; + QByteArray originalDataBuffer = QByteArray::fromRawData((const char*)data, dataLength); // id QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size - QUuid actualID = QUuid::fromRfc4122(encodedID); + idOut = QUuid::fromRfc4122(encodedID); bytesRead += encodedID.size(); // type QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size ByteCountCoded typeCoder = encodedType; encodedType = typeCoder; // determine true length - bytesRead += encodedType.size(); quint32 type = typeCoder; - EntityTypes::EntityType entityType = (EntityTypes::EntityType)type; - - EntityItemID tempEntityID(actualID); - EntityItemProperties tempProperties; - return constructEntityItem(entityType, tempEntityID, tempProperties); + typeOut = (EntityTypes::EntityType)type; } - - return NULL; +} + +EntityItemPointer EntityTypes::constructEntityItem(const unsigned char* data, int bytesToRead) { + QUuid id; + EntityTypes::EntityType type = EntityTypes::Unknown; + extractEntityTypeAndID(data, bytesToRead, type, id); + if (type > EntityTypes::Unknown && type <= EntityTypes::NUM_TYPES) { + EntityItemID tempEntityID(id); + EntityItemProperties tempProperties; + return constructEntityItem(type, tempEntityID, tempProperties); + } + return nullptr; +} + +EntityItemPointer EntityTypes::constructEntityItem(const QUuid& id, const EntityItemProperties& properties) { + return constructEntityItem(properties.getType(), id, properties); } diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 29a695718e..2e8914c8a7 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -109,11 +109,14 @@ public: NUM_TYPES } EntityType; + static bool typeIsValid(EntityType type); static const QString& getEntityTypeName(EntityType entityType); static EntityTypes::EntityType getEntityTypeFromName(const QString& name); static bool registerEntityType(EntityType entityType, const char* name, EntityTypeFactory factoryMethod); + static void extractEntityTypeAndID(const unsigned char* data, int dataLength, EntityTypes::EntityType& typeOut, QUuid& idOut); static EntityItemPointer constructEntityItem(EntityType entityType, const EntityItemID& entityID, const EntityItemProperties& properties); - static EntityItemPointer constructEntityItem(const unsigned char* data, int bytesToRead, ReadBitstreamToTreeParams& args); + static EntityItemPointer constructEntityItem(const unsigned char* data, int bytesToRead); + static EntityItemPointer constructEntityItem(const QUuid& id, const EntityItemProperties& properties); private: static QMap _typeToNameMap; diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 5d3f11cdc9..869ae2985f 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -112,7 +112,6 @@ EntityItemPointer ShapeEntityItem::sphereFactory(const EntityItemID& entityID, c ShapeEntityItem::ShapeEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { _type = EntityTypes::Shape; _volumeMultiplier *= PI / 6.0f; - _material = std::make_shared(); } EntityItemProperties ShapeEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const { @@ -234,7 +233,6 @@ void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit void ShapeEntityItem::setColor(const glm::u8vec3& value) { withWriteLock([&] { _color = value; - _material->setAlbedo(toGlm(_color)); }); } @@ -247,7 +245,6 @@ glm::u8vec3 ShapeEntityItem::getColor() const { void ShapeEntityItem::setAlpha(float alpha) { withWriteLock([&] { _alpha = alpha; - _material->setOpacity(alpha); }); } diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index bd0f65d9e2..28edf2e1a2 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -101,8 +101,6 @@ public: virtual void computeShapeInfo(ShapeInfo& info) override; virtual ShapeType getShapeType() const override; - std::shared_ptr getMaterial() { return _material; } - PulsePropertyGroup getPulseProperties() const; protected: @@ -115,8 +113,6 @@ protected: //! prior functionality where new or unsupported shapes are treated as //! ellipsoids. ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_ELLIPSOID }; - - std::shared_ptr _material; }; #endif // hifi_ShapeEntityItem_h diff --git a/libraries/entities/src/SimulationOwner.h b/libraries/entities/src/SimulationOwner.h index 353255728c..bd444d28dd 100644 --- a/libraries/entities/src/SimulationOwner.h +++ b/libraries/entities/src/SimulationOwner.h @@ -96,7 +96,7 @@ const uint8_t RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1; // When poking objects with scripts an observer will bid at SCRIPT_EDIT priority. const uint8_t SCRIPT_GRAB_SIMULATION_PRIORITY = 128; const uint8_t SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1; -const uint8_t AVATAR_ENTITY_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY + 1; +const uint8_t AVATAR_ENTITY_SIMULATION_PRIORITY = 255; // PERSONAL priority (needs a better name) is the level at which a simulation observer owns its own avatar // which really just means: things that collide with it will be bid at a priority level one lower diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 68019c2f4b..4c82b4f5d7 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -131,6 +131,7 @@ public: glm::vec3 geometricTranslation; glm::quat geometricRotation; glm::vec3 geometricScaling; + bool isLimbNode; // is this FBXModel transform is a "LimbNode" i.e. a joint }; glm::mat4 getGlobalTransform(const QMultiMap& _connectionParentMap, @@ -559,9 +560,11 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr glm::vec3 geometricRotation; glm::vec3 rotationMin, rotationMax; + + bool isLimbNode = object.properties.size() >= 3 && object.properties.at(2) == "LimbNode"; FBXModel fbxModel = { name, -1, glm::vec3(), glm::mat4(), glm::quat(), glm::quat(), glm::quat(), - glm::mat4(), glm::vec3(), glm::vec3(), - false, glm::vec3(), glm::quat(), glm::vec3(1.0f) }; + glm::mat4(), glm::vec3(), glm::vec3(), + false, glm::vec3(), glm::quat(), glm::vec3(1.0f), isLimbNode }; ExtractedMesh* mesh = NULL; QVector blendshapes; foreach (const FBXNode& subobject, object.children) { @@ -1258,6 +1261,7 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr // convert the models to joints QVariantList freeJoints = mapping.values("freeJoint"); hfmModel.hasSkeletonJoints = false; + foreach (const QString& modelID, modelIDs) { const FBXModel& fbxModel = fbxModels[modelID]; HFMJoint joint; @@ -1288,6 +1292,8 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr joint.geometricTranslation = fbxModel.geometricTranslation; joint.geometricRotation = fbxModel.geometricRotation; joint.geometricScaling = fbxModel.geometricScaling; + joint.isSkeletonJoint = fbxModel.isLimbNode; + hfmModel.hasSkeletonJoints = (hfmModel.hasSkeletonJoints || joint.isSkeletonJoint); glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation; @@ -1311,14 +1317,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr joint.name = hfmModel.hfmToHifiJointNameMapping.key(fbxModel.name); } - foreach (const QString& childID, _connectionChildMap.values(modelID)) { - QString type = typeFlags.value(childID); - if (!type.isEmpty()) { - hfmModel.hasSkeletonJoints |= (joint.isSkeletonJoint = type.toLower().contains("Skeleton")); - break; - } - } - joint.bindTransformFoundInCluster = false; hfmModel.joints.append(joint); diff --git a/libraries/fbx/src/FBXSerializer.h b/libraries/fbx/src/FBXSerializer.h index 7227ebac17..31ca301522 100644 --- a/libraries/fbx/src/FBXSerializer.h +++ b/libraries/fbx/src/FBXSerializer.h @@ -96,6 +96,8 @@ class ExtractedMesh; class FBXSerializer : public HFMSerializer { public: + virtual ~FBXSerializer() {} + MediaType getMediaType() const override; std::unique_ptr getFactory() const override; diff --git a/libraries/graphics-scripting/src/graphics-scripting/Forward.h b/libraries/graphics-scripting/src/graphics-scripting/Forward.h index 104674eddc..43b0daf407 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/Forward.h +++ b/libraries/graphics-scripting/src/graphics-scripting/Forward.h @@ -40,13 +40,13 @@ namespace scriptable { * @typedef {object} Graphics.Material * @property {string} name * @property {string} model - * @property {number} opacity - * @property {number} roughness - * @property {number} metallic - * @property {number} scattering - * @property {boolean} unlit - * @propety {Vec3} emissive - * @propety {Vec3} albedo + * @property {number|string} opacity + * @property {number|string} roughness + * @property {number|string} metallic + * @property {number|string} scattering + * @property {boolean|string} unlit + * @propety {Vec3|string} emissive + * @propety {Vec3|string} albedo * @property {string} emissiveMap * @property {string} albedoMap * @property {string} opacityMap @@ -59,6 +59,11 @@ namespace scriptable { * @property {string} occlusionMap * @property {string} lightmapMap * @property {string} scatteringMap + * @property {string} texCoordTransform0 + * @property {string} texCoordTransform1 + * @property {string} lightmapParams + * @property {string} materialParams + * @property {boolean} defaultFallthrough */ class ScriptableMaterial { public: @@ -88,6 +93,9 @@ namespace scriptable { QString occlusionMap; QString lightmapMap; QString scatteringMap; + + bool defaultFallthrough; + std::unordered_map propertyFallthroughs; // not actually exposed to script }; /**jsdoc diff --git a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp index 67f79db6b7..3293d294d8 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/GraphicsScriptingInterface.cpp @@ -362,25 +362,64 @@ namespace scriptable { QScriptValue obj = engine->newObject(); obj.setProperty("name", material.name); obj.setProperty("model", material.model); - obj.setProperty("opacity", material.opacity); - obj.setProperty("roughness", material.roughness); - obj.setProperty("metallic", material.metallic); - obj.setProperty("scattering", material.scattering); - obj.setProperty("unlit", material.unlit); - obj.setProperty("emissive", vec3ColorToScriptValue(engine, material.emissive)); - obj.setProperty("albedo", vec3ColorToScriptValue(engine, material.albedo)); - obj.setProperty("emissiveMap", material.emissiveMap); - obj.setProperty("albedoMap", material.albedoMap); + + const QScriptValue FALLTHROUGH("fallthrough"); + obj.setProperty("opacity", material.propertyFallthroughs.at(graphics::MaterialKey::OPACITY_VAL_BIT) ? FALLTHROUGH : material.opacity); + obj.setProperty("roughness", material.propertyFallthroughs.at(graphics::MaterialKey::GLOSSY_VAL_BIT) ? FALLTHROUGH : material.roughness); + obj.setProperty("metallic", material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_VAL_BIT) ? FALLTHROUGH : material.metallic); + obj.setProperty("scattering", material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_VAL_BIT) ? FALLTHROUGH : material.scattering); + obj.setProperty("unlit", material.propertyFallthroughs.at(graphics::MaterialKey::UNLIT_VAL_BIT) ? FALLTHROUGH : material.unlit); + obj.setProperty("emissive", material.propertyFallthroughs.at(graphics::MaterialKey::EMISSIVE_VAL_BIT) ? FALLTHROUGH : vec3ColorToScriptValue(engine, material.emissive)); + obj.setProperty("albedo", material.propertyFallthroughs.at(graphics::MaterialKey::ALBEDO_VAL_BIT) ? FALLTHROUGH : vec3ColorToScriptValue(engine, material.albedo)); + + obj.setProperty("emissiveMap", material.propertyFallthroughs.at(graphics::MaterialKey::EMISSIVE_MAP_BIT) ? FALLTHROUGH : material.emissiveMap); + obj.setProperty("albedoMap", material.propertyFallthroughs.at(graphics::MaterialKey::ALBEDO_MAP_BIT) ? FALLTHROUGH : material.albedoMap); obj.setProperty("opacityMap", material.opacityMap); - obj.setProperty("metallicMap", material.metallicMap); - obj.setProperty("specularMap", material.specularMap); - obj.setProperty("roughnessMap", material.roughnessMap); - obj.setProperty("glossMap", material.glossMap); - obj.setProperty("normalMap", material.normalMap); - obj.setProperty("bumpMap", material.bumpMap); - obj.setProperty("occlusionMap", material.occlusionMap); - obj.setProperty("lightmapMap", material.lightmapMap); - obj.setProperty("scatteringMap", material.scatteringMap); + obj.setProperty("occlusionMap", material.propertyFallthroughs.at(graphics::MaterialKey::OCCLUSION_MAP_BIT) ? FALLTHROUGH : material.occlusionMap); + obj.setProperty("lightmapMap", material.propertyFallthroughs.at(graphics::MaterialKey::LIGHTMAP_MAP_BIT) ? FALLTHROUGH : material.lightmapMap); + obj.setProperty("scatteringMap", material.propertyFallthroughs.at(graphics::MaterialKey::SCATTERING_MAP_BIT) ? FALLTHROUGH : material.scatteringMap); + + // Only set one of each of these + if (material.propertyFallthroughs.at(graphics::MaterialKey::METALLIC_MAP_BIT)) { + obj.setProperty("metallicMap", FALLTHROUGH); + } else if (!material.metallicMap.isEmpty()) { + obj.setProperty("metallicMap", material.metallicMap); + } else if (!material.specularMap.isEmpty()) { + obj.setProperty("specularMap", material.specularMap); + } + + if (material.propertyFallthroughs.at(graphics::MaterialKey::ROUGHNESS_MAP_BIT)) { + obj.setProperty("roughnessMap", FALLTHROUGH); + } else if (!material.roughnessMap.isEmpty()) { + obj.setProperty("roughnessMap", material.roughnessMap); + } else if (!material.glossMap.isEmpty()) { + obj.setProperty("glossMap", material.glossMap); + } + + if (material.propertyFallthroughs.at(graphics::MaterialKey::NORMAL_MAP_BIT)) { + obj.setProperty("normalMap", FALLTHROUGH); + } else if (!material.normalMap.isEmpty()) { + obj.setProperty("normalMap", material.normalMap); + } else if (!material.bumpMap.isEmpty()) { + obj.setProperty("bumpMap", material.bumpMap); + } + + // These need to be implemented, but set the fallthrough for now + if (material.propertyFallthroughs.at(graphics::Material::TEXCOORDTRANSFORM0)) { + obj.setProperty("texCoordTransform0", FALLTHROUGH); + } + if (material.propertyFallthroughs.at(graphics::Material::TEXCOORDTRANSFORM1)) { + obj.setProperty("texCoordTransform1", FALLTHROUGH); + } + if (material.propertyFallthroughs.at(graphics::Material::LIGHTMAP_PARAMS)) { + obj.setProperty("lightmapParams", FALLTHROUGH); + } + if (material.propertyFallthroughs.at(graphics::Material::MATERIAL_PARAMS)) { + obj.setProperty("materialParams", FALLTHROUGH); + } + + obj.setProperty("defaultFallthrough", material.defaultFallthrough); + return obj; } diff --git a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp index 51085561c3..4ff751782c 100644 --- a/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp +++ b/libraries/graphics-scripting/src/graphics-scripting/ScriptableModel.cpp @@ -42,6 +42,9 @@ scriptable::ScriptableMaterial& scriptable::ScriptableMaterial::operator=(const lightmapMap = material.lightmapMap; scatteringMap = material.scatteringMap; + defaultFallthrough = material.defaultFallthrough; + propertyFallthroughs = material.propertyFallthroughs; + return *this; } @@ -54,7 +57,9 @@ scriptable::ScriptableMaterial::ScriptableMaterial(const graphics::MaterialPoint scattering(material->getScattering()), unlit(material->isUnlit()), emissive(material->getEmissive()), - albedo(material->getAlbedo()) + albedo(material->getAlbedo()), + defaultFallthrough(material->getDefaultFallthrough()), + propertyFallthroughs(material->getPropertyFallthroughs()) { auto map = material->getTextureMap(graphics::Material::MapChannel::EMISSIVE_MAP); if (map && map->getTextureSource()) { diff --git a/libraries/graphics/src/graphics/Material.cpp b/libraries/graphics/src/graphics/Material.cpp index be99cea681..7befb7e053 100755 --- a/libraries/graphics/src/graphics/Material.cpp +++ b/libraries/graphics/src/graphics/Material.cpp @@ -17,125 +17,125 @@ using namespace graphics; using namespace gpu; -Material::Material() : - _key(0), - _schemaBuffer(), - _textureMaps() -{ - // created from nothing: create the Buffer to store the properties - Schema schema; - _schemaBuffer = gpu::BufferView(std::make_shared(sizeof(Schema), (const gpu::Byte*) &schema, sizeof(Schema))); +const float Material::DEFAULT_EMISSIVE { 0.0f }; +const float Material::DEFAULT_OPACITY { 1.0f }; +const float Material::DEFAULT_ALBEDO { 0.5f }; +const float Material::DEFAULT_METALLIC { 0.0f }; +const float Material::DEFAULT_ROUGHNESS { 1.0f }; +const float Material::DEFAULT_SCATTERING { 0.0f }; + +Material::Material() { + for (int i = 0; i < NUM_TOTAL_FLAGS; i++) { + _propertyFallthroughs[i] = false; + } } Material::Material(const Material& material) : _name(material._name), + _model(material._model), _key(material._key), - _textureMaps(material._textureMaps) + _emissive(material._emissive), + _opacity(material._opacity), + _albedo(material._albedo), + _roughness(material._roughness), + _metallic(material._metallic), + _scattering(material._scattering), + _texcoordTransforms(material._texcoordTransforms), + _lightmapParams(material._lightmapParams), + _materialParams(material._materialParams), + _textureMaps(material._textureMaps), + _defaultFallthrough(material._defaultFallthrough), + _propertyFallthroughs(material._propertyFallthroughs) { - // copied: create the Buffer to store the properties, avoid holding a ref to the old Buffer - Schema schema; - _schemaBuffer = gpu::BufferView(std::make_shared(sizeof(Schema), (const gpu::Byte*) &schema, sizeof(Schema))); - _schemaBuffer.edit() = material._schemaBuffer.get(); } -Material& Material::operator= (const Material& material) { +Material& Material::operator=(const Material& material) { QMutexLocker locker(&_textureMapsMutex); _name = material._name; + _model = material._model; + _key = material._key; + _emissive = material._emissive; + _opacity = material._opacity; + _albedo = material._albedo; + _roughness = material._roughness; + _metallic = material._metallic; + _scattering = material._scattering; + _texcoordTransforms = material._texcoordTransforms; + _lightmapParams = material._lightmapParams; + _materialParams = material._materialParams; + _textureMaps = material._textureMaps; - _key = (material._key); - _textureMaps = (material._textureMaps); - _hasCalculatedTextureInfo = false; - - // copied: create the Buffer to store the properties, avoid holding a ref to the old Buffer - Schema schema; - _schemaBuffer = gpu::BufferView(std::make_shared(sizeof(Schema), (const gpu::Byte*) &schema, sizeof(Schema))); - _schemaBuffer.edit() = material._schemaBuffer.get(); + _defaultFallthrough = material._defaultFallthrough; + _propertyFallthroughs = material._propertyFallthroughs; return (*this); } -Material::~Material() { -} - -void Material::setEmissive(const Color& emissive, bool isSRGB) { - _key.setEmissive(glm::any(glm::greaterThan(emissive, Color(0.0f)))); - _schemaBuffer.edit()._key = (uint32) _key._flags.to_ulong(); - _schemaBuffer.edit()._emissive = (isSRGB ? ColorUtils::sRGBToLinearVec3(emissive) : emissive); +void Material::setEmissive(const glm::vec3& emissive, bool isSRGB) { + _key.setEmissive(glm::any(glm::greaterThan(emissive, glm::vec3(0.0f)))); + _emissive = (isSRGB ? ColorUtils::sRGBToLinearVec3(emissive) : emissive); } void Material::setOpacity(float opacity) { _key.setTranslucentFactor((opacity < 1.0f)); - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); - _schemaBuffer.edit()._opacity = opacity; + _opacity = opacity; } void Material::setUnlit(bool value) { _key.setUnlit(value); - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); } -void Material::setAlbedo(const Color& albedo, bool isSRGB) { - _key.setAlbedo(glm::any(glm::greaterThan(albedo, Color(0.0f)))); - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); - _schemaBuffer.edit()._albedo = (isSRGB ? ColorUtils::sRGBToLinearVec3(albedo) : albedo); +void Material::setAlbedo(const glm::vec3& albedo, bool isSRGB) { + _key.setAlbedo(glm::any(glm::greaterThan(albedo, glm::vec3(0.0f)))); + _albedo = (isSRGB ? ColorUtils::sRGBToLinearVec3(albedo) : albedo); } void Material::setRoughness(float roughness) { roughness = std::min(1.0f, std::max(roughness, 0.0f)); - _key.setGlossy((roughness < 1.0f)); - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); - _schemaBuffer.edit()._roughness = roughness; + _key.setGlossy(roughness < 1.0f); + _roughness = roughness; } void Material::setMetallic(float metallic) { metallic = glm::clamp(metallic, 0.0f, 1.0f); _key.setMetallic(metallic > 0.0f); - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); - _schemaBuffer.edit()._metallic = metallic; + _metallic = metallic; } void Material::setScattering(float scattering) { scattering = glm::clamp(scattering, 0.0f, 1.0f); _key.setMetallic(scattering > 0.0f); - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); - _schemaBuffer.edit()._scattering = scattering; + _scattering = scattering; } void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textureMap) { QMutexLocker locker(&_textureMapsMutex); if (textureMap) { - _key.setMapChannel(channel, (true)); + _key.setMapChannel(channel, true); _textureMaps[channel] = textureMap; } else { - _key.setMapChannel(channel, (false)); + _key.setMapChannel(channel, false); _textureMaps.erase(channel); } - _hasCalculatedTextureInfo = false; - - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); if (channel == MaterialKey::ALBEDO_MAP) { resetOpacityMap(); - - // update the texcoord0 with albedo - _schemaBuffer.edit()._texcoordTransforms[0] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4()); + _texcoordTransforms[0] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4()); } if (channel == MaterialKey::OCCLUSION_MAP) { - _schemaBuffer.edit()._texcoordTransforms[1] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4()); + _texcoordTransforms[1] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4()); } if (channel == MaterialKey::LIGHTMAP_MAP) { // update the texcoord1 with lightmap - _schemaBuffer.edit()._texcoordTransforms[1] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4()); - _schemaBuffer.edit()._lightmapParams = (textureMap ? glm::vec2(textureMap->getLightmapOffsetScale()) : glm::vec2(0.0, 1.0)); + _texcoordTransforms[1] = (textureMap ? textureMap->getTextureTransform().getMatrix() : glm::mat4()); + _lightmapParams = (textureMap ? glm::vec2(textureMap->getLightmapOffsetScale()) : glm::vec2(0.0, 1.0)); } - _schemaBuffer.edit()._materialParams = (textureMap ? glm::vec2(textureMap->getMappingMode(), textureMap->getRepeat()) : glm::vec2(MaterialMappingMode::UV, 1.0)); - - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); + _materialParams = (textureMap ? glm::vec2(textureMap->getMappingMode(), textureMap->getRepeat()) : glm::vec2(MaterialMappingMode::UV, 1.0)); } @@ -163,11 +163,8 @@ void Material::resetOpacityMap() const { } } } - - _schemaBuffer.edit()._key = (uint32)_key._flags.to_ulong(); } - const TextureMapPointer Material::getTextureMap(MapChannel channel) const { QMutexLocker locker(&_textureMapsMutex); @@ -179,40 +176,6 @@ const TextureMapPointer Material::getTextureMap(MapChannel channel) const { } } - -bool Material::calculateMaterialInfo() const { - if (!_hasCalculatedTextureInfo) { - QMutexLocker locker(&_textureMapsMutex); - - bool allTextures = true; // assume we got this... - _textureSize = 0; - _textureCount = 0; - - for (auto const &textureMapItem : _textureMaps) { - auto textureMap = textureMapItem.second; - if (textureMap) { - auto textureSoure = textureMap->getTextureSource(); - if (textureSoure) { - auto texture = textureSoure->getGPUTexture(); - if (texture) { - auto size = texture->getSize(); - _textureSize += size; - _textureCount++; - } else { - allTextures = false; - } - } else { - allTextures = false; - } - } else { - allTextures = false; - } - } - _hasCalculatedTextureInfo = allTextures; - } - return _hasCalculatedTextureInfo; -} - void Material::setTextureTransforms(const Transform& transform, MaterialMappingMode mode, bool repeat) { for (auto &textureMapItem : _textureMaps) { if (textureMapItem.second) { @@ -222,7 +185,32 @@ void Material::setTextureTransforms(const Transform& transform, MaterialMappingM } } for (int i = 0; i < NUM_TEXCOORD_TRANSFORMS; i++) { - _schemaBuffer.edit()._texcoordTransforms[i] = transform.getMatrix(); + _texcoordTransforms[i] = transform.getMatrix(); } - _schemaBuffer.edit()._materialParams = glm::vec2(mode, repeat); + _materialParams = glm::vec2(mode, repeat); } + +MultiMaterial::MultiMaterial() { + Schema schema; + _schemaBuffer = gpu::BufferView(std::make_shared(sizeof(Schema), (const gpu::Byte*) &schema, sizeof(Schema))); +} + +void MultiMaterial::calculateMaterialInfo() const { + if (!_hasCalculatedTextureInfo) { + bool allTextures = true; // assume we got this... + _textureSize = 0; + _textureCount = 0; + + auto textures = _textureTable->getTextures(); + for (auto const &texture : textures) { + if (texture && texture->isDefined()) { + auto size = texture->getSize(); + _textureSize += size; + _textureCount++; + } else { + allTextures = false; + } + } + _hasCalculatedTextureInfo = allTextures; + } +} \ No newline at end of file diff --git a/libraries/graphics/src/graphics/Material.h b/libraries/graphics/src/graphics/Material.h index 9711bd9000..fdddf3640a 100755 --- a/libraries/graphics/src/graphics/Material.h +++ b/libraries/graphics/src/graphics/Material.h @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -176,7 +177,6 @@ public: bool isTexelOpaque() const { return isOpaque() && isOpacityMaskMap(); } }; - class MaterialFilter { public: MaterialKey::Flags _value{ 0 }; @@ -266,84 +266,44 @@ public: class Material { public: - typedef gpu::BufferView UniformBufferView; - - typedef glm::vec3 Color; - - // Texture Map Array Schema - static const int NUM_TEXCOORD_TRANSFORMS{ 2 }; - typedef MaterialKey::MapChannel MapChannel; typedef std::map TextureMaps; - typedef std::bitset MapFlags; Material(); Material(const Material& material); Material& operator= (const Material& material); - virtual ~Material(); const MaterialKey& getKey() const { return _key; } - void setEmissive(const Color& emissive, bool isSRGB = true); - Color getEmissive(bool SRGB = true) const { return (SRGB ? ColorUtils::tosRGBVec3(_schemaBuffer.get()._emissive) : _schemaBuffer.get()._emissive); } + static const float DEFAULT_EMISSIVE; + void setEmissive(const glm::vec3& emissive, bool isSRGB = true); + glm::vec3 getEmissive(bool SRGB = true) const { return (SRGB ? ColorUtils::tosRGBVec3(_emissive) : _emissive); } + static const float DEFAULT_OPACITY; void setOpacity(float opacity); - float getOpacity() const { return _schemaBuffer.get()._opacity; } + float getOpacity() const { return _opacity; } void setUnlit(bool value); bool isUnlit() const { return _key.isUnlit(); } - void setAlbedo(const Color& albedo, bool isSRGB = true); - Color getAlbedo(bool SRGB = true) const { return (SRGB ? ColorUtils::tosRGBVec3(_schemaBuffer.get()._albedo) : _schemaBuffer.get()._albedo); } + static const float DEFAULT_ALBEDO; + void setAlbedo(const glm::vec3& albedo, bool isSRGB = true); + glm::vec3 getAlbedo(bool SRGB = true) const { return (SRGB ? ColorUtils::tosRGBVec3(_albedo) : _albedo); } + static const float DEFAULT_METALLIC; void setMetallic(float metallic); - float getMetallic() const { return _schemaBuffer.get()._metallic; } + float getMetallic() const { return _metallic; } + static const float DEFAULT_ROUGHNESS; void setRoughness(float roughness); - float getRoughness() const { return _schemaBuffer.get()._roughness; } + float getRoughness() const { return _roughness; } + static const float DEFAULT_SCATTERING; void setScattering(float scattering); - float getScattering() const { return _schemaBuffer.get()._scattering; } - - // Schema to access the attribute values of the material - class Schema { - public: - glm::vec3 _emissive { 0.0f }; // No Emissive - float _opacity { 1.0f }; // Opacity = 1 => Not Transparent - - glm::vec3 _albedo { 0.5f }; // Grey albedo => isAlbedo - float _roughness { 1.0f }; // Roughness = 1 => Not Glossy - - float _metallic { 0.0f }; // Not Metallic - float _scattering { 0.0f }; // Scattering info -#if defined(__clang__) - __attribute__((unused)) -#endif - glm::vec2 _spare { 0.0f }; // Padding - - uint32_t _key { 0 }; // a copy of the materialKey -#if defined(__clang__) - __attribute__((unused)) -#endif - glm::vec3 _spare2 { 0.0f }; - - // for alignment beauty, Material size == Mat4x4 - - // Texture Coord Transform Array - glm::mat4 _texcoordTransforms[NUM_TEXCOORD_TRANSFORMS]; - - glm::vec2 _lightmapParams { 0.0, 1.0 }; - - // x: material mode (0 for UV, 1 for PROJECTED) - // y: 1 for texture repeat, 0 for discard outside of 0 - 1 - glm::vec2 _materialParams { 0.0, 1.0 }; - - Schema() {} - }; - - const UniformBufferView& getSchemaBuffer() const { return _schemaBuffer; } + float getScattering() const { return _scattering; } // The texture map to channel association + static const int NUM_TEXCOORD_TRANSFORMS { 2 }; void setTextureMap(MapChannel channel, const TextureMapPointer& textureMap); const TextureMaps& getTextureMaps() const { return _textureMaps; } // FIXME - not thread safe... const TextureMapPointer getTextureMap(MapChannel channel) const; @@ -355,10 +315,6 @@ public: // conversion from legacy material properties to PBR equivalent static float shininessToRoughness(float shininess) { return 1.0f - shininess / 100.0f; } - int getTextureCount() const { calculateMaterialInfo(); return _textureCount; } - size_t getTextureSize() const { calculateMaterialInfo(); return _textureSize; } - bool hasTextureInfo() const { return _hasCalculatedTextureInfo; } - void setTextureTransforms(const Transform& transform, MaterialMappingMode mode, bool repeat); const std::string& getName() const { return _name; } @@ -366,28 +322,50 @@ public: const std::string& getModel() const { return _model; } void setModel(const std::string& model) { _model = model; } - const gpu::TextureTablePointer& getTextureTable() const { return _textureTable; } + glm::mat4 getTexCoordTransform(uint i) const { return _texcoordTransforms[i]; } + glm::vec2 getLightmapParams() const { return _lightmapParams; } + glm::vec2 getMaterialParams() const { return _materialParams; } + + bool getDefaultFallthrough() const { return _defaultFallthrough; } + void setDefaultFallthrough(bool defaultFallthrough) { _defaultFallthrough = defaultFallthrough; } + + enum ExtraFlagBit { + TEXCOORDTRANSFORM0 = MaterialKey::NUM_FLAGS, + TEXCOORDTRANSFORM1, + LIGHTMAP_PARAMS, + MATERIAL_PARAMS, + + NUM_TOTAL_FLAGS + }; + std::unordered_map getPropertyFallthroughs() { return _propertyFallthroughs; } + bool getPropertyFallthrough(uint property) { return _propertyFallthroughs[property]; } + void setPropertyDoesFallthrough(uint property) { _propertyFallthroughs[property] = true; } protected: std::string _name { "" }; private: - mutable MaterialKey _key; - mutable UniformBufferView _schemaBuffer; - mutable gpu::TextureTablePointer _textureTable{ std::make_shared() }; + std::string _model { "hifi_pbr" }; + mutable MaterialKey _key { 0 }; + // Material properties + glm::vec3 _emissive { DEFAULT_EMISSIVE }; + float _opacity { DEFAULT_OPACITY }; + glm::vec3 _albedo { DEFAULT_ALBEDO }; + float _roughness { DEFAULT_ROUGHNESS }; + float _metallic { DEFAULT_METALLIC }; + float _scattering { DEFAULT_SCATTERING }; + std::array _texcoordTransforms; + glm::vec2 _lightmapParams { 0.0, 1.0 }; + glm::vec2 _materialParams { 0.0, 1.0 }; TextureMaps _textureMaps; + bool _defaultFallthrough { false }; + std::unordered_map _propertyFallthroughs { NUM_TOTAL_FLAGS }; + mutable QMutex _textureMapsMutex { QMutex::Recursive }; - mutable size_t _textureSize { 0 }; - mutable int _textureCount { 0 }; - mutable bool _hasCalculatedTextureInfo { false }; - bool calculateMaterialInfo() const; - - std::string _model { "hifi_pbr" }; - }; -typedef std::shared_ptr< Material > MaterialPointer; +typedef std::shared_ptr MaterialPointer; class MaterialLayer { public: @@ -403,9 +381,18 @@ public: return left.priority < right.priority; } }; +typedef std::priority_queue, MaterialLayerCompare> MaterialLayerQueue; -class MultiMaterial : public std::priority_queue, MaterialLayerCompare> { +class MultiMaterial : public MaterialLayerQueue { public: + MultiMaterial(); + + void push(const MaterialLayer& value) { + MaterialLayerQueue::push(value); + _hasCalculatedTextureInfo = false; + _needsUpdate = true; + } + bool remove(const MaterialPointer& value) { auto it = c.begin(); while (it != c.end()) { @@ -417,11 +404,78 @@ public: if (it != c.end()) { c.erase(it); std::make_heap(c.begin(), c.end(), comp); + _hasCalculatedTextureInfo = false; + _needsUpdate = true; return true; } else { return false; } } + + // Schema to access the attribute values of the material + class Schema { + public: + glm::vec3 _emissive { Material::DEFAULT_EMISSIVE }; // No Emissive + float _opacity { Material::DEFAULT_OPACITY }; // Opacity = 1 => Not Transparent + + glm::vec3 _albedo { Material::DEFAULT_ALBEDO }; // Grey albedo => isAlbedo + float _roughness { Material::DEFAULT_ROUGHNESS }; // Roughness = 1 => Not Glossy + + float _metallic { Material::DEFAULT_METALLIC }; // Not Metallic + float _scattering { Material::DEFAULT_SCATTERING }; // Scattering info +#if defined(__clang__) + __attribute__((unused)) +#endif + glm::vec2 _spare { 0.0f }; // Padding + + uint32_t _key { 0 }; // a copy of the materialKey +#if defined(__clang__) + __attribute__((unused)) +#endif + glm::vec3 _spare2 { 0.0f }; + + // for alignment beauty, Material size == Mat4x4 + + // Texture Coord Transform Array + glm::mat4 _texcoordTransforms[Material::NUM_TEXCOORD_TRANSFORMS]; + + glm::vec2 _lightmapParams { 0.0, 1.0 }; + + // x: material mode (0 for UV, 1 for PROJECTED) + // y: 1 for texture repeat, 0 for discard outside of 0 - 1 + glm::vec2 _materialParams { 0.0, 1.0 }; + + Schema() { + for (auto& transform : _texcoordTransforms) { + transform = glm::mat4(); + } + } + }; + + gpu::BufferView& getSchemaBuffer() { return _schemaBuffer; } + graphics::MaterialKey getMaterialKey() const { return graphics::MaterialKey(_schemaBuffer.get()._key); } + const gpu::TextureTablePointer& getTextureTable() const { return _textureTable; } + + bool needsUpdate() const { return _needsUpdate; } + void setNeedsUpdate(bool needsUpdate) { _needsUpdate = needsUpdate; } + + void setTexturesLoading(bool value) { _texturesLoading = value; } + bool areTexturesLoading() const { return _texturesLoading; } + + int getTextureCount() const { calculateMaterialInfo(); return _textureCount; } + size_t getTextureSize() const { calculateMaterialInfo(); return _textureSize; } + bool hasTextureInfo() const { return _hasCalculatedTextureInfo; } + +private: + gpu::BufferView _schemaBuffer; + gpu::TextureTablePointer _textureTable { std::make_shared() }; + bool _needsUpdate { false }; + bool _texturesLoading { false }; + + mutable size_t _textureSize { 0 }; + mutable int _textureCount { 0 }; + mutable bool _hasCalculatedTextureInfo { false }; + void calculateMaterialInfo() const; }; }; diff --git a/libraries/hfm/src/hfm/HFMSerializer.h b/libraries/hfm/src/hfm/HFMSerializer.h index 868ec3dd45..d0be588d60 100644 --- a/libraries/hfm/src/hfm/HFMSerializer.h +++ b/libraries/hfm/src/hfm/HFMSerializer.h @@ -23,6 +23,7 @@ class Serializer { public: class Factory { public: + virtual ~Factory() {} virtual std::shared_ptr get() = 0; }; diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index ddd51e9b9b..3383d71a52 100755 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -21,14 +21,27 @@ const char* KeyboardMouseDevice::NAME = "Keyboard/Mouse"; bool KeyboardMouseDevice::_enableTouch = true; +void KeyboardMouseDevice::updateDeltaAxisValue(int channel, float value) { + // Associate timestamps with non-zero delta values so that consecutive identical values can be output. + _inputDevice->_axisStateMap[channel] = { value, value != 0.0f ? usecTimestampNow() : 0 }; +} + void KeyboardMouseDevice::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { auto userInputMapper = DependencyManager::get(); userInputMapper->withLock([&, this]() { _inputDevice->update(deltaTime, inputCalibrationData); eraseMouseClicked(); - _inputDevice->_axisStateMap[MOUSE_AXIS_X] = _lastCursor.x(); - _inputDevice->_axisStateMap[MOUSE_AXIS_Y] = _lastCursor.y(); + _inputDevice->_axisStateMap[MOUSE_AXIS_X].value = _lastCursor.x(); + _inputDevice->_axisStateMap[MOUSE_AXIS_Y].value = _lastCursor.y(); + + QPoint currentMove = _lastCursor - _previousCursor; + updateDeltaAxisValue(MOUSE_AXIS_X_POS, currentMove.x() > 0 ? currentMove.x() : 0.0f); + updateDeltaAxisValue(MOUSE_AXIS_X_NEG, currentMove.x() < 0 ? -currentMove.x() : 0.0f); + // Y mouse is inverted positive is pointing up the screen + updateDeltaAxisValue(MOUSE_AXIS_Y_POS, currentMove.y() < 0 ? -currentMove.y() : 0.0f); + updateDeltaAxisValue(MOUSE_AXIS_Y_NEG, currentMove.y() > 0 ? currentMove.y() : 0.0f); + _previousCursor = _lastCursor; }); // For touch event, we need to check that the last event is not too long ago @@ -73,7 +86,6 @@ void KeyboardMouseDevice::mousePressEvent(QMouseEvent* event) { } _lastCursor = event->pos(); _mousePressTime = usecTimestampNow(); - _mouseMoved = false; _mousePressPos = event->pos(); _clickDeadspotActive = true; @@ -102,21 +114,12 @@ void KeyboardMouseDevice::eraseMouseClicked() { void KeyboardMouseDevice::mouseMoveEvent(QMouseEvent* event) { QPoint currentPos = event->pos(); - QPoint currentMove = currentPos - _lastCursor; - - _inputDevice->_axisStateMap[MOUSE_AXIS_X_POS] = (currentMove.x() > 0 ? currentMove.x() : 0.0f); - _inputDevice->_axisStateMap[MOUSE_AXIS_X_NEG] = (currentMove.x() < 0 ? -currentMove.x() : 0.0f); - // Y mouse is inverted positive is pointing up the screen - _inputDevice->_axisStateMap[MOUSE_AXIS_Y_POS] = (currentMove.y() < 0 ? -currentMove.y() : 0.0f); - _inputDevice->_axisStateMap[MOUSE_AXIS_Y_NEG] = (currentMove.y() > 0 ? currentMove.y() : 0.0f); // FIXME - this has the characteristic that it will show large jumps when you move the cursor // outside of the application window, because we don't get MouseEvents when the cursor is outside // of the application window. _lastCursor = currentPos; - _mouseMoved = true; - const int CLICK_EVENT_DEADSPOT = 6; // pixels if (_clickDeadspotActive && (_mousePressPos - currentPos).manhattanLength() > CLICK_EVENT_DEADSPOT) { _clickDeadspotActive = false; @@ -125,11 +128,10 @@ void KeyboardMouseDevice::mouseMoveEvent(QMouseEvent* event) { void KeyboardMouseDevice::wheelEvent(QWheelEvent* event) { auto currentMove = event->angleDelta() / 120.0f; - - _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_X_POS).getChannel()] = (currentMove.x() > 0 ? currentMove.x() : 0.0f); - _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_X_NEG).getChannel()] = (currentMove.x() < 0 ? -currentMove.x() : 0.0f); - _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_Y_POS).getChannel()] = (currentMove.y() > 0 ? currentMove.y() : 0.0f); - _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_Y_NEG).getChannel()] = (currentMove.y() < 0 ? -currentMove.y() : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_X_POS).getChannel()].value = currentMove.x() > 0 ? currentMove.x() : 0; + _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_X_NEG).getChannel()].value = currentMove.x() < 0 ? -currentMove.x() : 0; + _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_Y_POS).getChannel()].value = currentMove.y() > 0 ? currentMove.y() : 0; + _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_Y_NEG).getChannel()].value = currentMove.y() < 0 ? -currentMove.y() : 0; } glm::vec2 evalAverageTouchPoints(const QList& points) { @@ -168,12 +170,11 @@ void KeyboardMouseDevice::touchUpdateEvent(const QTouchEvent* event) { _isTouching = event->touchPointStates().testFlag(Qt::TouchPointPressed); } else { auto currentMove = currentPos - _lastTouch; - - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()] = (currentMove.x > 0 ? currentMove.x : 0.0f); - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()] = (currentMove.x < 0 ? -currentMove.x : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()].value = (currentMove.x > 0 ? currentMove.x : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()].value = (currentMove.x < 0 ? -currentMove.x : 0.0f); // Y mouse is inverted positive is pointing up the screen - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()] = (currentMove.y < 0 ? -currentMove.y : 0.0f); - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_NEG).getChannel()] = (currentMove.y > 0 ? currentMove.y : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()].value = (currentMove.y < 0 ? -currentMove.y : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_NEG).getChannel()].value = (currentMove.y > 0 ? currentMove.y : 0.0f); } _lastTouch = currentPos; @@ -247,16 +248,23 @@ controller::Input KeyboardMouseDevice::InputDevice::makeInput(KeyboardMouseDevic * * PgDownnumbernumberThe page down key on the keyboard or keypad is pressed. * - * LeftMouseButtonnumbernumberThe left mouse button pressed. - * MiddleMouseButtonnumbernumberThe middle mouse button pressed. - * RightMouseButtonnumbernumberThe right mouse button pressed. - * LeftMouseClickednumbernumberThe left mouse button clicked. - * MiddleMouseClickednumbernumberThe middle mouse button clicked. - * RightMouseClickednumbernumberThe right mouse button clicked. - * MouseMoveRightnumbernumberThe mouse moved right. - * MouseMoveLeftnumbernumberThe mouse moved left. - * MouseMoveUpnumbernumberThe mouse moved up. - * MouseMoveDownnumbernumberThe mouse moved down. + * LeftMouseButtonnumbernumberThe left mouse button is pressed. + * MiddleMouseButtonnumbernumberThe middle mouse button is pressed. + * + * RightMouseButtonnumbernumberThe right mouse button is pressed. + * LeftMouseClickednumbernumberThe left mouse button was clicked. + * MiddleMouseClickednumbernumberThe middle mouse button was clicked. + * + * RightMouseClickednumbernumberThe right mouse button was clicked. + * + * MouseMoveRightnumbernumberThe mouse moved right. The data value is how + * far it moved. + * MouseMoveLeftnumbernumberThe mouse moved left. The data value is how far + * it moved. + * MouseMoveUpnumbernumberThe mouse moved up. The data value is how far it + * moved. + * MouseMoveDownnumbernumberThe mouse moved down. The data value is how far + * it moved. * MouseXnumbernumberThe mouse x-coordinate changed. The data value is its * new x-coordinate value. * MouseYnumbernumberThe mouse y-coordinate changed. The data value is its diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h index b94acb8b71..f6921c8e23 100644 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h @@ -118,9 +118,9 @@ public: protected: QPoint _lastCursor; + QPoint _previousCursor; QPoint _mousePressPos; quint64 _mousePressTime; - bool _mouseMoved; bool _clickDeadspotActive; glm::vec2 _lastTouch; std::shared_ptr _inputDevice { std::make_shared() }; @@ -130,6 +130,9 @@ protected: std::chrono::high_resolution_clock::time_point _lastTouchTime; static bool _enableTouch; + +private: + void updateDeltaAxisValue(int channel, float value); }; #endif // hifi_KeyboardMouseDevice_h diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp index 20952df4d7..807d7f0ef9 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp @@ -42,24 +42,24 @@ void TouchscreenDevice::pluginUpdate(float deltaTime, const controller::InputCal if (_touchPointCount == 1) { if (_firstTouchVec.x < _currentTouchVec.x) { distanceScaleX = (_currentTouchVec.x - _firstTouchVec.x) / _screenDPIScale.x; - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()] = distanceScaleX; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()].value = distanceScaleX; } else if (_firstTouchVec.x > _currentTouchVec.x) { distanceScaleX = (_firstTouchVec.x - _currentTouchVec.x) / _screenDPIScale.x; - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()] = distanceScaleX; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()].value = distanceScaleX; } // Y axis is inverted, positive is pointing up the screen if (_firstTouchVec.y > _currentTouchVec.y) { distanceScaleY = (_firstTouchVec.y - _currentTouchVec.y) / _screenDPIScale.y; - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()] = distanceScaleY; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()].value = distanceScaleY; } else if (_firstTouchVec.y < _currentTouchVec.y) { distanceScaleY = (_currentTouchVec.y - _firstTouchVec.y) / _screenDPIScale.y; - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_NEG).getChannel()] = distanceScaleY; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_NEG).getChannel()].value = distanceScaleY; } } else if (_touchPointCount == 2) { if (_pinchScale > _lastPinchScale && _pinchScale != 0) { - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_GESTURE_PINCH_POS).getChannel()] = 1.0f; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_GESTURE_PINCH_POS).getChannel()].value = 1.0f; } else if (_pinchScale != 0) { - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_GESTURE_PINCH_NEG).getChannel()] = 1.0f; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_GESTURE_PINCH_NEG).getChannel()].value = 1.0; } _lastPinchScale = _pinchScale; } diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp index 32194e1b84..247f484c95 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp @@ -135,8 +135,8 @@ glm::vec2 TouchscreenVirtualPadDevice::clippedPointInCircle(float radius, glm::v void TouchscreenVirtualPadDevice::processInputDeviceForMove(VirtualPad::Manager& virtualPadManager) { vec2 clippedPoint = clippedPointInCircle(_fixedRadiusForCalc, _moveRefTouchPoint, _moveCurrentTouchPoint); - _inputDevice->_axisStateMap[controller::LX] = (clippedPoint.x - _moveRefTouchPoint.x) / _fixedRadiusForCalc; - _inputDevice->_axisStateMap[controller::LY] = (clippedPoint.y - _moveRefTouchPoint.y) / _fixedRadiusForCalc; + _inputDevice->_axisStateMap[controller::LX].value = (clippedPoint.x - _moveRefTouchPoint.x) / _fixedRadiusForCalc; + _inputDevice->_axisStateMap[controller::LY].value = (clippedPoint.y - _moveRefTouchPoint.y) / _fixedRadiusForCalc; virtualPadManager.getLeftVirtualPad()->setFirstTouch(_moveRefTouchPoint); virtualPadManager.getLeftVirtualPad()->setCurrentTouch(clippedPoint); @@ -148,8 +148,10 @@ void TouchscreenVirtualPadDevice::processInputDeviceForView() { // We use average across how many times we've got touchUpdate events. // Using the average instead of the full deltaX and deltaY, makes deltaTime in MyAvatar dont't accelerate rotation when there is a low touchUpdate rate (heavier domains). // (Because it multiplies this input value by deltaTime (with a coefficient)). - _inputDevice->_axisStateMap[controller::RX] = _viewTouchUpdateCount == 0 ? 0 : (_viewCurrentTouchPoint.x - _viewRefTouchPoint.x) / _viewTouchUpdateCount; - _inputDevice->_axisStateMap[controller::RY] = _viewTouchUpdateCount == 0 ? 0 : (_viewCurrentTouchPoint.y - _viewRefTouchPoint.y) / _viewTouchUpdateCount; + _inputDevice->_axisStateMap[controller::RX].value = + _viewTouchUpdateCount == 0 ? 0 : (_viewCurrentTouchPoint.x - _viewRefTouchPoint.x) / _viewTouchUpdateCount; + _inputDevice->_axisStateMap[controller::RY].value = + _viewTouchUpdateCount == 0 ? 0 : (_viewCurrentTouchPoint.y - _viewRefTouchPoint.y) / _viewTouchUpdateCount; // after use, save last touch point as ref _viewRefTouchPoint = _viewCurrentTouchPoint; @@ -442,8 +444,8 @@ void TouchscreenVirtualPadDevice::moveTouchUpdate(glm::vec2 touchPoint) { void TouchscreenVirtualPadDevice::moveTouchEnd() { if (_moveHasValidTouch) { // do stuff once _moveHasValidTouch = false; - _inputDevice->_axisStateMap[controller::LX] = 0; - _inputDevice->_axisStateMap[controller::LY] = 0; + _inputDevice->_axisStateMap[controller::LX].value = 0; + _inputDevice->_axisStateMap[controller::LY].value = 0; } } @@ -465,8 +467,8 @@ void TouchscreenVirtualPadDevice::viewTouchUpdate(glm::vec2 touchPoint) { void TouchscreenVirtualPadDevice::viewTouchEnd() { if (_viewHasValidTouch) { // do stuff once _viewHasValidTouch = false; - _inputDevice->_axisStateMap[controller::RX] = 0; - _inputDevice->_axisStateMap[controller::RY] = 0; + _inputDevice->_axisStateMap[controller::RX].value = 0; + _inputDevice->_axisStateMap[controller::RY].value = 0; } } diff --git a/libraries/model-networking/src/model-networking/MaterialCache.cpp b/libraries/model-networking/src/model-networking/MaterialCache.cpp index e6e3b0e812..b6550a5e9e 100644 --- a/libraries/model-networking/src/model-networking/MaterialCache.cpp +++ b/libraries/model-networking/src/model-networking/MaterialCache.cpp @@ -111,146 +111,300 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater /**jsdoc * A material such as may be used by a {@link Entities.EntityType|Material} entity. * @typedef {object} Material - * @property {string} name="" - A name for the material. - * @property {string} model="hifi_pbr" - Currently not used. - * @property {Color|RGBS} emissive - The emissive color, i.e., the color that the material emits. A {@link Color} value - * is treated as sRGB. A {@link RGBS} value can be either RGB or sRGB. - * @property {number} opacity=1.0 - The opacity, 0.01.0. - * @property {boolean} unlit=false - If true, the material is not lit. - * @property {Color|RGBS} albedo - The albedo color. A {@link Color} value is treated as sRGB. A {@link RGBS} value can - * be either RGB or sRGB. - * @property {number} roughness - The roughness, 0.01.0. - * @property {number} metallic - The metallicness, 0.01.0. - * @property {number} scattering - The scattering, 0.01.0. - * @property {string} emissiveMap - URL of emissive texture image. - * @property {string} albedoMap - URL of albedo texture image. + * @property {string} model="hifi_pbr" - Different material models support different properties and rendering modes. + * Supported models are: "hifi_pbr" + * @property {string} name="" - A name for the material. Supported by all material models. + * @property {Color|RGBS|string} emissive - The emissive color, i.e., the color that the material emits. A {@link Color} value + * is treated as sRGB. A {@link RGBS} value can be either RGB or sRGB. Set to "fallthrough" to fallthrough to + * the material below. "hifi_pbr" model only. + * @property {number|string} opacity=1.0 - The opacity, 0.01.0. Set to "fallthrough" to fallthrough to + * the material below. "hifi_pbr" model only. + * @property {boolean|string} unlit=false - If true, the material is not lit. Set to "fallthrough" to fallthrough to + * the material below. "hifi_pbr" model only. + * @property {Color|RGBS|string} albedo - The albedo color. A {@link Color} value is treated as sRGB. A {@link RGBS} value can + * be either RGB or sRGB. Set to "fallthrough" to fallthrough to the material below. Set to "fallthrough" to fallthrough to + * the material below. "hifi_pbr" model only. + * @property {number|string} roughness - The roughness, 0.01.0. Set to "fallthrough" to fallthrough to + * the material below. "hifi_pbr" model only. + * @property {number|string} metallic - The metallicness, 0.01.0. Set to "fallthrough" to fallthrough to + * the material below. "hifi_pbr" model only. + * @property {number|string} scattering - The scattering, 0.01.0. Set to "fallthrough" to fallthrough to + * the material below. "hifi_pbr" model only. + * @property {string} emissiveMap - URL of emissive texture image. Set to "fallthrough" to fallthrough to + * the material below. "hifi_pbr" model only. + * @property {string} albedoMap - URL of albedo texture image. Set to "fallthrough" to fallthrough to + * the material below. "hifi_pbr" model only. * @property {string} opacityMap - URL of opacity texture image. Set value the same as the albedoMap value for - * transparency. - * @property {string} roughnessMap - URL of roughness texture image. Can use this or glossMap, but not both. - * @property {string} glossMap - URL of gloss texture image. Can use this or roughnessMap, but not both. - * @property {string} metallicMap - URL of metallic texture image. Can use this or specularMap, but not both. - * @property {string} specularMap - URL of specular texture image. Can use this or metallicMap, but not both. - * @property {string} normalMap - URL of normal texture image. Can use this or bumpMap, but not both. - * @property {string} bumpMap - URL of bump texture image. Can use this or normalMap, but not both. - * @property {string} occlusionMap - URL of occlusion texture image. + * transparency. "hifi_pbr" model only. + * @property {string} roughnessMap - URL of roughness texture image. Can use this or glossMap, but not both. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} glossMap - URL of gloss texture image. Can use this or roughnessMap, but not both. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} metallicMap - URL of metallic texture image. Can use this or specularMap, but not both. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} specularMap - URL of specular texture image. Can use this or metallicMap, but not both. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} normalMap - URL of normal texture image. Can use this or bumpMap, but not both. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} bumpMap - URL of bump texture image. Can use this or normalMap, but not both. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} occlusionMap - URL of occlusion texture image. Set to "fallthrough" to fallthrough to the material below. "hifi_pbr" model only. * @property {string} scatteringMap - URL of scattering texture image. Only used if normalMap or - * bumpMap is specified. - * @property {string} lightMap - URL of light map texture image. Currently not used. + * bumpMap is specified. Set to "fallthrough" to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} lightMap - URL of light map texture image. Currently not used.. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} texCoordTransform0 - The transform to use for all of the maps besides occlusionMap and lightMap. Currently unused. Set to + * "fallthrough" to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} texCoordTransform1 - The transform to use for occlusionMap and lightMap. Currently unused. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} lightmapParams - Parameters for controlling how lightMap is used. Currently unused. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {string} materialParams - Parameters for controlling the material projection and repition. Currently unused. Set to "fallthrough" + * to fallthrough to the material below. "hifi_pbr" model only. + * @property {bool} defaultFallthrough=false - If true, all properties will fallthrough to the material below unless they are set. If + * false, they will respect the individual properties' fallthrough state. "hifi_pbr" model only. */ // Note: See MaterialEntityItem.h for default values used in practice. std::pair> NetworkMaterialResource::parseJSONMaterial(const QJsonObject& materialJSON, const QUrl& baseUrl) { std::string name = ""; std::shared_ptr material = std::make_shared(); - for (auto& key : materialJSON.keys()) { - if (key == "name") { - auto nameJSON = materialJSON.value(key); - if (nameJSON.isString()) { - name = nameJSON.toString().toStdString(); - } - } else if (key == "model") { - auto modelJSON = materialJSON.value(key); - if (modelJSON.isString()) { - material->setModel(modelJSON.toString().toStdString()); - } - } else if (key == "emissive") { - glm::vec3 color; - bool isSRGB; - bool valid = parseJSONColor(materialJSON.value(key), color, isSRGB); - if (valid) { - material->setEmissive(color, isSRGB); - } - } else if (key == "opacity") { - auto value = materialJSON.value(key); - if (value.isDouble()) { - material->setOpacity(value.toDouble()); - } - } else if (key == "unlit") { - auto value = materialJSON.value(key); - if (value.isBool()) { - material->setUnlit(value.toBool()); - } - } else if (key == "albedo") { - glm::vec3 color; - bool isSRGB; - bool valid = parseJSONColor(materialJSON.value(key), color, isSRGB); - if (valid) { - material->setAlbedo(color, isSRGB); - } - } else if (key == "roughness") { - auto value = materialJSON.value(key); - if (value.isDouble()) { - material->setRoughness(value.toDouble()); - } - } else if (key == "metallic") { - auto value = materialJSON.value(key); - if (value.isDouble()) { - material->setMetallic(value.toDouble()); - } - } else if (key == "scattering") { - auto value = materialJSON.value(key); - if (value.isDouble()) { - material->setScattering(value.toDouble()); - } - } else if (key == "emissiveMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setEmissiveMap(baseUrl.resolved(value.toString())); - } - } else if (key == "albedoMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - QString urlString = value.toString(); - bool useAlphaChannel = false; - auto opacityMap = materialJSON.find("opacityMap"); - if (opacityMap != materialJSON.end() && opacityMap->isString() && opacityMap->toString() == urlString) { - useAlphaChannel = true; + + const std::string HIFI_PBR = "hifi_pbr"; + std::string modelString = HIFI_PBR; + auto modelJSONIter = materialJSON.find("model"); + if (modelJSONIter != materialJSON.end() && modelJSONIter.value().isString()) { + modelString = modelJSONIter.value().toString().toStdString(); + material->setModel(modelString); + } + + if (modelString == HIFI_PBR) { + const QString FALLTHROUGH("fallthrough"); + for (auto& key : materialJSON.keys()) { + if (key == "name") { + auto nameJSON = materialJSON.value(key); + if (nameJSON.isString()) { + name = nameJSON.toString().toStdString(); + } + } else if (key == "model") { + auto modelJSON = materialJSON.value(key); + if (modelJSON.isString()) { + material->setModel(modelJSON.toString().toStdString()); + } + } else if (key == "emissive") { + auto value = materialJSON.value(key); + if (value.isString() && value.toString() == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::EMISSIVE_VAL_BIT); + } else { + glm::vec3 color; + bool isSRGB; + bool valid = parseJSONColor(value, color, isSRGB); + if (valid) { + material->setEmissive(color, isSRGB); + } + } + } else if (key == "opacity") { + auto value = materialJSON.value(key); + if (value.isString() && value.toString() == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::OPACITY_VAL_BIT); + } else if (value.isDouble()) { + material->setOpacity(value.toDouble()); + } + } else if (key == "unlit") { + auto value = materialJSON.value(key); + if (value.isString() && value.toString() == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::UNLIT_VAL_BIT); + } else if (value.isBool()) { + material->setUnlit(value.toBool()); + } + } else if (key == "albedo") { + auto value = materialJSON.value(key); + if (value.isString() && value.toString() == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::ALBEDO_VAL_BIT); + } else { + glm::vec3 color; + bool isSRGB; + bool valid = parseJSONColor(value, color, isSRGB); + if (valid) { + material->setAlbedo(color, isSRGB); + } + } + } else if (key == "roughness") { + auto value = materialJSON.value(key); + if (value.isString() && value.toString() == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::GLOSSY_VAL_BIT); + } else if (value.isDouble()) { + material->setRoughness(value.toDouble()); + } + } else if (key == "metallic") { + auto value = materialJSON.value(key); + if (value.isString() && value.toString() == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::METALLIC_VAL_BIT); + } else if (value.isDouble()) { + material->setMetallic(value.toDouble()); + } + } else if (key == "scattering") { + auto value = materialJSON.value(key); + if (value.isString() && value.toString() == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::SCATTERING_VAL_BIT); + } else if (value.isDouble()) { + material->setScattering(value.toDouble()); + } + } else if (key == "emissiveMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::EMISSIVE_MAP_BIT); + } else { + material->setEmissiveMap(baseUrl.resolved(valueString)); + } + } + } else if (key == "albedoMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + QString valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::ALBEDO_MAP_BIT); + } else { + bool useAlphaChannel = false; + auto opacityMap = materialJSON.find("opacityMap"); + if (opacityMap != materialJSON.end() && opacityMap->isString() && opacityMap->toString() == valueString) { + useAlphaChannel = true; + } + material->setAlbedoMap(baseUrl.resolved(valueString), useAlphaChannel); + } + } + } else if (key == "roughnessMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::ROUGHNESS_MAP_BIT); + } else { + material->setRoughnessMap(baseUrl.resolved(valueString), false); + } + } + } else if (key == "glossMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::ROUGHNESS_MAP_BIT); + } else { + material->setRoughnessMap(baseUrl.resolved(valueString), true); + } + } + } else if (key == "metallicMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::METALLIC_MAP_BIT); + } else { + material->setMetallicMap(baseUrl.resolved(valueString), false); + } + } + } else if (key == "specularMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::METALLIC_MAP_BIT); + } else { + material->setMetallicMap(baseUrl.resolved(valueString), true); + } + } + } else if (key == "normalMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::NORMAL_MAP_BIT); + } else { + material->setNormalMap(baseUrl.resolved(valueString), false); + } + } + } else if (key == "bumpMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::NORMAL_MAP_BIT); + } else { + material->setNormalMap(baseUrl.resolved(valueString), true); + } + } + } else if (key == "occlusionMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::OCCLUSION_MAP_BIT); + } else { + material->setOcclusionMap(baseUrl.resolved(valueString)); + } + } + } else if (key == "scatteringMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::SCATTERING_MAP_BIT); + } else { + material->setScatteringMap(baseUrl.resolved(valueString)); + } + } + } else if (key == "lightMap") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::MaterialKey::FlagBit::LIGHTMAP_MAP_BIT); + } else { + material->setLightmapMap(baseUrl.resolved(valueString)); + } + } + } else if (key == "texCoordTransform0") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::TEXCOORDTRANSFORM0); + } + } + // TODO: implement texCoordTransform0 + } else if (key == "texCoordTransform1") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::TEXCOORDTRANSFORM1); + } + } + // TODO: implement texCoordTransform1 + } else if (key == "lightmapParams") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::LIGHTMAP_PARAMS); + } + } + // TODO: implement lightmapParams + } else if (key == "materialParams") { + auto value = materialJSON.value(key); + if (value.isString()) { + auto valueString = value.toString(); + if (valueString == FALLTHROUGH) { + material->setPropertyDoesFallthrough(graphics::Material::ExtraFlagBit::MATERIAL_PARAMS); + } + } + // TODO: implement materialParams + } else if (key == "defaultFallthrough") { + auto value = materialJSON.value(key); + if (value.isBool()) { + material->setDefaultFallthrough(value.toBool()); } - material->setAlbedoMap(baseUrl.resolved(urlString), useAlphaChannel); - } - } else if (key == "roughnessMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setRoughnessMap(baseUrl.resolved(value.toString()), false); - } - } else if (key == "glossMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setRoughnessMap(baseUrl.resolved(value.toString()), true); - } - } else if (key == "metallicMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setMetallicMap(baseUrl.resolved(value.toString()), false); - } - } else if (key == "specularMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setMetallicMap(baseUrl.resolved(value.toString()), true); - } - } else if (key == "normalMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setNormalMap(baseUrl.resolved(value.toString()), false); - } - } else if (key == "bumpMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setNormalMap(baseUrl.resolved(value.toString()), true); - } - } else if (key == "occlusionMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setOcclusionMap(baseUrl.resolved(value.toString())); - } - } else if (key == "scatteringMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setScatteringMap(baseUrl.resolved(value.toString())); - } - } else if (key == "lightMap") { - auto value = materialJSON.value(key); - if (value.isString()) { - material->setLightmapMap(baseUrl.resolved(value.toString())); } } } diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index 418dc8f417..7ab2296935 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -31,6 +31,7 @@ using namespace udt; using namespace std::chrono; Connection::Connection(Socket* parentSocket, HifiSockAddr destination, std::unique_ptr congestionControl) : + QObject(parentSocket), _parentSocket(parentSocket), _destination(destination), _congestionControl(move(congestionControl)) diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 18fb23e8ed..7f20f881da 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -38,10 +38,10 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(EntityQueryPacketVersion::ConicalFrustums); case PacketType::AvatarIdentity: case PacketType::AvatarData: - return static_cast(AvatarMixerPacketVersion::CollisionFlag); + return static_cast(AvatarMixerPacketVersion::SendMaxTranslationDimension); case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::GrabTraits); + return static_cast(AvatarMixerPacketVersion::SendMaxTranslationDimension); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); // ICE packets @@ -93,10 +93,11 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(PingVersion::IncludeConnectionID); case PacketType::AvatarQuery: return static_cast(AvatarQueryVersion::ConicalFrustums); - case PacketType::AvatarIdentityRequest: - return 22; case PacketType::EntityQueryInitialResultsComplete: return static_cast(EntityVersion::ParticleSpin); + case PacketType::BulkAvatarTraitsAck: + case PacketType::BulkAvatarTraits: + return static_cast(AvatarMixerPacketVersion::AvatarTraitsAck); default: return 22; } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index b5d4e6c8ad..105e9d116b 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -57,7 +57,7 @@ public: ICEServerQuery, OctreeStats, SetAvatarTraits, - AvatarIdentityRequest, + UNUSED_PACKET_TYPE, AssignmentClientStatus, NoisyMute, AvatarIdentity, @@ -133,7 +133,7 @@ public: EntityQueryInitialResultsComplete, BulkAvatarTraits, AudioSoloRequest, - + BulkAvatarTraitsAck, NUM_PACKET_TYPE }; @@ -317,7 +317,10 @@ enum class AvatarMixerPacketVersion : PacketVersion { FarGrabJointsRedux, JointTransScaled, GrabTraits, - CollisionFlag + CollisionFlag, + AvatarTraitsAck, + FasterAvatarEntities, + SendMaxTranslationDimension }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/networking/src/udt/PacketQueue.cpp b/libraries/networking/src/udt/PacketQueue.cpp index 0560855ecb..b2e3000f74 100644 --- a/libraries/networking/src/udt/PacketQueue.cpp +++ b/libraries/networking/src/udt/PacketQueue.cpp @@ -16,7 +16,8 @@ using namespace udt; PacketQueue::PacketQueue(MessageNumber messageNumber) : _currentMessageNumber(messageNumber) { - _channels.emplace_back(new std::list()); + _channels.emplace_front(new std::list()); + _currentChannel = _channels.begin(); } MessageNumber PacketQueue::getNextMessageNumber() { @@ -27,21 +28,28 @@ MessageNumber PacketQueue::getNextMessageNumber() { bool PacketQueue::isEmpty() const { LockGuard locker(_packetsLock); + // Only the main channel and it is empty - return (_channels.size() == 1) && _channels.front()->empty(); + return _channels.size() == 1 && _channels.front()->empty(); } PacketQueue::PacketPointer PacketQueue::takePacket() { LockGuard locker(_packetsLock); + if (isEmpty()) { return PacketPointer(); } - // Find next non empty channel - if (_channels[nextIndex()]->empty()) { - nextIndex(); + // handle the case where we are looking at the first channel and it is empty + if (_currentChannel == _channels.begin() && (*_currentChannel)->empty()) { + ++_currentChannel; } - auto& channel = _channels[_currentIndex]; + + // at this point the current channel should always not be at the end and should also not be empty + Q_ASSERT(_currentChannel != _channels.end()); + + auto& channel = *_currentChannel; + Q_ASSERT(!channel->empty()); // Take front packet @@ -49,20 +57,28 @@ PacketQueue::PacketPointer PacketQueue::takePacket() { channel->pop_front(); // Remove now empty channel (Don't remove the main channel) - if (channel->empty() && _currentIndex != 0) { - channel->swap(*_channels.back()); - _channels.pop_back(); - --_currentIndex; + if (channel->empty() && _currentChannel != _channels.begin()) { + // erase the current channel and slide the iterator to the next channel + _currentChannel = _channels.erase(_currentChannel); + } else { + ++_currentChannel; + } + + // push forward our number of channels taken from + ++_channelsVisitedCount; + + // check if we need to restart back at the front channel (main) + // to respect our capped number of channels considered concurrently + static const int MAX_CHANNELS_SENT_CONCURRENTLY = 16; + + if (_currentChannel == _channels.end() || _channelsVisitedCount >= MAX_CHANNELS_SENT_CONCURRENTLY) { + _channelsVisitedCount = 0; + _currentChannel = _channels.begin(); } return packet; } -unsigned int PacketQueue::nextIndex() { - _currentIndex = (_currentIndex + 1) % _channels.size(); - return _currentIndex; -} - void PacketQueue::queuePacket(PacketPointer packet) { LockGuard locker(_packetsLock); _channels.front()->push_back(std::move(packet)); diff --git a/libraries/networking/src/udt/PacketQueue.h b/libraries/networking/src/udt/PacketQueue.h index bc4c5e3432..f696864e6b 100644 --- a/libraries/networking/src/udt/PacketQueue.h +++ b/libraries/networking/src/udt/PacketQueue.h @@ -30,8 +30,9 @@ class PacketQueue { using LockGuard = std::lock_guard; using PacketPointer = std::unique_ptr; using PacketListPointer = std::unique_ptr; - using Channel = std::unique_ptr>; - using Channels = std::vector; + using RawChannel = std::list; + using Channel = std::unique_ptr; + using Channels = std::list; public: PacketQueue(MessageNumber messageNumber = 0); @@ -47,16 +48,17 @@ public: private: MessageNumber getNextMessageNumber(); - unsigned int nextIndex(); - + MessageNumber _currentMessageNumber { 0 }; mutable Mutex _packetsLock; // Protects the packets to be sent. Channels _channels; // One channel per packet list + Main channel - unsigned int _currentIndex { 0 }; + + Channels::iterator _currentChannel; + unsigned int _channelsVisitedCount { 0 }; }; } -#endif // hifi_PacketQueue_h \ No newline at end of file +#endif // hifi_PacketQueue_h diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index b507f0921d..15841b5c21 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -61,6 +61,9 @@ private: Mutex2& _mutex2; }; +const microseconds SendQueue::MAXIMUM_ESTIMATED_TIMEOUT = seconds(5); +const microseconds SendQueue::MINIMUM_ESTIMATED_TIMEOUT = milliseconds(10); + std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destination, SequenceNumber currentSequenceNumber, MessageNumber currentMessageNumber, bool hasReceivedHandshakeACK) { Q_ASSERT_X(socket, "SendQueue::create", "Must be called with a valid Socket*"); @@ -507,12 +510,8 @@ bool SendQueue::isInactive(bool attemptedToSendPacket) { auto estimatedTimeout = std::chrono::microseconds(_estimatedTimeout); - // cap our maximum estimated timeout to the already unreasonable 5 seconds - const auto MAXIMUM_ESTIMATED_TIMEOUT = std::chrono::seconds(5); - - if (estimatedTimeout > MAXIMUM_ESTIMATED_TIMEOUT) { - estimatedTimeout = MAXIMUM_ESTIMATED_TIMEOUT; - } + // Clamp timeout beween 10 ms and 5 s + estimatedTimeout = std::min(MAXIMUM_ESTIMATED_TIMEOUT, std::max(MINIMUM_ESTIMATED_TIMEOUT, estimatedTimeout)); // use our condition_variable_any to wait auto cvStatus = _emptyCondition.wait_for(locker, estimatedTimeout); diff --git a/libraries/networking/src/udt/SendQueue.h b/libraries/networking/src/udt/SendQueue.h index 148d813fc1..c1a2b59075 100644 --- a/libraries/networking/src/udt/SendQueue.h +++ b/libraries/networking/src/udt/SendQueue.h @@ -140,6 +140,9 @@ private: std::condition_variable_any _emptyCondition; std::chrono::high_resolution_clock::time_point _lastPacketSentAt; + + static const std::chrono::microseconds MAXIMUM_ESTIMATED_TIMEOUT; + static const std::chrono::microseconds MINIMUM_ESTIMATED_TIMEOUT; }; } diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index eef23493f6..aac29201f1 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -149,6 +149,7 @@ public: OctreeElementPointer getRoot() { return _rootElement; } + virtual void eraseNonLocalEntities() { _isDirty = true; }; virtual void eraseAllOctreeElements(bool createNewRoot = true); virtual void readBitstreamToTree(const unsigned char* bitstream, uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args); diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index 6c0bba5ec6..fd57f2fa3a 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -38,7 +38,11 @@ void OctreePacketData::changeSettings(bool enableCompression, unsigned int targe _enableCompression = enableCompression; _targetSize = targetSize; _uncompressedByteArray.resize(_targetSize); - _compressedByteArray.resize(_targetSize); + if (_enableCompression) { + _compressedByteArray.resize(_targetSize); + } else { + _compressedByteArray.resize(0); + } _uncompressed = (unsigned char*)_uncompressedByteArray.data(); _compressed = (unsigned char*)_compressedByteArray.data(); @@ -586,13 +590,10 @@ bool OctreePacketData::appendRawData(QByteArray data) { AtomicUIntStat OctreePacketData::_compressContentTime { 0 }; AtomicUIntStat OctreePacketData::_compressContentCalls { 0 }; -bool OctreePacketData::compressContent() { +bool OctreePacketData::compressContent() { PerformanceWarning warn(false, "OctreePacketData::compressContent()", false, &_compressContentTime, &_compressContentCalls); - - // without compression, we always pass... - if (!_enableCompression) { - return true; - } + assert(_dirty); + assert(_enableCompression); _bytesInUseLastCheck = _bytesInUse; @@ -605,13 +606,13 @@ bool OctreePacketData::compressContent() { QByteArray compressedData = qCompress(uncompressedData, uncompressedSize, MAX_COMPRESSION); - if (compressedData.size() < (int)MAX_OCTREE_PACKET_DATA_SIZE) { + if (compressedData.size() < _compressedByteArray.size()) { _compressedBytes = compressedData.size(); memcpy(_compressed, compressedData.constData(), _compressedBytes); _dirty = false; success = true; } else { - qCWarning(octree) << "OctreePacketData::compressContent -- compressedData.size >= MAX_OCTREE_PACKET_DATA_SIZE"; + qCWarning(octree) << "OctreePacketData::compressContent -- compressedData.size >= " << _compressedByteArray.size(); assert(false); } return success; @@ -644,8 +645,7 @@ void OctreePacketData::loadFinalizedContent(const unsigned char* data, int lengt memcpy(_uncompressed, uncompressedData.constData(), _bytesInUse); } else { memcpy(_uncompressed, data, length); - memcpy(_compressed, data, length); - _bytesInUse = _compressedBytes = length; + _bytesInUse = length; } } else { if (_debug) { diff --git a/libraries/octree/src/OctreeProcessor.cpp b/libraries/octree/src/OctreeProcessor.cpp index 206ff399d9..18c8630391 100644 --- a/libraries/octree/src/OctreeProcessor.cpp +++ b/libraries/octree/src/OctreeProcessor.cpp @@ -51,15 +51,15 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe bool showTimingDetails = false; // Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showTimingDetails, "OctreeProcessor::processDatagram()", showTimingDetails); - + if (message.getType() == getExpectedPacketType()) { PerformanceWarning warn(showTimingDetails, "OctreeProcessor::processDatagram expected PacketType", showTimingDetails); // if we are getting inbound packets, then our tree is also viewing, and we should remember that fact. _tree->setIsViewing(true); - + OCTREE_PACKET_FLAGS flags; message.readPrimitive(&flags); - + OCTREE_PACKET_SEQUENCE sequence; message.readPrimitive(&sequence); @@ -68,7 +68,7 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe bool packetIsColored = oneAtBit(flags, PACKET_IS_COLOR_BIT); bool packetIsCompressed = oneAtBit(flags, PACKET_IS_COMPRESSED_BIT); - + OCTREE_PACKET_SENT_TIME arrivedAt = usecTimestampNow(); qint64 clockSkew = sourceNode ? sourceNode->getClockSkewUsec() : 0; qint64 flightTime = arrivedAt - sentAt + clockSkew; @@ -79,27 +79,27 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe qCDebug(octree) << "OctreeProcessor::processDatagram() ... " "Got Packet Section color:" << packetIsColored << "compressed:" << packetIsCompressed << - "sequence: " << sequence << + "sequence: " << sequence << "flight: " << flightTime << " usec" << "size:" << message.getSize() << "data:" << message.getBytesLeftToRead(); } - + _packetsInLastWindow++; - + int elementsPerPacket = 0; int entitiesPerPacket = 0; - + quint64 totalWaitingForLock = 0; quint64 totalUncompress = 0; quint64 totalReadBitsteam = 0; const QUuid& sourceUUID = sourceNode->getUUID(); - + int subsection = 1; - + bool error = false; - + while (message.getBytesLeftToRead() > 0 && !error) { if (packetIsCompressed) { if (message.getBytesLeftToRead() > (qint64) sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE)) { @@ -111,7 +111,7 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe } else { sectionLength = message.getBytesLeftToRead(); } - + if (sectionLength) { // ask the VoxelTree to read the bitstream into the tree ReadBitstreamToTreeParams args(WANT_EXISTS_BITS, NULL, @@ -149,7 +149,7 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe qCDebug(octree) << "OctreeProcessor::processDatagram() ******* END _tree->readBitstreamToTree()..."; } }); - + // seek forwards in packet message.seek(message.getPosition() + sectionLength); @@ -172,13 +172,13 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe _waitLockPerPacket.updateAverage(totalWaitingForLock); _uncompressPerPacket.updateAverage(totalUncompress); _readBitstreamPerPacket.updateAverage(totalReadBitsteam); - + quint64 now = usecTimestampNow(); if (_lastWindowAt == 0) { _lastWindowAt = now; } quint64 sinceLastWindow = now - _lastWindowAt; - + if (sinceLastWindow > USECS_PER_SECOND) { float packetsPerSecondInWindow = (float)_packetsInLastWindow / (float)(sinceLastWindow / USECS_PER_SECOND); float elementsPerSecondInWindow = (float)_elementsInLastWindow / (float)(sinceLastWindow / USECS_PER_SECOND); @@ -197,6 +197,14 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe } } + +void OctreeProcessor::clearNonLocalEntities() { + if (_tree) { + _tree->withWriteLock([&] { + _tree->eraseNonLocalEntities(); + }); + } +} void OctreeProcessor::clear() { if (_tree) { _tree->withWriteLock([&] { diff --git a/libraries/octree/src/OctreeProcessor.h b/libraries/octree/src/OctreeProcessor.h index 1bc3bd10f9..bc5618e657 100644 --- a/libraries/octree/src/OctreeProcessor.h +++ b/libraries/octree/src/OctreeProcessor.h @@ -43,6 +43,7 @@ public: virtual void init(); /// clears the tree + virtual void clearNonLocalEntities(); virtual void clear(); float getAverageElementsPerPacket() const { return _elementsPerPacket.getAverage(); } diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index 8fd6d4eada..d5ded6f909 100755 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -109,7 +109,8 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { } _dynamicsWorld = nullptr; } - int32_t collisionGroup = computeCollisionGroup(); + int32_t collisionMask = computeCollisionMask(); + int32_t collisionGroup = BULLET_COLLISION_GROUP_MY_AVATAR; if (_rigidBody) { updateMassProperties(); } @@ -117,7 +118,7 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { // add to new world _dynamicsWorld = world; _pendingFlags &= ~PENDING_FLAG_JUMP; - _dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR); + _dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, collisionMask); _dynamicsWorld->addAction(this); // restore gravity settings because adding an object to the world overwrites its gravity setting _rigidBody->setGravity(_currentGravity * _currentUp); @@ -127,7 +128,7 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { assert(shape && shape->getShapeType() == CONVEX_HULL_SHAPE_PROXYTYPE); _ghost.setCharacterShape(static_cast(shape)); } - _ghost.setCollisionGroupAndMask(collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ collisionGroup)); + _ghost.setCollisionGroupAndMask(collisionGroup, collisionMask & (~ collisionGroup)); _ghost.setCollisionWorld(_dynamicsWorld); _ghost.setRadiusAndHalfHeight(_radius, _halfHeight); if (_rigidBody) { @@ -384,8 +385,8 @@ static const char* stateToStr(CharacterController::State state) { #endif // #ifdef DEBUG_STATE_CHANGE void CharacterController::updateCurrentGravity() { - int32_t collisionGroup = computeCollisionGroup(); - if (_state == State::Hover || collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS) { + int32_t collisionMask = computeCollisionMask(); + if (_state == State::Hover || collisionMask == BULLET_COLLISION_MASK_COLLISIONLESS) { _currentGravity = 0.0f; } else { _currentGravity = _gravity; @@ -458,28 +459,7 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const void CharacterController::setCollisionless(bool collisionless) { if (collisionless != _collisionless) { _collisionless = collisionless; - _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_GROUP; - } -} - -int32_t CharacterController::computeCollisionGroup() const { - if (_collisionless) { - return _collisionlessAllowed ? BULLET_COLLISION_GROUP_COLLISIONLESS : BULLET_COLLISION_GROUP_MY_AVATAR; - } else { - return BULLET_COLLISION_GROUP_MY_AVATAR; - } -} - -void CharacterController::handleChangedCollisionGroup() { - if (_pendingFlags & PENDING_FLAG_UPDATE_COLLISION_GROUP) { - // ATM the easiest way to update collision groups is to remove/re-add the RigidBody - if (_dynamicsWorld) { - _dynamicsWorld->removeRigidBody(_rigidBody); - int32_t collisionGroup = computeCollisionGroup(); - _dynamicsWorld->addRigidBody(_rigidBody, collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR); - } - _pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_GROUP; - updateCurrentGravity(); + _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK; } } @@ -567,8 +547,8 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel btScalar angle = motor.rotation.getAngle(); btVector3 velocity = worldVelocity.rotate(axis, -angle); - int32_t collisionGroup = computeCollisionGroup(); - if (collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS || + int32_t collisionMask = computeCollisionMask(); + if (collisionMask == BULLET_COLLISION_MASK_COLLISIONLESS || _state == State::Hover || motor.hTimescale == motor.vTimescale) { // modify velocity btScalar tau = dt / motor.hTimescale; @@ -708,11 +688,11 @@ void CharacterController::updateState() { btVector3 rayStart = _position; btScalar rayLength = _radius; - int32_t collisionGroup = computeCollisionGroup(); - if (collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) { - rayLength += _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT; - } else { + int32_t collisionMask = computeCollisionMask(); + if (collisionMask == BULLET_COLLISION_MASK_COLLISIONLESS) { rayLength += MIN_HOVER_HEIGHT; + } else { + rayLength += _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT; } btVector3 rayEnd = rayStart - rayLength * _currentUp; @@ -746,69 +726,7 @@ void CharacterController::updateState() { // disable normal state transitions while collisionless const btScalar MAX_WALKING_SPEED = 2.65f; - if (collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) { - switch (_state) { - case State::Ground: - if (!rayHasHit && !_hasSupport) { - SET_STATE(State::Hover, "no ground detected"); - } else if (_pendingFlags & PENDING_FLAG_JUMP && _jumpButtonDownCount != _takeoffJumpButtonID) { - _takeoffJumpButtonID = _jumpButtonDownCount; - _takeoffToInAirStartTime = now; - SET_STATE(State::Takeoff, "jump pressed"); - } else if (rayHasHit && !_hasSupport && _floorDistance > GROUND_TO_FLY_THRESHOLD) { - SET_STATE(State::InAir, "falling"); - } - break; - case State::Takeoff: - if (!rayHasHit && !_hasSupport) { - SET_STATE(State::Hover, "no ground"); - } else if ((now - _takeoffToInAirStartTime) > TAKE_OFF_TO_IN_AIR_PERIOD) { - SET_STATE(State::InAir, "takeoff done"); - - // compute jumpSpeed based on the scaled jump height for the default avatar in default gravity. - const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT); - const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight); - velocity += jumpSpeed * _currentUp; - _rigidBody->setLinearVelocity(velocity); - } - break; - case State::InAir: { - const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT); - const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight); - if ((velocity.dot(_currentUp) <= (jumpSpeed / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) { - SET_STATE(State::Ground, "hit ground"); - } else if (_flyingAllowed) { - btVector3 desiredVelocity = _targetVelocity; - if (desiredVelocity.length2() < MIN_TARGET_SPEED_SQUARED) { - desiredVelocity = btVector3(0.0f, 0.0f, 0.0f); - } - bool vertTargetSpeedIsNonZero = desiredVelocity.dot(_currentUp) > MIN_TARGET_SPEED; - if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (_takeoffJumpButtonID != _jumpButtonDownCount)) { - SET_STATE(State::Hover, "double jump button"); - } else if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD) { - SET_STATE(State::Hover, "jump button held"); - } else if (_floorDistance > _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT) { - // Transition to hover if we are above the fall threshold - SET_STATE(State::Hover, "above fall threshold"); - } - } else if (!rayHasHit && !_hasSupport) { - SET_STATE(State::Hover, "no ground detected"); - } - break; - } - case State::Hover: - btScalar horizontalSpeed = (velocity - velocity.dot(_currentUp) * _currentUp).length(); - bool flyingFast = horizontalSpeed > (MAX_WALKING_SPEED * 0.75f); - if (!_flyingAllowed && rayHasHit) { - SET_STATE(State::InAir, "flying not allowed"); - } else if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) { - SET_STATE(State::InAir, "near ground"); - } else if (((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport) && !flyingFast) { - SET_STATE(State::Ground, "touching ground"); - } - break; - } - } else { + if (collisionMask == BULLET_COLLISION_MASK_COLLISIONLESS) { // when collisionless: only switch between State::Ground and State::Hover // and bypass state debugging if (rayHasHit) { @@ -820,6 +738,68 @@ void CharacterController::updateState() { } else { _state = State::Hover; } + } else { + switch (_state) { + case State::Ground: + if (!rayHasHit && !_hasSupport) { + SET_STATE(State::Hover, "no ground detected"); + } else if (_pendingFlags & PENDING_FLAG_JUMP && _jumpButtonDownCount != _takeoffJumpButtonID) { + _takeoffJumpButtonID = _jumpButtonDownCount; + _takeoffToInAirStartTime = now; + SET_STATE(State::Takeoff, "jump pressed"); + } else if (rayHasHit && !_hasSupport && _floorDistance > GROUND_TO_FLY_THRESHOLD) { + SET_STATE(State::InAir, "falling"); + } + break; + case State::Takeoff: + if (!rayHasHit && !_hasSupport) { + SET_STATE(State::Hover, "no ground"); + } else if ((now - _takeoffToInAirStartTime) > TAKE_OFF_TO_IN_AIR_PERIOD) { + SET_STATE(State::InAir, "takeoff done"); + + // compute jumpSpeed based on the scaled jump height for the default avatar in default gravity. + const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT); + const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight); + velocity += jumpSpeed * _currentUp; + _rigidBody->setLinearVelocity(velocity); + } + break; + case State::InAir: { + const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT); + const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight); + if ((velocity.dot(_currentUp) <= (jumpSpeed / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) { + SET_STATE(State::Ground, "hit ground"); + } else if (_flyingAllowed) { + btVector3 desiredVelocity = _targetVelocity; + if (desiredVelocity.length2() < MIN_TARGET_SPEED_SQUARED) { + desiredVelocity = btVector3(0.0f, 0.0f, 0.0f); + } + bool vertTargetSpeedIsNonZero = desiredVelocity.dot(_currentUp) > MIN_TARGET_SPEED; + if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (_takeoffJumpButtonID != _jumpButtonDownCount)) { + SET_STATE(State::Hover, "double jump button"); + } else if ((jumpButtonHeld || vertTargetSpeedIsNonZero) && (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD) { + SET_STATE(State::Hover, "jump button held"); + } else if (_floorDistance > _scaleFactor * DEFAULT_AVATAR_FALL_HEIGHT) { + // Transition to hover if we are above the fall threshold + SET_STATE(State::Hover, "above fall threshold"); + } + } else if (!rayHasHit && !_hasSupport) { + SET_STATE(State::Hover, "no ground detected"); + } + break; + } + case State::Hover: + btScalar horizontalSpeed = (velocity - velocity.dot(_currentUp) * _currentUp).length(); + bool flyingFast = horizontalSpeed > (MAX_WALKING_SPEED * 0.75f); + if (!_flyingAllowed && rayHasHit) { + SET_STATE(State::InAir, "flying not allowed"); + } else if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) { + SET_STATE(State::InAir, "near ground"); + } else if (((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport) && !flyingFast) { + SET_STATE(State::Ground, "touching ground"); + } + break; + } } } @@ -866,6 +846,6 @@ void CharacterController::setFlyingAllowed(bool value) { void CharacterController::setCollisionlessAllowed(bool value) { if (value != _collisionlessAllowed) { _collisionlessAllowed = value; - _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_GROUP; + _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK; } } diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h old mode 100644 new mode 100755 index 50db2bea12..cac37da0b9 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -30,7 +30,7 @@ const uint32_t PENDING_FLAG_ADD_TO_SIMULATION = 1U << 0; const uint32_t PENDING_FLAG_REMOVE_FROM_SIMULATION = 1U << 1; const uint32_t PENDING_FLAG_UPDATE_SHAPE = 1U << 2; const uint32_t PENDING_FLAG_JUMP = 1U << 3; -const uint32_t PENDING_FLAG_UPDATE_COLLISION_GROUP = 1U << 4; +const uint32_t PENDING_FLAG_UPDATE_COLLISION_MASK = 1U << 4; const uint32_t PENDING_FLAG_RECOMPUTE_FLYING = 1U << 5; const float DEFAULT_MIN_FLOOR_NORMAL_DOT_UP = cosf(PI / 3.0f); @@ -120,14 +120,16 @@ public: bool isStuck() const { return _isStuck; } void setCollisionless(bool collisionless); - int32_t computeCollisionGroup() const; - void handleChangedCollisionGroup(); + + virtual int32_t computeCollisionMask() const = 0; + virtual void handleChangedCollisionMask() = 0; bool getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation); void setFlyingAllowed(bool value); void setCollisionlessAllowed(bool value); + void setPendingFlagsUpdateCollisionMask(){ _pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_MASK; } protected: #ifdef DEBUG_STATE_CHANGE diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index dd906fe5c1..9814e358c3 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -306,6 +306,8 @@ const btCollisionShape* EntityMotionState::computeNewShape() { return getShapeManager()->getShape(shapeInfo); } +const uint8_t MAX_NUM_INACTIVE_UPDATES = 20; + bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { // NOTE: this method is only ever called when the entity simulation is locally owned DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync"); @@ -315,15 +317,10 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { // TODO: need to be able to detect when logic dictates we *decrease* priority // WIP: print info whenever _bidPriority mismatches what is known to the entity - if (_entity->dynamicDataNeedsTransmit()) { - return true; - } - int numSteps = simulationStep - _lastStep; float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP; if (_numInactiveUpdates > 0) { - const uint8_t MAX_NUM_INACTIVE_UPDATES = 20; if (_numInactiveUpdates > MAX_NUM_INACTIVE_UPDATES) { // clear local ownership (stop sending updates) and let the server clear itself _entity->clearSimulationOwnership(); @@ -451,8 +448,13 @@ void EntityMotionState::updateSendVelocities() { if (!_body->isKinematicObject()) { clearObjectVelocities(); } - // we pretend we sent the inactive update for this object - _numInactiveUpdates = 1; + if (_entity->getEntityHostType() == entity::HostType::AVATAR) { + // AvatarEntities only ever need to send one update (their updates are sent over a lossless protocol) + // so we set the count to the max to prevent resends + _numInactiveUpdates = MAX_NUM_INACTIVE_UPDATES; + } else { + ++_numInactiveUpdates; + } } else { glm::vec3 gravity = _entity->getGravity(); diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index 32d8486e7a..13fa75f030 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -14,6 +14,11 @@ #include #include +//#define HIFI_PLUGINMANAGER_DEBUG +#if defined(HIFI_PLUGINMANAGER_DEBUG) +#include +#endif + #include #include @@ -44,33 +49,24 @@ PluginManagerPointer PluginManager::getInstance() { return DependencyManager::get(); } -QString getPluginNameFromMetaData(QJsonObject object) { +QString getPluginNameFromMetaData(const QJsonObject& object) { static const char* METADATA_KEY = "MetaData"; static const char* NAME_KEY = "name"; - - if (!object.contains(METADATA_KEY) || !object[METADATA_KEY].isObject()) { - return QString(); - } - - auto metaDataObject = object[METADATA_KEY].toObject(); - - if (!metaDataObject.contains(NAME_KEY) || !metaDataObject[NAME_KEY].isString()) { - return QString(); - } - - return metaDataObject[NAME_KEY].toString(); + return object[METADATA_KEY][NAME_KEY].toString(""); } -QString getPluginIIDFromMetaData(QJsonObject object) { +QString getPluginIIDFromMetaData(const QJsonObject& object) { static const char* IID_KEY = "IID"; - - if (!object.contains(IID_KEY) || !object[IID_KEY].isString()) { - return QString(); - } - - return object[IID_KEY].toString(); + return object[IID_KEY].toString(""); } +int getPluginInterfaceVersionFromMetaData(const QJsonObject& object) { + static const QString METADATA_KEY = "MetaData"; + static const QString NAME_KEY = "version"; + return object[METADATA_KEY][NAME_KEY].toInt(0); +} + + QStringList preferredDisplayPlugins; QStringList disabledDisplays; QStringList disabledInputs; @@ -88,10 +84,7 @@ bool isDisabled(QJsonObject metaData) { return false; } -using Loader = QSharedPointer; -using LoaderList = QList; - -const LoaderList& getLoadedPlugins() { + auto PluginManager::getLoadedPlugins() const -> const LoaderList& { static std::once_flag once; static LoaderList loadedPlugins; std::call_once(once, [&] { @@ -115,13 +108,29 @@ const LoaderList& getLoadedPlugins() { for (auto plugin : candidates) { qCDebug(plugins) << "Attempting plugin" << qPrintable(plugin); QSharedPointer loader(new QPluginLoader(pluginPath + plugin)); - - if (isDisabled(loader->metaData())) { - qWarning() << "Plugin" << qPrintable(plugin) << "is disabled"; + const QJsonObject pluginMetaData = loader->metaData(); +#if defined(HIFI_PLUGINMANAGER_DEBUG) + QJsonDocument metaDataDoc(pluginMetaData); + qCInfo(plugins) << "Metadata for " << qPrintable(plugin) << ": " << QString(metaDataDoc.toJson()); +#endif + if (isDisabled(pluginMetaData)) { + qCWarning(plugins) << "Plugin" << qPrintable(plugin) << "is disabled"; // Skip this one, it's disabled continue; } + if (!_pluginFilter(pluginMetaData)) { + qCDebug(plugins) << "Plugin" << qPrintable(plugin) << "doesn't pass provided filter"; + continue; + } + + if (getPluginInterfaceVersionFromMetaData(pluginMetaData) != HIFI_PLUGIN_INTERFACE_VERSION) { + qCWarning(plugins) << "Plugin" << qPrintable(plugin) << "interface version doesn't match, not loading:" + << getPluginInterfaceVersionFromMetaData(pluginMetaData) + << "doesn't match" << HIFI_PLUGIN_INTERFACE_VERSION; + continue; + } + if (loader->load()) { qCDebug(plugins) << "Plugin" << qPrintable(plugin) << "loaded successfully"; loadedPlugins.push_back(loader); diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h index c7489fd7e4..e340b2fa21 100644 --- a/libraries/plugins/src/plugins/PluginManager.h +++ b/libraries/plugins/src/plugins/PluginManager.h @@ -13,8 +13,7 @@ #include "Forward.h" - -class PluginManager; +class QPluginLoader; using PluginManagerPointer = QSharedPointer; class PluginManager : public QObject, public Dependency { @@ -47,6 +46,9 @@ public: void setInputPluginSettingsPersister(const InputPluginSettingsPersister& persister); QStringList getRunningInputDeviceNames() const; + using PluginFilter = std::function; + void setPluginFilter(PluginFilter pluginFilter) { _pluginFilter = pluginFilter; } + signals: void inputDeviceRunningChanged(const QString& pluginName, bool isRunning, const QStringList& runningDevices); @@ -60,4 +62,19 @@ private: PluginContainer* _container { nullptr }; DisplayPluginList _displayPlugins; InputPluginList _inputPlugins; + PluginFilter _pluginFilter { [](const QJsonObject&) { return true; } }; + + using Loader = QSharedPointer; + using LoaderList = QList; + + const LoaderList& getLoadedPlugins() const; }; + +// TODO: we should define this value in CMake, and then use CMake +// templating to generate the individual plugin.json files, so that we +// don't have to update every plugin.json file whenever we update this +// value. The value should match "version" in +// plugins/*/src/plugin.json +// plugins/oculus/src/oculus.json +// etc +static const int HIFI_PLUGIN_INTERFACE_VERSION = 1; diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index 7a52ad77da..bb2e784807 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -49,8 +49,6 @@ template <> void payloadRender(const MeshPartPayload::Pointer& payload, RenderAr } } -const graphics::MaterialPointer MeshPartPayload::DEFAULT_MATERIAL = std::make_shared(); - MeshPartPayload::MeshPartPayload(const std::shared_ptr& mesh, int partIndex, graphics::MaterialPointer material) { updateMeshPart(mesh, partIndex); addMaterial(graphics::MaterialLayer(material, 0)); @@ -85,11 +83,13 @@ void MeshPartPayload::updateKey(const render::ItemKey& key) { ItemKey::Builder builder(key); builder.withTypeShape(); - if (topMaterialExists()) { - auto matKey = _drawMaterials.top().material->getKey(); - if (matKey.isTranslucent()) { - builder.withTransparent(); - } + if (_drawMaterials.needsUpdate()) { + RenderPipelines::updateMultiMaterial(_drawMaterials); + } + + auto matKey = _drawMaterials.getMaterialKey(); + if (matKey.isTranslucent()) { + builder.withTransparent(); } _itemKey = builder.build(); @@ -104,10 +104,7 @@ Item::Bound MeshPartPayload::getBound() const { } ShapeKey MeshPartPayload::getShapeKey() const { - graphics::MaterialKey drawMaterialKey; - if (topMaterialExists()) { - drawMaterialKey = _drawMaterials.top().material->getKey(); - } + graphics::MaterialKey drawMaterialKey = _drawMaterials.getMaterialKey(); ShapeKey::Builder builder; builder.withMaterial(); @@ -158,7 +155,7 @@ void MeshPartPayload::render(RenderArgs* args) { // apply material properties if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) { - RenderPipelines::bindMaterial(!_drawMaterials.empty() ? _drawMaterials.top().material : DEFAULT_MATERIAL, batch, args->_enableTexturing); + RenderPipelines::bindMaterials(_drawMaterials, batch, args->_enableTexturing); args->_details._materialSwitches++; } @@ -332,11 +329,13 @@ void ModelMeshPartPayload::updateKey(const render::ItemKey& key) { builder.withDeformed(); } - if (topMaterialExists()) { - auto matKey = _drawMaterials.top().material->getKey(); - if (matKey.isTranslucent()) { - builder.withTransparent(); - } + if (_drawMaterials.needsUpdate()) { + RenderPipelines::updateMultiMaterial(_drawMaterials); + } + + auto matKey = _drawMaterials.getMaterialKey(); + if (matKey.isTranslucent()) { + builder.withTransparent(); } _itemKey = builder.build(); @@ -348,11 +347,12 @@ void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, PrimitiveMode pr return; } - graphics::MaterialKey drawMaterialKey; - if (topMaterialExists()) { - drawMaterialKey = _drawMaterials.top().material->getKey(); + if (_drawMaterials.needsUpdate()) { + RenderPipelines::updateMultiMaterial(_drawMaterials); } + graphics::MaterialKey drawMaterialKey = _drawMaterials.getMaterialKey(); + bool isTranslucent = drawMaterialKey.isTranslucent(); bool hasTangents = drawMaterialKey.isNormalMap() && _hasTangents; bool hasLightmap = drawMaterialKey.isLightmapMap(); @@ -435,7 +435,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) { // apply material properties if (args->_renderMode != render::Args::RenderMode::SHADOW_RENDER_MODE) { - RenderPipelines::bindMaterial(!_drawMaterials.empty() ? _drawMaterials.top().material : DEFAULT_MATERIAL, batch, args->_enableTexturing); + RenderPipelines::bindMaterials(_drawMaterials, batch, args->_enableTexturing); args->_details._materialSwitches++; } diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 3b0590b4a9..deae91dda9 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -66,18 +66,15 @@ public: graphics::Mesh::Part _drawPart; size_t getVerticesCount() const { return _drawMesh ? _drawMesh->getNumVertices() : 0; } - size_t getMaterialTextureSize() { return topMaterialExists() ? _drawMaterials.top().material->getTextureSize() : 0; } - int getMaterialTextureCount() { return topMaterialExists() ? _drawMaterials.top().material->getTextureCount() : 0; } - bool hasTextureInfo() const { return topMaterialExists() ? _drawMaterials.top().material->hasTextureInfo() : false; } + size_t getMaterialTextureSize() { return _drawMaterials.getTextureSize(); } + int getMaterialTextureCount() { return _drawMaterials.getTextureCount(); } + bool hasTextureInfo() const { return _drawMaterials.hasTextureInfo(); } void addMaterial(graphics::MaterialLayer material); void removeMaterial(graphics::MaterialPointer material); protected: - static const graphics::MaterialPointer DEFAULT_MATERIAL; render::ItemKey _itemKey{ render::ItemKey::Builder::opaqueShape().build() }; - - bool topMaterialExists() const { return !_drawMaterials.empty() && _drawMaterials.top().material; } }; namespace render { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 4140d33499..da8dceb176 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1475,29 +1475,56 @@ bool Model::isRenderable() const { return !_meshStates.empty() || (isLoaded() && _renderGeometry->getMeshes().empty()); } -std::vector Model::getMeshIDsFromMaterialID(QString parentMaterialName) { - // try to find all meshes with materials that match parentMaterialName as a string - // if none, return parentMaterialName as a uint - std::vector toReturn; - const QString MATERIAL_NAME_PREFIX = "mat::"; - if (parentMaterialName.startsWith(MATERIAL_NAME_PREFIX)) { - parentMaterialName.replace(0, MATERIAL_NAME_PREFIX.size(), QString("")); - for (unsigned int i = 0; i < (unsigned int)_modelMeshMaterialNames.size(); i++) { - if (_modelMeshMaterialNames[i] == parentMaterialName.toStdString()) { - toReturn.push_back(i); - } - } - } +std::set Model::getMeshIDsFromMaterialID(QString parentMaterialName) { + std::set toReturn; - if (toReturn.empty()) { - toReturn.push_back(parentMaterialName.toUInt()); + const QString all("all"); + if (parentMaterialName == all) { + for (unsigned int i = 0; i < (unsigned int)_modelMeshRenderItemIDs.size(); i++) { + toReturn.insert(i); + } + } else if (!parentMaterialName.isEmpty()) { + auto parseFunc = [this, &toReturn] (QString& target) { + if (target.isEmpty()) { + return; + } + // if target starts with "mat::", try to find all meshes with materials that match target as a string + // otherwise, return target as a uint + const QString MATERIAL_NAME_PREFIX("mat::"); + if (target.startsWith(MATERIAL_NAME_PREFIX)) { + std::string targetStdString = target.replace(0, MATERIAL_NAME_PREFIX.size(), "").toStdString(); + for (unsigned int i = 0; i < (unsigned int)_modelMeshMaterialNames.size(); i++) { + if (_modelMeshMaterialNames[i] == targetStdString) { + toReturn.insert(i); + } + } + return; + } + toReturn.insert(target.toUInt()); + }; + + if (parentMaterialName.length() > 2 && parentMaterialName.startsWith("[") && parentMaterialName.endsWith("]")) { + QStringList list = parentMaterialName.split(",", QString::SkipEmptyParts); + for (int i = 0; i < list.length(); i++) { + auto& target = list[i]; + if (i == 0) { + target = target.replace(0, 1, ""); + } + if (i == list.length() - 1) { + target = target.replace(target.length() - 1, 1, ""); + } + parseFunc(target); + } + } else { + parseFunc(parentMaterialName); + } } return toReturn; } void Model::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { - std::vector shapeIDs = getMeshIDsFromMaterialID(QString(parentMaterialName.c_str())); + std::set shapeIDs = getMeshIDsFromMaterialID(QString(parentMaterialName.c_str())); render::Transaction transaction; for (auto shapeID : shapeIDs) { if (shapeID < _modelMeshRenderItemIDs.size()) { @@ -1520,7 +1547,7 @@ void Model::addMaterial(graphics::MaterialLayer material, const std::string& par } void Model::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { - std::vector shapeIDs = getMeshIDsFromMaterialID(QString(parentMaterialName.c_str())); + std::set shapeIDs = getMeshIDsFromMaterialID(QString(parentMaterialName.c_str())); render::Transaction transaction; for (auto shapeID : shapeIDs) { if (shapeID < _modelMeshRenderItemIDs.size()) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 3d251605ea..16e08c2b23 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -510,7 +510,7 @@ private: void calculateTextureInfo(); - std::vector getMeshIDsFromMaterialID(QString parentMaterialName); + std::set getMeshIDsFromMaterialID(QString parentMaterialName); }; Q_DECLARE_METATYPE(ModelPointer) diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index cd685a54a1..07dc683719 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -308,24 +308,33 @@ void addPlumberPipeline(ShapePlumber& plumber, void batchSetter(const ShapePipeline& pipeline, gpu::Batch& batch, RenderArgs* args) { // Set a default albedo map - batch.setResourceTexture(gr::Texture::MaterialAlbedo, - DependencyManager::get()->getWhiteTexture()); + batch.setResourceTexture(gr::Texture::MaterialAlbedo, DependencyManager::get()->getWhiteTexture()); // Set a default material if (pipeline.locations->materialBufferUnit) { // Create a default schema - static bool isMaterialSet = false; - static graphics::Material material; - if (!isMaterialSet) { - material.setAlbedo(vec3(1.0f)); - material.setOpacity(1.0f); - material.setMetallic(0.1f); - material.setRoughness(0.9f); - isMaterialSet = true; - } + static gpu::BufferView schemaBuffer; + static std::once_flag once; + std::call_once(once, [] { + graphics::MultiMaterial::Schema schema; + graphics::MaterialKey schemaKey; - // Set a default schema - batch.setUniformBuffer(gr::Buffer::Material, material.getSchemaBuffer()); + schema._albedo = vec3(1.0f); + schema._opacity = 1.0f; + schema._metallic = 0.1f; + schema._roughness = 0.9f; + + schemaKey.setAlbedo(true); + schemaKey.setTranslucentFactor(false); + schemaKey.setMetallic(true); + schemaKey.setGlossy(true); + schema._key = (uint32_t)schemaKey._flags.to_ulong(); + + auto schemaSize = sizeof(graphics::MultiMaterial::Schema); + schemaBuffer = gpu::BufferView(std::make_shared(schemaSize, (const gpu::Byte*) &schema, schemaSize)); + }); + + batch.setUniformBuffer(gr::Buffer::Material, schemaBuffer); } } @@ -364,103 +373,400 @@ void initZPassPipelines(ShapePlumber& shapePlumber, gpu::StatePointer state, con gpu::Shader::createProgram(deformed_model_shadow_fade_dq), state, extraBatchSetter, itemSetter); } -// FIXME find a better way to setup the default textures -void RenderPipelines::bindMaterial(const graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures) { - if (!material) { +void RenderPipelines::bindMaterial(graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures) { + graphics::MultiMaterial multiMaterial; + multiMaterial.push(graphics::MaterialLayer(material, 0)); + bindMaterials(multiMaterial, batch, enableTextures); +} + +void RenderPipelines::updateMultiMaterial(graphics::MultiMaterial& multiMaterial) { + auto& schemaBuffer = multiMaterial.getSchemaBuffer(); + + if (multiMaterial.size() == 0) { + schemaBuffer.edit() = graphics::MultiMaterial::Schema(); return; } + auto& drawMaterialTextures = multiMaterial.getTextureTable(); + multiMaterial.setTexturesLoading(false); + + // The total list of things we need to look for + static std::set allFlags; + static std::once_flag once; + std::call_once(once, [] { + for (int i = 0; i < graphics::Material::NUM_TOTAL_FLAGS; i++) { + // The opacity mask/map are derived from the albedo map + if (i != graphics::MaterialKey::OPACITY_MASK_MAP_BIT && + i != graphics::MaterialKey::OPACITY_TRANSLUCENT_MAP_BIT) { + allFlags.insert(i); + } + } + }); + + graphics::MultiMaterial materials = multiMaterial; + graphics::MultiMaterial::Schema schema; + graphics::MaterialKey schemaKey; + + std::set flagsToCheck = allFlags; + std::set flagsToSetDefault; + + while (!materials.empty()) { + auto material = materials.top().material; + if (!material) { + break; + } + materials.pop(); + + bool defaultFallthrough = material->getDefaultFallthrough(); + const auto& materialKey = material->getKey(); + const auto& textureMaps = material->getTextureMaps(); + + auto it = flagsToCheck.begin(); + while (it != flagsToCheck.end()) { + auto flag = *it; + bool fallthrough = defaultFallthrough || material->getPropertyFallthrough(flag); + + bool wasSet = false; + bool forceDefault = false; + switch (flag) { + case graphics::MaterialKey::EMISSIVE_VAL_BIT: + if (materialKey.isEmissive()) { + schema._emissive = material->getEmissive(false); + schemaKey.setEmissive(true); + wasSet = true; + } + break; + case graphics::MaterialKey::UNLIT_VAL_BIT: + if (materialKey.isUnlit()) { + schemaKey.setUnlit(true); + wasSet = true; + } + break; + case graphics::MaterialKey::ALBEDO_VAL_BIT: + if (materialKey.isAlbedo()) { + schema._albedo = material->getAlbedo(false); + schemaKey.setAlbedo(true); + wasSet = true; + } + break; + case graphics::MaterialKey::METALLIC_VAL_BIT: + if (materialKey.isMetallic()) { + schema._metallic = material->getMetallic(); + schemaKey.setMetallic(true); + wasSet = true; + } + break; + case graphics::MaterialKey::GLOSSY_VAL_BIT: + if (materialKey.isRough() || materialKey.isGlossy()) { + schema._roughness = material->getRoughness(); + schemaKey.setGlossy(materialKey.isGlossy()); + wasSet = true; + } + break; + case graphics::MaterialKey::OPACITY_VAL_BIT: + if (materialKey.isTranslucentFactor()) { + schema._opacity = material->getOpacity(); + schemaKey.setTranslucentFactor(true); + wasSet = true; + } + break; + case graphics::MaterialKey::SCATTERING_VAL_BIT: + if (materialKey.isScattering()) { + schema._scattering = material->getScattering(); + schemaKey.setScattering(true); + wasSet = true; + } + break; + case graphics::MaterialKey::ALBEDO_MAP_BIT: + if (materialKey.isAlbedoMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::ALBEDO_MAP); + if (itr != textureMaps.end()) { + if (itr->second->isDefined()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialAlbedo, itr->second->getTextureView()); + wasSet = true; + } else { + multiMaterial.setTexturesLoading(true); + forceDefault = true; + } + } else { + forceDefault = true; + } + schemaKey.setAlbedoMap(true); + schemaKey.setOpacityMaskMap(materialKey.isOpacityMaskMap()); + schemaKey.setTranslucentMap(materialKey.isTranslucentMap()); + } + break; + case graphics::MaterialKey::METALLIC_MAP_BIT: + if (materialKey.isMetallicMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::METALLIC_MAP); + if (itr != textureMaps.end()) { + if (itr->second->isDefined()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialMetallic, itr->second->getTextureView()); + wasSet = true; + } else { + multiMaterial.setTexturesLoading(true); + forceDefault = true; + } + } else { + forceDefault = true; + } + schemaKey.setMetallicMap(true); + } + break; + case graphics::MaterialKey::ROUGHNESS_MAP_BIT: + if (materialKey.isRoughnessMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::ROUGHNESS_MAP); + if (itr != textureMaps.end()) { + if (itr->second->isDefined()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialRoughness, itr->second->getTextureView()); + wasSet = true; + } else { + multiMaterial.setTexturesLoading(true); + forceDefault = true; + } + } else { + forceDefault = true; + } + schemaKey.setRoughnessMap(true); + } + break; + case graphics::MaterialKey::NORMAL_MAP_BIT: + if (materialKey.isNormalMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::NORMAL_MAP); + if (itr != textureMaps.end()) { + if (itr->second->isDefined()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialNormal, itr->second->getTextureView()); + wasSet = true; + } else { + multiMaterial.setTexturesLoading(true); + forceDefault = true; + } + } else { + forceDefault = true; + } + schemaKey.setNormalMap(true); + } + break; + case graphics::MaterialKey::OCCLUSION_MAP_BIT: + if (materialKey.isOcclusionMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::OCCLUSION_MAP); + if (itr != textureMaps.end()) { + if (itr->second->isDefined()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialOcclusion, itr->second->getTextureView()); + wasSet = true; + } else { + multiMaterial.setTexturesLoading(true); + forceDefault = true; + } + } else { + forceDefault = true; + } + schemaKey.setOcclusionMap(true); + } + break; + case graphics::MaterialKey::SCATTERING_MAP_BIT: + if (materialKey.isScatteringMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::SCATTERING_MAP); + if (itr != textureMaps.end()) { + if (itr->second->isDefined()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialScattering, itr->second->getTextureView()); + wasSet = true; + } else { + multiMaterial.setTexturesLoading(true); + forceDefault = true; + } + } else { + forceDefault = true; + } + schemaKey.setScattering(true); + } + break; + case graphics::MaterialKey::EMISSIVE_MAP_BIT: + // Lightmap takes precendence over emissive map for legacy reasons + if (materialKey.isEmissiveMap() && !materialKey.isLightmapMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::EMISSIVE_MAP); + if (itr != textureMaps.end()) { + if (itr->second->isDefined()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, itr->second->getTextureView()); + wasSet = true; + } else { + multiMaterial.setTexturesLoading(true); + forceDefault = true; + } + } else { + forceDefault = true; + } + schemaKey.setEmissiveMap(true); + } else if (materialKey.isLightmapMap()) { + // We'll set this later when we check the lightmap + wasSet = true; + } + break; + case graphics::MaterialKey::LIGHTMAP_MAP_BIT: + if (materialKey.isLightmapMap()) { + auto itr = textureMaps.find(graphics::MaterialKey::LIGHTMAP_MAP); + if (itr != textureMaps.end()) { + if (itr->second->isDefined()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, itr->second->getTextureView()); + wasSet = true; + } else { + multiMaterial.setTexturesLoading(true); + forceDefault = true; + } + } else { + forceDefault = true; + } + schemaKey.setLightmapMap(true); + } + break; + case graphics::Material::TEXCOORDTRANSFORM0: + if (!fallthrough) { + schema._texcoordTransforms[0] = material->getTexCoordTransform(0); + wasSet = true; + } + break; + case graphics::Material::TEXCOORDTRANSFORM1: + if (!fallthrough) { + schema._texcoordTransforms[1] = material->getTexCoordTransform(1); + wasSet = true; + } + break; + case graphics::Material::LIGHTMAP_PARAMS: + if (!fallthrough) { + schema._lightmapParams = material->getLightmapParams(); + wasSet = true; + } + break; + case graphics::Material::MATERIAL_PARAMS: + if (!fallthrough) { + schema._materialParams = material->getMaterialParams(); + wasSet = true; + } + break; + default: + break; + } + + if (wasSet) { + flagsToCheck.erase(it++); + } else if (forceDefault || !fallthrough) { + flagsToSetDefault.insert(flag); + flagsToCheck.erase(it++); + } else { + ++it; + } + } + + if (flagsToCheck.empty()) { + break; + } + } + + for (auto flagBit : flagsToCheck) { + flagsToSetDefault.insert(flagBit); + } + + auto textureCache = DependencyManager::get(); + // Handle defaults + for (auto flag : flagsToSetDefault) { + switch (flag) { + case graphics::MaterialKey::EMISSIVE_VAL_BIT: + case graphics::MaterialKey::UNLIT_VAL_BIT: + case graphics::MaterialKey::ALBEDO_VAL_BIT: + case graphics::MaterialKey::METALLIC_VAL_BIT: + case graphics::MaterialKey::GLOSSY_VAL_BIT: + case graphics::MaterialKey::OPACITY_VAL_BIT: + case graphics::MaterialKey::SCATTERING_VAL_BIT: + case graphics::Material::TEXCOORDTRANSFORM0: + case graphics::Material::TEXCOORDTRANSFORM1: + case graphics::Material::LIGHTMAP_PARAMS: + case graphics::Material::MATERIAL_PARAMS: + // these are initialized to the correct default values in Schema() + break; + case graphics::MaterialKey::ALBEDO_MAP_BIT: + if (schemaKey.isAlbedoMap()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialAlbedo, textureCache->getWhiteTexture()); + } + break; + case graphics::MaterialKey::METALLIC_MAP_BIT: + if (schemaKey.isMetallicMap()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialMetallic, textureCache->getBlackTexture()); + } + break; + case graphics::MaterialKey::ROUGHNESS_MAP_BIT: + if (schemaKey.isRoughnessMap()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialRoughness, textureCache->getWhiteTexture()); + } + break; + case graphics::MaterialKey::NORMAL_MAP_BIT: + if (schemaKey.isNormalMap()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialNormal, textureCache->getBlueTexture()); + } + break; + case graphics::MaterialKey::OCCLUSION_MAP_BIT: + if (schemaKey.isOcclusionMap()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialOcclusion, textureCache->getWhiteTexture()); + } + break; + case graphics::MaterialKey::SCATTERING_MAP_BIT: + if (schemaKey.isScatteringMap()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialScattering, textureCache->getWhiteTexture()); + } + break; + case graphics::MaterialKey::EMISSIVE_MAP_BIT: + if (schemaKey.isEmissiveMap() && !schemaKey.isLightmapMap()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getGrayTexture()); + } + break; + case graphics::MaterialKey::LIGHTMAP_MAP_BIT: + if (schemaKey.isLightmapMap()) { + drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getBlackTexture()); + } + break; + default: + break; + } + } + + schema._key = (uint32_t)schemaKey._flags.to_ulong(); + schemaBuffer.edit() = schema; + multiMaterial.setNeedsUpdate(false); +} + +void RenderPipelines::bindMaterials(graphics::MultiMaterial& multiMaterial, gpu::Batch& batch, bool enableTextures) { + if (multiMaterial.size() == 0) { + return; + } + + if (multiMaterial.needsUpdate() || multiMaterial.areTexturesLoading()) { + updateMultiMaterial(multiMaterial); + } + auto textureCache = DependencyManager::get(); - batch.setUniformBuffer(gr::Buffer::Material, material->getSchemaBuffer()); + static gpu::TextureTablePointer defaultMaterialTextures = std::make_shared(); + static std::once_flag once; + std::call_once(once, [textureCache] { + defaultMaterialTextures->setTexture(gr::Texture::MaterialAlbedo, textureCache->getWhiteTexture()); + defaultMaterialTextures->setTexture(gr::Texture::MaterialMetallic, textureCache->getBlackTexture()); + defaultMaterialTextures->setTexture(gr::Texture::MaterialRoughness, textureCache->getWhiteTexture()); + defaultMaterialTextures->setTexture(gr::Texture::MaterialNormal, textureCache->getBlueTexture()); + defaultMaterialTextures->setTexture(gr::Texture::MaterialOcclusion, textureCache->getWhiteTexture()); + defaultMaterialTextures->setTexture(gr::Texture::MaterialScattering, textureCache->getWhiteTexture()); + // MaterialEmissiveLightmap has to be set later + }); - const auto& materialKey = material->getKey(); - const auto& textureMaps = material->getTextureMaps(); - - int numUnlit = 0; - if (materialKey.isUnlit()) { - numUnlit++; - } - - const auto& drawMaterialTextures = material->getTextureTable(); - - // Albedo - if (materialKey.isAlbedoMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::ALBEDO_MAP); - if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(gr::Texture::MaterialAlbedo, itr->second->getTextureView()); - } else { - drawMaterialTextures->setTexture(gr::Texture::MaterialAlbedo, textureCache->getWhiteTexture()); + auto& schemaBuffer = multiMaterial.getSchemaBuffer(); + batch.setUniformBuffer(gr::Buffer::Material, schemaBuffer); + if (enableTextures) { + batch.setResourceTextureTable(multiMaterial.getTextureTable()); + } else { + auto key = multiMaterial.getMaterialKey(); + if (key.isLightmapMap()) { + defaultMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getBlackTexture()); + } else if (key.isEmissiveMap()) { + defaultMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getGrayTexture()); } + batch.setResourceTextureTable(defaultMaterialTextures); } - - // Roughness map - if (materialKey.isRoughnessMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::ROUGHNESS_MAP); - if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(gr::Texture::MaterialRoughness, itr->second->getTextureView()); - } else { - drawMaterialTextures->setTexture(gr::Texture::MaterialRoughness, textureCache->getWhiteTexture()); - } - } - - // Normal map - if (materialKey.isNormalMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::NORMAL_MAP); - if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(gr::Texture::MaterialNormal, itr->second->getTextureView()); - } else { - drawMaterialTextures->setTexture(gr::Texture::MaterialNormal, textureCache->getBlueTexture()); - } - } - - // Metallic map - if (materialKey.isMetallicMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::METALLIC_MAP); - if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(gr::Texture::MaterialMetallic, itr->second->getTextureView()); - } else { - drawMaterialTextures->setTexture(gr::Texture::MaterialMetallic, textureCache->getBlackTexture()); - } - } - - // Occlusion map - if (materialKey.isOcclusionMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::OCCLUSION_MAP); - if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(gr::Texture::MaterialOcclusion, itr->second->getTextureView()); - } else { - drawMaterialTextures->setTexture(gr::Texture::MaterialOcclusion, textureCache->getWhiteTexture()); - } - } - - // Scattering map - if (materialKey.isScatteringMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::SCATTERING_MAP); - if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(gr::Texture::MaterialScattering, itr->second->getTextureView()); - } else { - drawMaterialTextures->setTexture(gr::Texture::MaterialScattering, textureCache->getWhiteTexture()); - } - } - - // Emissive / Lightmap - if (materialKey.isLightmapMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::LIGHTMAP_MAP); - - if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, itr->second->getTextureView()); - } else { - drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getGrayTexture()); - } - } else if (materialKey.isEmissiveMap()) { - auto itr = textureMaps.find(graphics::MaterialKey::EMISSIVE_MAP); - if (enableTextures && itr != textureMaps.end() && itr->second->isDefined()) { - drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, itr->second->getTextureView()); - } else { - drawMaterialTextures->setTexture(gr::Texture::MaterialEmissiveLightmap, textureCache->getBlackTexture()); - } - } - - batch.setResourceTextureTable(material->getTextureTable()); } diff --git a/libraries/render-utils/src/RenderPipelines.h b/libraries/render-utils/src/RenderPipelines.h index b7d22bc72d..0f3d1160ef 100644 --- a/libraries/render-utils/src/RenderPipelines.h +++ b/libraries/render-utils/src/RenderPipelines.h @@ -15,7 +15,9 @@ class RenderPipelines { public: - static void bindMaterial(const graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures); + static void bindMaterial(graphics::MaterialPointer& material, gpu::Batch& batch, bool enableTextures); + static void updateMultiMaterial(graphics::MultiMaterial& multiMaterial); + static void bindMaterials(graphics::MultiMaterial& multiMaterial, gpu::Batch& batch, bool enableTextures); }; diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index bfa6f5e5d1..5456453d8a 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -61,12 +61,11 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende // FIXME: calling this here before the zones/lights are drawn during the deferred/forward passes means we're actually using the frames from the previous draw // Fetch the current frame stacks from all the stages // Starting with the Light Frame genreated in previous tasks - - const auto& lightFrame = input.getN(0); const auto setupOutput = task.addJob("ShadowSetup", input); const auto queryResolution = setupOutput.getN(1); const auto shadowFrame = setupOutput.getN(3); + const auto currentKeyLight = setupOutput.getN(4); // Fetch and cull the items from the scene static const auto shadowCasterReceiverFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask); @@ -108,7 +107,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende antiFrustum = cascadeFrustums[i - 2]; } - const auto cullInputs = CullShadowBounds::Inputs(sortedShapes, shadowFilter, antiFrustum, lightFrame, cascadeSetupOutput.getN(2)).asVarying(); + const auto cullInputs = CullShadowBounds::Inputs(sortedShapes, shadowFilter, antiFrustum, currentKeyLight, cascadeSetupOutput.getN(2)).asVarying(); sprintf(jobName, "CullShadowCascade%d", i); const auto culledShadowItemsAndBounds = task.addJob(jobName, cullInputs); @@ -356,16 +355,17 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, c _shadowFrameCache->_objects.clear(); output.edit3() = _shadowFrameCache; - if (!lightingModel->isShadowEnabled() || !lightStage->getCurrentKeyLight(lightFrame) || !lightStage->getCurrentKeyLight(lightFrame)->getCastShadows()) { + const auto currentKeyLight = lightStage->getCurrentKeyLight(lightFrame); + if (!lightingModel->isShadowEnabled() || !currentKeyLight || !currentKeyLight->getCastShadows()) { renderContext->taskFlow.abortTask(); return; } + output.edit4() = currentKeyLight; // Cache old render args RenderArgs* args = renderContext->args; output.edit0() = args->_renderMode; - output.edit1() = glm::ivec2(0, 0); // Save main camera frustum *_cameraFrustum = args->getViewFrustum(); output.edit2() = _cameraFrustum; @@ -373,75 +373,72 @@ void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, c if (!_globalShadowObject) { _globalShadowObject = std::make_shared(graphics::LightPointer(), SHADOW_MAX_DISTANCE, SHADOW_CASCADE_COUNT); } - - const auto theGlobalLight = lightStage->getCurrentKeyLight(lightFrame); - if (theGlobalLight && theGlobalLight->getCastShadows()) { - _globalShadowObject->setLight(theGlobalLight); - _globalShadowObject->setKeylightFrustum(args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR); - auto& firstCascade = _globalShadowObject->getCascade(0); - auto& firstCascadeFrustum = firstCascade.getFrustum(); - unsigned int cascadeIndex; + _globalShadowObject->setLight(currentKeyLight); + _globalShadowObject->setKeylightFrustum(args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR); - // Adjust each cascade frustum - for (cascadeIndex = 0; cascadeIndex < _globalShadowObject->getCascadeCount(); ++cascadeIndex) { - auto& bias = _bias[cascadeIndex]; - _globalShadowObject->setKeylightCascadeFrustum(cascadeIndex, args->getViewFrustum(), - SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR, - bias._constant, bias._slope); - } + auto& firstCascade = _globalShadowObject->getCascade(0); + auto& firstCascadeFrustum = firstCascade.getFrustum(); + unsigned int cascadeIndex; - _shadowFrameCache->pushShadow(_globalShadowObject); - - // Now adjust coarse frustum bounds - auto frustumPosition = firstCascadeFrustum->getPosition(); - auto farTopLeft = firstCascadeFrustum->getFarTopLeft() - frustumPosition; - auto farBottomRight = firstCascadeFrustum->getFarBottomRight() - frustumPosition; - - auto left = glm::dot(farTopLeft, firstCascadeFrustum->getRight()); - auto right = glm::dot(farBottomRight, firstCascadeFrustum->getRight()); - auto top = glm::dot(farTopLeft, firstCascadeFrustum->getUp()); - auto bottom = glm::dot(farBottomRight, firstCascadeFrustum->getUp()); - auto near = firstCascadeFrustum->getNearClip(); - auto far = firstCascadeFrustum->getFarClip(); - - for (cascadeIndex = 1; cascadeIndex < _globalShadowObject->getCascadeCount(); ++cascadeIndex) { - auto& cascadeFrustum = _globalShadowObject->getCascade(cascadeIndex).getFrustum(); - - farTopLeft = cascadeFrustum->getFarTopLeft() - frustumPosition; - farBottomRight = cascadeFrustum->getFarBottomRight() - frustumPosition; - - auto cascadeLeft = glm::dot(farTopLeft, cascadeFrustum->getRight()); - auto cascadeRight = glm::dot(farBottomRight, cascadeFrustum->getRight()); - auto cascadeTop = glm::dot(farTopLeft, cascadeFrustum->getUp()); - auto cascadeBottom = glm::dot(farBottomRight, cascadeFrustum->getUp()); - auto cascadeNear = cascadeFrustum->getNearClip(); - auto cascadeFar = cascadeFrustum->getFarClip(); - left = glm::min(left, cascadeLeft); - right = glm::max(right, cascadeRight); - bottom = glm::min(bottom, cascadeBottom); - top = glm::max(top, cascadeTop); - near = glm::min(near, cascadeNear); - far = glm::max(far, cascadeFar); - } - - _coarseShadowFrustum->setPosition(firstCascadeFrustum->getPosition()); - _coarseShadowFrustum->setOrientation(firstCascadeFrustum->getOrientation()); - _coarseShadowFrustum->setProjection(glm::ortho(left, right, bottom, top, near, far)); - _coarseShadowFrustum->calculate(); - - // Push frustum for further culling and selection - args->pushViewFrustum(*_coarseShadowFrustum); - - args->_renderMode = RenderArgs::SHADOW_RENDER_MODE; - - // We want for the octree query enough resolution to catch the details in the lowest cascade. So compute - // the desired resolution for the first cascade frustum and extrapolate it to the coarse frustum. - glm::ivec2 queryResolution = firstCascade.framebuffer->getSize(); - queryResolution.x = int(queryResolution.x * _coarseShadowFrustum->getWidth() / firstCascadeFrustum->getWidth()); - queryResolution.y = int(queryResolution.y * _coarseShadowFrustum->getHeight() / firstCascadeFrustum->getHeight()); - output.edit1() = queryResolution; + // Adjust each cascade frustum + for (cascadeIndex = 0; cascadeIndex < _globalShadowObject->getCascadeCount(); ++cascadeIndex) { + auto& bias = _bias[cascadeIndex]; + _globalShadowObject->setKeylightCascadeFrustum(cascadeIndex, args->getViewFrustum(), + SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR, + bias._constant, bias._slope); } + + _shadowFrameCache->pushShadow(_globalShadowObject); + + // Now adjust coarse frustum bounds + auto frustumPosition = firstCascadeFrustum->getPosition(); + auto farTopLeft = firstCascadeFrustum->getFarTopLeft() - frustumPosition; + auto farBottomRight = firstCascadeFrustum->getFarBottomRight() - frustumPosition; + + auto left = glm::dot(farTopLeft, firstCascadeFrustum->getRight()); + auto right = glm::dot(farBottomRight, firstCascadeFrustum->getRight()); + auto top = glm::dot(farTopLeft, firstCascadeFrustum->getUp()); + auto bottom = glm::dot(farBottomRight, firstCascadeFrustum->getUp()); + auto near = firstCascadeFrustum->getNearClip(); + auto far = firstCascadeFrustum->getFarClip(); + + for (cascadeIndex = 1; cascadeIndex < _globalShadowObject->getCascadeCount(); ++cascadeIndex) { + auto& cascadeFrustum = _globalShadowObject->getCascade(cascadeIndex).getFrustum(); + + farTopLeft = cascadeFrustum->getFarTopLeft() - frustumPosition; + farBottomRight = cascadeFrustum->getFarBottomRight() - frustumPosition; + + auto cascadeLeft = glm::dot(farTopLeft, cascadeFrustum->getRight()); + auto cascadeRight = glm::dot(farBottomRight, cascadeFrustum->getRight()); + auto cascadeTop = glm::dot(farTopLeft, cascadeFrustum->getUp()); + auto cascadeBottom = glm::dot(farBottomRight, cascadeFrustum->getUp()); + auto cascadeNear = cascadeFrustum->getNearClip(); + auto cascadeFar = cascadeFrustum->getFarClip(); + left = glm::min(left, cascadeLeft); + right = glm::max(right, cascadeRight); + bottom = glm::min(bottom, cascadeBottom); + top = glm::max(top, cascadeTop); + near = glm::min(near, cascadeNear); + far = glm::max(far, cascadeFar); + } + + _coarseShadowFrustum->setPosition(firstCascadeFrustum->getPosition()); + _coarseShadowFrustum->setOrientation(firstCascadeFrustum->getOrientation()); + _coarseShadowFrustum->setProjection(glm::ortho(left, right, bottom, top, near, far)); + _coarseShadowFrustum->calculate(); + + // Push frustum for further culling and selection + args->pushViewFrustum(*_coarseShadowFrustum); + + args->_renderMode = RenderArgs::SHADOW_RENDER_MODE; + + // We want for the octree query enough resolution to catch the details in the lowest cascade. So compute + // the desired resolution for the first cascade frustum and extrapolate it to the coarse frustum. + glm::ivec2 queryResolution = firstCascade.framebuffer->getSize(); + queryResolution.x = int(queryResolution.x * _coarseShadowFrustum->getWidth() / firstCascadeFrustum->getWidth()); + queryResolution.y = int(queryResolution.y * _coarseShadowFrustum->getHeight() / firstCascadeFrustum->getHeight()); + output.edit1() = queryResolution; } void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderContext, const Inputs& input, Outputs& output) { @@ -539,20 +536,20 @@ void CullShadowBounds::run(const render::RenderContextPointer& renderContext, co outShapes.clear(); outBounds = AABox(); - const auto& lightFrame = *inputs.get3(); + const auto currentKeyLight = inputs.get3(); auto cullFunctor = inputs.get4(); render::CullFunctor shadowCullFunctor = [cullFunctor](const RenderArgs* args, const AABox& bounds) { return cullFunctor(args, bounds); }; - if (!filter.selectsNothing()) { + if (!filter.selectsNothing() && currentKeyLight) { auto& details = args->_details.edit(RenderDetails::SHADOW); render::CullTest test(shadowCullFunctor, args, details, antiFrustum); auto scene = args->_scene; auto lightStage = renderContext->_scene->getStage(); assert(lightStage); - const auto globalLightDir = lightStage->getCurrentKeyLight(lightFrame)->getDirection(); + const auto globalLightDir = currentKeyLight->getDirection(); auto castersFilter = render::ItemFilter::Builder(filter).withShadowCaster().build(); const auto& receiversFilter = filter; diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index 271c3e8200..7e7d59763e 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -104,7 +104,7 @@ signals: class RenderShadowSetup { public: using Input = RenderShadowTask::Input; - using Output = render::VaryingSet4; + using Output = render::VaryingSet5; using Config = RenderShadowSetupConfig; using JobModel = render::Job::ModelIO; @@ -161,7 +161,7 @@ public: class CullShadowBounds { public: - using Inputs = render::VaryingSet5; + using Inputs = render::VaryingSet5; using Outputs = render::VaryingSet2; using JobModel = render::Job::ModelIO; diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf index 582549ade1..469c0976aa 100644 --- a/libraries/render-utils/src/simple.slf +++ b/libraries/render-utils/src/simple.slf @@ -91,14 +91,14 @@ void main(void) { position.xyz, #endif normal, - vec3(0.0), + diffuse, DEFAULT_SPECULAR, - DEFAULT_EMISSIVE, + emissive, 1.0, - DEFAULT_ROUGHNESS, - DEFAULT_METALLIC, - DEFAULT_OCCLUSION, - DEFAULT_SCATTERING + roughness, + metallic, + occlusion, + scattering ); #if defined(PROCEDURAL_V3) diff --git a/libraries/render-utils/src/simple_transparent.slf b/libraries/render-utils/src/simple_transparent.slf index ea444d6113..6d8348f50c 100644 --- a/libraries/render-utils/src/simple_transparent.slf +++ b/libraries/render-utils/src/simple_transparent.slf @@ -92,22 +92,22 @@ void main(void) { emissive = vec3(clamp(emissiveAmount, 0.0, 1.0)); #elif defined(PROCEDURAL_V3) || defined(PROCEDURAL_V4) #if defined(PROCEDURAL_V3) - ProceduralFragment proceduralData = { + ProceduralFragment proceduralData = ProceduralFragment( #else vec4 position = cam._viewInverse * _positionES; - ProceduralFragmentWithPosition proceduralData = { + ProceduralFragmentWithPosition proceduralData = ProceduralFragmentWithPosition( position.xyz, #endif normal, - vec3(0.0), - DEFAULT_SPECULAR, - DEFAULT_EMISSIVE, - 1.0, - DEFAULT_ROUGHNESS, - DEFAULT_METALLIC, - DEFAULT_OCCLUSION, + diffuse, + fresnel, + emissive, + alpha, + roughness, + metallic, + occlusion, DEFAULT_SCATTERING - }; + ); #if defined(PROCEDURAL_V3) emissiveAmount = getProceduralFragment(proceduralData); diff --git a/libraries/script-engine/src/ScriptCache.cpp b/libraries/script-engine/src/ScriptCache.cpp index 9922b125f4..b23aa48762 100644 --- a/libraries/script-engine/src/ScriptCache.cpp +++ b/libraries/script-engine/src/ScriptCache.cpp @@ -87,7 +87,7 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable if (_scriptCache.contains(url) && !forceDownload) { auto scriptContent = _scriptCache[url]; lock.unlock(); - qCDebug(scriptengine) << "Found script in cache:" << url.toString(); + qCDebug(scriptengine) << "Found script in cache:" << url.fileName(); contentAvailable(url.toString(), scriptContent, true, true, STATUS_CACHED); } else { auto& scriptRequest = _activeScriptRequests[url]; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 8dad5932be..fa7a8e1114 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -164,7 +164,9 @@ ScriptEnginePointer scriptEngineFactory(ScriptEngine::Context context, const QString& fileNameString) { ScriptEngine* engine = new ScriptEngine(context, scriptContents, fileNameString); ScriptEnginePointer engineSP = ScriptEnginePointer(engine); - DependencyManager::get()->addScriptEngine(qSharedPointerCast(engineSP)); + auto scriptEngines = DependencyManager::get(); + scriptEngines->addScriptEngine(qSharedPointerCast(engineSP)); + engine->setScriptEngines(scriptEngines); return engineSP; } @@ -259,7 +261,7 @@ bool ScriptEngine::isDebugMode() const { } ScriptEngine::~ScriptEngine() { - auto scriptEngines = DependencyManager::get(); + QSharedPointer scriptEngines(_scriptEngines); if (scriptEngines) { scriptEngines->removeScriptEngine(qSharedPointerCast(sharedFromThis())); } @@ -555,6 +557,10 @@ using ScriptableResourceRawPtr = ScriptableResource*; static QScriptValue scriptableResourceToScriptValue(QScriptEngine* engine, const ScriptableResourceRawPtr& resource) { + if (!resource) { + return QScriptValue(); // probably shutting down + } + // The first script to encounter this resource will track its memory. // In this way, it will be more likely to GC. // This fails in the case that the resource is used across many scripts, but @@ -1012,7 +1018,8 @@ QScriptValue ScriptEngine::evaluateInClosure(const QScriptValue& closure, const } QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fileName, int lineNumber) { - if (DependencyManager::get()->isStopped()) { + QSharedPointer scriptEngines(_scriptEngines); + if (!scriptEngines || scriptEngines->isStopped()) { return QScriptValue(); // bail early } @@ -1062,7 +1069,8 @@ void ScriptEngine::run() { auto name = filenameParts.size() > 0 ? filenameParts[filenameParts.size() - 1] : "unknown"; PROFILE_SET_THREAD_NAME("Script: " + name); - if (DependencyManager::get()->isStopped()) { + QSharedPointer scriptEngines(_scriptEngines); + if (!scriptEngines || scriptEngines->isStopped()) { return; // bail early - avoid setting state in init(), as evaluate() will bail too } @@ -1319,8 +1327,8 @@ void ScriptEngine::updateMemoryCost(const qint64& deltaSize) { void ScriptEngine::timerFired() { { - auto engine = DependencyManager::get(); - if (!engine || engine->isStopped()) { + QSharedPointer scriptEngines(_scriptEngines); + if (!scriptEngines || scriptEngines->isStopped()) { scriptWarningMessage("Script.timerFired() while shutting down is ignored... parent script:" + getFilename()); return; // bail early } @@ -1373,7 +1381,8 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int } QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) { - if (DependencyManager::get()->isStopped()) { + QSharedPointer scriptEngines(_scriptEngines); + if (!scriptEngines || scriptEngines->isStopped()) { scriptWarningMessage("Script.setInterval() while shutting down is ignored... parent script:" + getFilename()); return NULL; // bail early } @@ -1382,7 +1391,8 @@ QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) } QObject* ScriptEngine::setTimeout(const QScriptValue& function, int timeoutMS) { - if (DependencyManager::get()->isStopped()) { + QSharedPointer scriptEngines(_scriptEngines); + if (!scriptEngines || scriptEngines->isStopped()) { scriptWarningMessage("Script.setTimeout() while shutting down is ignored... parent script:" + getFilename()); return NULL; // bail early } @@ -1595,7 +1605,7 @@ QScriptValue ScriptEngine::newModule(const QString& modulePath, const QScriptVal auto closure = newObject(); auto exports = newObject(); auto module = newObject(); - qCDebug(scriptengine_module) << "newModule" << modulePath << parent.property("filename").toString(); + qCDebug(scriptengine_module) << "newModule" << parent.property("filename").toString(); closure.setProperty("module", module, READONLY_PROP_FLAGS); @@ -1813,7 +1823,8 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return; } - if (DependencyManager::get()->isStopped()) { + QSharedPointer scriptEngines(_scriptEngines); + if (!scriptEngines || scriptEngines->isStopped()) { scriptWarningMessage("Script.include() while shutting down is ignored... includeFiles:" + includeFiles.join(",") + "parent script:" + getFilename()); return; // bail early @@ -1907,7 +1918,8 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac } void ScriptEngine::include(const QString& includeFile, QScriptValue callback) { - if (DependencyManager::get()->isStopped()) { + QSharedPointer scriptEngines(_scriptEngines); + if (!scriptEngines || scriptEngines->isStopped()) { scriptWarningMessage("Script.include() while shutting down is ignored... includeFile:" + includeFile + "parent script:" + getFilename()); return; // bail early @@ -1925,7 +1937,8 @@ void ScriptEngine::load(const QString& loadFile) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return; } - if (DependencyManager::get()->isStopped()) { + QSharedPointer scriptEngines(_scriptEngines); + if (!scriptEngines || scriptEngines->isStopped()) { scriptWarningMessage("Script.load() while shutting down is ignored... loadFile:" + loadFile + "parent script:" + getFilename()); return; // bail early @@ -2073,10 +2086,11 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& } PROFILE_RANGE(script, __FUNCTION__); - if (isStopping() || DependencyManager::get()->isStopped()) { + QSharedPointer scriptEngines(_scriptEngines); + if (isStopping() || !scriptEngines || scriptEngines->isStopped()) { qCDebug(scriptengine) << "loadEntityScript.start " << entityID.toString() << " but isStopping==" << isStopping() - << " || engines->isStopped==" << DependencyManager::get()->isStopped(); + << " || engines->isStopped==" << scriptEngines->isStopped(); return; } @@ -2393,6 +2407,11 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldR } } +QList ScriptEngine::getListOfEntityScriptIDs() { + QReadLocker locker{ &_entityScriptsLock }; + return _entityScripts.keys(); +} + void ScriptEngine::unloadAllEntityScripts() { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 8fe50aee78..8753010089 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -145,6 +145,9 @@ public: QString getFilename() const; + + QList getListOfEntityScriptIDs(); + /**jsdoc * Stop the current script. * @function Script.stop @@ -563,6 +566,8 @@ public: bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const; bool hasEntityScriptDetails(const EntityItemID& entityID) const; + void setScriptEngines(QSharedPointer& scriptEngines) { _scriptEngines = scriptEngines; } + public slots: /**jsdoc @@ -814,6 +819,8 @@ protected: static const QString _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS; Setting::Handle _enableExtendedJSExceptions { _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS, true }; + + QWeakPointer _scriptEngines; }; ScriptEnginePointer scriptEngineFactory(ScriptEngine::Context context, diff --git a/libraries/shared/src/JointData.h b/libraries/shared/src/JointData.h index f4c8b89e7a..7a2420262a 100644 --- a/libraries/shared/src/JointData.h +++ b/libraries/shared/src/JointData.h @@ -14,7 +14,7 @@ public: }; // Used by the avatar mixer to describe a single joint -// Translations relative to their parent and are in meters. +// Translations relative to their parent joint // Rotations are absolute (i.e. not relative to parent) and are in rig space. class JointData { public: diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 8805205361..c524e3183b 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -108,7 +108,8 @@ SpatiallyNestablePointer SpatiallyNestable::getParentPointer(bool& success) cons return nullptr; } - if (parent && parent->getID() == parentID) { + if (parent && (parent->getID() == parentID || + (parentID == AVATAR_SELF_ID && parent->isMyAvatar()))) { // parent pointer is up-to-date if (!_parentKnowsMe) { SpatialParentTree* parentTree = parent->getParentTree(); diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index a55f32896b..319f07236b 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -47,6 +47,8 @@ public: virtual const QUuid getParentID() const; virtual void setParentID(const QUuid& parentID); + virtual bool isMyAvatar() const { return false; } + virtual quint16 getParentJointIndex() const { return _parentJointIndex; } virtual void setParentJointIndex(quint16 parentJointIndex); diff --git a/plugins/hifiCodec/src/plugin.json b/plugins/hifiCodec/src/plugin.json index df26a67ea8..27391a484d 100644 --- a/plugins/hifiCodec/src/plugin.json +++ b/plugins/hifiCodec/src/plugin.json @@ -1 +1,4 @@ -{"name":"HiFi 4:1 Audio Codec"} +{ + "name":"HiFi 4:1 Audio Codec", + "version":1 +} diff --git a/plugins/hifiKinect/src/plugin.json b/plugins/hifiKinect/src/plugin.json index daa3a668dd..b401bb8c83 100644 --- a/plugins/hifiKinect/src/plugin.json +++ b/plugins/hifiKinect/src/plugin.json @@ -1 +1,4 @@ -{"name":"Kinect"} +{ + "name":"Kinect", + "version":1 +} diff --git a/plugins/hifiLeapMotion/src/plugin.json b/plugins/hifiLeapMotion/src/plugin.json index 2e867d96e4..92e410ef11 100644 --- a/plugins/hifiLeapMotion/src/plugin.json +++ b/plugins/hifiLeapMotion/src/plugin.json @@ -1 +1,4 @@ -{"name":"Leap Motion"} +{ + "name":"Leap Motion", + "version":1 +} diff --git a/plugins/hifiNeuron/src/plugin.json b/plugins/hifiNeuron/src/plugin.json index d153b5cebd..7059248934 100644 --- a/plugins/hifiNeuron/src/plugin.json +++ b/plugins/hifiNeuron/src/plugin.json @@ -1 +1,4 @@ -{"name":"Neuron"} +{ + "name":"Neuron", + "version":1 +} diff --git a/plugins/hifiSdl2/src/Joystick.cpp b/plugins/hifiSdl2/src/Joystick.cpp index be4ad6e4ee..ad0b459544 100644 --- a/plugins/hifiSdl2/src/Joystick.cpp +++ b/plugins/hifiSdl2/src/Joystick.cpp @@ -46,8 +46,8 @@ void Joystick::closeJoystick() { void Joystick::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { for (auto axisState : _axisStateMap) { - if (fabsf(axisState.second) < CONTROLLER_THRESHOLD) { - _axisStateMap[axisState.first] = 0.0f; + if (fabsf(axisState.second.value) < CONTROLLER_THRESHOLD) { + _axisStateMap[axisState.first].value = 0.0f; } } } @@ -59,7 +59,7 @@ void Joystick::focusOutEvent() { void Joystick::handleAxisEvent(const SDL_ControllerAxisEvent& event) { SDL_GameControllerAxis axis = (SDL_GameControllerAxis) event.axis; - _axisStateMap[makeInput((controller::StandardAxisChannel)axis).getChannel()] = (float)event.value / MAX_AXIS; + _axisStateMap[makeInput((controller::StandardAxisChannel)axis).getChannel()].value = (float)event.value / MAX_AXIS; } void Joystick::handleButtonEvent(const SDL_ControllerButtonEvent& event) { diff --git a/plugins/hifiSdl2/src/plugin.json b/plugins/hifiSdl2/src/plugin.json index a65846ecab..e1963f9995 100644 --- a/plugins/hifiSdl2/src/plugin.json +++ b/plugins/hifiSdl2/src/plugin.json @@ -1 +1,4 @@ -{"name":"SDL2"} +{ + "name":"SDL2", + "version":1 +} diff --git a/plugins/hifiSixense/src/plugin.json b/plugins/hifiSixense/src/plugin.json index 9e6e15a354..a56a1ba384 100644 --- a/plugins/hifiSixense/src/plugin.json +++ b/plugins/hifiSixense/src/plugin.json @@ -1 +1,4 @@ -{"name":"Sixense"} +{ + "name":"Sixense", + "version":1 +} diff --git a/plugins/hifiSpacemouse/src/plugin.json b/plugins/hifiSpacemouse/src/plugin.json index 294f436039..6eac13ac66 100644 --- a/plugins/hifiSpacemouse/src/plugin.json +++ b/plugins/hifiSpacemouse/src/plugin.json @@ -1 +1,4 @@ -{"name":"Spacemouse"} +{ + "name":"Spacemouse", + "version":1 +} diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index 392d990638..76ff4a1755 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -258,15 +258,15 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, using namespace controller; // Axes const auto& inputState = _parent._touchInputState; - _axisStateMap[LX] = inputState.Thumbstick[ovrHand_Left].x; - _axisStateMap[LY] = inputState.Thumbstick[ovrHand_Left].y; - _axisStateMap[LT] = inputState.IndexTrigger[ovrHand_Left]; - _axisStateMap[LEFT_GRIP] = inputState.HandTrigger[ovrHand_Left]; + _axisStateMap[LX].value = inputState.Thumbstick[ovrHand_Left].x; + _axisStateMap[LY].value = inputState.Thumbstick[ovrHand_Left].y; + _axisStateMap[LT].value = inputState.IndexTrigger[ovrHand_Left]; + _axisStateMap[LEFT_GRIP].value = inputState.HandTrigger[ovrHand_Left]; - _axisStateMap[RX] = inputState.Thumbstick[ovrHand_Right].x; - _axisStateMap[RY] = inputState.Thumbstick[ovrHand_Right].y; - _axisStateMap[RT] = inputState.IndexTrigger[ovrHand_Right]; - _axisStateMap[RIGHT_GRIP] = inputState.HandTrigger[ovrHand_Right]; + _axisStateMap[RX].value = inputState.Thumbstick[ovrHand_Right].x; + _axisStateMap[RY].value = inputState.Thumbstick[ovrHand_Right].y; + _axisStateMap[RT].value = inputState.IndexTrigger[ovrHand_Right]; + _axisStateMap[RIGHT_GRIP].value = inputState.HandTrigger[ovrHand_Right]; // Buttons for (const auto& pair : BUTTON_MAP) { diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index 29691e73a5..548afb97ab 100644 --- a/plugins/oculus/src/OculusHelpers.cpp +++ b/plugins/oculus/src/OculusHelpers.cpp @@ -82,15 +82,18 @@ private: } #ifdef OCULUS_APP_ID - if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) { - if (ovr_PlatformInitializeWindows(OCULUS_APP_ID) != ovrPlatformInitialize_Success) { - qCWarning(oculusLog) << "Unable to initialize the platform for entitlement check - fail the check" << ovr::getError(); - return; - } else { - qCDebug(oculusLog) << "Performing Oculus Platform entitlement check"; - ovr_Entitlement_GetIsViewerEntitled(); + static std::once_flag once; + std::call_once(once, []() { + if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) { + if (ovr_PlatformInitializeWindows(OCULUS_APP_ID) != ovrPlatformInitialize_Success) { + qCWarning(oculusLog) << "Unable to initialize the platform for entitlement check - fail the check" << ovr::getError(); + return; + } else { + qCDebug(oculusLog) << "Performing Oculus Platform entitlement check"; + ovr_Entitlement_GetIsViewerEntitled(); + } } - } + }); #endif ovrGraphicsLuid luid; diff --git a/plugins/oculus/src/oculus.json b/plugins/oculus/src/oculus.json index 86546c8dd5..0043a8b50f 100644 --- a/plugins/oculus/src/oculus.json +++ b/plugins/oculus/src/oculus.json @@ -1 +1,4 @@ -{"name":"Oculus Rift"} +{ + "name":"Oculus Rift", + "version":1 +} diff --git a/plugins/oculusLegacy/src/oculus.json b/plugins/oculusLegacy/src/oculus.json index 86546c8dd5..0043a8b50f 100644 --- a/plugins/oculusLegacy/src/oculus.json +++ b/plugins/oculusLegacy/src/oculus.json @@ -1 +1,4 @@ -{"name":"Oculus Rift"} +{ + "name":"Oculus Rift", + "version":1 +} diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 53c23403a5..3aea5f1ce0 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -928,8 +928,8 @@ void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxi const float CENTER_DEADBAND = 0.6f; const float DIAGONAL_DIVIDE_IN_RADIANS = PI / 4.0f; if (_buttonPressedMap.find(sButton) != _buttonPressedMap.end()) { - float absX = abs(_axisStateMap[xAxis]); - float absY = abs(_axisStateMap[yAxis]); + float absX = abs(_axisStateMap[xAxis].value); + float absY = abs(_axisStateMap[yAxis].value); glm::vec2 cartesianQuadrantI(absX, absY); float angle = glm::atan(cartesianQuadrantI.y / cartesianQuadrantI.x); float radius = glm::length(cartesianQuadrantI); @@ -956,10 +956,10 @@ void ViveControllerManager::InputDevice::handleAxisEvent(float deltaTime, uint32 } else { stick = _filteredRightStick.process(deltaTime, stick); } - _axisStateMap[isLeftHand ? LX : RX] = stick.x; - _axisStateMap[isLeftHand ? LY : RY] = stick.y; + _axisStateMap[isLeftHand ? LX : RX].value = stick.x; + _axisStateMap[isLeftHand ? LY : RY].value = stick.y; } else if (axis == vr::k_EButton_SteamVR_Trigger) { - _axisStateMap[isLeftHand ? LT : RT] = x; + _axisStateMap[isLeftHand ? LT : RT].value = x; // The click feeling on the Vive controller trigger represents a value of *precisely* 1.0, // so we can expose that as an additional button if (x >= 1.0f) { @@ -1000,7 +1000,7 @@ void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint if (button == vr::k_EButton_ApplicationMenu) { _buttonPressedMap.insert(isLeftHand ? LEFT_APP_MENU : RIGHT_APP_MENU); } else if (button == vr::k_EButton_Grip) { - _axisStateMap[isLeftHand ? LEFT_GRIP : RIGHT_GRIP] = 1.0f; + _axisStateMap[isLeftHand ? LEFT_GRIP : RIGHT_GRIP].value = 1.0f; } else if (button == vr::k_EButton_SteamVR_Trigger) { _buttonPressedMap.insert(isLeftHand ? LT : RT); } else if (button == vr::k_EButton_SteamVR_Touchpad) { @@ -1008,7 +1008,7 @@ void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint } } else { if (button == vr::k_EButton_Grip) { - _axisStateMap[isLeftHand ? LEFT_GRIP : RIGHT_GRIP] = 0.0f; + _axisStateMap[isLeftHand ? LEFT_GRIP : RIGHT_GRIP].value = 0.0f; } } diff --git a/plugins/openvr/src/plugin.json b/plugins/openvr/src/plugin.json index d68c8e68d3..763414cd8b 100644 --- a/plugins/openvr/src/plugin.json +++ b/plugins/openvr/src/plugin.json @@ -1 +1,4 @@ -{"name":"OpenVR (Vive)"} +{ + "name":"OpenVR (Vive)", + "version":1 +} diff --git a/plugins/pcmCodec/src/plugin.json b/plugins/pcmCodec/src/plugin.json index 2d86251845..525124592b 100644 --- a/plugins/pcmCodec/src/plugin.json +++ b/plugins/pcmCodec/src/plugin.json @@ -1 +1,4 @@ -{"name":"PCM Codec"} +{ + "name":"PCM Codec", + "version":1 +} diff --git a/plugins/steamClient/src/plugin.json b/plugins/steamClient/src/plugin.json index dfe37917d2..ce4647188f 100644 --- a/plugins/steamClient/src/plugin.json +++ b/plugins/steamClient/src/plugin.json @@ -1 +1,4 @@ -{"name":"Steam Client"} +{ + "name":"Steam Client", + "version":1 +} diff --git a/prebuild.py b/prebuild.py index a758dcbea2..fb54b8d6fe 100644 --- a/prebuild.py +++ b/prebuild.py @@ -43,8 +43,7 @@ def parse_args(): defaultPortsPath = hifi_utils.scriptRelative('cmake', 'ports') from argparse import ArgumentParser parser = ArgumentParser(description='Prepare build dependencies.') - parser.add_argument('--android', action='store_true') - #parser.add_argument('--android', type=str) + parser.add_argument('--android', type=str) parser.add_argument('--debug', action='store_true') parser.add_argument('--force-bootstrap', action='store_true') parser.add_argument('--force-build', action='store_true') @@ -87,6 +86,17 @@ def main(): # here shouldn't invalidte the vcpkg install) pm.cleanBuilds() + # If we're running in android mode, we also need to grab a bunch of additional binaries + # (this logic is all migrated from the old setupDependencies tasks in gradle) + if args.android: + # Find the target location + appPath = hifi_utils.scriptRelative('android/apps/' + args.android) + # Copy the non-Qt libraries specified in the config in hifi_android.py + hifi_android.copyAndroidLibs(pm.androidPackagePath, appPath) + # Determine the Qt package path + qtPath = os.path.join(pm.androidPackagePath, 'qt') + hifi_android.QtPackager(appPath, qtPath).bundle() + # Write the vcpkg config to the build directory last pm.writeConfig() diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json index 98e9088e1d..bf3ff3f324 100644 --- a/scripts/system/assets/data/createAppTooltips.json +++ b/scripts/system/assets/data/createAppTooltips.json @@ -11,9 +11,15 @@ "textColor": { "tooltip": "The color of the text." }, + "textAlpha": { + "tooltip": "The alpha of the text." + }, "backgroundColor": { "tooltip": "The color of the background." }, + "backgroundAlpha": { + "tooltip": "The alpha of the background." + }, "lineHeight": { "tooltip": "The height of each line of text. This determines the size of the text." }, @@ -21,6 +27,26 @@ "tooltip": "If enabled, determines how the entity will face the camera.", "jsPropertyName": "billboardMode" }, + "topMargin": { + "tooltip": "The top margin, in meters." + }, + "rightMargin": { + "tooltip": "The right margin, in meters." + }, + "bottomMargin": { + "tooltip": "The bottom margin, in meters." + }, + "leftMargin": { + "tooltip": "The left margin, in meters." + }, + "zoneShapeType": { + "tooltip": "The shape of the volume in which the zone's lighting effects and avatar permissions have effect.", + "jsPropertyName": "shapeType" + }, + "zoneCompoundShapeURL": { + "tooltip": "The model file to use for the compound shape if Shape Type is \"Use Compound Shape URL\".", + "jsPropertyName": "compoundShapeURL" + }, "flyingAllowed": { "tooltip": "If enabled, users can fly in the zone." }, @@ -115,7 +141,7 @@ "tooltip": "The shape of the collision hull used if collisions are enabled. This affects how an entity collides." }, "compoundShapeURL": { - "tooltip": "The OBJ file to use for the compound shape if Collision Shape is \"compound\"." + "tooltip": "The model file to use for the compound shape if Collision Shape is \"Compound\"." }, "animation.url": { "tooltip": "An animation to play on the model." diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index 2b9a738202..631c0e03e8 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -70,9 +70,9 @@ function getMyAvatarSettings() { } } -function updateAvatarWearables(avatar, callback) { +function updateAvatarWearables(avatar, callback, wearablesOverride) { executeLater(function() { - var wearables = getMyAvatarWearables(); + var wearables = wearablesOverride ? wearablesOverride : getMyAvatarWearables(); avatar[ENTRY_AVATAR_ENTITIES] = wearables; sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables}) @@ -210,7 +210,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See break; case 'adjustWearable': if(message.properties.localRotationAngles) { - message.properties.localRotation = Quat.fromVec3Degrees(message.properties.localRotationAngles) + message.properties.localRotation = Quat.fromVec3Degrees(message.properties.localRotationAngles); } Entities.editEntity(message.entityID, message.properties); @@ -235,7 +235,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See // revert changes using snapshot of wearables if(currentAvatarWearablesBackup !== null) { AvatarBookmarks.updateAvatarEntities(currentAvatarWearablesBackup); - updateAvatarWearables(currentAvatar); + updateAvatarWearables(currentAvatar, null, currentAvatarWearablesBackup); } } else { sendToQml({'method' : 'updateAvatarInBookmarks'}); diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index c61e46c8eb..b1c1bc7765 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -6,11 +6,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, Camera, print, - getControllerJointIndex, enableDispatcherModule, disableDispatcherModule, entityIsFarGrabbedByOther, - Messages, makeDispatcherModuleParameters, makeRunningValues, Settings, entityHasActions, - Vec3, Overlays, flatten, Xform, getControllerWorldLocation, ensureDynamic, entityIsCloneable, - cloneEntity, DISPATCHER_PROPERTIES, Uuid, unhighlightTargetEntity, isInEditMode, getGrabbableData +/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, Camera, print, getControllerJointIndex, + enableDispatcherModule, disableDispatcherModule, entityIsFarGrabbedByOther, Messages, makeDispatcherModuleParameters, + makeRunningValues, Settings, entityHasActions, Vec3, Overlays, flatten, Xform, getControllerWorldLocation, ensureDynamic, + entityIsCloneable, cloneEntity, DISPATCHER_PROPERTIES, Uuid, unhighlightTargetEntity, isInEditMode, getGrabbableData, + entityIsEquippable */ Script.include("/~/system/libraries/Xform.js"); @@ -767,7 +767,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var entityProperties = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); entityProperties.id = entityID; var hasEquipData = getWearableData(entityProperties); - if (hasEquipData && entityProperties.parentID === EMPTY_PARENT_ID && !entityIsFarGrabbedByOther(entityID)) { + if (hasEquipData && entityIsEquippable(entityProperties)) { entityProperties.id = entityID; var rightHandPosition = MyAvatar.getJointPosition("RightHand"); var leftHandPosition = MyAvatar.getJointPosition("LeftHand"); diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js b/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js deleted file mode 100644 index 0ba3dd6e6b..0000000000 --- a/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js +++ /dev/null @@ -1,572 +0,0 @@ -"use strict"; - -// farActionGrabEntity.js -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html - -/* jslint bitwise: true */ - -/* global Script, Controller, RIGHT_HAND, LEFT_HAND, Mat4, MyAvatar, Vec3, Camera, Quat, getEnabledModuleByName, - makeRunningValues, Entities, enableDispatcherModule, disableDispatcherModule, entityIsGrabbable, - makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, - TRIGGER_ON_VALUE, ZERO_VEC, getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, - Picks, makeLaserLockInfo, makeLaserParams, AddressManager, getEntityParents, Selection, DISPATCHER_HOVERING_LIST, - Uuid, worldPositionToRegistrationFrameMatrix, DISPATCHER_PROPERTIES -*/ - -Script.include("/~/system/libraries/controllerDispatcherUtils.js"); -Script.include("/~/system/libraries/controllers.js"); - -(function() { - - var MARGIN = 25; - - function TargetObject(entityID, entityProps) { - this.entityID = entityID; - this.entityProps = entityProps; - this.targetEntityID = null; - this.targetEntityProps = null; - - this.getTargetEntity = function() { - var parentPropsLength = this.parentProps.length; - if (parentPropsLength !== 0) { - var targetEntity = { - id: this.parentProps[parentPropsLength - 1].id, - props: this.parentProps[parentPropsLength - 1]}; - this.targetEntityID = targetEntity.id; - this.targetEntityProps = targetEntity.props; - return targetEntity; - } - this.targetEntityID = this.entityID; - this.targetEntityProps = this.entityProps; - return { - id: this.entityID, - props: this.entityProps}; - }; - } - - function FarActionGrabEntity(hand) { - this.hand = hand; - this.grabbing = false; - this.grabbedThingID = null; - this.targetObject = null; - this.actionID = null; // action this script created... - this.entityToLockOnto = null; - this.potentialEntityWithContextOverlay = false; - this.entityWithContextOverlay = false; - this.contextOverlayTimer = false; - this.locked = false; - this.highlightedEntity = null; - this.reticleMinX = MARGIN; - this.reticleMaxX = 0; - this.reticleMinY = MARGIN; - this.reticleMaxY = 0; - - var ACTION_TTL = 15; // seconds - - var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object - var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position - var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified - var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified - - this.parameters = makeDispatcherModuleParameters( - 550, - this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], - [], - 100, - makeLaserParams(this.hand, false)); - - - this.handToController = function() { - return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - }; - - this.distanceGrabTimescale = function(mass, distance) { - var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass / - DISTANCE_HOLDING_UNITY_MASS * distance / - DISTANCE_HOLDING_UNITY_DISTANCE; - if (timeScale < DISTANCE_HOLDING_ACTION_TIMEFRAME) { - timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME; - } - return timeScale; - }; - - this.getMass = function(dimensions, density) { - return (dimensions.x * dimensions.y * dimensions.z) * density; - }; - - this.startFarGrabAction = function (controllerData, grabbedProperties) { - var controllerLocation = controllerData.controllerLocations[this.hand]; - var worldControllerPosition = controllerLocation.position; - var worldControllerRotation = controllerLocation.orientation; - - // transform the position into room space - var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); - var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); - - var now = Date.now(); - - // add the action and initialize some variables - this.currentObjectPosition = grabbedProperties.position; - this.currentObjectRotation = grabbedProperties.rotation; - this.currentObjectTime = now; - this.currentCameraOrientation = Camera.orientation; - - this.grabRadius = this.grabbedDistance; - this.grabRadialVelocity = 0.0; - - // offset between controller vector at the grab radius and the entity position - var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); - targetPosition = Vec3.sum(targetPosition, worldControllerPosition); - this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); - - // compute a constant based on the initial conditions which we use below to exaggerate hand motion - // onto the held object - this.radiusScalar = Math.log(this.grabRadius + 1.0); - if (this.radiusScalar < 1.0) { - this.radiusScalar = 1.0; - } - - // compute the mass for the purpose of energy and how quickly to move object - this.mass = this.getMass(grabbedProperties.dimensions, grabbedProperties.density); - var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, grabbedProperties.position)); - var timeScale = this.distanceGrabTimescale(this.mass, distanceToObject); - this.linearTimeScale = timeScale; - this.actionID = Entities.addAction("far-grab", this.grabbedThingID, { - targetPosition: this.currentObjectPosition, - linearTimeScale: timeScale, - targetRotation: this.currentObjectRotation, - angularTimeScale: timeScale, - tag: "far-grab-" + MyAvatar.sessionUUID, - ttl: ACTION_TTL - }); - if (this.actionID === Uuid.NULL) { - this.actionID = null; - } - - if (this.actionID !== null) { - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.grabbedThingID, "startDistanceGrab", args); - } - - Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); - this.previousRoomControllerPosition = roomControllerPosition; - this.grabbing = true; - }; - - this.continueDistanceHolding = function(controllerData) { - var controllerLocation = controllerData.controllerLocations[this.hand]; - var worldControllerPosition = controllerLocation.position; - var worldControllerRotation = controllerLocation.orientation; - - // also transform the position into room space - var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); - var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); - - var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, DISPATCHER_PROPERTIES); - var now = Date.now(); - var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds - this.currentObjectTime = now; - - // the action was set up when this.distanceHolding was called. update the targets. - var radius = Vec3.distance(this.currentObjectPosition, worldControllerPosition) * - this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; - if (radius < 1.0) { - radius = 1.0; - } - - var roomHandDelta = Vec3.subtract(roomControllerPosition, this.previousRoomControllerPosition); - var worldHandDelta = Mat4.transformVector(MyAvatar.getSensorToWorldMatrix(), roomHandDelta); - var handMoved = Vec3.multiply(worldHandDelta, radius); - this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved); - - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.grabbedThingID, "continueDistanceGrab", args); - - // Update radialVelocity - var lastVelocity = Vec3.multiply(worldHandDelta, 1.0 / deltaObjectTime); - var delta = Vec3.normalize(Vec3.subtract(grabbedProperties.position, worldControllerPosition)); - var newRadialVelocity = Vec3.dot(lastVelocity, delta); - - var VELOCITY_AVERAGING_TIME = 0.016; - var blendFactor = deltaObjectTime / VELOCITY_AVERAGING_TIME; - if (blendFactor < 0.0) { - blendFactor = 0.0; - } else if (blendFactor > 1.0) { - blendFactor = 1.0; - } - this.grabRadialVelocity = blendFactor * newRadialVelocity + (1.0 - blendFactor) * this.grabRadialVelocity; - - var RADIAL_GRAB_AMPLIFIER = 10.0; - if (Math.abs(this.grabRadialVelocity) > 0.0) { - this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime * - this.grabRadius * RADIAL_GRAB_AMPLIFIER); - } - - // don't let grabRadius go all the way to zero, because it can't come back from that - var MINIMUM_GRAB_RADIUS = 0.1; - if (this.grabRadius < MINIMUM_GRAB_RADIUS) { - this.grabRadius = MINIMUM_GRAB_RADIUS; - } - var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); - newTargetPosition = Vec3.sum(newTargetPosition, worldControllerPosition); - newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition); - - // XXX - // this.maybeScale(grabbedProperties); - - var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition)); - - this.linearTimeScale = (this.linearTimeScale / 2); - if (this.linearTimeScale <= DISTANCE_HOLDING_ACTION_TIMEFRAME) { - this.linearTimeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME; - } - var success = Entities.updateAction(this.grabbedThingID, this.actionID, { - targetPosition: newTargetPosition, - linearTimeScale: this.linearTimeScale, - targetRotation: this.currentObjectRotation, - angularTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject), - ttl: ACTION_TTL - }); - if (!success) { - print("farActionGrabEntity continueDistanceHolding -- updateAction failed: " + this.actionID); - this.actionID = null; - } - - this.previousRoomControllerPosition = roomControllerPosition; - }; - - this.endFarGrabAction = function () { - this.distanceHolding = false; - this.distanceRotating = false; - Entities.deleteAction(this.grabbedThingID, this.actionID); - - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.grabbedThingID, "releaseGrab", args); - this.actionID = null; - this.grabbedThingID = null; - this.targetObject = null; - this.potentialEntityWithContextOverlay = false; - this.grabbing = false; - }; - - this.updateRecommendedArea = function() { - var dims = Controller.getViewportDimensions(); - this.reticleMaxX = dims.x - MARGIN; - this.reticleMaxY = dims.y - MARGIN; - }; - - this.calculateNewReticlePosition = function(intersection) { - this.updateRecommendedArea(); - var point2d = HMD.overlayFromWorldPoint(intersection); - point2d.x = Math.max(this.reticleMinX, Math.min(point2d.x, this.reticleMaxX)); - point2d.y = Math.max(this.reticleMinY, Math.min(point2d.y, this.reticleMaxY)); - return point2d; - }; - - this.notPointingAtEntity = function(controllerData) { - var intersection = controllerData.rayPicks[this.hand]; - var entityProperty = Entities.getEntityProperties(intersection.objectID, DISPATCHER_PROPERTIES); - var entityType = entityProperty.type; - var hudRayPick = controllerData.hudRayPicks[this.hand]; - var point2d = this.calculateNewReticlePosition(hudRayPick.intersection); - if ((intersection.type === Picks.INTERSECTED_ENTITY && entityType === "Web") || - intersection.type === Picks.INTERSECTED_OVERLAY || Window.isPointOnDesktopWindow(point2d)) { - return true; - } - return false; - }; - - this.distanceRotate = function(otherFarGrabModule) { - this.distanceRotating = true; - this.distanceHolding = false; - - var worldControllerRotation = getControllerWorldLocation(this.handToController(), true).orientation; - var controllerRotationDelta = - Quat.multiply(worldControllerRotation, Quat.inverse(this.previousWorldControllerRotation)); - // Rotate entity by twice the delta rotation. - controllerRotationDelta = Quat.multiply(controllerRotationDelta, controllerRotationDelta); - - // Perform the rotation in the translation controller's action update. - otherFarGrabModule.currentObjectRotation = Quat.multiply(controllerRotationDelta, - otherFarGrabModule.currentObjectRotation); - - this.previousWorldControllerRotation = worldControllerRotation; - }; - - this.prepareDistanceRotatingData = function(controllerData) { - var intersection = controllerData.rayPicks[this.hand]; - - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - var worldControllerPosition = controllerLocation.position; - var worldControllerRotation = controllerLocation.orientation; - - var grabbedProperties = Entities.getEntityProperties(intersection.objectID, DISPATCHER_PROPERTIES); - this.currentObjectPosition = grabbedProperties.position; - this.grabRadius = intersection.distance; - - // Offset between controller vector at the grab radius and the entity position. - var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); - targetPosition = Vec3.sum(targetPosition, worldControllerPosition); - this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); - - // Initial controller rotation. - this.previousWorldControllerRotation = worldControllerRotation; - }; - - this.destroyContextOverlay = function(controllerData) { - if (this.entityWithContextOverlay) { - ContextOverlay.destroyContextOverlay(this.entityWithContextOverlay); - this.entityWithContextOverlay = false; - this.potentialEntityWithContextOverlay = false; - } - }; - - this.targetIsNull = function() { - var properties = Entities.getEntityProperties(this.grabbedThingID, DISPATCHER_PROPERTIES); - if (Object.keys(properties).length === 0 && this.distanceHolding) { - return true; - } - return false; - }; - - this.getTargetProps = function (controllerData) { - var targetEntityID = controllerData.rayPicks[this.hand].objectID; - if (targetEntityID) { - return Entities.getEntityProperties(targetEntityID, DISPATCHER_PROPERTIES); - } - return null; - }; - - this.isReady = function (controllerData) { - if (HMD.active) { - if (this.notPointingAtEntity(controllerData)) { - return makeRunningValues(false, [], []); - } - - this.distanceHolding = false; - this.distanceRotating = false; - - if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { - this.prepareDistanceRotatingData(controllerData); - return makeRunningValues(true, [], []); - } else { - this.destroyContextOverlay(); - return makeRunningValues(false, [], []); - } - } - return makeRunningValues(false, [], []); - }; - - this.run = function (controllerData) { - if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.targetIsNull()) { - this.endFarGrabAction(); - Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", - this.highlightedEntity); - this.highlightedEntity = null; - return makeRunningValues(false, [], []); - } - this.intersectionDistance = controllerData.rayPicks[this.hand].distance; - - var otherModuleName = this.hand === RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity"; - var otherFarGrabModule = getEnabledModuleByName(otherModuleName); - - // gather up the readiness of the near-grab modules - var nearGrabNames = [ - this.hand === RIGHT_HAND ? "RightScaleAvatar" : "LeftScaleAvatar", - this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity", - this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity", - this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity" - ]; - if (!this.grabbing) { - nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"); - nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight"); - } - - var nearGrabReadiness = []; - for (var i = 0; i < nearGrabNames.length; i++) { - var nearGrabModule = getEnabledModuleByName(nearGrabNames[i]); - var ready = nearGrabModule ? nearGrabModule.isReady(controllerData) : makeRunningValues(false, [], []); - nearGrabReadiness.push(ready); - } - - if (this.actionID) { - // if we are doing a distance grab and the object or tablet gets close enough to the controller, - // stop the far-grab so the near-grab or equip can take over. - for (var k = 0; k < nearGrabReadiness.length; k++) { - if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.grabbedThingID || - HMD.tabletID && nearGrabReadiness[k].targets[0] === HMD.tabletID)) { - this.endFarGrabAction(); - return makeRunningValues(false, [], []); - } - } - - this.continueDistanceHolding(controllerData); - } else { - // if we are doing a distance search and this controller moves into a position - // where it could near-grab something, stop searching. - for (var j = 0; j < nearGrabReadiness.length; j++) { - if (nearGrabReadiness[j].active) { - this.endFarGrabAction(); - return makeRunningValues(false, [], []); - } - } - - var rayPickInfo = controllerData.rayPicks[this.hand]; - if (rayPickInfo.type === Picks.INTERSECTED_ENTITY) { - if (controllerData.triggerClicks[this.hand]) { - var entityID = rayPickInfo.objectID; - Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", - this.highlightedEntity); - this.highlightedEntity = null; - var targetProps = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); - if (targetProps.href !== "") { - AddressManager.handleLookupString(targetProps.href); - return makeRunningValues(false, [], []); - } - - this.targetObject = new TargetObject(entityID, targetProps); - this.targetObject.parentProps = getEntityParents(targetProps); - - if (this.contextOverlayTimer) { - Script.clearTimeout(this.contextOverlayTimer); - } - this.contextOverlayTimer = false; - if (entityID === this.entityWithContextOverlay) { - this.destroyContextOverlay(); - } else { - Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID); - } - - var targetEntity = this.targetObject.getTargetEntity(); - entityID = targetEntity.id; - targetProps = targetEntity.props; - - if (!targetProps.dynamic && !this.targetObject.entityProps.dynamic) { - // let farParentGrabEntity handle it - return makeRunningValues(false, [], []); - } - - if (entityIsGrabbable(targetProps) || entityIsGrabbable(this.targetObject.entityProps)) { - if (!this.distanceRotating) { - this.grabbedThingID = entityID; - this.grabbedDistance = rayPickInfo.distance; - } - - if (otherFarGrabModule.grabbedThingID === this.grabbedThingID && - otherFarGrabModule.distanceHolding) { - this.prepareDistanceRotatingData(controllerData); - this.distanceRotate(otherFarGrabModule); - } else { - this.distanceHolding = true; - this.distanceRotating = false; - this.startFarGrabAction(controllerData, targetProps); - } - } - } else { - var targetEntityID = rayPickInfo.objectID; - if (this.highlightedEntity !== targetEntityID) { - Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", - this.highlightedEntity); - var selectionTargetProps = Entities.getEntityProperties(targetEntityID, DISPATCHER_PROPERTIES); - - var selectionTargetObject = new TargetObject(targetEntityID, selectionTargetProps); - selectionTargetObject.parentProps = getEntityParents(selectionTargetProps); - var selectionTargetEntity = selectionTargetObject.getTargetEntity(); - - if (entityIsGrabbable(selectionTargetEntity.props) || - entityIsGrabbable(selectionTargetObject.entityProps)) { - - Selection.addToSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", rayPickInfo.objectID); - } - this.highlightedEntity = rayPickInfo.objectID; - } - - if (!this.entityWithContextOverlay) { - var _this = this; - - if (_this.potentialEntityWithContextOverlay !== rayPickInfo.objectID) { - if (_this.contextOverlayTimer) { - Script.clearTimeout(_this.contextOverlayTimer); - } - _this.contextOverlayTimer = false; - _this.potentialEntityWithContextOverlay = rayPickInfo.objectID; - } - - if (!_this.contextOverlayTimer) { - _this.contextOverlayTimer = Script.setTimeout(function () { - if (!_this.entityWithContextOverlay && - _this.contextOverlayTimer && - _this.potentialEntityWithContextOverlay === rayPickInfo.objectID) { - var pEvProps = Entities.getEntityProperties(rayPickInfo.objectID, - DISPATCHER_PROPERTIES); - var pointerEvent = { - type: "Move", - id: _this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID, - rayPickInfo.intersection, pEvProps), - pos3D: rayPickInfo.intersection, - normal: rayPickInfo.surfaceNormal, - direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal), - button: "Secondary" - }; - if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) { - _this.entityWithContextOverlay = rayPickInfo.objectID; - } - } - _this.contextOverlayTimer = false; - }, 500); - } - } - } - } else if (this.distanceRotating) { - this.distanceRotate(otherFarGrabModule); - } else if (this.highlightedEntity) { - Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); - this.highlightedEntity = null; - } - } - return this.exitIfDisabled(controllerData); - }; - - this.exitIfDisabled = function(controllerData) { - var moduleName = this.hand === RIGHT_HAND ? "RightDisableModules" : "LeftDisableModules"; - var disableModule = getEnabledModuleByName(moduleName); - if (disableModule) { - if (disableModule.disableModules) { - this.endFarGrabAction(); - Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", - this.highlightedEntity); - this.highlightedEntity = null; - return makeRunningValues(false, [], []); - } - } - var grabbedThing = (this.distanceHolding || this.distanceRotating) ? this.targetObject.entityID : null; - var offset = this.calculateOffset(controllerData); - var laserLockInfo = makeLaserLockInfo(grabbedThing, false, this.hand, offset); - return makeRunningValues(true, [], [], laserLockInfo); - }; - - this.calculateOffset = function(controllerData) { - if (this.distanceHolding || this.distanceRotating) { - var targetProps = Entities.getEntityProperties(this.targetObject.entityID, - [ "position", "rotation", "registrationPoint", "dimensions" ]); - return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection); - } - return undefined; - }; - } - - var leftFarActionGrabEntity = new FarActionGrabEntity(LEFT_HAND); - var rightFarActionGrabEntity = new FarActionGrabEntity(RIGHT_HAND); - - enableDispatcherModule("LeftFarActionGrabEntity", leftFarActionGrabEntity); - enableDispatcherModule("RightFarActionGrabEntity", rightFarActionGrabEntity); - - function cleanup() { - disableDispatcherModule("LeftFarActionGrabEntity"); - disableDispatcherModule("RightFarActionGrabEntity"); - } - Script.scriptEnding.connect(cleanup); -}()); diff --git a/scripts/system/controllers/controllerModules/farGrabEntity.js b/scripts/system/controllers/controllerModules/farGrabEntity.js index dab1aa97af..197a809e91 100644 --- a/scripts/system/controllers/controllerModules/farGrabEntity.js +++ b/scripts/system/controllers/controllerModules/farGrabEntity.js @@ -11,7 +11,7 @@ Entities, enableDispatcherModule, disableDispatcherModule, entityIsGrabbable, makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, projectOntoEntityXYPlane, ContextOverlay, HMD, Picks, makeLaserLockInfo, makeLaserParams, AddressManager, - getEntityParents, Selection, DISPATCHER_HOVERING_LIST, unhighlightTargetEntity, Messages, findGroupParent, + getEntityParents, Selection, DISPATCHER_HOVERING_LIST, unhighlightTargetEntity, Messages, findGrabbableGroupParent, worldPositionToRegistrationFrameMatrix, DISPATCHER_PROPERTIES */ @@ -308,7 +308,7 @@ Script.include("/~/system/libraries/controllers.js"); var gtProps = Entities.getEntityProperties(targetEntity, DISPATCHER_PROPERTIES); if (entityIsGrabbable(gtProps)) { // if we've attempted to grab a child, roll up to the root of the tree - var groupRootProps = findGroupParent(controllerData, gtProps); + var groupRootProps = findGrabbableGroupParent(controllerData, gtProps); if (entityIsGrabbable(groupRootProps)) { return groupRootProps; } diff --git a/scripts/system/controllers/controllerModules/farParentGrabEntity.js b/scripts/system/controllers/controllerModules/farParentGrabEntity.js deleted file mode 100644 index 9960b08292..0000000000 --- a/scripts/system/controllers/controllerModules/farParentGrabEntity.js +++ /dev/null @@ -1,664 +0,0 @@ -"use strict"; - -// farParentGrabEntity.js -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html - -/* jslint bitwise: true */ - -/* global Script, Controller, RIGHT_HAND, LEFT_HAND, Mat4, MyAvatar, Vec3, Quat, getEnabledModuleByName, makeRunningValues, - Entities, enableDispatcherModule, disableDispatcherModule, entityIsGrabbable, makeDispatcherModuleParameters, MSECS_PER_SEC, - HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, getControllerWorldLocation, - projectOntoEntityXYPlane, ContextOverlay, HMD, Picks, makeLaserLockInfo, makeLaserParams, AddressManager, - getEntityParents, Selection, DISPATCHER_HOVERING_LIST, unhighlightTargetEntity, Messages, Uuid, findGroupParent, - worldPositionToRegistrationFrameMatrix, DISPATCHER_PROPERTIES, findFarGrabJointChildEntities -*/ - -Script.include("/~/system/libraries/controllerDispatcherUtils.js"); -Script.include("/~/system/libraries/controllers.js"); - -(function() { - var MARGIN = 25; - - function TargetObject(entityID, entityProps) { - this.entityID = entityID; - this.entityProps = entityProps; - this.targetEntityID = null; - this.targetEntityProps = null; - - this.getTargetEntity = function() { - var parentPropsLength = this.parentProps.length; - if (parentPropsLength !== 0) { - var targetEntity = { - id: this.parentProps[parentPropsLength - 1].id, - props: this.parentProps[parentPropsLength - 1]}; - this.targetEntityID = targetEntity.id; - this.targetEntityProps = targetEntity.props; - return targetEntity; - } - this.targetEntityID = this.entityID; - this.targetEntityProps = this.entityProps; - return { - id: this.entityID, - props: this.entityProps}; - }; - } - - function FarParentGrabEntity(hand) { - this.hand = hand; - this.grabbing = false; - this.targetEntityID = null; - this.targetObject = null; - this.previouslyUnhooked = {}; - this.previousParentID = {}; - this.previousParentJointIndex = {}; - this.potentialEntityWithContextOverlay = false; - this.entityWithContextOverlay = false; - this.contextOverlayTimer = false; - this.highlightedEntity = null; - this.reticleMinX = MARGIN; - this.reticleMaxX = 0; - this.reticleMinY = MARGIN; - this.reticleMaxY = 0; - this.lastUnexpectedChildrenCheckTime = 0; - this.endedGrab = 0; - this.MIN_HAPTIC_PULSE_INTERVAL = 500; // ms - - var FAR_GRAB_JOINTS = [65527, 65528]; // FARGRAB_LEFTHAND_INDEX, FARGRAB_RIGHTHAND_INDEX - - var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object - var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position - var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified - var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified - - this.parameters = makeDispatcherModuleParameters( - 540, - this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], - [], - 100, - makeLaserParams(this.hand, false)); - - - this.handToController = function() { - return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - }; - - this.distanceGrabTimescale = function(mass, distance) { - var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass / - DISTANCE_HOLDING_UNITY_MASS * distance / - DISTANCE_HOLDING_UNITY_DISTANCE; - if (timeScale < DISTANCE_HOLDING_ACTION_TIMEFRAME) { - timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME; - } - return timeScale; - }; - - this.getMass = function(dimensions, density) { - return (dimensions.x * dimensions.y * dimensions.z) * density; - }; - - this.thisFarGrabJointIsParent = function(isParentProps) { - if (!isParentProps) { - return false; - } - - if (isParentProps.parentID !== MyAvatar.sessionUUID && isParentProps.parentID !== MyAvatar.SELF_ID) { - return false; - } - - if (isParentProps.parentJointIndex === FAR_GRAB_JOINTS[this.hand]) { - return true; - } - - return false; - }; - - this.startFarParentGrab = function (controllerData, grabbedProperties) { - var controllerLocation = controllerData.controllerLocations[this.hand]; - var worldControllerPosition = controllerLocation.position; - var worldControllerRotation = controllerLocation.orientation; - // transform the position into room space - var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); - var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); - - var now = Date.now(); - - // add the action and initialize some variables - this.currentObjectPosition = grabbedProperties.position; - this.currentObjectRotation = grabbedProperties.rotation; - this.currentObjectTime = now; - - this.grabRadius = this.grabbedDistance; - this.grabRadialVelocity = 0.0; - - // offset between controller vector at the grab radius and the entity position - var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); - targetPosition = Vec3.sum(targetPosition, worldControllerPosition); - this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); - - // compute a constant based on the initial conditions which we use below to exaggerate hand motion - // onto the held object - this.radiusScalar = Math.log(this.grabRadius + 1.0); - if (this.radiusScalar < 1.0) { - this.radiusScalar = 1.0; - } - - // compute the mass for the purpose of energy and how quickly to move object - this.mass = this.getMass(grabbedProperties.dimensions, grabbedProperties.density); - - // Debounce haptic pules. Can occur as near grab controller module vacillates between being ready or not due to - // changing positions and floating point rounding. - if (Date.now() - this.endedGrab > this.MIN_HAPTIC_PULSE_INTERVAL) { - Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); - } - - unhighlightTargetEntity(this.targetEntityID); - var message = { - hand: this.hand, - entityID: this.targetEntityID - }; - - Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message)); - - var newTargetPosLocal = MyAvatar.worldToJointPoint(grabbedProperties.position); - MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], newTargetPosLocal); - - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(grabbedProperties.id, "startNearGrab", args); - - var reparentProps = { - parentID: MyAvatar.SELF_ID, - parentJointIndex: FAR_GRAB_JOINTS[this.hand], - localVelocity: {x: 0, y: 0, z: 0}, - localAngularVelocity: {x: 0, y: 0, z: 0} - }; - - if (this.thisFarGrabJointIsParent(grabbedProperties)) { - // this should never happen, but if it does, don't set previous parent to be this hand. - this.previousParentID[grabbedProperties.id] = null; - this.previousParentJointIndex[grabbedProperties.id] = -1; - } else { - this.previousParentID[grabbedProperties.id] = grabbedProperties.parentID; - this.previousParentJointIndex[grabbedProperties.id] = grabbedProperties.parentJointIndex; - } - - this.targetEntityID = grabbedProperties.id; - Entities.editEntity(grabbedProperties.id, reparentProps); - - Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ - action: 'grab', - grabbedEntity: grabbedProperties.id, - joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" - })); - this.grabbing = true; - - this.previousRoomControllerPosition = roomControllerPosition; - }; - - this.continueDistanceHolding = function(controllerData) { - var controllerLocation = controllerData.controllerLocations[this.hand]; - var worldControllerPosition = controllerLocation.position; - var worldControllerRotation = controllerLocation.orientation; - - // also transform the position into room space - var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); - var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); - - var grabbedProperties = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES); - var now = Date.now(); - var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds - this.currentObjectTime = now; - - // the action was set up when this.distanceHolding was called. update the targets. - var radius = Vec3.distance(this.currentObjectPosition, worldControllerPosition) * - this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; - if (radius < 1.0) { - radius = 1.0; - } - - var roomHandDelta = Vec3.subtract(roomControllerPosition, this.previousRoomControllerPosition); - var worldHandDelta = Mat4.transformVector(MyAvatar.getSensorToWorldMatrix(), roomHandDelta); - var handMoved = Vec3.multiply(worldHandDelta, radius); - this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved); - - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.targetEntityID, "continueDistanceGrab", args); - - // Update radialVelocity - var lastVelocity = Vec3.multiply(worldHandDelta, 1.0 / deltaObjectTime); - var delta = Vec3.normalize(Vec3.subtract(grabbedProperties.position, worldControllerPosition)); - var newRadialVelocity = Vec3.dot(lastVelocity, delta); - - var VELOCITY_AVERAGING_TIME = 0.016; - var blendFactor = deltaObjectTime / VELOCITY_AVERAGING_TIME; - if (blendFactor < 0.0) { - blendFactor = 0.0; - } else if (blendFactor > 1.0) { - blendFactor = 1.0; - } - this.grabRadialVelocity = blendFactor * newRadialVelocity + (1.0 - blendFactor) * this.grabRadialVelocity; - - var RADIAL_GRAB_AMPLIFIER = 10.0; - if (Math.abs(this.grabRadialVelocity) > 0.0) { - this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime * - this.grabRadius * RADIAL_GRAB_AMPLIFIER); - } - - // don't let grabRadius go all the way to zero, because it can't come back from that - var MINIMUM_GRAB_RADIUS = 0.1; - if (this.grabRadius < MINIMUM_GRAB_RADIUS) { - this.grabRadius = MINIMUM_GRAB_RADIUS; - } - var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); - newTargetPosition = Vec3.sum(newTargetPosition, worldControllerPosition); - newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition); - - // MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], MyAvatar.worldToJointPoint(newTargetPosition)); - - // var newTargetPosLocal = Mat4.transformPoint(MyAvatar.getSensorToWorldMatrix(), newTargetPosition); - var newTargetPosLocal = MyAvatar.worldToJointPoint(newTargetPosition); - MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], newTargetPosLocal); - - this.previousRoomControllerPosition = roomControllerPosition; - }; - - this.endFarParentGrab = function (controllerData) { - this.endedGrab = Date.now(); - // var endProps = controllerData.nearbyEntityPropertiesByID[this.targetEntityID]; - var endProps = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES); - if (this.thisFarGrabJointIsParent(endProps)) { - Entities.editEntity(this.targetEntityID, { - parentID: this.previousParentID[this.targetEntityID], - parentJointIndex: this.previousParentJointIndex[this.targetEntityID], - localVelocity: {x: 0, y: 0, z: 0}, - localAngularVelocity: {x: 0, y: 0, z: 0} - }); - } - - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.targetEntityID, "releaseGrab", args); - Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ - action: 'release', - grabbedEntity: this.targetEntityID, - joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" - })); - unhighlightTargetEntity(this.targetEntityID); - this.grabbing = false; - this.targetEntityID = null; - this.potentialEntityWithContextOverlay = false; - MyAvatar.clearJointData(FAR_GRAB_JOINTS[this.hand]); - }; - - this.updateRecommendedArea = function() { - var dims = Controller.getViewportDimensions(); - this.reticleMaxX = dims.x - MARGIN; - this.reticleMaxY = dims.y - MARGIN; - }; - - this.calculateNewReticlePosition = function(intersection) { - this.updateRecommendedArea(); - var point2d = HMD.overlayFromWorldPoint(intersection); - point2d.x = Math.max(this.reticleMinX, Math.min(point2d.x, this.reticleMaxX)); - point2d.y = Math.max(this.reticleMinY, Math.min(point2d.y, this.reticleMaxY)); - return point2d; - }; - - this.notPointingAtEntity = function(controllerData) { - var intersection = controllerData.rayPicks[this.hand]; - var entityProperty = Entities.getEntityProperties(intersection.objectID, DISPATCHER_PROPERTIES); - var entityType = entityProperty.type; - var hudRayPick = controllerData.hudRayPicks[this.hand]; - var point2d = this.calculateNewReticlePosition(hudRayPick.intersection); - if ((intersection.type === Picks.INTERSECTED_ENTITY && entityType === "Web") || - intersection.type === Picks.INTERSECTED_OVERLAY || Window.isPointOnDesktopWindow(point2d)) { - return true; - } - return false; - }; - - this.distanceRotate = function(otherFarGrabModule) { - this.distanceRotating = true; - this.distanceHolding = false; - - var worldControllerRotation = getControllerWorldLocation(this.handToController(), true).orientation; - var controllerRotationDelta = - Quat.multiply(worldControllerRotation, Quat.inverse(this.previousWorldControllerRotation)); - // Rotate entity by twice the delta rotation. - controllerRotationDelta = Quat.multiply(controllerRotationDelta, controllerRotationDelta); - - // Perform the rotation in the translation controller's action update. - otherFarGrabModule.currentObjectRotation = Quat.multiply(controllerRotationDelta, - otherFarGrabModule.currentObjectRotation); - - this.previousWorldControllerRotation = worldControllerRotation; - }; - - this.prepareDistanceRotatingData = function(controllerData) { - var intersection = controllerData.rayPicks[this.hand]; - - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - var worldControllerPosition = controllerLocation.position; - var worldControllerRotation = controllerLocation.orientation; - - var grabbedProperties = Entities.getEntityProperties(intersection.objectID, DISPATCHER_PROPERTIES); - this.currentObjectPosition = grabbedProperties.position; - this.grabRadius = intersection.distance; - - // Offset between controller vector at the grab radius and the entity position. - var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); - targetPosition = Vec3.sum(targetPosition, worldControllerPosition); - this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); - - // Initial controller rotation. - this.previousWorldControllerRotation = worldControllerRotation; - }; - - this.destroyContextOverlay = function(controllerData) { - if (this.entityWithContextOverlay) { - ContextOverlay.destroyContextOverlay(this.entityWithContextOverlay); - this.entityWithContextOverlay = false; - this.potentialEntityWithContextOverlay = false; - } - }; - - this.checkForUnexpectedChildren = function (controllerData) { - // sometimes things can get parented to a hand and this script is unaware. Search for such entities and - // unhook them. - - var now = Date.now(); - var UNEXPECTED_CHILDREN_CHECK_TIME = 0.1; // seconds - if (now - this.lastUnexpectedChildrenCheckTime > MSECS_PER_SEC * UNEXPECTED_CHILDREN_CHECK_TIME) { - this.lastUnexpectedChildrenCheckTime = now; - - var children = findFarGrabJointChildEntities(this.hand); - var _this = this; - - children.forEach(function(childID) { - // we appear to be holding something and this script isn't in a state that would be holding something. - // unhook it. if we previously took note of this entity's parent, put it back where it was. This - // works around some problems that happen when more than one hand or avatar is passing something around. - if (_this.previousParentID[childID]) { - var previousParentID = _this.previousParentID[childID]; - var previousParentJointIndex = _this.previousParentJointIndex[childID]; - - // The main flaw with keeping track of previous parentage in individual scripts is: - // (1) A grabs something (2) B takes it from A (3) A takes it from B (4) A releases it - // now A and B will take turns passing it back to the other. Detect this and stop the loop here... - var UNHOOK_LOOP_DETECT_MS = 200; - if (_this.previouslyUnhooked[childID]) { - if (now - _this.previouslyUnhooked[childID] < UNHOOK_LOOP_DETECT_MS) { - previousParentID = Uuid.NULL; - previousParentJointIndex = -1; - } - } - _this.previouslyUnhooked[childID] = now; - - Entities.editEntity(childID, { - parentID: previousParentID, - parentJointIndex: previousParentJointIndex - }); - } else { - Entities.editEntity(childID, { parentID: Uuid.NULL }); - } - }); - } - }; - - this.targetIsNull = function() { - var properties = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES); - if (Object.keys(properties).length === 0 && this.distanceHolding) { - return true; - } - return false; - }; - - this.getTargetProps = function (controllerData) { - var targetEntity = controllerData.rayPicks[this.hand].objectID; - if (targetEntity) { - var gtProps = Entities.getEntityProperties(targetEntity, DISPATCHER_PROPERTIES); - if (entityIsGrabbable(gtProps)) { - // if we've attempted to grab a child, roll up to the root of the tree - var groupRootProps = findGroupParent(controllerData, gtProps); - if (entityIsGrabbable(groupRootProps)) { - return groupRootProps; - } - return gtProps; - } - } - return null; - }; - - this.isReady = function (controllerData) { - if (HMD.active) { - if (this.notPointingAtEntity(controllerData)) { - return makeRunningValues(false, [], []); - } - - this.distanceHolding = false; - this.distanceRotating = false; - - if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { - var targetProps = this.getTargetProps(controllerData); - if (targetProps && (targetProps.dynamic && targetProps.parentID === Uuid.NULL)) { - return makeRunningValues(false, [], []); // let farActionGrabEntity handle it - } else { - this.prepareDistanceRotatingData(controllerData); - return makeRunningValues(true, [], []); - } - } else { - this.checkForUnexpectedChildren(controllerData); - this.destroyContextOverlay(); - return makeRunningValues(false, [], []); - } - } - return makeRunningValues(false, [], []); - }; - - this.run = function (controllerData) { - if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.targetIsNull()) { - this.endFarParentGrab(controllerData); - Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); - this.highlightedEntity = null; - return makeRunningValues(false, [], []); - } - this.intersectionDistance = controllerData.rayPicks[this.hand].distance; - - var otherModuleName = this.hand === RIGHT_HAND ? "LeftFarParentGrabEntity" : "RightFarParentGrabEntity"; - var otherFarGrabModule = getEnabledModuleByName(otherModuleName); - - // gather up the readiness of the near-grab modules - var nearGrabNames = [ - this.hand === RIGHT_HAND ? "RightScaleAvatar" : "LeftScaleAvatar", - this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity", - this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity", - this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity" - ]; - if (!this.grabbing) { - nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"); - nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight"); - } - - var nearGrabReadiness = []; - for (var i = 0; i < nearGrabNames.length; i++) { - var nearGrabModule = getEnabledModuleByName(nearGrabNames[i]); - var ready = nearGrabModule ? nearGrabModule.isReady(controllerData) : makeRunningValues(false, [], []); - nearGrabReadiness.push(ready); - } - - if (this.targetEntityID) { - // if we are doing a distance grab and the object gets close enough to the controller, - // stop the far-grab so the near-grab or equip can take over. - for (var k = 0; k < nearGrabReadiness.length; k++) { - if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.targetEntityID)) { - this.endFarParentGrab(controllerData); - return makeRunningValues(false, [], []); - } - } - - this.continueDistanceHolding(controllerData); - } else { - // if we are doing a distance search and this controller moves into a position - // where it could near-grab something, stop searching. - for (var j = 0; j < nearGrabReadiness.length; j++) { - if (nearGrabReadiness[j].active) { - this.endFarParentGrab(controllerData); - return makeRunningValues(false, [], []); - } - } - - var rayPickInfo = controllerData.rayPicks[this.hand]; - if (rayPickInfo.type === Picks.INTERSECTED_ENTITY) { - if (controllerData.triggerClicks[this.hand]) { - var entityID = rayPickInfo.objectID; - Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); - this.highlightedEntity = null; - var targetProps = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); - if (targetProps.href !== "") { - AddressManager.handleLookupString(targetProps.href); - return makeRunningValues(false, [], []); - } - - this.targetObject = new TargetObject(entityID, targetProps); - this.targetObject.parentProps = getEntityParents(targetProps); - - if (this.contextOverlayTimer) { - Script.clearTimeout(this.contextOverlayTimer); - } - this.contextOverlayTimer = false; - if (entityID === this.entityWithContextOverlay) { - this.destroyContextOverlay(); - } else { - Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID); - } - - var targetEntity = this.targetObject.getTargetEntity(); - entityID = targetEntity.id; - targetProps = targetEntity.props; - - if (targetProps.dynamic || this.targetObject.entityProps.dynamic) { - // let farActionGrabEntity handle it - return makeRunningValues(false, [], []); - } - - if (entityIsGrabbable(targetProps) || entityIsGrabbable(this.targetObject.entityProps)) { - - if (!this.distanceRotating) { - this.targetEntityID = entityID; - this.grabbedDistance = rayPickInfo.distance; - } - - if (otherFarGrabModule.targetEntityID === this.targetEntityID && - otherFarGrabModule.distanceHolding) { - this.prepareDistanceRotatingData(controllerData); - this.distanceRotate(otherFarGrabModule); - } else { - this.distanceHolding = true; - this.distanceRotating = false; - this.startFarParentGrab(controllerData, targetProps); - } - } - } else { - var targetEntityID = rayPickInfo.objectID; - if (this.highlightedEntity !== targetEntityID) { - Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); - var selectionTargetProps = Entities.getEntityProperties(targetEntityID, DISPATCHER_PROPERTIES); - - var selectionTargetObject = new TargetObject(targetEntityID, selectionTargetProps); - selectionTargetObject.parentProps = getEntityParents(selectionTargetProps); - var selectionTargetEntity = selectionTargetObject.getTargetEntity(); - - if (entityIsGrabbable(selectionTargetEntity.props) || - entityIsGrabbable(selectionTargetObject.entityProps)) { - - Selection.addToSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", rayPickInfo.objectID); - } - this.highlightedEntity = rayPickInfo.objectID; - } - - if (!this.entityWithContextOverlay) { - var _this = this; - - if (_this.potentialEntityWithContextOverlay !== rayPickInfo.objectID) { - if (_this.contextOverlayTimer) { - Script.clearTimeout(_this.contextOverlayTimer); - } - _this.contextOverlayTimer = false; - _this.potentialEntityWithContextOverlay = rayPickInfo.objectID; - } - - if (!_this.contextOverlayTimer) { - _this.contextOverlayTimer = Script.setTimeout(function () { - if (!_this.entityWithContextOverlay && - _this.contextOverlayTimer && - _this.potentialEntityWithContextOverlay === rayPickInfo.objectID) { - var cotProps = Entities.getEntityProperties(rayPickInfo.objectID, - DISPATCHER_PROPERTIES); - var pointerEvent = { - type: "Move", - id: _this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID, - rayPickInfo.intersection, cotProps), - pos3D: rayPickInfo.intersection, - normal: rayPickInfo.surfaceNormal, - direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal), - button: "Secondary" - }; - if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) { - _this.entityWithContextOverlay = rayPickInfo.objectID; - } - } - _this.contextOverlayTimer = false; - }, 500); - } - } - } - } else if (this.distanceRotating) { - this.distanceRotate(otherFarGrabModule); - } else if (this.highlightedEntity) { - Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); - this.highlightedEntity = null; - } - } - return this.exitIfDisabled(controllerData); - }; - - this.exitIfDisabled = function(controllerData) { - var moduleName = this.hand === RIGHT_HAND ? "RightDisableModules" : "LeftDisableModules"; - var disableModule = getEnabledModuleByName(moduleName); - if (disableModule) { - if (disableModule.disableModules) { - this.endFarParentGrab(controllerData); - Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); - this.highlightedEntity = null; - return makeRunningValues(false, [], []); - } - } - var grabbedThing = (this.distanceHolding || this.distanceRotating) ? this.targetObject.entityID : null; - var offset = this.calculateOffset(controllerData); - var laserLockInfo = makeLaserLockInfo(grabbedThing, false, this.hand, offset); - return makeRunningValues(true, [], [], laserLockInfo); - }; - - this.calculateOffset = function(controllerData) { - if (this.distanceHolding || this.distanceRotating) { - var targetProps = Entities.getEntityProperties(this.targetObject.entityID, - [ "position", "rotation", "registrationPoint", "dimensions" ]); - return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection); - } - return undefined; - }; - } - - var leftFarParentGrabEntity = new FarParentGrabEntity(LEFT_HAND); - var rightFarParentGrabEntity = new FarParentGrabEntity(RIGHT_HAND); - - enableDispatcherModule("LeftFarParentGrabEntity", leftFarParentGrabEntity); - enableDispatcherModule("RightFarParentGrabEntity", rightFarParentGrabEntity); - - function cleanup() { - disableDispatcherModule("LeftFarParentGrabEntity"); - disableDispatcherModule("RightFarParentGrabEntity"); - } - Script.scriptEnding.connect(cleanup); -}()); diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js deleted file mode 100644 index ddff35b9e7..0000000000 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ /dev/null @@ -1,250 +0,0 @@ -"use strict"; - -// nearActionGrabEntity.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, - getControllerJointIndex, getGrabbableData, enableDispatcherModule, disableDispatcherModule, - propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, entityIsGrabbable, - MSECS_PER_SEC, makeDispatcherModuleParameters, makeRunningValues, - TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS, findGroupParent, entityIsCloneable, propsAreCloneDynamic, cloneEntity, - HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, unhighlightTargetEntity, Uuid, - DISPATCHER_PROPERTIES, HMD -*/ - -Script.include("/~/system/libraries/controllerDispatcherUtils.js"); -Script.include("/~/system/libraries/controllers.js"); -Script.include("/~/system/libraries/cloneEntityUtils.js"); - -(function() { - - function NearActionGrabEntity(hand) { - this.hand = hand; - this.targetEntityID = null; - this.actionID = null; // action this script created... - - this.parameters = makeDispatcherModuleParameters( - 500, - this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], - [], - 100); - - var NEAR_GRABBING_ACTION_TIMEFRAME = 0.05; // how quickly objects move to their new position - var ACTION_TTL = 15; // seconds - var ACTION_TTL_REFRESH = 5; - - // XXX does handJointIndex change if the avatar changes? - this.handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); - this.controllerJointIndex = getControllerJointIndex(this.hand); - - - // handPosition is where the avatar's hand appears to be, in-world. - this.getHandPosition = function () { - if (this.hand === RIGHT_HAND) { - return MyAvatar.getRightPalmPosition(); - } else { - return MyAvatar.getLeftPalmPosition(); - } - }; - - this.getHandRotation = function () { - if (this.hand === RIGHT_HAND) { - return MyAvatar.getRightPalmRotation(); - } else { - return MyAvatar.getLeftPalmRotation(); - } - }; - - - this.startNearGrabAction = function (controllerData, targetProps) { - Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); - - var grabbableData = getGrabbableData(targetProps); - this.grabFollowsController = grabbableData.grabFollowsController; - this.kinematicGrab = grabbableData.grabKinematic; - - var handJointIndex; - if (HMD.mounted && HMD.isHandControllerAvailable() && grabbableData.grabFollowsController) { - handJointIndex = getControllerJointIndex(this.hand); - } else { - handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); - } - this.offsetPosition = Entities.worldToLocalPosition(targetProps.position, MyAvatar.SELF_ID, handJointIndex); - this.offsetRotation = Entities.worldToLocalRotation(targetProps.rotation, MyAvatar.SELF_ID, handJointIndex); - - var now = Date.now(); - this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); - - if (this.actionID) { - Entities.deleteAction(this.targetEntityID, this.actionID); - } - this.actionID = Entities.addAction("hold", this.targetEntityID, { - hand: this.hand === RIGHT_HAND ? "right" : "left", - timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, - relativePosition: this.offsetPosition, - relativeRotation: this.offsetRotation, - ttl: ACTION_TTL, - kinematic: this.kinematicGrab, - kinematicSetVelocity: true, - ignoreIK: this.grabFollowsController - }); - if (this.actionID === Uuid.NULL) { - this.actionID = null; - return; - } - - Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ - action: 'grab', - grabbedEntity: this.targetEntityID, - joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" - })); - - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.targetEntityID, "startNearGrab", args); - }; - - // this is for when the action is going to time-out - this.refreshNearGrabAction = function (controllerData) { - var now = Date.now(); - if (this.actionID && this.actionTimeout - now < ACTION_TTL_REFRESH * MSECS_PER_SEC) { - // if less than a 5 seconds left, refresh the actions ttl - var success = Entities.updateAction(this.targetEntityID, this.actionID, { - hand: this.hand === RIGHT_HAND ? "right" : "left", - timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, - relativePosition: this.offsetPosition, - relativeRotation: this.offsetRotation, - ttl: ACTION_TTL, - kinematic: this.kinematicGrab, - kinematicSetVelocity: true, - ignoreIK: this.grabFollowsController - }); - if (success) { - this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); - } - } - }; - - this.endNearGrabAction = function () { - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.targetEntityID, "releaseGrab", args); - - Entities.deleteAction(this.targetEntityID, this.actionID); - this.actionID = null; - - Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ - action: 'release', - grabbedEntity: this.targetEntityID, - joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" - })); - - this.targetEntityID = null; - }; - - this.getTargetProps = function (controllerData) { - // nearbyEntityProperties is already sorted by distance from controller - var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand]; - var sensorScaleFactor = MyAvatar.sensorToWorldScale; - for (var i = 0; i < nearbyEntityProperties.length; i++) { - var props = nearbyEntityProperties[i]; - if (props.distance > NEAR_GRAB_RADIUS * sensorScaleFactor) { - break; - } - if (entityIsGrabbable(props) || entityIsCloneable(props)) { - if (!entityIsCloneable(props)) { - // if we've attempted to grab a non-cloneable child, roll up to the root of the tree - var groupRootProps = findGroupParent(controllerData, props); - if (entityIsGrabbable(groupRootProps)) { - return groupRootProps; - } - } - return props; - } - } - return null; - }; - - this.isReady = function (controllerData) { - this.targetEntityID = null; - - var targetProps = this.getTargetProps(controllerData); - if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE && - controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) { - return makeRunningValues(false, [], []); - } - - if (targetProps) { - if ((!propsArePhysical(targetProps) && !propsAreCloneDynamic(targetProps)) || - targetProps.parentID !== Uuid.NULL) { - return makeRunningValues(false, [], []); // let nearParentGrabEntity handle it - } else { - this.targetEntityID = targetProps.id; - return makeRunningValues(true, [this.targetEntityID], []); - } - } else { - return makeRunningValues(false, [], []); - } - }; - - this.run = function (controllerData) { - if (this.actionID) { - if (controllerData.triggerClicks[this.hand] < TRIGGER_OFF_VALUE && - controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) { - this.endNearGrabAction(); - return makeRunningValues(false, [], []); - } - - this.refreshNearGrabAction(controllerData); - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.targetEntityID, "continueNearGrab", args); - } else { - - // still searching / highlighting - var readiness = this.isReady (controllerData); - if (!readiness.active) { - return readiness; - } - - var targetProps = this.getTargetProps(controllerData); - if (targetProps) { - if (controllerData.triggerClicks[this.hand] || - controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) { - // switch to grabbing - var targetCloneable = entityIsCloneable(targetProps); - if (targetCloneable) { - var cloneID = cloneEntity(targetProps); - var cloneProps = Entities.getEntityProperties(cloneID, DISPATCHER_PROPERTIES); - this.targetEntityID = cloneID; - this.startNearGrabAction(controllerData, cloneProps); - } else { - this.startNearGrabAction(controllerData, targetProps); - } - } - } - } - - return makeRunningValues(true, [this.targetEntityID], []); - }; - - this.cleanup = function () { - if (this.targetEntityID) { - this.endNearGrabAction(); - } - }; - } - - var leftNearActionGrabEntity = new NearActionGrabEntity(LEFT_HAND); - var rightNearActionGrabEntity = new NearActionGrabEntity(RIGHT_HAND); - - enableDispatcherModule("LeftNearActionGrabEntity", leftNearActionGrabEntity); - enableDispatcherModule("RightNearActionGrabEntity", rightNearActionGrabEntity); - - function cleanup() { - leftNearActionGrabEntity.cleanup(); - rightNearActionGrabEntity.cleanup(); - disableDispatcherModule("LeftNearActionGrabEntity"); - disableDispatcherModule("RightNearActionGrabEntity"); - } - Script.scriptEnding.connect(cleanup); -}()); diff --git a/scripts/system/controllers/controllerModules/nearGrabEntity.js b/scripts/system/controllers/controllerModules/nearGrabEntity.js index 60a5781ca4..0f8071677c 100644 --- a/scripts/system/controllers/controllerModules/nearGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearGrabEntity.js @@ -8,9 +8,10 @@ /* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, getControllerJointIndex, enableDispatcherModule, disableDispatcherModule, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, - makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS, findGroupParent, Vec3, cloneEntity, - entityIsCloneable, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, distanceBetweenPointAndEntityBoundingBox, - getGrabbableData, getEnabledModuleByName, DISPATCHER_PROPERTIES, HMD, NEAR_GRAB_DISTANCE + makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS, findGrabbableGroupParent, Vec3, + cloneEntity, entityIsCloneable, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, + distanceBetweenPointAndEntityBoundingBox, getGrabbableData, getEnabledModuleByName, DISPATCHER_PROPERTIES, HMD, + NEAR_GRAB_DISTANCE */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -80,9 +81,6 @@ Script.include("/~/system/libraries/controllers.js"); this.endNearGrabEntity = function () { this.endGrab(); - this.grabbing = false; - this.targetEntityID = null; - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; Entities.callEntityMethod(this.targetEntityID, "releaseGrab", args); Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ @@ -90,6 +88,9 @@ Script.include("/~/system/libraries/controllers.js"); grabbedEntity: this.targetEntityID, joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" })); + + this.grabbing = false; + this.targetEntityID = null; }; this.getTargetProps = function (controllerData) { @@ -110,7 +111,7 @@ Script.include("/~/system/libraries/controllers.js"); if (entityIsGrabbable(props) || entityIsCloneable(props)) { if (!entityIsCloneable(props)) { // if we've attempted to grab a non-cloneable child, roll up to the root of the tree - var groupRootProps = findGroupParent(controllerData, props); + var groupRootProps = findGrabbableGroupParent(controllerData, props); if (entityIsGrabbable(groupRootProps)) { return groupRootProps; } diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js deleted file mode 100644 index 13557bdb7e..0000000000 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ /dev/null @@ -1,359 +0,0 @@ -"use strict"; - -// nearParentGrabEntity.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, getControllerJointIndex, - enableDispatcherModule, disableDispatcherModule, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, - TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS, - findGroupParent, Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic, HAPTIC_PULSE_STRENGTH, - HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, findHandChildEntities, TEAR_AWAY_DISTANCE, MSECS_PER_SEC, TEAR_AWAY_CHECK_TIME, - TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox, print, Uuid, NEAR_GRAB_DISTANCE, - distanceBetweenEntityLocalPositionAndBoundingBox, getGrabbableData, getGrabPointSphereOffset, DISPATCHER_PROPERTIES -*/ - -Script.include("/~/system/libraries/controllerDispatcherUtils.js"); -Script.include("/~/system/libraries/cloneEntityUtils.js"); -Script.include("/~/system/libraries/controllers.js"); - -(function() { - - // XXX this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true; - // XXX this.kinematicGrab = (grabbableData.kinematic !== undefined) ? grabbableData.kinematic : NEAR_GRABBING_KINEMATIC; - - function NearParentingGrabEntity(hand) { - this.hand = hand; - this.targetEntityID = null; - this.grabbing = false; - this.previousParentID = {}; - this.previousParentJointIndex = {}; - this.previouslyUnhooked = {}; - this.lastUnequipCheckTime = 0; - this.autoUnequipCounter = 0; - this.lastUnexpectedChildrenCheckTime = 0; - this.robbed = false; - this.cloneAllowed = true; - - this.parameters = makeDispatcherModuleParameters( - 500, - this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], - [], - 100); - - this.thisHandIsParent = function(props) { - if (!props) { - return false; - } - - if (props.parentID !== MyAvatar.sessionUUID && props.parentID !== MyAvatar.SELF_ID) { - return false; - } - - var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); - if (props.parentJointIndex === handJointIndex) { - return true; - } - - if (props.parentJointIndex === getControllerJointIndex(this.hand)) { - return true; - } - - var controllerCRJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? - "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : - "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); - - if (props.parentJointIndex === controllerCRJointIndex) { - return true; - } - - return false; - }; - - this.getOtherModule = function() { - return this.hand === RIGHT_HAND ? leftNearParentingGrabEntity : rightNearParentingGrabEntity; - }; - - this.otherHandIsParent = function(props) { - var otherModule = this.getOtherModule(); - return (otherModule.thisHandIsParent(props) && otherModule.grabbing); - }; - - this.startNearParentingGrabEntity = function (controllerData, targetProps) { - var grabData = getGrabbableData(targetProps); - Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); - - var handJointIndex; - if (grabData.grabFollowsController) { - handJointIndex = getControllerJointIndex(this.hand); - } else { - handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); - } - - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(targetProps.id, "startNearGrab", args); - - var reparentProps = { - parentID: MyAvatar.SELF_ID, - parentJointIndex: handJointIndex, - localVelocity: {x: 0, y: 0, z: 0}, - localAngularVelocity: {x: 0, y: 0, z: 0} - }; - - if (this.thisHandIsParent(targetProps)) { - // this should never happen, but if it does, don't set previous parent to be this hand. - this.previousParentID[targetProps.id] = null; - this.previousParentJointIndex[targetProps.id] = -1; - } else if (this.otherHandIsParent(targetProps)) { - var otherModule = this.getOtherModule(); - this.previousParentID[this.grabbedThingID] = otherModule.previousParentID[this.grabbedThingID]; - this.previousParentJointIndex[this.grabbedThingID] = otherModule.previousParentJointIndex[this.grabbedThingID]; - otherModule.robbed = true; - } else { - this.previousParentID[targetProps.id] = targetProps.parentID; - this.previousParentJointIndex[targetProps.id] = targetProps.parentJointIndex; - } - - this.targetEntityID = targetProps.id; - Entities.editEntity(targetProps.id, reparentProps); - - Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ - action: 'grab', - grabbedEntity: targetProps.id, - joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" - })); - this.grabbing = true; - }; - - this.endNearParentingGrabEntity = function (controllerData) { - var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID]; - if (this.thisHandIsParent(props) && !this.robbed) { - Entities.editEntity(this.targetEntityID, { - parentID: this.previousParentID[this.targetEntityID], - parentJointIndex: this.previousParentJointIndex[this.targetEntityID], - localVelocity: {x: 0, y: 0, z: 0}, - localAngularVelocity: {x: 0, y: 0, z: 0} - }); - } - - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.targetEntityID, "releaseGrab", args); - Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ - action: 'release', - grabbedEntity: this.targetEntityID, - joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" - })); - - this.grabbing = false; - this.targetEntityID = null; - this.robbed = false; - }; - - this.checkForChildTooFarAway = function (controllerData) { - var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID]; - var now = Date.now(); - if (now - this.lastUnequipCheckTime > MSECS_PER_SEC * TEAR_AWAY_CHECK_TIME) { - this.lastUnequipCheckTime = now; - if (props.parentID === MyAvatar.SELF_ID) { - var tearAwayDistance = TEAR_AWAY_DISTANCE * MyAvatar.sensorToWorldScale; - var controllerIndex = - this.hand === LEFT_HAND ? Controller.Standard.LeftHand : Controller.Standard.RightHand; - var controllerGrabOffset = getGrabPointSphereOffset(controllerIndex, true); - controllerGrabOffset = Vec3.multiply(-MyAvatar.sensorToWorldScale, controllerGrabOffset); - var distance = distanceBetweenEntityLocalPositionAndBoundingBox(props, controllerGrabOffset); - if (distance > tearAwayDistance) { - this.autoUnequipCounter++; - } else { - this.autoUnequipCounter = 0; - } - if (this.autoUnequipCounter >= TEAR_AWAY_COUNT) { - return true; - } - } - } - return false; - }; - - - this.checkForUnexpectedChildren = function (controllerData) { - // sometimes things can get parented to a hand and this script is unaware. Search for such entities and - // unhook them. - - var now = Date.now(); - var UNEXPECTED_CHILDREN_CHECK_TIME = 0.1; // seconds - if (now - this.lastUnexpectedChildrenCheckTime > MSECS_PER_SEC * UNEXPECTED_CHILDREN_CHECK_TIME) { - this.lastUnexpectedChildrenCheckTime = now; - - var children = findHandChildEntities(this.hand); - var _this = this; - - children.forEach(function(childID) { - // we appear to be holding something and this script isn't in a state that would be holding something. - // unhook it. if we previously took note of this entity's parent, put it back where it was. This - // works around some problems that happen when more than one hand or avatar is passing something around. - if (_this.previousParentID[childID]) { - var previousParentID = _this.previousParentID[childID]; - var previousParentJointIndex = _this.previousParentJointIndex[childID]; - - // The main flaw with keeping track of previous parentage in individual scripts is: - // (1) A grabs something (2) B takes it from A (3) A takes it from B (4) A releases it - // now A and B will take turns passing it back to the other. Detect this and stop the loop here... - var UNHOOK_LOOP_DETECT_MS = 200; - if (_this.previouslyUnhooked[childID]) { - if (now - _this.previouslyUnhooked[childID] < UNHOOK_LOOP_DETECT_MS) { - previousParentID = Uuid.NULL; - previousParentJointIndex = -1; - } - } - _this.previouslyUnhooked[childID] = now; - - Entities.editEntity(childID, { - parentID: previousParentID, - parentJointIndex: previousParentJointIndex - }); - } else { - Entities.editEntity(childID, { parentID: Uuid.NULL }); - } - }); - } - }; - - this.getTargetProps = function (controllerData) { - // nearbyEntityProperties is already sorted by length from controller - var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand]; - var sensorScaleFactor = MyAvatar.sensorToWorldScale; - var nearGrabDistance = NEAR_GRAB_DISTANCE * sensorScaleFactor; - var nearGrabRadius = NEAR_GRAB_RADIUS * sensorScaleFactor; - for (var i = 0; i < nearbyEntityProperties.length; i++) { - var props = nearbyEntityProperties[i]; - var grabPosition = controllerData.controllerLocations[this.hand].position; // Is offset from hand position. - var dist = distanceBetweenPointAndEntityBoundingBox(grabPosition, props); - var distance = Vec3.distance(grabPosition, props.position); - if ((dist > nearGrabDistance) || - (distance > nearGrabRadius)) { // Only smallish entities can be near grabbed. - continue; - } - if (entityIsGrabbable(props) || entityIsCloneable(props)) { - if (!entityIsCloneable(props)) { - // if we've attempted to grab a non-cloneable child, roll up to the root of the tree - var groupRootProps = findGroupParent(controllerData, props); - if (entityIsGrabbable(groupRootProps)) { - return groupRootProps; - } - } - return props; - } - } - return null; - }; - - this.isReady = function (controllerData, deltaTime) { - this.targetEntityID = null; - this.grabbing = false; - - var targetProps = this.getTargetProps(controllerData); - if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE && - controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) { - this.checkForUnexpectedChildren(controllerData); - this.robbed = false; - this.cloneAllowed = true; - return makeRunningValues(false, [], []); - } - - if (targetProps) { - if ((propsArePhysical(targetProps) || propsAreCloneDynamic(targetProps)) && - targetProps.parentID === Uuid.NULL) { - this.robbed = false; - return makeRunningValues(false, [], []); // let nearActionGrabEntity handle it - } else { - this.targetEntityID = targetProps.id; - return makeRunningValues(true, [this.targetEntityID], []); - } - } else { - this.robbed = false; - return makeRunningValues(false, [], []); - } - }; - - this.run = function (controllerData, deltaTime) { - if (this.grabbing) { - if (controllerData.triggerClicks[this.hand] < TRIGGER_OFF_VALUE && - controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) { - this.endNearParentingGrabEntity(controllerData); - return makeRunningValues(false, [], []); - } - - var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID]; - if (!props) { - // entity was deleted - this.grabbing = false; - this.targetEntityID = null; - this.robbed = false; - return makeRunningValues(false, [], []); - } - - if (this.checkForChildTooFarAway(controllerData)) { - // if the held entity moves too far from the hand, release it - print("nearParentGrabEntity -- autoreleasing held item because it is far from hand"); - this.endNearParentingGrabEntity(controllerData); - return makeRunningValues(false, [], []); - } - - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.targetEntityID, "continueNearGrab", args); - } else { - // still searching - var readiness = this.isReady(controllerData); - if (!readiness.active) { - this.robbed = false; - return readiness; - } - if (controllerData.triggerClicks[this.hand] || controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) { - // switch to grab - var targetProps = this.getTargetProps(controllerData); - var targetCloneable = entityIsCloneable(targetProps); - - if (targetCloneable) { - if (this.cloneAllowed) { - var cloneID = cloneEntity(targetProps); - if (cloneID !== null) { - var cloneProps = Entities.getEntityProperties(cloneID, DISPATCHER_PROPERTIES); - this.grabbing = true; - this.targetEntityID = cloneID; - this.startNearParentingGrabEntity(controllerData, cloneProps); - this.cloneAllowed = false; // prevent another clone call until inputs released - } - } - } else if (targetProps) { - this.grabbing = true; - this.startNearParentingGrabEntity(controllerData, targetProps); - } - } - } - - return makeRunningValues(true, [this.targetEntityID], []); - }; - - this.cleanup = function () { - if (this.targetEntityID) { - this.endNearParentingGrabEntity(); - } - }; - } - - var leftNearParentingGrabEntity = new NearParentingGrabEntity(LEFT_HAND); - var rightNearParentingGrabEntity = new NearParentingGrabEntity(RIGHT_HAND); - - enableDispatcherModule("LeftNearParentingGrabEntity", leftNearParentingGrabEntity); - enableDispatcherModule("RightNearParentingGrabEntity", rightNearParentingGrabEntity); - - function cleanup() { - leftNearParentingGrabEntity.cleanup(); - rightNearParentingGrabEntity.cleanup(); - disableDispatcherModule("LeftNearParentingGrabEntity"); - disableDispatcherModule("RightNearParentingGrabEntity"); - } - Script.scriptEnding.connect(cleanup); -}()); diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 2114f2c0b2..86ff7701c3 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -32,22 +32,13 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/mouseHMD.js", "controllerModules/scaleEntity.js", "controllerModules/nearGrabHyperLinkEntity.js", - "controllerModules/nearTabletHighlight.js" + "controllerModules/nearTabletHighlight.js", + "controllerModules/nearGrabEntity.js", + "controllerModules/farGrabEntity.js" ]; -if (Settings.getValue("useTraitsGrab", true)) { - CONTOLLER_SCRIPTS.push("controllerModules/nearGrabEntity.js"); - CONTOLLER_SCRIPTS.push("controllerModules/farGrabEntity.js"); -} else { - CONTOLLER_SCRIPTS.push("controllerModules/nearParentGrabEntity.js"); - CONTOLLER_SCRIPTS.push("controllerModules/nearActionGrabEntity.js"); - CONTOLLER_SCRIPTS.push("controllerModules/farActionGrabEntityDynOnly.js"); - CONTOLLER_SCRIPTS.push("controllerModules/farParentGrabEntity.js"); -} - var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; - function runDefaultsTogether() { for (var j in CONTOLLER_SCRIPTS) { if (CONTOLLER_SCRIPTS.hasOwnProperty(j)) { diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index a78a2971e9..1fb82d3843 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -14,79 +14,25 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global MyAvatar, Entities, Script, HMD, Camera, Vec3, Reticle, Overlays, getEntityCustomData, Messages, Quat, Controller, +/* global MyAvatar, Entities, Script, HMD, Camera, Vec3, Reticle, Overlays, Messages, Quat, Controller, isInEditMode, entityIsGrabbable, Picks, PickType, Pointers, unhighlightTargetEntity, DISPATCHER_PROPERTIES, - entityIsGrabbable, entityIsEquipped, getMainTabletIDs + entityIsGrabbable, getMainTabletIDs */ /* jslint bitwise: true */ (function() { // BEGIN LOCAL_SCOPE - Script.include("/~/system/libraries/utils.js"); - Script.include("/~/system/libraries/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/utils.js"); +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); + +var MOUSE_GRAB_JOINT = 65526; // FARGRAB_MOUSE_INDEX + var MAX_SOLID_ANGLE = 0.01; // objects that appear smaller than this can't be grabbed var DELAY_FOR_30HZ = 33; // milliseconds -var ZERO_VEC3 = { - x: 0, - y: 0, - z: 0 -}; -var IDENTITY_QUAT = { - x: 0, - y: 0, - z: 0, - w: 0 -}; - -var DEFAULT_GRABBABLE_DATA = { - grabbable: true, - invertSolidWhileHeld: false -}; - - -var ACTION_TTL = 10; // seconds - -function getTag() { - return "grab-" + MyAvatar.sessionUUID; -} - -var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position -var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified -var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified - -function distanceGrabTimescale(mass, distance) { - var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass / - DISTANCE_HOLDING_UNITY_MASS * distance / - DISTANCE_HOLDING_UNITY_DISTANCE; - if (timeScale < DISTANCE_HOLDING_ACTION_TIMEFRAME) { - timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME; - } - return timeScale; -} -function getMass(dimensions, density) { - return (dimensions.x * dimensions.y * dimensions.z) * density; -} - -function entityIsGrabbedByOther(entityID) { - // by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*. - var actionIDs = Entities.getActionIDs(entityID); - for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { - var actionID = actionIDs[actionIndex]; - var actionArguments = Entities.getActionArguments(entityID, actionID); - var tag = actionArguments.tag; - if (tag == getTag()) { - // we see a grab-*uuid* shaped tag, but it's our tag, so that's okay. - continue; - } - if (tag.slice(0, 5) == "grab-") { - // we see a grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it. - return true; - } - } - return false; -} +var ZERO_VEC3 = { x: 0, y: 0, z: 0 }; +var IDENTITY_QUAT = { x: 0, y: 0, z: 0, w: 1 }; // helper function function mouseIntersectionWithPlane(pointOnPlane, planeNormal, event, maxDistance) { @@ -227,7 +173,6 @@ var beacon = { function Grabber() { this.isGrabbing = false; this.entityID = null; - this.actionID = null; this.startPosition = ZERO_VEC3; this.lastRotation = IDENTITY_QUAT; this.currentPosition = ZERO_VEC3; @@ -253,9 +198,6 @@ function Grabber() { z: 0 }; - this.targetPosition = null; - this.targetRotation = null; - this.liftKey = false; // SHIFT this.rotateKey = false; // CONTROL @@ -305,7 +247,7 @@ Grabber.prototype.computeNewGrabPlane = function() { } } - this.pointOnPlane = Vec3.sum(this.currentPosition, this.offset); + this.pointOnPlane = Vec3.subtract(this.currentPosition, this.offset); var xzOffset = Vec3.subtract(this.pointOnPlane, Camera.getPosition()); xzOffset.y = 0; this.xzDistanceToGrab = Vec3.length(xzOffset); @@ -315,15 +257,12 @@ Grabber.prototype.pressEvent = function(event) { if (isInEditMode() || HMD.active) { return; } - if (event.button !== "LEFT") { return; } - if (event.isAlt || event.isMeta) { return; } - if (Overlays.getOverlayAtPoint(Reticle.position) > 0) { // the mouse is pointing at an overlay; don't look for entities underneath the overlay. return; @@ -341,13 +280,12 @@ Grabber.prototype.pressEvent = function(event) { } var props = Entities.getEntityProperties(pickResults.objectID, DISPATCHER_PROPERTIES); - var isDynamic = props.dynamic; if (!entityIsGrabbable(props)) { // only grab grabbable objects return; } - - if (!props.grab.grabbable) { + if (props.grab.equippable) { + // don't mouse-grab click-to-equip entities (let equipEntity.js handle these) return; } @@ -361,7 +299,6 @@ Grabber.prototype.pressEvent = function(event) { var entityProperties = Entities.getEntityProperties(clickedEntity, DISPATCHER_PROPERTIES); this.startPosition = entityProperties.position; this.lastRotation = entityProperties.rotation; - this.madeDynamic = false; var cameraPosition = Camera.getPosition(); var objectBoundingDiameter = Vec3.length(entityProperties.dimensions); @@ -373,21 +310,10 @@ Grabber.prototype.pressEvent = function(event) { return; } - if (entityIsGrabbable(props) && !isDynamic) { - entityProperties.dynamic = true; - Entities.editEntity(clickedEntity, entityProperties); - this.madeDynamic = true; - } - // this.activateEntity(clickedEntity, entityProperties); this.isGrabbing = true; this.entityID = clickedEntity; this.currentPosition = entityProperties.position; - this.targetPosition = { - x: this.startPosition.x, - y: this.startPosition.y, - z: this.startPosition.z - }; // compute the grab point var pickRay = Camera.computePickRay(event.x, event.y); @@ -396,14 +322,13 @@ Grabber.prototype.pressEvent = function(event) { nearestPoint = Vec3.multiply(distanceToGrab, pickRay.direction); this.pointOnPlane = Vec3.sum(cameraPosition, nearestPoint); - // compute the grab offset (points from object center to point of grab) - this.offset = Vec3.subtract(this.pointOnPlane, this.startPosition); + // compute the grab offset (points from point of grab to object center) + this.offset = Vec3.subtract(this.startPosition, this.pointOnPlane); // offset in world-space + MyAvatar.setJointTranslation(MOUSE_GRAB_JOINT, MyAvatar.worldToJointPoint(this.startPosition)); + MyAvatar.setJointRotation(MOUSE_GRAB_JOINT, MyAvatar.worldToJointRotation(this.lastRotation)); this.computeNewGrabPlane(); - - if (!entityIsGrabbedByOther(this.entityID)) { - this.moveEvent(event); - } + this.moveEvent(event); var args = "mouse"; Entities.callEntityMethod(this.entityID, "startDistanceGrab", args); @@ -413,6 +338,12 @@ Grabber.prototype.pressEvent = function(event) { grabbedEntity: this.entityID })); + if (this.grabID) { + MyAvatar.releaseGrab(this.grabID); + this.grabID = null; + } + this.grabID = MyAvatar.grab(this.entityID, MOUSE_GRAB_JOINT, ZERO_VEC3, IDENTITY_QUAT); + // TODO: play sounds again when we aren't leaking AudioInjector threads //Audio.playSound(grabSound, { position: entityProperties.position, volume: VOLUME }); }; @@ -428,20 +359,7 @@ Grabber.prototype.releaseEvent = function(event) { } if (this.isGrabbing) { - // this.deactivateEntity(this.entityID); this.isGrabbing = false; - if (this.actionID) { - Entities.deleteAction(this.entityID, this.actionID); - } - - if (this.madeDynamic) { - var entityProps = {}; - entityProps.dynamic = false; - entityProps.localVelocity = {x: 0, y: 0, z: 0}; - Entities.editEntity(this.entityID, entityProps); - } - - this.actionID = null; Pointers.setRenderState(this.mouseRayEntities, ""); Pointers.setLockEndUUID(this.mouseRayEntities, null, false); @@ -455,6 +373,13 @@ Grabber.prototype.releaseEvent = function(event) { joint: "mouse" })); + if (this.grabID) { + MyAvatar.releaseGrab(this.grabID); + this.grabID = null; + } + + MyAvatar.clearJointData(MOUSE_GRAB_JOINT); + // TODO: play sounds again when we aren't leaking AudioInjector threads //Audio.playSound(releaseSound, { position: entityProperties.position, volume: VOLUME }); } @@ -482,23 +407,12 @@ Grabber.prototype.moveEvent = function(event) { Grabber.prototype.moveEventProcess = function() { this.moveEventTimer = null; - // see if something added/restored gravity var entityProperties = Entities.getEntityProperties(this.entityID, DISPATCHER_PROPERTIES); - if (!entityProperties || !entityProperties.gravity || HMD.active) { + if (!entityProperties || HMD.active) { return; } - if (Vec3.length(entityProperties.gravity) !== 0.0) { - this.originalGravity = entityProperties.gravity; - } this.currentPosition = entityProperties.position; - this.mass = getMass(entityProperties.dimensions, entityProperties.density); - var cameraPosition = Camera.getPosition(); - - var actionArgs = { - tag: getTag(), - ttl: ACTION_TTL - }; if (this.mode === "rotate") { var drag = mouse.getDrag(); @@ -510,19 +424,9 @@ Grabber.prototype.moveEventProcess = function() { var ROTATE_STRENGTH = 0.4; // magic number tuned by hand var angle = ROTATE_STRENGTH * Math.sqrt((drag.x * drag.x) + (drag.y * drag.y)); var deltaQ = Quat.angleAxis(angle, axis); - // var qZero = entityProperties.rotation; - //var qZero = this.lastRotation; + this.lastRotation = Quat.multiply(deltaQ, this.lastRotation); - - var distanceToCameraR = Vec3.length(Vec3.subtract(this.currentPosition, cameraPosition)); - var angularTimeScale = distanceGrabTimescale(this.mass, distanceToCameraR); - - actionArgs = { - targetRotation: this.lastRotation, - angularTimeScale: angularTimeScale, - tag: getTag(), - ttl: ACTION_TTL - }; + MyAvatar.setJointRotation(MOUSE_GRAB_JOINT, MyAvatar.worldToJointRotation(this.lastRotation)); } else { var newPointOnPlane; @@ -534,17 +438,10 @@ Grabber.prototype.moveEventProcess = function() { planeNormal = Vec3.normalize(planeNormal); var pointOnCylinder = Vec3.multiply(planeNormal, this.xzDistanceToGrab); pointOnCylinder = Vec3.sum(Camera.getPosition(), pointOnCylinder); - this.pointOnPlane = mouseIntersectionWithPlane(pointOnCylinder, planeNormal, mouse.current, this.maxDistance); - newPointOnPlane = { - x: this.pointOnPlane.x, - y: this.pointOnPlane.y, - z: this.pointOnPlane.z - }; - + newPointOnPlane = mouseIntersectionWithPlane(pointOnCylinder, planeNormal, mouse.current, this.maxDistance); } else { - - newPointOnPlane = mouseIntersectionWithPlane( - this.pointOnPlane, this.planeNormal, mouse.current, this.maxDistance); + var cameraPosition = Camera.getPosition(); + newPointOnPlane = mouseIntersectionWithPlane(this.pointOnPlane, this.planeNormal, mouse.current, this.maxDistance); var relativePosition = Vec3.subtract(newPointOnPlane, cameraPosition); var distance = Vec3.length(relativePosition); if (distance > this.maxDistance) { @@ -553,26 +450,8 @@ Grabber.prototype.moveEventProcess = function() { newPointOnPlane = Vec3.sum(relativePosition, cameraPosition); } } - this.targetPosition = Vec3.subtract(newPointOnPlane, this.offset); - var distanceToCameraL = Vec3.length(Vec3.subtract(this.targetPosition, cameraPosition)); - var linearTimeScale = distanceGrabTimescale(this.mass, distanceToCameraL); - - actionArgs = { - targetPosition: this.targetPosition, - linearTimeScale: linearTimeScale, - tag: getTag(), - ttl: ACTION_TTL - }; - - } - - if (!this.actionID) { - if (!entityIsGrabbedByOther(this.entityID) && !entityIsEquipped(this.entityID)) { - this.actionID = Entities.addAction("far-grab", this.entityID, actionArgs); - } - } else { - Entities.updateAction(this.entityID, this.actionID, actionArgs); + MyAvatar.setJointTranslation(MOUSE_GRAB_JOINT, MyAvatar.worldToJointPoint(Vec3.sum(newPointOnPlane, this.offset))); } this.scheduleMouseMoveProcessor(); @@ -601,6 +480,10 @@ Grabber.prototype.keyPressEvent = function(event) { Grabber.prototype.cleanup = function() { Pointers.removePointer(this.mouseRayEntities); Picks.removePick(this.mouseRayOverlays); + if (this.grabID) { + MyAvatar.releaseGrab(this.grabID); + this.grabID = null; + } }; var grabber = new Grabber(); diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index a4322c0941..0bec77aa41 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -111,11 +111,29 @@ const GROUPS = [ type: "color", propertyID: "textColor", }, + { + label: "Text Alpha", + type: "number-draggable", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "textAlpha", + }, { label: "Background Color", type: "color", propertyID: "backgroundColor", }, + { + label: "Background Alpha", + type: "number-draggable", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "backgroundAlpha", + }, { label: "Line Height", type: "number-draggable", @@ -132,12 +150,54 @@ const GROUPS = [ propertyID: "textBillboardMode", propertyName: "billboardMode", // actual entity property name }, + { + label: "Top Margin", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "topMargin", + }, + { + label: "Right Margin", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "rightMargin", + }, + { + label: "Bottom Margin", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "bottomMargin", + }, + { + label: "Left Margin", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "leftMargin", + }, ] }, { id: "zone", addToGroup: "base", properties: [ + { + label: "Shape Type", + type: "dropdown", + options: { "box": "Box", "sphere": "Sphere", "ellipsoid": "Ellipsoid", + "cylinder-y": "Cylinder", "compound": "Use Compound Shape URL" }, + propertyID: "zoneShapeType", + propertyName: "shapeType", // actual entity property name + }, + { + label: "Compound Shape URL", + type: "string", + propertyID: "zoneCompoundShapeURL", + propertyName: "compoundShapeURL", // actual entity property name + }, { label: "Flying Allowed", type: "bool", @@ -1299,24 +1359,6 @@ const GROUPS = [ }, ] }, - { - id: "zone_shape", - label: "ZONE SHAPE", - properties: [ - { - label: "Shape Type", - type: "dropdown", - options: { "box": "Box", "sphere": "Sphere", "ellipsoid": "Ellipsoid", - "cylinder-y": "Cylinder", "compound": "Use Compound Shape URL" }, - propertyID: "shapeType", - }, - { - label: "Compound Shape URL", - type: "string", - propertyID: "compoundShapeURL", - }, - ] - }, { id: "physics", label: "PHYSICS", @@ -1408,7 +1450,7 @@ const GROUPS_PER_TYPE = { None: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], Shape: [ 'base', 'shape', 'spatial', 'behavior', 'collision', 'physics' ], Text: [ 'base', 'text', 'spatial', 'behavior', 'collision', 'physics' ], - Zone: [ 'base', 'zone', 'spatial', 'behavior', 'zone_shape', 'physics' ], + Zone: [ 'base', 'zone', 'spatial', 'behavior', 'physics' ], Model: [ 'base', 'model', 'spatial', 'behavior', 'collision', 'physics' ], Image: [ 'base', 'image', 'spatial', 'behavior', 'collision', 'physics' ], Web: [ 'base', 'web', 'spatial', 'behavior', 'collision', 'physics' ], @@ -3271,7 +3313,8 @@ function loaded() { } } - let doSelectElement = lastEntityID === '"' + selectedEntityProperties.id + '"'; + let hasSelectedEntityChanged = lastEntityID !== '"' + selectedEntityProperties.id + '"'; + let doSelectElement = !hasSelectedEntityChanged; // the event bridge and json parsing handle our avatar id string differently. lastEntityID = '"' + selectedEntityProperties.id + '"'; @@ -3391,7 +3434,7 @@ function loaded() { property.elColorPicker.style.backgroundColor = "rgb(" + propertyValue.red + "," + propertyValue.green + "," + propertyValue.blue + ")"; - if ($(property.elColorPicker).attr('active') === 'true') { + if (hasSelectedEntityChanged && $(property.elColorPicker).attr('active') === 'true') { // Set the color picker inactive before setting the color, // otherwise an update will be sent directly after setting it here. $(property.elColorPicker).attr('active', 'false'); diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 221af07474..385ed954b0 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -33,6 +33,7 @@ getGrabbableData:true, isAnothersAvatarEntity:true, isAnothersChildEntity:true, + entityIsEquippable:true, entityIsGrabbable:true, entityIsDistanceGrabbable:true, getControllerJointIndexCacheTime:true, @@ -46,7 +47,7 @@ makeLaserLockInfo:true, entityHasActions:true, ensureDynamic:true, - findGroupParent:true, + findGrabbableGroupParent:true, BUMPER_ON_VALUE:true, getEntityParents:true, findHandChildEntities:true, @@ -58,7 +59,6 @@ NEAR_GRAB_DISTANCE: true, distanceBetweenPointAndEntityBoundingBox:true, entityIsEquipped:true, - entityIsFarGrabbedByOther:true, highlightTargetEntity:true, clearHighlightedEntities:true, unhighlightTargetEntity:true, @@ -323,6 +323,18 @@ isAnothersChildEntity = function (iaceProps) { return false; }; + +entityIsEquippable = function (eieProps) { + var grabbable = getGrabbableData(eieProps).grabbable; + if (!grabbable || + isAnothersAvatarEntity(eieProps) || + isAnothersChildEntity(eieProps) || + FORBIDDEN_GRAB_TYPES.indexOf(eieProps.type) >= 0) { + return false; + } + return true; +}; + entityIsGrabbable = function (eigProps) { var grabbable = getGrabbableData(eigProps).grabbable; if (!grabbable || @@ -439,7 +451,7 @@ ensureDynamic = function (entityID) { } }; -findGroupParent = function (controllerData, targetProps) { +findGrabbableGroupParent = function (controllerData, targetProps) { while (targetProps.grab.grabDelegateToParent && targetProps.parentID && targetProps.parentID !== Uuid.NULL && @@ -448,6 +460,9 @@ findGroupParent = function (controllerData, targetProps) { if (!parentProps) { break; } + if (!entityIsGrabbable(parentProps)) { + break; + } parentProps.id = targetProps.parentID; targetProps = parentProps; controllerData.nearbyEntityPropertiesByID[targetProps.id] = targetProps; @@ -561,27 +576,6 @@ entityIsEquipped = function(entityID) { return equippedInRightHand || equippedInLeftHand; }; -entityIsFarGrabbedByOther = function(entityID) { - // by convention, a far grab sets the tag of its action to be far-grab-*owner-session-id*. - var actionIDs = Entities.getActionIDs(entityID); - var myFarGrabTag = "far-grab-" + MyAvatar.sessionUUID; - for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { - var actionID = actionIDs[actionIndex]; - var actionArguments = Entities.getActionArguments(entityID, actionID); - var tag = actionArguments.tag; - if (tag == myFarGrabTag) { - // we see a far-grab-*uuid* shaped tag, but it's our tag, so that's okay. - continue; - } - if (tag.slice(0, 9) == "far-grab-") { - // we see a far-grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it. - return true; - } - } - return false; -}; - - worldPositionToRegistrationFrameMatrix = function(wptrProps, pos) { // get world matrix for intersection point var intersectionMat = new Xform({ x: 0, y: 0, z:0, w: 1 }, pos); @@ -614,12 +608,13 @@ if (typeof module !== 'undefined') { unhighlightTargetEntity: unhighlightTargetEntity, clearHighlightedEntities: clearHighlightedEntities, makeRunningValues: makeRunningValues, - findGroupParent: findGroupParent, + findGrabbableGroupParent: findGrabbableGroupParent, LEFT_HAND: LEFT_HAND, RIGHT_HAND: RIGHT_HAND, BUMPER_ON_VALUE: BUMPER_ON_VALUE, TEAR_AWAY_DISTANCE: TEAR_AWAY_DISTANCE, propsArePhysical: propsArePhysical, + entityIsEquippable: entityIsEquippable, entityIsGrabbable: entityIsGrabbable, NEAR_GRAB_RADIUS: NEAR_GRAB_RADIUS, projectOntoOverlayXYPlane: projectOntoOverlayXYPlane, diff --git a/server-console/resources/console-beta.icns b/server-console/resources/console-beta.icns index 70a00698bd..a7317c863a 100644 Binary files a/server-console/resources/console-beta.icns and b/server-console/resources/console-beta.icns differ diff --git a/server-console/resources/console-beta.ico b/server-console/resources/console-beta.ico index 57df32f878..c551fc5e37 100644 Binary files a/server-console/resources/console-beta.ico and b/server-console/resources/console-beta.ico differ diff --git a/server-console/resources/console-beta.png b/server-console/resources/console-beta.png index c2df15832d..288ca42bb3 100644 Binary files a/server-console/resources/console-beta.png and b/server-console/resources/console-beta.png differ diff --git a/server-console/resources/console.icns b/server-console/resources/console.icns index 8810f804c3..61e8108c95 100644 Binary files a/server-console/resources/console.icns and b/server-console/resources/console.icns differ diff --git a/server-console/resources/console.ico b/server-console/resources/console.ico index c5a4bb0381..007c97f92a 100644 Binary files a/server-console/resources/console.ico and b/server-console/resources/console.ico differ diff --git a/server-console/resources/console.png b/server-console/resources/console.png index 9fa1b843ec..cfb848d47b 100644 Binary files a/server-console/resources/console.png and b/server-console/resources/console.png differ diff --git a/tools/dissectors/1-hfudt.lua b/tools/dissectors/1-hfudt.lua index fe8a72682d..de99c1ce3c 100644 --- a/tools/dissectors/1-hfudt.lua +++ b/tools/dissectors/1-hfudt.lua @@ -151,13 +151,16 @@ local packet_types = { [96] = "OctreeDataFileReply", [97] = "OctreeDataPersist", [98] = "EntityClone", - [99] = "EntityQueryInitialResultsComplete" + [99] = "EntityQueryInitialResultsComplete", + [100] = "BulkAvatarTraits" } local unsourced_packet_types = { ["DomainList"] = true } +local fragments = {} + function p_hfudt.dissector(buf, pinfo, tree) -- make sure this isn't a STUN packet - those don't follow HFUDT format @@ -235,6 +238,10 @@ function p_hfudt.dissector(buf, pinfo, tree) local payload_offset = 4 + local message_number = 0 + local message_part_number = 0 + local message_position = 0 + -- if the message bit is set, handle the second word if message_bit == 1 then payload_offset = 12 @@ -242,7 +249,7 @@ function p_hfudt.dissector(buf, pinfo, tree) local second_word = buf(4, 4):le_uint() -- read message position from upper 2 bits - local message_position = bit32.rshift(second_word, 30) + message_position = bit32.rshift(second_word, 30) local position = subtree:add(f_message_position, message_position) if message_positions[message_position] ~= nil then @@ -251,10 +258,12 @@ function p_hfudt.dissector(buf, pinfo, tree) end -- read message number from lower 30 bits - subtree:add(f_message_number, bit32.band(second_word, 0x3FFFFFFF)) + message_number = bit32.band(second_word, 0x3FFFFFFF) + subtree:add(f_message_number, message_number) -- read the message part number - subtree:add(f_message_part_number, buf(8, 4):le_uint()) + message_part_number = buf(8, 4):le_uint() + subtree:add(f_message_part_number, message_part_number) end if obfuscation_bits ~= 0 then @@ -288,25 +297,85 @@ function p_hfudt.dissector(buf, pinfo, tree) i = i + 16 end - -- Domain packets - if packet_type_text == "DomainList" then - Dissector.get("hf-domain"):call(buf(i):tvb(), pinfo, tree) + local payload_to_dissect = nil + + -- check if we have part of a message that we need to re-assemble + -- before it can be dissected + if obfuscation_bits == 0 then + if message_bit == 1 and message_position ~= 0 then + if fragments[message_number] == nil then + fragments[message_number] = {} + end + + if fragments[message_number][message_part_number] == nil then + fragments[message_number][message_part_number] = {} + end + + -- set the properties for this fragment + fragments[message_number][message_part_number] = { + payload = buf(i):bytes() + } + + -- if this is the last part, set our maximum part number + if message_position == 1 then + fragments[message_number].last_part_number = message_part_number + end + + -- if we have the last part + -- enumerate our parts for this message and see if everything is present + if fragments[message_number].last_part_number ~= nil then + local i = 0 + local has_all = true + + local finalMessage = ByteArray.new() + local message_complete = true + + while i <= fragments[message_number].last_part_number do + if fragments[message_number][i] ~= nil then + finalMessage = finalMessage .. fragments[message_number][i].payload + else + -- missing this part, have to break until we have it + message_complete = false + end + + i = i + 1 + end + + if message_complete then + debug("Message " .. message_number .. " is " .. finalMessage:len()) + payload_to_dissect = ByteArray.tvb(finalMessage, message_number) + end + end + + else + payload_to_dissect = buf(i):tvb() + end end - -- AvatarData or BulkAvatarDataPacket - if packet_type_text == "AvatarData" or packet_type_text == "BulkAvatarData" then - Dissector.get("hf-avatar"):call(buf(i):tvb(), pinfo, tree) + if payload_to_dissect ~= nil then + -- Domain packets + if packet_type_text == "DomainList" then + Dissector.get("hf-domain"):call(payload_to_dissect, pinfo, tree) + end + + -- AvatarData or BulkAvatarDataPacket + if packet_type_text == "AvatarData" or + packet_type_text == "BulkAvatarData" or + packet_type_text == "BulkAvatarTraits" then + Dissector.get("hf-avatar"):call(payload_to_dissect, pinfo, tree) + end + + if packet_type_text == "EntityEdit" then + Dissector.get("hf-entity"):call(payload_to_dissect, pinfo, tree) + end + + if packet_types[packet_type] == "MicrophoneAudioNoEcho" or + packet_types[packet_type] == "MicrophoneAudioWithEcho" or + packet_types[packet_type] == "SilentAudioFrame" then + Dissector.get("hf-audio"):call(payload_to_dissect, pinfo, tree) + end end - if packet_type_text == "EntityEdit" then - Dissector.get("hf-entity"):call(buf(i):tvb(), pinfo, tree) - end - - if packet_types[packet_type] == "MicrophoneAudioNoEcho" or - packet_types[packet_type] == "MicrophoneAudioWithEcho" or - packet_types[packet_type] == "SilentAudioFrame" then - Dissector.get("hf-audio"):call(buf(i):tvb(), pinfo, tree) - end end -- return the size of the header diff --git a/tools/dissectors/3-hf-avatar.lua b/tools/dissectors/3-hf-avatar.lua index 0fa551c6f8..bc449770f5 100644 --- a/tools/dissectors/3-hf-avatar.lua +++ b/tools/dissectors/3-hf-avatar.lua @@ -21,13 +21,31 @@ local f_avatar_data_default_rotations = ProtoField.string("hf_avatar.avatar_data local f_avatar_data_default_translations = ProtoField.string("hf_avatar.avatar_data_default_translations", "Valid Default") local f_avatar_data_sizes = ProtoField.string("hf_avatar.avatar_sizes", "Sizes") +-- avatar trait data fields +local f_avatar_trait_data = ProtoField.bytes("hf_avatar.avatar_trait_data", "Avatar Trait Data") + +local f_avatar_trait_id = ProtoField.guid("hf_avatar.trait_avatar_id", "Trait Avatar ID") +local f_avatar_trait_type = ProtoField.int8("hf_avatar.trait_type", "Trait Type") +local f_avatar_trait_version = ProtoField.int32("hf_avatar.trait_version", "Trait Version") +local f_avatar_trait_instance_id = ProtoField.guid("hf_avatar.trait_instance_id", "Trait Instance ID") +local f_avatar_trait_binary = ProtoField.bytes("hf_avatar.trait_binary", "Trait Binary Data") p_hf_avatar.fields = { - f_avatar_id, f_avatar_data_parent_id + f_avatar_id, f_avatar_data_parent_id, + f_avatar_trait_data, + f_avatar_trait_type, f_avatar_trait_id, + f_avatar_trait_version, f_avatar_trait_binary, + f_avatar_trait_instance_id } local packet_type_extractor = Field.new('hfudt.type') +INSTANCED_TYPES = { + [1] = true +} + +TOTAL_TRAIT_TYPES = 2 + function p_hf_avatar.dissector(buf, pinfo, tree) pinfo.cols.protocol = p_hf_avatar.name @@ -52,7 +70,7 @@ function p_hf_avatar.dissector(buf, pinfo, tree) add_avatar_data_subtrees(avatar_data) - else + elseif packet_type == 11 then -- BulkAvatarData packet while i < buf:len() do -- avatar_id is first 16 bytes @@ -65,9 +83,88 @@ function p_hf_avatar.dissector(buf, pinfo, tree) add_avatar_data_subtrees(avatar_data) end + elseif packet_type == 100 then + -- BulkAvatarTraits packet + + -- loop over the packet until we're done reading it + while i < buf:len() do + i = i + read_avatar_trait_data(buf(i)) + end end end +function read_avatar_trait_data(buf) + local i = 0 + + -- avatar_id is first 16 bytes + local avatar_id_bytes = buf(i, 16) + i = i + 16 + + local traits_map = {} + + -- loop over all of the traits for this avatar + while i < buf:len() do + -- pull out this trait type + local trait_type = buf(i, 1):le_int() + i = i + 1 + + debug("The trait type is " .. trait_type) + + -- bail on our while if the trait type is null (-1) + if trait_type == -1 then break end + + local trait_map = {} + + -- pull out the trait version + trait_map.version = buf(i, 4):le_int() + i = i + 4 + + if INSTANCED_TYPES[trait_type] ~= nil then + -- pull out the trait instance ID + trait_map.instance_ID = buf(i, 16) + i = i + 16 + end + + -- pull out the trait binary size + trait_map.binary_size = buf(i, 2):le_int() + i = i + 2 + + -- unpack the binary data as long as this wasn't a delete + if trait_map.binary_size ~= -1 then + -- pull out the binary data for the trait + trait_map.binary_data = buf(i, trait_map.binary_size) + i = i + trait_map.binary_size + end + + traits_map[trait_type] = trait_map + end + + -- add a subtree including all of the data for this avatar + debug("Adding trait data of " .. i .. " bytes to the avatar tree") + local this_avatar_tree = avatar_subtree:add(f_avatar_trait_data, buf(0, i)) + + this_avatar_tree:add(f_avatar_trait_id, avatar_id_bytes) + + -- enumerate the pulled traits and add them to the tree + local trait_type = 0 + while trait_type < TOTAL_TRAIT_TYPES do + trait = traits_map[trait_type] + + if trait ~= nil then + this_avatar_tree:add(f_avatar_trait_type, trait_type) + this_avatar_tree:add(f_avatar_trait_version, trait.version) + this_avatar_tree:add(f_avatar_trait_binary, trait.binary_data) + + if trait.instance_ID ~= nil then + this_avatar_tree:add(f_avatar_trait_instance_id, trait.instance_ID) + end + end + + trait_type = trait_type + 1 + end + + return i +end function add_avatar_data_subtrees(avatar_data) if avatar_data["has_flags"] then @@ -282,6 +379,9 @@ function decode_avatar_data_packet(buf) i = i + num_validity_bytes result["valid_translations"] = "Valid Translations: " .. string.format("(%d/%d) {", #indices, num_joints) .. table.concat(indices, ", ") .. "}" + -- TODO: skip maxTranslationDimension + i = i + 4 + -- TODO: skip translations for now i = i + #indices * 6 diff --git a/tools/jsdoc/gravPrep.js b/tools/jsdoc/gravPrep.js index 849837bae0..00dd4850ea 100644 --- a/tools/jsdoc/gravPrep.js +++ b/tools/jsdoc/gravPrep.js @@ -25,7 +25,7 @@ let dir_css = path.join(dir_grav, 'css'); let dir_js = path.join(dir_grav, 'js'); let dir_template = path.join(dir_grav, 'templates'); -let dir_md = path.join(dir_grav, '06.api-reference'); +let dir_md = path.join(dir_grav, '08.api-reference'); let dir_md_objects = path.join(dir_md, '02.Objects'); let dir_md_namespaces = path.join(dir_md, '01.Namespaces'); let dir_md_globals = path.join(dir_md, '03.Globals'); @@ -662,7 +662,7 @@ if (copyLocal){ copyFolderRecursiveSync(dir_template, targetTemplateDirectory); // Copy files to the Md Directory - let baseMdRefDir = path.join(targetMDDirectory,"06.api-reference"); + let baseMdRefDir = path.join(targetMDDirectory,"08.api-reference"); // Remove existing MD directory if (fs.existsSync(baseMdRefDir)){ rimraf.sync(baseMdRefDir); diff --git a/tools/nitpick/README.md b/tools/nitpick/README.md index 23105a0e02..3a664a12e9 100644 --- a/tools/nitpick/README.md +++ b/tools/nitpick/README.md @@ -13,36 +13,39 @@ Nitpick has 5 functions, separated into separate tabs: 1. Web interface ## Build (for developers) -Nitpick is built as part of the High Fidelity build. +Nitpick is built as part of the High Fidelity build. +XXXX refers to the version number - replace as necessary. For example, replace with 3.2.11 ### Creating installers #### Windows +1. Perform Release build 1. Verify that 7Zip is installed. 1. cd to the `build\tools\nitpick\Release` directory 1. Delete any existing installers (named nitpick-installer-###.exe) 1. Select all, right-click and select 7-Zip->Add to archive... 1. Set Archive format to 7z 1. Check "Create SFX archive -1. Enter installer name (i.e. `nitpick-installer-v1.2.exe`) +1. Enter installer name (i.e. `nitpick-installer-vXXXX.exe`) 1. Click "OK" -1. Copy created installer to https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-v1.2.exe: aws s3 cp nitpick-installer-v1.2.exe s3://hifi-qa/nitpick/Mac/nitpick-installer-v1.2.exe +1. Copy created installer to https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-vXXXX.exe: aws s3 cp nitpick-installer-vXXXX.exe s3://hifi-qa/nitpick/Mac/nitpick-installer-vXXXX.exe #### Mac These steps assume the hifi repository has been cloned to `~/hifi`. 1. (first time) Install brew In a terminal: `/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)` 1. (First time) install create-dmg: In a terminal: `brew install create-dmg` +1. Perform Release build 1. In a terminal: cd to the `build/tools/nitpick/Release` folder 1. Copy the quazip dynamic library (note final period): In a terminal: `cp ~/hifi/build/ext/Xcode/quazip/project/lib/libquazip5.1.dylib .` 1. Change the loader instruction to find the dynamic library locally In a terminal: `install_name_tool -change ~/hifi/build/ext/Xcode/quazip/project/lib/libquazip5.1.dylib libquazip5.1.dylib nitpick` 1. Delete any existing disk images. In a terminal: `rm *.dmg` -1. Create installer (note final period).In a terminal: `create-dmg --volname nitpick-installer-v1.2 nitpick-installer-v1.2.dmg .` +1. Create installer (note final period).In a terminal: `create-dmg --volname nitpick-installer-vXXXX nitpick-installer-vXXXX.dmg .` Make sure to wait for completion. -1. Copy created installer to AWS: `~/Library/Python/3.7/bin/aws s3 cp nitpick-installer-v1.2.dmg s3://hifi-qa/nitpick/Mac/nitpick-installer-v1.2.dmg` +1. Copy created installer to AWS: `~/Library/Python/3.7/bin/aws s3 cp nitpick-installer-vXXXX.dmg s3://hifi-qa/nitpick/Mac/nitpick-installer-vXXXX.dmg` ### Installation #### Windows -1. (First time) download and install vc_redist.x64.exe (available at https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-v1.2.exe) +1. (First time) download and install vc_redist.x64.exe (available at https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-vXXXX.exe) 1. (First time) download and install Python 3 from https://hifi-qa.s3.amazonaws.com/nitpick/Windows/python-3.7.0-amd64.exe (also located at https://www.python.org/downloads/) 1. After installation - create an environment variable called PYTHON_PATH and set it to the folder containing the Python executable. 1. (First time) download and install AWS CLI from https://hifi-qa.s3.amazonaws.com/nitpick/Windows/AWSCLI64PY3.msi (also available at https://aws.amazon.com/cli/ @@ -52,7 +55,7 @@ These steps assume the hifi repository has been cloned to `~/hifi`. 1. Leave region name and ouput format as default [None] 1. Install the latest release of Boto3 via pip: `pip install boto3` -1. Download the installer by browsing to [here]() +1. Download the installer by browsing to [here]() 1. Double click on the installer and install to a convenient location ![](./setup_7z.PNG) @@ -76,14 +79,14 @@ In a terminal: `python3 get-pip.py --user` 1. Enter the secret key 1. Leave region name and ouput format as default [None] 1. Install the latest release of Boto3 via pip: pip3 install boto3 -1. Download the installer by browsing to [here](). +1. Download the installer by browsing to [here](). 1. Double-click on the downloaded image to mount it 1. Create a folder for the nitpick files (e.g. ~/nitpick) If this folder exists then delete all it's contents. 1. Copy the downloaded files to the folder In a terminal: `cd ~/nitpick` - `cp -r /Volumes/nitpick-installer-v1.2/* .` + `cp -r /Volumes/nitpick-installer-vXXXX/* .` 1. __To run nitpick, cd to the folder that you copied to and run `./nitpick`__ # Usage