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>("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<File>(), - features: new HashSet<String>(), - permissions: new HashSet<String>() -] - -def qmlRoot = new File(HIFI_ANDROID_PRECOMPILED, 'qt') - -def captureOutput = { String command, List<String> 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('<?xml version="1.0" encoding="UTF-8"?><resources/>') - 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 @@ +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="io.highfidelity.shared.qt" /> 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<String> readAssetLines(AssetManager assetManager, String asset) throws IOException { + LinkedList<String> 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<String> 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<AssignmentParentFinder>(_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<ScriptableAvatar>(); 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<ScriptableAvatar>(); - scriptedAvatar->markIdentityDataChanged(); - scriptedAvatar->sendIdentityPacket(); - } -} - void Agent::queryAvatars() { auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>(); @@ -681,44 +654,6 @@ void Agent::queryAvatars() { { NodeType::AvatarMixer }); } -void Agent::processAgentAvatar() { - if (!_scriptEngine->isFinished() && _isAvatar) { - auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>(); - - 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>(); - - 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<QUuid, quint16> _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<PluginManager>(); + // 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/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 6b90a8fbbd..500772c1b5 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -54,7 +54,6 @@ 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"); @@ -582,36 +581,6 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> mes _handleAvatarIdentityPacketElapsedTime += (end - start); } -void AvatarMixer::handleAvatarIdentityRequestPacket(QSharedPointer<ReceivedMessage> 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<NodeList>(); - auto requestedNode = nodeList->nodeWithUUID(avatarID); - - if (requestedNode) { - AvatarMixerClientData* avatarClientData = static_cast<AvatarMixerClientData*>(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<AvatarMixerClientData*>(senderNode->getLinkedData()); - if (senderData) { - senderData->resetSentTraitData(requestedNode->getLocalID()); - } - } - } -} - void AvatarMixer::handleKillAvatarPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node) { auto start = usecTimestampNow(); handleAvatarKilled(node); 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<ReceivedMessage> message, SharedNodePointer senderNode); void handleReplicatedPacket(QSharedPointer<ReceivedMessage> message); void handleReplicatedBulkAvatarPacket(QSharedPointer<ReceivedMessage> message); - void handleAvatarIdentityRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode); void domainSettingsRequestComplete(); void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); void start(); diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index c61e41fbbe..044ab86942 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -91,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); @@ -161,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() { diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index df949f8bff..e93be897d5 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -123,6 +123,10 @@ class ScriptableAvatar : public AvatarData, public Dependency { Q_OBJECT + + using Clock = std::chrono::system_clock; + using TimePoint = Clock::time_point; + public: ScriptableAvatar(); @@ -177,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); @@ -228,6 +234,10 @@ private: /// 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<String, List<String>> 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/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index 341f7ba1c9..2d5c68c0e8 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -54,6 +54,7 @@ FocusScope { Image { z: -10 id: loginDialogBackground + fillMode: Image.PreserveAspectCrop source: "LoginDialog/images/background.png" anchors.fill: parent } diff --git a/interface/resources/qml/OverlayLoginDialog.qml b/interface/resources/qml/OverlayLoginDialog.qml index 8a08fea3de..0ad2c57e5f 100644 --- a/interface/resources/qml/OverlayLoginDialog.qml +++ b/interface/resources/qml/OverlayLoginDialog.qml @@ -55,6 +55,7 @@ FocusScope { Image { z: -10 id: loginDialogBackground + 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 01a597fb9e..8d6444bc0e 100644 --- a/interface/resources/qml/dialogs/TabletLoginDialog.qml +++ b/interface/resources/qml/dialogs/TabletLoginDialog.qml @@ -97,6 +97,7 @@ FocusScope { Image { z: -10 id: loginDialogBackground + 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/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index 668a950d0d..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; @@ -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 } } 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 964ac60a90..2db440291d 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -919,6 +919,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set<Wallet>(); DependencyManager::set<WalletScriptingInterface>(); DependencyManager::set<TTSScriptingInterface>(); + DependencyManager::set<QmlCommerce>(); DependencyManager::set<FadeEffect>(); DependencyManager::set<ResourceRequestObserver>(); @@ -1200,6 +1201,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<TabletScriptingInterface>(); + if (tabletScriptingInterface) { + tabletScriptingInterface->setQmlTabletRoot(SYSTEM_TABLET, nullptr); + } getOverlays().deleteOverlay(getTabletScreenID()); getOverlays().deleteOverlay(getTabletHomeButtonID()); getOverlays().deleteOverlay(getTabletFrameID()); @@ -2201,7 +2206,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<AvatarManager>()->getIdentityRequestsSent(); UserActivityLogger::getInstance().logAction("stats", properties); }); @@ -2599,6 +2603,7 @@ void Application::cleanupBeforeQuit() { DependencyManager::destroy<ContextOverlayInterface>(); // Must be destroyed before TabletScriptingInterface // stop QML + DependencyManager::destroy<QmlCommerce>(); DependencyManager::destroy<TabletScriptingInterface>(); DependencyManager::destroy<ToolbarScriptingInterface>(); DependencyManager::destroy<OffscreenUi>(); @@ -2888,7 +2893,7 @@ void Application::initializeUi() { Tooltip::registerType(); UpdateDialog::registerType(); QmlContextCallback commerceCallback = [](QQmlContext* context) { - context->setContextProperty("Commerce", new QmlCommerce()); + context->setContextProperty("Commerce", DependencyManager::get<QmlCommerce>().data()); }; OffscreenQmlSurface::addWhitelistContextHandler({ QUrl{ "hifi/commerce/checkout/Checkout.qml" }, @@ -2913,6 +2918,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<TTSScriptingInterface>().data()); @@ -6279,7 +6285,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(); @@ -8020,8 +8026,7 @@ void Application::openUrl(const QUrl& url) const { if (url.scheme() == URL_SCHEME_HIFI) { DependencyManager::get<AddressManager>()->handleLookupString(url.toString()); } else if (url.scheme() == URL_SCHEME_HIFIAPP) { - QmlCommerce commerce; - commerce.openSystemApp(url.path()); + DependencyManager::get<QmlCommerce>()->openSystemApp(url.path()); } else { // address manager did not handle - ask QDesktopServices to handle QDesktopServices::openUrl(url); 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<Avatar>(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<NodeList>(); - QWeakPointer<NodeList> 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<NodeList>(); + 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 <PhysicsEngine.h> #include <PhysicsHelpers.h> - 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<EntityTreeRenderer>(); auto entityTree = entityTreeRenderer->getTree(); + auto sessionID = DependencyManager::get<NodeList>()->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 cc7742c517..fec988b29f 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -205,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); } } @@ -668,12 +668,6 @@ 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, true); currentEnergy += energyChargeRate; @@ -773,7 +767,7 @@ void MyAvatar::simulate(float deltaTime, bool inView) { 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) { @@ -823,7 +817,9 @@ void MyAvatar::simulate(float deltaTime, bool inView) { // 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 @@ -879,9 +875,13 @@ void MyAvatar::simulate(float deltaTime, bool inView) { 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<EntityItem>(object); + bool iShouldTellServer = !(entity && entity->isAvatarEntity()); + const bool force = false; entityTree->updateEntityQueryAACube(object, packetSender, force, iShouldTellServer); }); }); @@ -2534,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 { @@ -2544,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 @@ -2599,7 +2599,7 @@ void MyAvatar::prepareForPhysicsSimulation() { qDebug() << "Warning: getParentVelocity failed" << getID(); parentVelocity = glm::vec3(); } - _characterController.handleChangedCollisionGroup(); + _characterController.handleChangedCollisionMask(); _characterController.setParentVelocity(parentVelocity); _characterController.setScaleFactor(getSensorToWorldScale()); @@ -3103,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 { @@ -3246,7 +3279,7 @@ 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); } @@ -3292,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; } @@ -3848,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) { @@ -3857,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); } @@ -3865,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 58880acb08..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, @@ -294,6 +297,8 @@ public: void reset(bool andRecenter = false, bool andReload = true, bool andHead = true); + void setCollisionWithOtherAvatarsFlags() override; + /**jsdoc * @function MyAvatar.resetSensorsAndBody */ @@ -1213,6 +1218,7 @@ public: 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: @@ -1728,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; @@ -1937,6 +1943,8 @@ private: bool _skeletonModelLoaded { false }; bool _reloadAvatarEntityDataFromSettings { true }; + TimePoint _nextTraitsSendWindow; + Setting::Handle<QString> _dominantHandSetting; Setting::Handle<float> _headPitchSetting; Setting::Handle<float> _scaleSetting; 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 0dfc349e18..754d914135 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -46,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() { @@ -138,17 +135,9 @@ 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; - } - auto newCollisionGroup = _collideWithOtherAvatars ? BULLET_COLLISION_GROUP_OTHER_AVATAR : BULLET_COLLISION_GROUP_COLLISIONLESS; - _motionState->setCollisionGroup(newCollisionGroup); - _motionState->addDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); - } + _motionState->addDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); } } diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h old mode 100644 new mode 100755 index a1dc5724a9..969f551783 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -46,7 +46,9 @@ 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; 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 <ui/TabletScriptingInterface.h> #include "scripting/HMDScriptingInterface.h" -QmlCommerce::QmlCommerce() { +QmlCommerce::QmlCommerce() : + _appsPath(PathUtils::getAppDataPath() + "Apps/") +{ auto ledger = DependencyManager::get<Ledger>(); auto wallet = DependencyManager::get<Wallet>(); connect(ledger.data(), &Ledger::buyResult, this, &QmlCommerce::buyResult); @@ -44,22 +46,18 @@ QmlCommerce::QmlCommerce() { auto accountManager = DependencyManager::get<AccountManager>(); connect(accountManager.data(), &AccountManager::usernameChanged, this, [&]() { setPassphrase(""); }); - - _appsPath = PathUtils::getAppDataPath() + "Apps/"; } - - void QmlCommerce::openSystemApp(const QString& appName) { - static QMap<QString, QString> systemApps { + static const QMap<QString, QString> systemApps { {"GOTO", "hifi/tablet/TabletAddressDialog.qml"}, {"PEOPLE", "hifi/Pal.qml"}, {"WALLET", "hifi/commerce/wallet/Wallet.qml"}, {"MARKET", "/marketplace.html"} }; - static QMap<QString, QString> systemInject{ + static const QMap<QString, QString> 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 <QPixmap> -class QmlCommerce : public QObject { +#include <DependencyManager.h> + +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/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 14b8182abf..14e5cdc7f5 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -114,14 +114,9 @@ void ModelOverlay::update(float deltatime) { _model->setVisibleInScene(getVisible(), scene); metaDirty = true; } - if (_drawInFrontDirty) { - _drawInFrontDirty = false; - _model->setLayeredInFront(getDrawInFront(), scene); - metaDirty = true; - } - if (_drawInHUDDirty) { - _drawInHUDDirty = false; - _model->setLayeredInHUD(getDrawHUDLayer(), scene); + if (_renderLayerDirty) { + _renderLayerDirty = false; + _model->setHifiRenderLayer(_drawHUDLayer ? render::hifi::LAYER_3D_HUD : (_drawInFront ? render::hifi::LAYER_3D_FRONT : render::hifi::LAYER_3D), scene); metaDirty = true; } if (_groupCulledDirty) { @@ -175,14 +170,14 @@ void ModelOverlay::setVisible(bool visible) { void ModelOverlay::setDrawInFront(bool drawInFront) { if (drawInFront != getDrawInFront()) { Base3DOverlay::setDrawInFront(drawInFront); - _drawInFrontDirty = true; + _renderLayerDirty = true; } } void ModelOverlay::setDrawHUDLayer(bool drawHUDLayer) { if (drawHUDLayer != getDrawHUDLayer()) { Base3DOverlay::setDrawHUDLayer(drawHUDLayer); - _drawInHUDDirty = true; + _renderLayerDirty = true; } } diff --git a/interface/src/ui/overlays/ModelOverlay.h b/interface/src/ui/overlays/ModelOverlay.h index bd922e258a..17a2327d02 100644 --- a/interface/src/ui/overlays/ModelOverlay.h +++ b/interface/src/ui/overlays/ModelOverlay.h @@ -126,8 +126,7 @@ private: QVector<int> _jointMapping; // domain is index into model-joints, range is index into animation-joints bool _visibleDirty { true }; - bool _drawInFrontDirty { false }; - bool _drawInHUDDirty { false }; + bool _renderLayerDirty { false }; bool _isGroupCulled { false }; bool _groupCulledDirty { false }; diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index ec6b62e237..4fe3708ba9 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -134,13 +134,6 @@ void Web3DOverlay::destroyWebSurface() { QQuickItem* rootItem = _webSurface->getRootItem(); - if (rootItem && rootItem->objectName() == "tabletRoot") { - auto tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>(); - 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. if (rootItem) { 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<HFMJoint>& 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 6e27bee06f..bc4dca54f2 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1984,11 +1984,10 @@ void Rig::copyJointsIntoJointData(QVector<JointData>& 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; @@ -2015,7 +2014,6 @@ void Rig::copyJointsFromJointData(const QVector<JointData>& jointDataVec) { std::vector<glm::quat> 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); @@ -2041,8 +2039,8 @@ void Rig::copyJointsFromJointData(const QVector<JointData>& 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/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index a6185d7e79..17d10cdf49 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -324,8 +324,8 @@ void Avatar::removeAvatarEntitiesFromTree() { } } -void Avatar::updateGrabs() { - +bool Avatar::updateGrabs() { + bool grabAddedOrRemoved = false; // update the Grabs according to any changes in _avatarGrabData _avatarGrabsLock.withWriteLock([&] { if (_avatarGrabDataChanged) { @@ -385,6 +385,7 @@ void Avatar::updateGrabs() { entityTree->updateEntityQueryAACube(target, packetSender, force, iShouldTellServer); }); } + grabAddedOrRemoved = true; } _avatarGrabs.remove(grabID); _changedAvatarGrabs.remove(grabID); @@ -402,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<QUuid, GrabLocationAccumulator>& grabAccumulators) { @@ -1375,7 +1378,7 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { AvatarData::setSkeletonModelURL(skeletonModelURL); if (QThread::currentThread() == thread()) { - if (!isMyAvatar()) { + if (!isMyAvatar() && !DependencyManager::get<NodeList>()->isIgnoringNode(getSessionUUID())) { createOrb(); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 4ff3e9cc13..d5431ad2d2 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -538,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/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp old mode 100644 new mode 100755 index 24e30d20c1..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; } @@ -2157,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<NodeList>(); // about 2% of the time, we send a full update (meaning, we transmit all the joint data), even if nothing has changed. @@ -2170,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; } } } @@ -2191,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<NodeList>(); 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); @@ -2216,6 +2254,7 @@ void AvatarData::sendIdentityPacket() { }); _identityDataChanged = false; + return identityData.size(); } static const QString JSON_ATTACHMENT_URL = QStringLiteral("modelUrl"); @@ -2392,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) { @@ -2409,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(); @@ -2428,7 +2477,7 @@ void AvatarData::avatarEntityDataToJson(QJsonObject& root) const { 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(); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h old mode 100644 new mode 100755 index bd17233dbf..9128c2dbf9 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -277,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; @@ -286,6 +286,7 @@ namespace AvatarDataPacket { }; */ size_t maxJointDataSize(size_t numJoints, bool hasGrabJoints); + size_t minJointDataSize(size_t numJoints); /* struct JointDefaultPoseFlags { @@ -495,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); @@ -1271,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 diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 6a67ef6638..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<AvatarData>(); +AvatarSharedPointer AvatarHashMap::newSharedAvatar(const QUuid& sessionUUID) { + auto avatarData = std::make_shared<AvatarData>(); + avatarData->setSessionUUID(sessionUUID); + return avatarData; } AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& 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; } 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 * <strong>Note:</strong> An <code>AvatarList</code> 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<ReceivedMessage> message, SharedNodePointer sendingNode); - virtual AvatarSharedPointer newSharedAvatar(); + virtual AvatarSharedPointer newSharedAvatar(const QUuid& sessionUUID); virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer); AvatarSharedPointer newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer, bool& isNew); 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<Mutex> 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<ReceivedMessage> 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/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 5fb5a15d2c..53e62ee35f 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -159,20 +159,40 @@ Item::Bound EntityRenderer::getBound() { return _bound; } +ShapeKey EntityRenderer::getShapeKey() { + if (_primitiveMode == PrimitiveMode::LINES) { + return ShapeKey::Builder().withOwnPipeline().withWireframe(); + } + return ShapeKey::Builder().withOwnPipeline(); +} + render::hifi::Tag EntityRenderer::getTagMask() const { return _isVisibleInSecondaryCamera ? render::hifi::TAG_ALL_VIEWS : render::hifi::TAG_MAIN_VIEW; } +render::hifi::Layer EntityRenderer::getHifiRenderLayer() const { + switch (_renderLayer) { + case RenderLayer::WORLD: + return render::hifi::LAYER_3D; + case RenderLayer::FRONT: + return render::hifi::LAYER_3D_FRONT; + case RenderLayer::HUD: + return render::hifi::LAYER_3D_HUD; + default: + return render::hifi::LAYER_3D; + } +} + ItemKey EntityRenderer::getKey() { if (isTransparent()) { - return ItemKey::Builder::transparentShape().withTypeMeta().withTagBits(getTagMask()); + return ItemKey::Builder::transparentShape().withTypeMeta().withTagBits(getTagMask()).withLayer(getHifiRenderLayer()); } // This allows shapes to cast shadows if (_canCastShadow) { - return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(getTagMask()).withShadowCaster(); + return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(getTagMask()).withShadowCaster().withLayer(getHifiRenderLayer()); } else { - return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(getTagMask()); + return ItemKey::Builder::opaqueShape().withTypeMeta().withTagBits(getTagMask()).withLayer(getHifiRenderLayer()); } } @@ -411,6 +431,8 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa _moving = entity->isMovingRelativeToParent(); _visible = entity->getVisible(); setIsVisibleInSecondaryCamera(entity->isVisibleInSecondaryCamera()); + setRenderLayer(entity->getRenderLayer()); + setPrimitiveMode(entity->getPrimitiveMode()); _canCastShadow = entity->getCanCastShadow(); _cauterized = entity->getCauterized(); _needsRenderUpdate = false; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 9c4d10190c..fde63f78fa 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -72,11 +72,12 @@ protected: // Implementing the PayloadProxyInterface methods virtual ItemKey getKey() override; - virtual ShapeKey getShapeKey() override { return ShapeKey::Builder::ownPipeline(); } + virtual ShapeKey getShapeKey() override; virtual Item::Bound getBound() override; virtual void render(RenderArgs* args) override final; virtual uint32_t metaFetchMetaSubItems(ItemIDs& subItems) override; virtual render::hifi::Tag getTagMask() const; + virtual render::hifi::Layer getHifiRenderLayer() const; // Returns true if the item in question needs to have updateInScene called because of internal rendering state changes virtual bool needsRenderUpdate() const; @@ -103,6 +104,8 @@ protected: inline bool isValidRenderItem() const { return _renderItemID != Item::INVALID_ITEM_ID; } virtual void setIsVisibleInSecondaryCamera(bool value) { _isVisibleInSecondaryCamera = value; } + virtual void setRenderLayer(RenderLayer value) { _renderLayer = value; } + virtual void setPrimitiveMode(PrimitiveMode value) { _primitiveMode = value; } template <typename F, typename T> T withReadLockResult(const std::function<T()>& f) { @@ -136,6 +139,8 @@ protected: bool _visible { false }; bool _isVisibleInSecondaryCamera { false }; bool _canCastShadow { false }; + RenderLayer _renderLayer { RenderLayer::WORLD }; + PrimitiveMode _primitiveMode { PrimitiveMode::SOLID }; bool _cauterized { false }; bool _moving { false }; bool _needsRenderUpdate { false }; diff --git a/libraries/entities-renderer/src/RenderableGridEntityItem.cpp b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp index 22cf72cec6..4576358699 100644 --- a/libraries/entities-renderer/src/RenderableGridEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp @@ -97,6 +97,10 @@ ShapeKey GridEntityRenderer::getShapeKey() { builder.withTranslucent(); } + if (_primitiveMode == PrimitiveMode::LINES) { + builder.withWireframe(); + } + return builder.build(); } diff --git a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp index 7c5b7fc0da..c1d6d3211d 100644 --- a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp @@ -123,6 +123,10 @@ ShapeKey ImageEntityRenderer::getShapeKey() { if (_emissive) { builder.withUnlit(); } + + if (_primitiveMode == PrimitiveMode::LINES) { + builder.withWireframe(); + } }); return builder.build(); diff --git a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp index 6451e873c9..483f9ffe1c 100644 --- a/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableMaterialEntityItem.cpp @@ -55,7 +55,7 @@ void MaterialEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& ItemKey MaterialEntityRenderer::getKey() { ItemKey::Builder builder; - builder.withTypeShape().withTagBits(getTagMask()); + builder.withTypeShape().withTagBits(getTagMask()).withLayer(getHifiRenderLayer()); if (!_visible) { builder.withInvisible(); @@ -98,6 +98,10 @@ ShapeKey MaterialEntityRenderer::getShapeKey() { builder.withUnlit(); } + if (_primitiveMode == PrimitiveMode::LINES) { + builder.withWireframe(); + } + return builder.build(); } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index aa449b8919..7e01af04dd 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1074,10 +1074,16 @@ ModelEntityRenderer::ModelEntityRenderer(const EntityItemPointer& entity) : Pare } void ModelEntityRenderer::setKey(bool didVisualGeometryRequestSucceed) { + auto builder = ItemKey::Builder().withTypeMeta().withTagBits(getTagMask()).withLayer(getHifiRenderLayer()); + + if (_model && _model->isGroupCulled()) { + builder.withMetaCullGroup(); + } + if (didVisualGeometryRequestSucceed) { - _itemKey = ItemKey::Builder().withTypeMeta().withTagBits(getTagMask()); + _itemKey = builder.build(); } else { - _itemKey = ItemKey::Builder().withTypeMeta().withTypeShape().withTagBits(getTagMask()); + _itemKey = builder.withTypeShape().build(); } } @@ -1295,6 +1301,10 @@ bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin model->getRegistrationPoint() != entity->getRegistrationPoint()) { return true; } + + if (model->isGroupCulled() != entity->getGroupCulled()) { + return true; + } } return false; @@ -1351,6 +1361,8 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce connect(model.get(), &Model::requestRenderUpdate, this, &ModelEntityRenderer::requestRenderUpdate); connect(model.get(), &Model::setURLFinished, this, [&](bool didVisualGeometryRequestSucceed) { setKey(didVisualGeometryRequestSucceed); + _model->setTagMask(getTagMask()); + _model->setHifiRenderLayer(getHifiRenderLayer()); emit requestRenderUpdate(); if(didVisualGeometryRequestSucceed) { emit DependencyManager::get<scriptable::ModelProviderFactory>()-> @@ -1437,6 +1449,14 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce model->setCanCastShadow(_canCastShadow, scene); } + { + bool groupCulled = entity->getGroupCulled(); + if (model->isGroupCulled() != groupCulled) { + model->setGroupCulled(groupCulled); + setKey(_didLastVisualGeometryRequestSucceed); + } + } + { DETAILED_PROFILE_RANGE(simulation_physics, "Fixup"); if (model->needsFixupInScene()) { @@ -1494,6 +1514,24 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce void ModelEntityRenderer::setIsVisibleInSecondaryCamera(bool value) { Parent::setIsVisibleInSecondaryCamera(value); setKey(_didLastVisualGeometryRequestSucceed); + if (_model) { + _model->setTagMask(getTagMask()); + } +} + +void ModelEntityRenderer::setRenderLayer(RenderLayer value) { + Parent::setRenderLayer(value); + setKey(_didLastVisualGeometryRequestSucceed); + if (_model) { + _model->setHifiRenderLayer(getHifiRenderLayer()); + } +} + +void ModelEntityRenderer::setPrimitiveMode(PrimitiveMode value) { + Parent::setPrimitiveMode(value); + if (_model) { + _model->setPrimitiveMode(_primitiveMode); + } } // NOTE: this only renders the "meta" portion of the Model, namely it renders debugging items diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 725c1d96c3..16c3664f28 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -169,6 +169,8 @@ protected: render::hifi::Tag getTagMask() const override; void setIsVisibleInSecondaryCamera(bool value) override; + void setRenderLayer(RenderLayer value) override; + void setPrimitiveMode(PrimitiveMode value) override; private: void animate(const TypedEntityPointer& entity); diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 38027a80ed..351d72baf5 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -159,14 +159,18 @@ void ParticleEffectEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEn ItemKey ParticleEffectEntityRenderer::getKey() { if (_visible) { - return ItemKey::Builder::transparentShape().withTagBits(getTagMask()); + return ItemKey::Builder::transparentShape().withTagBits(getTagMask()).withLayer(getHifiRenderLayer()); } else { - return ItemKey::Builder().withInvisible().withTagBits(getTagMask()).build(); + return ItemKey::Builder().withInvisible().withTagBits(getTagMask()).withLayer(getHifiRenderLayer()).build(); } } ShapeKey ParticleEffectEntityRenderer::getShapeKey() { - return ShapeKey::Builder().withCustom(CUSTOM_PIPELINE_NUMBER).withTranslucent().build(); + auto builder = ShapeKey::Builder().withCustom(CUSTOM_PIPELINE_NUMBER).withTranslucent(); + if (_primitiveMode == PrimitiveMode::LINES) { + builder.withWireframe(); + } + return builder.build(); } Item::Bound ParticleEffectEntityRenderer::getBound() { diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index d4a10e551d..68371e4e13 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -55,11 +55,15 @@ void PolyLineEntityRenderer::buildPipeline() { } ItemKey PolyLineEntityRenderer::getKey() { - return ItemKey::Builder::transparentShape().withTypeMeta().withTagBits(getTagMask()); + return ItemKey::Builder::transparentShape().withTypeMeta().withTagBits(getTagMask()).withLayer(getHifiRenderLayer()); } ShapeKey PolyLineEntityRenderer::getShapeKey() { - return ShapeKey::Builder().withOwnPipeline().withTranslucent().withoutCullFace(); + auto builder = ShapeKey::Builder().withOwnPipeline().withTranslucent().withoutCullFace(); + if (_primitiveMode == PrimitiveMode::LINES) { + builder.withWireframe(); + } + return builder.build(); } bool PolyLineEntityRenderer::needsRenderUpdate() const { diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 514ed3cec1..183d2881f3 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -1613,7 +1613,11 @@ PolyVoxEntityRenderer::PolyVoxEntityRenderer(const EntityItemPointer& entity) : } ShapeKey PolyVoxEntityRenderer::getShapeKey() { - return ShapeKey::Builder().withCustom(CUSTOM_PIPELINE_NUMBER).build(); + auto builder = ShapeKey::Builder().withCustom(CUSTOM_PIPELINE_NUMBER); + if (_primitiveMode == PrimitiveMode::LINES) { + builder.withWireframe(); + } + return builder.build(); } bool PolyVoxEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 366a3fdc70..7aea87535e 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -173,7 +173,7 @@ public: } protected: - virtual ItemKey getKey() override { return ItemKey::Builder::opaqueShape().withTagBits(getTagMask()); } + virtual ItemKey getKey() override { return ItemKey::Builder::opaqueShape().withTagBits(getTagMask()).withLayer(getHifiRenderLayer()); } virtual ShapeKey getShapeKey() override; virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 1569c75eec..c47904213b 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -186,6 +186,10 @@ ShapeKey ShapeEntityRenderer::getShapeKey() { builder.withUnlit(); } + if (_primitiveMode == PrimitiveMode::LINES) { + builder.withWireframe(); + } + return builder.build(); } else { ShapeKey::Builder builder; @@ -198,6 +202,10 @@ ShapeKey ShapeEntityRenderer::getShapeKey() { if (isTransparent()) { builder.withTranslucent(); } + + if (_primitiveMode == PrimitiveMode::LINES) { + builder.withWireframe(); + } return builder.build(); } } @@ -241,8 +249,13 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { } else if (!useMaterialPipeline()) { // FIXME, support instanced multi-shape rendering using multidraw indirect outColor.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; - auto pipeline = outColor.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline(); - if (render::ShapeKey(args->_globalShapeKey).isWireframe()) { + render::ShapePipelinePointer pipeline; + if (_renderLayer == RenderLayer::WORLD) { + pipeline = outColor.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline(); + } else { + pipeline = outColor.a < 1.0f ? geometryCache->getForwardTransparentShapePipeline() : geometryCache->getForwardOpaqueShapePipeline(); + } + if (render::ShapeKey(args->_globalShapeKey).isWireframe() || _primitiveMode == PrimitiveMode::LINES) { geometryCache->renderWireShapeInstance(args, batch, geometryShape, outColor, pipeline); } else { geometryCache->renderSolidShapeInstance(args, batch, geometryShape, outColor, pipeline); diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index 4ddb398fbf..d7da8e7e1a 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -49,6 +49,9 @@ ShapeKey TextEntityRenderer::getShapeKey() { if (isTransparent()) { builder.withTranslucent(); } + if (_primitiveMode == PrimitiveMode::LINES) { + builder.withWireframe(); + } return builder.build(); } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 498f0ff066..024c891da4 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -95,6 +95,8 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_QUERY_AA_CUBE; requestedProperties += PROP_CAN_CAST_SHADOW; // requestedProperties += PROP_VISIBLE_IN_SECONDARY_CAMERA; // not sent over the wire + requestedProperties += PROP_RENDER_LAYER; + requestedProperties += PROP_PRIMITIVE_MODE; withReadLock([&] { requestedProperties += _grabProperties.getEntityProperties(params); }); @@ -263,8 +265,8 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, getRegistrationPoint()); APPEND_ENTITY_PROPERTY(PROP_CREATED, getCreated()); APPEND_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, getLastEditedBy()); - // APPEND_ENTITY_PROPERTY(PROP_ENTITY_HOST_TYPE, getEntityHostType()); // not sent over the wire - // APPEND_ENTITY_PROPERTY(PROP_OWNING_AVATAR_ID, getOwningAvatarID()); // not sent over the wire + // APPEND_ENTITY_PROPERTY(PROP_ENTITY_HOST_TYPE, (uint32_t)getEntityHostType()); // not sent over the wire + // APPEND_ENTITY_PROPERTY(PROP_OWNING_AVATAR_ID, getOwningAvatarID()); // not sent over the wire // convert AVATAR_SELF_ID to actual sessionUUID. QUuid actualParentID = getParentID(); if (actualParentID == AVATAR_SELF_ID) { @@ -276,6 +278,8 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, getQueryAACube()); APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, getCanCastShadow()); // APPEND_ENTITY_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, getIsVisibleInSecondaryCamera()); // not sent over the wire + APPEND_ENTITY_PROPERTY(PROP_RENDER_LAYER, (uint32_t)getRenderLayer()); + APPEND_ENTITY_PROPERTY(PROP_PRIMITIVE_MODE, (uint32_t)getPrimitiveMode()); withReadLock([&] { _grabProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); @@ -770,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); }; @@ -839,6 +846,8 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef } READ_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); // READ_ENTITY_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, bool, setIsVisibleInSecondaryCamera); // not sent over the wire + READ_ENTITY_PROPERTY(PROP_RENDER_LAYER, RenderLayer, setRenderLayer); + READ_ENTITY_PROPERTY(PROP_PRIMITIVE_MODE, PrimitiveMode, setPrimitiveMode); withWriteLock([&] { int bytesFromGrab = _grabProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData, @@ -1310,6 +1319,8 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire COPY_ENTITY_PROPERTY_TO_PROPERTIES(queryAACube, getQueryAACube); COPY_ENTITY_PROPERTY_TO_PROPERTIES(canCastShadow, getCanCastShadow); COPY_ENTITY_PROPERTY_TO_PROPERTIES(isVisibleInSecondaryCamera, isVisibleInSecondaryCamera); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(renderLayer, getRenderLayer); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(primitiveMode, getPrimitiveMode); withReadLock([&] { _grabProperties.getProperties(properties); }); @@ -1454,6 +1465,8 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(queryAACube, setQueryAACube); SET_ENTITY_PROPERTY_FROM_PROPERTIES(canCastShadow, setCanCastShadow); SET_ENTITY_PROPERTY_FROM_PROPERTIES(isVisibleInSecondaryCamera, setIsVisibleInSecondaryCamera); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(renderLayer, setRenderLayer); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(primitiveMode, setPrimitiveMode); withWriteLock([&] { bool grabPropertiesChanged = _grabProperties.setProperties(properties); somethingChanged |= grabPropertiesChanged; @@ -2931,6 +2944,46 @@ void EntityItem::setIsVisibleInSecondaryCamera(bool value) { } } +RenderLayer EntityItem::getRenderLayer() const { + return resultWithReadLock<RenderLayer>([&] { + return _renderLayer; + }); +} + +void EntityItem::setRenderLayer(RenderLayer value) { + bool changed = false; + withWriteLock([&] { + if (_renderLayer != value) { + changed = true; + _renderLayer = value; + } + }); + + if (changed) { + emit requestRenderUpdate(); + } +} + +PrimitiveMode EntityItem::getPrimitiveMode() const { + return resultWithReadLock<PrimitiveMode>([&] { + return _primitiveMode; + }); +} + +void EntityItem::setPrimitiveMode(PrimitiveMode value) { + bool changed = false; + withWriteLock([&] { + if (_primitiveMode != value) { + changed = true; + _primitiveMode = value; + } + }); + + if (changed) { + emit requestRenderUpdate(); + } +} + bool EntityItem::getCanCastShadow() const { bool result; withReadLock([&] { @@ -3349,7 +3402,8 @@ void EntityItem::prepareForSimulationOwnershipBid(EntityItemProperties& properti } bool EntityItem::isWearable() const { - return isVisible() && (getParentID() == DependencyManager::get<NodeList>()->getSessionUUID() || getParentID() == AVATAR_SELF_ID); + return isVisible() && + (getParentID() == DependencyManager::get<NodeList>()->getSessionUUID() || getParentID() == AVATAR_SELF_ID); } void EntityItem::addGrab(GrabPointer grab) { @@ -3368,7 +3422,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/EntityItem.h b/libraries/entities/src/EntityItem.h index 826a9c34a0..cac4192cd5 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -293,6 +293,12 @@ public: bool isVisibleInSecondaryCamera() const; void setIsVisibleInSecondaryCamera(bool value); + RenderLayer getRenderLayer() const; + void setRenderLayer(RenderLayer value); + + PrimitiveMode getPrimitiveMode() const; + void setPrimitiveMode(PrimitiveMode value); + bool getCanCastShadow() const; void setCanCastShadow(bool value); @@ -621,6 +627,8 @@ protected: float _angularDamping { ENTITY_ITEM_DEFAULT_ANGULAR_DAMPING }; bool _visible { ENTITY_ITEM_DEFAULT_VISIBLE }; bool _isVisibleInSecondaryCamera { ENTITY_ITEM_DEFAULT_VISIBLE_IN_SECONDARY_CAMERA }; + RenderLayer _renderLayer { RenderLayer::WORLD }; + PrimitiveMode _primitiveMode { PrimitiveMode::SOLID }; bool _canCastShadow{ ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW }; bool _collisionless { ENTITY_ITEM_DEFAULT_COLLISIONLESS }; uint16_t _collisionMask { ENTITY_COLLISION_MASK_DEFAULT }; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 1fdff19e38..60592abe91 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -340,17 +340,70 @@ QString EntityItemProperties::getBillboardModeAsString() const { return BillboardModeHelpers::getNameForBillboardMode(_billboardMode); } -void EntityItemProperties::setBillboardModeFromString(const QString& materialMappingMode) { +void EntityItemProperties::setBillboardModeFromString(const QString& billboardMode) { if (stringToBillboardModeLookup.empty()) { buildStringToBillboardModeLookup(); } - auto billboardModeItr = stringToBillboardModeLookup.find(materialMappingMode.toLower()); + auto billboardModeItr = stringToBillboardModeLookup.find(billboardMode.toLower()); if (billboardModeItr != stringToBillboardModeLookup.end()) { _billboardMode = billboardModeItr.value(); _billboardModeChanged = true; } } +QHash<QString, RenderLayer> stringToRenderLayerLookup; + +void addRenderLayer(RenderLayer mode) { + stringToRenderLayerLookup[RenderLayerHelpers::getNameForRenderLayer(mode)] = mode; +} + +void buildStringToRenderLayerLookup() { + addRenderLayer(RenderLayer::WORLD); + addRenderLayer(RenderLayer::FRONT); + addRenderLayer(RenderLayer::HUD); +} + +QString EntityItemProperties::getRenderLayerAsString() const { + return RenderLayerHelpers::getNameForRenderLayer(_renderLayer); +} + +void EntityItemProperties::setRenderLayerFromString(const QString& renderLayer) { + if (stringToRenderLayerLookup.empty()) { + buildStringToRenderLayerLookup(); + } + auto renderLayerItr = stringToRenderLayerLookup.find(renderLayer.toLower()); + if (renderLayerItr != stringToRenderLayerLookup.end()) { + _renderLayer = renderLayerItr.value(); + _renderLayerChanged = true; + } +} + +QHash<QString, PrimitiveMode> stringToPrimitiveModeLookup; + +void addPrimitiveMode(PrimitiveMode mode) { + stringToPrimitiveModeLookup[PrimitiveModeHelpers::getNameForPrimitiveMode(mode)] = mode; +} + +void buildStringToPrimitiveModeLookup() { + addPrimitiveMode(PrimitiveMode::SOLID); + addPrimitiveMode(PrimitiveMode::LINES); +} + +QString EntityItemProperties::getPrimitiveModeAsString() const { + return PrimitiveModeHelpers::getNameForPrimitiveMode(_primitiveMode); +} + +void EntityItemProperties::setPrimitiveModeFromString(const QString& primitiveMode) { + if (stringToPrimitiveModeLookup.empty()) { + buildStringToPrimitiveModeLookup(); + } + auto primitiveModeItr = stringToPrimitiveModeLookup.find(primitiveMode.toLower()); + if (primitiveModeItr != stringToPrimitiveModeLookup.end()) { + _primitiveMode = primitiveModeItr.value(); + _primitiveModeChanged = true; + } +} + EntityPropertyFlags EntityItemProperties::getChangedProperties() const { EntityPropertyFlags changedProperties; @@ -375,6 +428,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_QUERY_AA_CUBE, queryAACube); CHECK_PROPERTY_CHANGE(PROP_CAN_CAST_SHADOW, canCastShadow); CHECK_PROPERTY_CHANGE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera); + CHECK_PROPERTY_CHANGE(PROP_RENDER_LAYER, renderLayer); + CHECK_PROPERTY_CHANGE(PROP_PRIMITIVE_MODE, primitiveMode); changedProperties += _grab.getChangedProperties(); // Physics @@ -474,6 +529,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_JOINT_TRANSLATIONS_SET, jointTranslationsSet); CHECK_PROPERTY_CHANGE(PROP_JOINT_TRANSLATIONS, jointTranslations); CHECK_PROPERTY_CHANGE(PROP_RELAY_PARENT_JOINTS, relayParentJoints); + CHECK_PROPERTY_CHANGE(PROP_GROUP_CULLED, groupCulled); changedProperties += _animation.getChangedProperties(); // Light @@ -579,15 +635,15 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * the <code>shape</code> property set for entities of these types.) <em>Read-only.</em> * @property {EntityHostType} entityHostType="domain" - How this entity will behave, including if and how it is sent to other people. * The value can only be set at entity creation by using the <code>entityHostType</code> parameter in - * {@link Entities.addEntity}. + * {@link Entities.addEntity}. Read-only. * @property {boolean} avatarEntity=false - If <code>true</code> then the entity is an avatar entity; An avatar entity follows you to each domain you visit, * rendering at the same world coordinates unless it's parented to your avatar. <em>Value cannot be changed after the entity is created.</em><br /> * The value can only be set at entity creation by using the <code>entityHostType</code> parameter in - * {@link Entities.addEntity}. <code>clientOnly</code> is an alias. + * {@link Entities.addEntity}. <code>clientOnly</code> is an alias. Read-only. * @property {boolean} localEntity=false - If <code>true</code> then the entity is a local entity; Local entities only render for you and are not sent over the wire. * <em>Value cannot be changed after the entity is created.</em><br /> * The value can only be set at entity creation by using the <code>entityHostType</code> parameter in - * {@link Entities.addEntity}. + * {@link Entities.addEntity}. Read-only. * @property {Uuid} owningAvatarID=Uuid.NULL - The session ID of the owning avatar if <code>avatarEntity</code> is * <code>true</code>, otherwise {@link Uuid|Uuid.NULL}. <em>Read-only.</em> * @@ -611,6 +667,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * {@link Entities.EntityType|Zone} entity with <code>castShadows</code> enabled in its * {@link Entities.EntityProperties-Zone|keyLight} property. * @property {boolean} isVisibleInSecondaryCamera=true - Whether or not the entity is rendered in the secondary camera. If <code>true</code> then the entity is rendered. + * @property {RenderLayer} renderLayer="world" - In which layer this entity renders. + * @property {PrimitiveMode} primitiveMode="solid" - How this entity's geometry is rendered. * * @property {Vec3} position=0,0,0 - The position of the entity. * @property {Quat} rotation=0,0,0,1 - The orientation of the entity with respect to world coordinates. @@ -841,10 +899,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 * <code>0</code>. - * @property {string|number} parentMaterialName="0" - Selects the submesh or submeshes within the parent to apply the material - * to. If in the format <code>"mat::string"</code>, all submeshes with material name <code>"string"</code> are replaced. - * Otherwise the property value is parsed as an unsigned integer, specifying the mesh index to modify. Invalid values are - * parsed to <code>0</code>. + * @property {string} parentMaterialName="0" - Selects the mesh part or parts within the parent to which to apply the material. + * If in the format <code>"mat::string"</code>, all mesh parts with material name <code>"string"</code> are replaced. + * Otherwise the property value is parsed as an unsigned integer, specifying the mesh part index to modify. If <code>"all"</code>, + * all mesh parts will be replaced. If an array (starts with <code>"["</code> and ends with <code>"]"</code>), the string will be + * split at each <code>","</code> and each element will be parsed as either a number or a string if it starts with + * <code>"mat::"</code>. In other words, <code>"[0,1,mat::string,mat::string2]"</code> will replace mesh parts 0 and 1, and any + * mesh parts with material <code>"string"</code> or <code>"string2"</code>. Do not put spaces around the commas. Invalid values + * are parsed to <code>0</code>. * @property {string} materialMappingMode="uv" - How the material is mapped to the entity. Either <code>"uv"</code> or * <code>"projected"</code>. 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. @@ -900,7 +962,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * parse the JSON string into a JavaScript object of name, URL pairs. <em>Read-only.</em> * * @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 <code>shapeType</code> is + * @property {string} compoundShapeURL="" - The model file to use for the compound shape if <code>shapeType</code> is * <code>"compound"</code>. * * @property {Entities.AnimationProperties} animation - An animation to play on the model. @@ -925,6 +987,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * {@link Entities.getJointIndex|getJointIndex}. * @property {boolean} relayParentJoints=false - If <code>true</code> and the entity is parented to an avatar, then the * avatar's joint rotations are applied to the entity's joints. + * @property {boolean} groupCulled=false - If <code>true</code>, the mesh parts of the model are LOD culled as a group. + * If <code>false</code>, separate mesh parts will be LOD culled individually. * * @example <caption>Rez a Vive tracker puck.</caption> * var entity = Entities.addEntity({ @@ -1238,7 +1302,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 <code>"none"</code>, or set to <code>"compound"</code> * and <code>compoundShapeURL</code> is <code>""</code>. - * @property {string} compoundShapeURL="" - The OBJ file to use for the compound shape if <code>shapeType</code> is + * @property {string} compoundShapeURL="" - The model file to use for the compound shape if <code>shapeType</code> is * <code>"compound"</code>. * * @property {string} keyLightMode="inherit" - Configures the key light in the zone. Possible values:<br /> @@ -1416,6 +1480,8 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_QUERY_AA_CUBE, queryAACube); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CAN_CAST_SHADOW, canCastShadow); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_RENDER_LAYER, renderLayer, getRenderLayerAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_PRIMITIVE_MODE, primitiveMode, getPrimitiveModeAsString()); _grab.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); // Physics @@ -1532,6 +1598,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_TRANSLATIONS_SET, jointTranslationsSet); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_TRANSLATIONS, jointTranslations); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RELAY_PARENT_JOINTS, relayParentJoints); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GROUP_CULLED, groupCulled); if (!psuedoPropertyFlagsButDesiredEmpty) { _animation.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); } @@ -1797,6 +1864,8 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(queryAACube, AACube, setQueryAACube); // TODO: should scripts be able to set this? COPY_PROPERTY_FROM_QSCRIPTVALUE(canCastShadow, bool, setCanCastShadow); COPY_PROPERTY_FROM_QSCRIPTVALUE(isVisibleInSecondaryCamera, bool, setIsVisibleInSecondaryCamera); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(renderLayer, RenderLayer); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(primitiveMode, PrimitiveMode); _grab.copyFromScriptValue(object, _defaultSettings); // Physics @@ -1901,6 +1970,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslationsSet, qVectorBool, setJointTranslationsSet); COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslations, qVectorVec3, setJointTranslations); COPY_PROPERTY_FROM_QSCRIPTVALUE(relayParentJoints, bool, setRelayParentJoints); + COPY_PROPERTY_FROM_QSCRIPTVALUE(groupCulled, bool, setGroupCulled); _animation.copyFromScriptValue(object, _defaultSettings); // Light @@ -2068,6 +2138,8 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(queryAACube); COPY_PROPERTY_IF_CHANGED(canCastShadow); COPY_PROPERTY_IF_CHANGED(isVisibleInSecondaryCamera); + COPY_PROPERTY_IF_CHANGED(renderLayer); + COPY_PROPERTY_IF_CHANGED(primitiveMode); _grab.merge(other._grab); // Physics @@ -2167,6 +2239,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(jointTranslationsSet); COPY_PROPERTY_IF_CHANGED(jointTranslations); COPY_PROPERTY_IF_CHANGED(relayParentJoints); + COPY_PROPERTY_IF_CHANGED(groupCulled); _animation.merge(other._animation); // Light @@ -2338,6 +2411,8 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_PROPERTY_TO_MAP(PROP_QUERY_AA_CUBE, QueryAACube, queryAACube, AACube); ADD_PROPERTY_TO_MAP(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool); ADD_PROPERTY_TO_MAP(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool); + ADD_PROPERTY_TO_MAP(PROP_RENDER_LAYER, RenderLayer, renderLayer, RenderLayer); + ADD_PROPERTY_TO_MAP(PROP_PRIMITIVE_MODE, PrimitiveMode, primitiveMode, PrimitiveMode); { // Grab ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_GRABBABLE, Grab, grab, Grabbable, grabbable); ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_KINEMATIC, Grab, grab, GrabKinematic, grabKinematic); @@ -2491,6 +2566,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_PROPERTY_TO_MAP(PROP_JOINT_TRANSLATIONS_SET, JointTranslationsSet, jointTranslationsSet, QVector<bool>); ADD_PROPERTY_TO_MAP(PROP_JOINT_TRANSLATIONS, JointTranslations, jointTranslations, QVector<vec3>); ADD_PROPERTY_TO_MAP(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool); + ADD_PROPERTY_TO_MAP(PROP_GROUP_CULLED, GroupCulled, groupCulled, bool); { // Animation ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_URL, Animation, animation, URL, url); ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_ALLOW_TRANSLATION, Animation, animation, AllowTranslation, allowTranslation); @@ -2767,6 +2843,8 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, properties.getQueryAACube()); APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, properties.getCanCastShadow()); // APPEND_ENTITY_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, properties.getIsVisibleInSecondaryCamera()); // not sent over the wire + APPEND_ENTITY_PROPERTY(PROP_RENDER_LAYER, (uint32_t)properties.getRenderLayer()); + APPEND_ENTITY_PROPERTY(PROP_PRIMITIVE_MODE, (uint32_t)properties.getPrimitiveMode()); _staticGrab.setProperties(properties); _staticGrab.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); @@ -2873,6 +2951,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, properties.getJointTranslationsSet()); APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS, properties.getJointTranslations()); APPEND_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, properties.getRelayParentJoints()); + APPEND_ENTITY_PROPERTY(PROP_GROUP_CULLED, properties.getGroupCulled()); _staticAnimation.setProperties(properties); _staticAnimation.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); @@ -3208,6 +3287,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_QUERY_AA_CUBE, AACube, setQueryAACube); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); // READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VISIBLE_IN_SECONDARY_CAMERA, bool, setIsVisibleInSecondaryCamera); // not sent over the wire + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RENDER_LAYER, RenderLayer, setRenderLayer); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PRIMITIVE_MODE, PrimitiveMode, setPrimitiveMode); properties.getGrab().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); // Physics @@ -3312,6 +3393,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_TRANSLATIONS_SET, QVector<bool>, setJointTranslationsSet); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_TRANSLATIONS, QVector<vec3>, setJointTranslations); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GROUP_CULLED, bool, setGroupCulled); properties.getAnimation().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); } @@ -3590,6 +3672,8 @@ void EntityItemProperties::markAllChanged() { _queryAACubeChanged = true; _canCastShadowChanged = true; _isVisibleInSecondaryCameraChanged = true; + _renderLayerChanged = true; + _primitiveModeChanged = true; _grab.markAllChanged(); // Physics @@ -3682,6 +3766,7 @@ void EntityItemProperties::markAllChanged() { _jointTranslationsSetChanged = true; _jointTranslationsChanged = true; _relayParentJointsChanged = true; + _groupCulledChanged = true; _animation.markAllChanged(); // Light @@ -3962,6 +4047,12 @@ QList<QString> EntityItemProperties::listChangedProperties() { if (isVisibleInSecondaryCameraChanged()) { out += "isVisibleInSecondaryCamera"; } + if (renderLayerChanged()) { + out += "renderLayer"; + } + if (primitiveModeChanged()) { + out += "primitiveMode"; + } getGrab().listChangedProperties(out); // Physics @@ -4208,6 +4299,9 @@ QList<QString> EntityItemProperties::listChangedProperties() { if (relayParentJointsChanged()) { out += "relayParentJoints"; } + if (groupCulledChanged()) { + out += "groupCulled"; + } getAnimation().listChangedProperties(out); // Light diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index bb4d8c5878..86150efe3a 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -51,6 +51,8 @@ #include "MaterialMappingMode.h" #include "BillboardMode.h" +#include "RenderLayer.h" +#include "PrimitiveMode.h" const quint64 UNKNOWN_CREATED_TIME = 0; @@ -169,6 +171,8 @@ public: DEFINE_PROPERTY_REF(PROP_QUERY_AA_CUBE, QueryAACube, queryAACube, AACube, AACube()); DEFINE_PROPERTY(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool, ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW); DEFINE_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool, ENTITY_ITEM_DEFAULT_VISIBLE_IN_SECONDARY_CAMERA); + DEFINE_PROPERTY_REF_ENUM(PROP_RENDER_LAYER, RenderLayer, renderLayer, RenderLayer, RenderLayer::WORLD); + DEFINE_PROPERTY_REF_ENUM(PROP_PRIMITIVE_MODE, PrimitiveMode, primitiveMode, PrimitiveMode, PrimitiveMode::SOLID); DEFINE_PROPERTY_GROUP(Grab, grab, GrabPropertyGroup); // Physics @@ -268,6 +272,7 @@ public: DEFINE_PROPERTY_REF(PROP_JOINT_TRANSLATIONS_SET, JointTranslationsSet, jointTranslationsSet, QVector<bool>, QVector<bool>()); DEFINE_PROPERTY_REF(PROP_JOINT_TRANSLATIONS, JointTranslations, jointTranslations, QVector<glm::vec3>, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); DEFINE_PROPERTY(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool, ENTITY_ITEM_DEFAULT_RELAY_PARENT_JOINTS); + DEFINE_PROPERTY_REF(PROP_GROUP_CULLED, GroupCulled, groupCulled, bool, false); DEFINE_PROPERTY_GROUP(Animation, animation, AnimationPropertyGroup); // Light diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 5e4b27858c..a3d0d937cb 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -39,6 +39,8 @@ enum EntityPropertyList { PROP_QUERY_AA_CUBE, PROP_CAN_CAST_SHADOW, PROP_VISIBLE_IN_SECONDARY_CAMERA, // not sent over the wire + PROP_RENDER_LAYER, + PROP_PRIMITIVE_MODE, // Grab PROP_GRAB_GRABBABLE, PROP_GRAB_KINEMATIC, @@ -198,16 +200,17 @@ enum EntityPropertyList { PROP_JOINT_TRANSLATIONS_SET = PROP_DERIVED_3, PROP_JOINT_TRANSLATIONS = PROP_DERIVED_4, PROP_RELAY_PARENT_JOINTS = PROP_DERIVED_5, + PROP_GROUP_CULLED = PROP_DERIVED_6, // Animation - PROP_ANIMATION_URL = PROP_DERIVED_6, - PROP_ANIMATION_ALLOW_TRANSLATION = PROP_DERIVED_7, - PROP_ANIMATION_FPS = PROP_DERIVED_8, - PROP_ANIMATION_FRAME_INDEX = PROP_DERIVED_9, - PROP_ANIMATION_PLAYING = PROP_DERIVED_10, - PROP_ANIMATION_LOOP = PROP_DERIVED_11, - PROP_ANIMATION_FIRST_FRAME = PROP_DERIVED_12, - PROP_ANIMATION_LAST_FRAME = PROP_DERIVED_13, - PROP_ANIMATION_HOLD = PROP_DERIVED_14, + PROP_ANIMATION_URL = PROP_DERIVED_7, + PROP_ANIMATION_ALLOW_TRANSLATION = PROP_DERIVED_8, + PROP_ANIMATION_FPS = PROP_DERIVED_9, + PROP_ANIMATION_FRAME_INDEX = PROP_DERIVED_10, + PROP_ANIMATION_PLAYING = PROP_DERIVED_11, + PROP_ANIMATION_LOOP = PROP_DERIVED_12, + PROP_ANIMATION_FIRST_FRAME = PROP_DERIVED_13, + PROP_ANIMATION_LAST_FRAME = PROP_DERIVED_14, + PROP_ANIMATION_HOLD = PROP_DERIVED_15, // Light PROP_IS_SPOTLIGHT = PROP_DERIVED_0, diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index b8babc60b8..4c64a38e26 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -3008,8 +3008,8 @@ void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, // if the queryBox has changed, tell the entity-server EntityItemPointer entity = std::dynamic_pointer_cast<EntityItem>(object); if (entity) { - bool tellServerThis = tellServer && (entity->getEntityHostType() != entity::HostType::AVATAR); - if ((entity->updateQueryAACube() || force)) { + // 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) { @@ -3017,7 +3017,7 @@ void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, } // 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 (tellServerThis && packetSender && (entity->isDomainEntity() || entity->isAvatarEntity())) { + if (tellServer && packetSender && (entity->isDomainEntity() || entity->isAvatarEntity())) { quint64 now = usecTimestampNow(); EntityItemProperties properties = entity->getProperties(); properties.setQueryAACubeDirty(); @@ -3025,6 +3025,7 @@ void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, properties.setLastEdited(now); 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 } diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 55ae1c6c3b..ddbb028b6e 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -67,6 +67,7 @@ EntityItemProperties ModelEntityItem::getProperties(const EntityPropertyFlags& d COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointTranslationsSet, getJointTranslationsSet); COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointTranslations, getJointTranslations); COPY_ENTITY_PROPERTY_TO_PROPERTIES(relayParentJoints, getRelayParentJoints); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(groupCulled, getGroupCulled); withReadLock([&] { _animationProperties.getProperties(properties); }); @@ -88,6 +89,7 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslationsSet, setJointTranslationsSet); SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslations, setJointTranslations); SET_ENTITY_PROPERTY_FROM_PROPERTIES(relayParentJoints, setRelayParentJoints); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(groupCulled, setGroupCulled); withWriteLock([&] { AnimationPropertyGroup animationProperties = _animationProperties; @@ -130,6 +132,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, QVector<bool>, setJointTranslationsSet); READ_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS, QVector<glm::vec3>, setJointTranslations); READ_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints); + READ_ENTITY_PROPERTY(PROP_GROUP_CULLED, bool, setGroupCulled); // grab a local copy of _animationProperties to avoid multiple locks int bytesFromAnimation; @@ -166,6 +169,7 @@ EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& requestedProperties += PROP_JOINT_TRANSLATIONS_SET; requestedProperties += PROP_JOINT_TRANSLATIONS; requestedProperties += PROP_RELAY_PARENT_JOINTS; + requestedProperties += PROP_GROUP_CULLED; requestedProperties += _animationProperties.getEntityProperties(params); return requestedProperties; @@ -192,6 +196,7 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, getJointTranslationsSet()); APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS, getJointTranslations()); APPEND_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, getRelayParentJoints()); + APPEND_ENTITY_PROPERTY(PROP_GROUP_CULLED, getGroupCulled()); withReadLock([&] { _animationProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, @@ -548,6 +553,18 @@ bool ModelEntityItem::getRelayParentJoints() const { }); } +void ModelEntityItem::setGroupCulled(bool value) { + withWriteLock([&] { + _groupCulled = value; + }); +} + +bool ModelEntityItem::getGroupCulled() const { + return resultWithReadLock<bool>([&] { + return _groupCulled; + }); +} + QString ModelEntityItem::getCompoundShapeURL() const { return _compoundShapeURL.get(); } diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index 5ca3e2caa1..8c9fbdc45f 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -100,6 +100,9 @@ public: void setRelayParentJoints(bool relayJoints); bool getRelayParentJoints() const; + void setGroupCulled(bool value); + bool getGroupCulled() const; + bool getAnimationIsPlaying() const; float getAnimationCurrentFrame() const; float getAnimationFPS() const; @@ -154,6 +157,7 @@ protected: glm::u8vec3 _color; QString _modelURL; bool _relayParentJoints; + bool _groupCulled { false }; ThreadSafeValueCache<QString> _compoundShapeURL; 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<QString, QString>& _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<ExtractedBlendshape> 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/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> 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 642914cd56..7f20f881da 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -33,15 +33,15 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return static_cast<PacketVersion>(EntityVersion::FixProtocolVersionBumpMismatch); + return static_cast<PacketVersion>(EntityVersion::LAST_PACKET_TYPE); case PacketType::EntityQuery: return static_cast<PacketVersion>(EntityQueryPacketVersion::ConicalFrustums); case PacketType::AvatarIdentity: case PacketType::AvatarData: - return static_cast<PacketVersion>(AvatarMixerPacketVersion::CollisionFlag); + return static_cast<PacketVersion>(AvatarMixerPacketVersion::SendMaxTranslationDimension); case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast<PacketVersion>(AvatarMixerPacketVersion::FasterAvatarEntities); + return static_cast<PacketVersion>(AvatarMixerPacketVersion::SendMaxTranslationDimension); case PacketType::MessagesData: return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData); // ICE packets @@ -93,8 +93,6 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast<PacketVersion>(PingVersion::IncludeConnectionID); case PacketType::AvatarQuery: return static_cast<PacketVersion>(AvatarQueryVersion::ConicalFrustums); - case PacketType::AvatarIdentityRequest: - return 22; case PacketType::EntityQueryInitialResultsComplete: return static_cast<PacketVersion>(EntityVersion::ParticleSpin); case PacketType::BulkAvatarTraitsAck: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index f53a287d71..2425d6f689 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, @@ -255,7 +255,12 @@ enum class EntityVersion : PacketVersion { MorePropertiesCleanup, FixPropertiesFromCleanup, UpdatedPolyLines, - FixProtocolVersionBumpMismatch + FixProtocolVersionBumpMismatch, + MigrateOverlayRenderProperties, + + // Add new versions above here + NUM_PACKET_TYPE, + LAST_PACKET_TYPE = NUM_PACKET_TYPE - 1 }; enum class EntityScriptCallMethodVersion : PacketVersion { @@ -312,7 +317,8 @@ enum class AvatarMixerPacketVersion : PacketVersion { GrabTraits, CollisionFlag, AvatarTraitsAck, - FasterAvatarEntities + FasterAvatarEntities, + SendMaxTranslationDimension }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index bd1abf8744..debc6786f1 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -35,6 +35,8 @@ #include "MaterialMappingMode.h" #include "BillboardMode.h" +#include "RenderLayer.h" +#include "PrimitiveMode.h" #include "OctreeConstants.h" #include "OctreeElement.h" @@ -263,6 +265,8 @@ public: static int unpackDataFromBytes(const unsigned char* dataBytes, ShapeType& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, MaterialMappingMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, BillboardMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, RenderLayer& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, PrimitiveMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec2& result); static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec3& result); static int unpackDataFromBytes(const unsigned char* dataBytes, glm::u8vec3& result); 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<btConvexHullShape*>(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/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index a546f69d23..0d0209e35f 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -14,6 +14,11 @@ #include <QtCore/QDebug> #include <QtCore/QPluginLoader> +//#define HIFI_PLUGINMANAGER_DEBUG +#if defined(HIFI_PLUGINMANAGER_DEBUG) +#include <QJsonDocument> +#endif + #include <DependencyManager.h> #include <UserActivityLogger.h> @@ -79,10 +84,7 @@ bool isDisabled(QJsonObject metaData) { return false; } -using Loader = QSharedPointer<QPluginLoader>; -using LoaderList = QList<Loader>; - -const LoaderList& getLoadedPlugins() { + auto PluginManager::getLoadedPlugins() const -> const LoaderList& { static std::once_flag once; static LoaderList loadedPlugins; std::call_once(once, [&] { @@ -106,15 +108,25 @@ const LoaderList& getLoadedPlugins() { for (auto plugin : candidates) { qCDebug(plugins) << "Attempting plugin" << qPrintable(plugin); QSharedPointer<QPluginLoader> loader(new QPluginLoader(pluginPath + plugin)); - - if (isDisabled(loader->metaData())) { + 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 (getPluginInterfaceVersionFromMetaData(loader->metaData()) != HIFI_PLUGIN_INTERFACE_VERSION) { + + 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(loader->metaData()) + << getPluginInterfaceVersionFromMetaData(pluginMetaData) << "doesn't match" << HIFI_PLUGIN_INTERFACE_VERSION; continue; } diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h index 593daeca11..1a578c7406 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<PluginManager>; class PluginManager : public QObject, public Dependency { @@ -48,6 +47,9 @@ public: void setInputPluginSettingsPersister(const InputPluginSettingsPersister& persister); QStringList getRunningInputDeviceNames() const; + using PluginFilter = std::function<bool(const QJsonObject&)>; + void setPluginFilter(PluginFilter pluginFilter) { _pluginFilter = pluginFilter; } + signals: void inputDeviceRunningChanged(const QString& pluginName, bool isRunning, const QStringList& runningDevices); @@ -61,6 +63,12 @@ private: PluginContainer* _container { nullptr }; DisplayPluginList _displayPlugins; InputPluginList _inputPlugins; + PluginFilter _pluginFilter { [](const QJsonObject&) { return true; } }; + + using Loader = QSharedPointer<QPluginLoader>; + using LoaderList = QList<Loader>; + + const LoaderList& getLoadedPlugins() const; }; // TODO: we should define this value in CMake, and then use CMake diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 3e32d19b49..81a81c5602 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -215,7 +215,7 @@ void CauterizedModel::updateRenderItems() { modelTransform.setTranslation(self->getTranslation()); modelTransform.setRotation(self->getRotation()); - bool isWireframe = self->isWireframe(); + PrimitiveMode primitiveMode = self->getPrimitiveMode(); auto renderItemKeyGlobalFlags = self->getRenderItemKeyGlobalFlags(); bool enableCauterization = self->getEnableCauterization(); @@ -232,7 +232,7 @@ void CauterizedModel::updateRenderItems() { bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning(); transaction.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, meshState, useDualQuaternionSkinning, cauterizedMeshState, invalidatePayloadShapeKey, - isWireframe, renderItemKeyGlobalFlags, enableCauterization](ModelMeshPartPayload& mmppData) { + primitiveMode, renderItemKeyGlobalFlags, enableCauterization](ModelMeshPartPayload& mmppData) { CauterizedMeshPartPayload& data = static_cast<CauterizedMeshPartPayload&>(mmppData); if (useDualQuaternionSkinning) { data.updateClusterBuffer(meshState.clusterDualQuaternions, @@ -276,7 +276,7 @@ void CauterizedModel::updateRenderItems() { data.setEnableCauterization(enableCauterization); data.updateKey(renderItemKeyGlobalFlags); - data.setShapeKey(invalidatePayloadShapeKey, isWireframe, useDualQuaternionSkinning); + data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning); }); } diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index d6d6f4903e..02f51e558a 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -721,11 +721,16 @@ QHash<SimpleProgramKey, gpu::PipelinePointer> GeometryCache::_simplePrograms; gpu::ShaderPointer GeometryCache::_simpleShader; gpu::ShaderPointer GeometryCache::_transparentShader; gpu::ShaderPointer GeometryCache::_unlitShader; +gpu::ShaderPointer GeometryCache::_forwardSimpleShader; +gpu::ShaderPointer GeometryCache::_forwardTransparentShader; +gpu::ShaderPointer GeometryCache::_forwardUnlitShader; gpu::ShaderPointer GeometryCache::_simpleFadeShader; gpu::ShaderPointer GeometryCache::_unlitFadeShader; render::ShapePipelinePointer GeometryCache::_simpleOpaquePipeline; render::ShapePipelinePointer GeometryCache::_simpleTransparentPipeline; +render::ShapePipelinePointer GeometryCache::_forwardSimpleOpaquePipeline; +render::ShapePipelinePointer GeometryCache::_forwardSimpleTransparentPipeline; render::ShapePipelinePointer GeometryCache::_simpleOpaqueFadePipeline; render::ShapePipelinePointer GeometryCache::_simpleTransparentFadePipeline; render::ShapePipelinePointer GeometryCache::_simpleWirePipeline; @@ -805,6 +810,8 @@ void GeometryCache::initializeShapePipelines() { if (!_simpleOpaquePipeline) { _simpleOpaquePipeline = getShapePipeline(false, false, true, false); _simpleTransparentPipeline = getShapePipeline(false, true, true, false); + _forwardSimpleOpaquePipeline = getShapePipeline(false, false, true, false, false, true); + _forwardSimpleTransparentPipeline = getShapePipeline(false, true, true, false, false, true); _simpleOpaqueFadePipeline = getFadingShapePipeline(false, false, false, false, false); _simpleTransparentFadePipeline = getFadingShapePipeline(false, true, false, false, false); _simpleWirePipeline = getShapePipeline(false, false, true, true); @@ -812,9 +819,9 @@ void GeometryCache::initializeShapePipelines() { } render::ShapePipelinePointer GeometryCache::getShapePipeline(bool textured, bool transparent, bool culled, - bool unlit, bool depthBias) { + bool unlit, bool depthBias, bool forward) { - return std::make_shared<render::ShapePipeline>(getSimplePipeline(textured, transparent, culled, unlit, depthBias, false, true), nullptr, + return std::make_shared<render::ShapePipeline>(getSimplePipeline(textured, transparent, culled, unlit, depthBias, false, true, forward), nullptr, [](const render::ShapePipeline& pipeline, gpu::Batch& batch, render::Args* args) { batch.setResourceTexture(gr::Texture::MaterialAlbedo, DependencyManager::get<TextureCache>()->getWhiteTexture()); DependencyManager::get<DeferredLightingEffect>()->setupKeyLightBatch(args, batch); @@ -2165,6 +2172,7 @@ public: HAS_DEPTH_BIAS_FLAG, IS_FADING_FLAG, IS_ANTIALIASED_FLAG, + IS_FORWARD_FLAG, NUM_FLAGS, }; @@ -2177,6 +2185,7 @@ public: HAS_DEPTH_BIAS = (1 << HAS_DEPTH_BIAS_FLAG), IS_FADING = (1 << IS_FADING_FLAG), IS_ANTIALIASED = (1 << IS_ANTIALIASED_FLAG), + IS_FORWARD = (1 << IS_FORWARD_FLAG), }; typedef unsigned short Flags; @@ -2189,6 +2198,7 @@ public: bool hasDepthBias() const { return isFlag(HAS_DEPTH_BIAS); } bool isFading() const { return isFlag(IS_FADING); } bool isAntiAliased() const { return isFlag(IS_ANTIALIASED); } + bool isForward() const { return isFlag(IS_FORWARD); } Flags _flags = 0; #if defined(__clang__) @@ -2200,9 +2210,9 @@ public: SimpleProgramKey(bool textured = false, bool transparent = false, bool culled = true, - bool unlit = false, bool depthBias = false, bool fading = false, bool isAntiAliased = true) { + bool unlit = false, bool depthBias = false, bool fading = false, bool isAntiAliased = true, bool forward = false) { _flags = (textured ? IS_TEXTURED : 0) | (transparent ? IS_TRANSPARENT : 0) | (culled ? IS_CULLED : 0) | - (unlit ? IS_UNLIT : 0) | (depthBias ? HAS_DEPTH_BIAS : 0) | (fading ? IS_FADING : 0) | (isAntiAliased ? IS_ANTIALIASED : 0); + (unlit ? IS_UNLIT : 0) | (depthBias ? HAS_DEPTH_BIAS : 0) | (fading ? IS_FADING : 0) | (isAntiAliased ? IS_ANTIALIASED : 0) | (forward ? IS_FORWARD : 0); } SimpleProgramKey(int bitmask) : _flags(bitmask) {} @@ -2255,8 +2265,8 @@ void GeometryCache::bindSimpleProgram(gpu::Batch& batch, bool textured, bool tra } } -gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transparent, bool culled, bool unlit, bool depthBiased, bool fading, bool isAntiAliased) { - SimpleProgramKey config { textured, transparent, culled, unlit, depthBiased, fading, isAntiAliased }; +gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transparent, bool culled, bool unlit, bool depthBiased, bool fading, bool isAntiAliased, bool forward) { + SimpleProgramKey config { textured, transparent, culled, unlit, depthBiased, fading, isAntiAliased, forward }; // If the pipeline already exists, return it auto it = _simplePrograms.find(config); @@ -2269,13 +2279,20 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp static std::once_flag once; std::call_once(once, [&]() { using namespace shader::render_utils::program; - auto PS = DISABLE_DEFERRED ? forward_simple_textured : simple_textured; - // Use the forward pipeline for both here, otherwise transparents will be unlit - auto PSTransparent = DISABLE_DEFERRED ? forward_simple_textured_transparent : forward_simple_textured_transparent; - auto PSUnlit = DISABLE_DEFERRED ? forward_simple_textured_unlit : simple_textured_unlit; - _simpleShader = gpu::Shader::createProgram(PS); - _transparentShader = gpu::Shader::createProgram(PSTransparent); - _unlitShader = gpu::Shader::createProgram(PSUnlit); + + _forwardSimpleShader = gpu::Shader::createProgram(forward_simple_textured); + _forwardTransparentShader = gpu::Shader::createProgram(forward_simple_textured_transparent); + _forwardUnlitShader = gpu::Shader::createProgram(forward_simple_textured_unlit); + if (DISABLE_DEFERRED) { + _simpleShader = _forwardSimpleShader; + _transparentShader = _forwardTransparentShader; + _unlitShader = _forwardUnlitShader; + } else { + _simpleShader = gpu::Shader::createProgram(simple_textured); + // Use the forward pipeline for both here, otherwise transparents will be unlit + _transparentShader = gpu::Shader::createProgram(forward_simple_textured_transparent); + _unlitShader = gpu::Shader::createProgram(simple_textured_unlit); + } }); } else { static std::once_flag once; @@ -2308,8 +2325,14 @@ gpu::PipelinePointer GeometryCache::getSimplePipeline(bool textured, bool transp PrepareStencil::testMaskDrawShapeNoAA(*state); } - gpu::ShaderPointer program = (config.isUnlit()) ? (config.isFading() ? _unlitFadeShader : _unlitShader) : - (config.isFading() ? _simpleFadeShader : (config.isTransparent() ? _transparentShader : _simpleShader)); + gpu::ShaderPointer program; + if (config.isForward()) { + program = (config.isUnlit()) ? (config.isFading() ? _unlitFadeShader : _forwardUnlitShader) : + (config.isFading() ? _simpleFadeShader : (config.isTransparent() ? _forwardTransparentShader : _forwardSimpleShader)); + } else { + program = (config.isUnlit()) ? (config.isFading() ? _unlitFadeShader : _unlitShader) : + (config.isFading() ? _simpleFadeShader : (config.isTransparent() ? _transparentShader : _simpleShader)); + } gpu::PipelinePointer pipeline = gpu::Pipeline::create(program, state); _simplePrograms.insert(config, pipeline); return pipeline; diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 589be4dcc1..e46bb4a899 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -174,7 +174,7 @@ public: bool unlit = false, bool depthBias = false, bool isAntiAliased = true); // Get the pipeline to render static geometry static gpu::PipelinePointer getSimplePipeline(bool textured = false, bool transparent = false, bool culled = true, - bool unlit = false, bool depthBias = false, bool fading = false, bool isAntiAliased = true); + bool unlit = false, bool depthBias = false, bool fading = false, bool isAntiAliased = true, bool forward = false); void bindWebBrowserProgram(gpu::Batch& batch, bool transparent = false); gpu::PipelinePointer getWebBrowserProgram(bool transparent); @@ -183,6 +183,8 @@ public: render::ShapePipelinePointer getOpaqueShapePipeline() { assert(_simpleOpaquePipeline != nullptr); return _simpleOpaquePipeline; } render::ShapePipelinePointer getTransparentShapePipeline() { assert(_simpleTransparentPipeline != nullptr); return _simpleTransparentPipeline; } + render::ShapePipelinePointer getForwardOpaqueShapePipeline() { assert(_forwardSimpleOpaquePipeline != nullptr); return _forwardSimpleOpaquePipeline; } + render::ShapePipelinePointer getForwardTransparentShapePipeline() { assert(_forwardSimpleTransparentPipeline != nullptr); return _forwardSimpleTransparentPipeline; } render::ShapePipelinePointer getOpaqueFadeShapePipeline() { assert(_simpleOpaqueFadePipeline != nullptr); return _simpleOpaqueFadePipeline; } render::ShapePipelinePointer getTransparentFadeShapePipeline() { assert(_simpleTransparentFadePipeline != nullptr); return _simpleTransparentFadePipeline; } render::ShapePipelinePointer getOpaqueShapePipeline(bool isFading); @@ -465,13 +467,19 @@ private: QHash<int, Vec2FloatPairPair> _lastRegisteredGridBuffer; QHash<int, GridBuffer> _registeredGridBuffers; + // FIXME: clean these up static gpu::ShaderPointer _simpleShader; static gpu::ShaderPointer _transparentShader; static gpu::ShaderPointer _unlitShader; + static gpu::ShaderPointer _forwardSimpleShader; + static gpu::ShaderPointer _forwardTransparentShader; + static gpu::ShaderPointer _forwardUnlitShader; static gpu::ShaderPointer _simpleFadeShader; static gpu::ShaderPointer _unlitFadeShader; static render::ShapePipelinePointer _simpleOpaquePipeline; static render::ShapePipelinePointer _simpleTransparentPipeline; + static render::ShapePipelinePointer _forwardSimpleOpaquePipeline; + static render::ShapePipelinePointer _forwardSimpleTransparentPipeline; static render::ShapePipelinePointer _simpleOpaqueFadePipeline; static render::ShapePipelinePointer _simpleTransparentFadePipeline; static render::ShapePipelinePointer _simpleWirePipeline; @@ -485,7 +493,7 @@ private: gpu::PipelinePointer _simpleTransparentWebBrowserPipeline; static render::ShapePipelinePointer getShapePipeline(bool textured = false, bool transparent = false, bool culled = true, - bool unlit = false, bool depthBias = false); + bool unlit = false, bool depthBias = false, bool forward = false); static render::ShapePipelinePointer getFadingShapePipeline(bool textured = false, bool transparent = false, bool culled = true, bool unlit = false, bool depthBias = false); }; diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index ca2e56862d..7a52ad77da 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -342,7 +342,7 @@ void ModelMeshPartPayload::updateKey(const render::ItemKey& key) { _itemKey = builder.build(); } -void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, bool isWireframe, bool useDualQuaternionSkinning) { +void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, PrimitiveMode primitiveMode, bool useDualQuaternionSkinning) { if (invalidateShapeKey) { _shapeKey = ShapeKey::Builder::invalid(); return; @@ -359,6 +359,7 @@ void ModelMeshPartPayload::setShapeKey(bool invalidateShapeKey, bool isWireframe bool isUnlit = drawMaterialKey.isUnlit(); bool isDeformed = _isBlendShaped || _isSkinned; + bool isWireframe = primitiveMode == PrimitiveMode::LINES; if (isWireframe) { isTranslucent = hasTangents = hasLightmap = false; diff --git a/libraries/render-utils/src/MeshPartPayload.h b/libraries/render-utils/src/MeshPartPayload.h index 29c0091f11..3b0590b4a9 100644 --- a/libraries/render-utils/src/MeshPartPayload.h +++ b/libraries/render-utils/src/MeshPartPayload.h @@ -109,7 +109,7 @@ public: render::ShapeKey getShapeKey() const override; // shape interface void render(RenderArgs* args) override; - void setShapeKey(bool invalidateShapeKey, bool isWireframe, bool useDualQuaternionSkinning); + void setShapeKey(bool invalidateShapeKey, PrimitiveMode primitiveMode, bool useDualQuaternionSkinning); // ModelMeshPartPayload functions to perform render void bindMesh(gpu::Batch& batch) override; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index ec29fb009e..da8dceb176 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -62,7 +62,6 @@ Model::Model(QObject* parent, SpatiallyNestable* spatiallyNestableOverride) : _snapModelToRegistrationPoint(false), _snappedToRegistrationPoint(false), _url(HTTP_INVALID_COM), - _isWireframe(false), _renderItemKeyGlobalFlags(render::ItemKey::Builder().withVisible().withTagBits(render::hifi::TAG_ALL_VIEWS).build()) { // we may have been created in the network thread, but we live in the main thread @@ -223,7 +222,7 @@ void Model::updateRenderItems() { Transform modelTransform = self->getTransform(); modelTransform.setScale(glm::vec3(1.0f)); - bool isWireframe = self->isWireframe(); + PrimitiveMode primitiveMode = self->getPrimitiveMode(); auto renderItemKeyGlobalFlags = self->getRenderItemKeyGlobalFlags(); render::Transaction transaction; @@ -238,7 +237,7 @@ void Model::updateRenderItems() { bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning(); transaction.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, meshState, useDualQuaternionSkinning, - invalidatePayloadShapeKey, isWireframe, renderItemKeyGlobalFlags](ModelMeshPartPayload& data) { + invalidatePayloadShapeKey, primitiveMode, renderItemKeyGlobalFlags](ModelMeshPartPayload& data) { if (useDualQuaternionSkinning) { data.updateClusterBuffer(meshState.clusterDualQuaternions); } else { @@ -263,7 +262,7 @@ void Model::updateRenderItems() { data.updateTransformForSkinnedMesh(renderTransform, modelTransform); data.updateKey(renderItemKeyGlobalFlags); - data.setShapeKey(invalidatePayloadShapeKey, isWireframe, useDualQuaternionSkinning); + data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning); }); } @@ -276,6 +275,11 @@ void Model::setRenderItemsNeedUpdate() { emit requestRenderUpdate(); } +void Model::setPrimitiveMode(PrimitiveMode primitiveMode) { + _primitiveMode = primitiveMode; + setRenderItemsNeedUpdate(); +} + void Model::reset() { if (isLoaded()) { const HFMModel& hfmModel = getHFMModel(); @@ -878,30 +882,14 @@ bool Model::canCastShadow() const { return _renderItemKeyGlobalFlags.isShadowCaster(); } -void Model::setLayeredInFront(bool layeredInFront, const render::ScenePointer& scene) { - if (Model::isLayeredInFront() != layeredInFront) { +void Model::setHifiRenderLayer(render::hifi::Layer renderLayer, const render::ScenePointer& scene) { + if (_renderItemKeyGlobalFlags.getLayer() != renderLayer) { auto keyBuilder = render::ItemKey::Builder(_renderItemKeyGlobalFlags); - _renderItemKeyGlobalFlags = (layeredInFront ? keyBuilder.withLayer(render::hifi::LAYER_3D_FRONT) : keyBuilder.withoutLayer()); + _renderItemKeyGlobalFlags = keyBuilder.withLayer(renderLayer); updateRenderItemsKey(scene); } } -bool Model::isLayeredInFront() const { - return _renderItemKeyGlobalFlags.isLayer(render::hifi::LAYER_3D_FRONT); -} - -void Model::setLayeredInHUD(bool layeredInHUD, const render::ScenePointer& scene) { - if (Model::isLayeredInHUD() != layeredInHUD) { - auto keyBuilder = render::ItemKey::Builder(_renderItemKeyGlobalFlags); - _renderItemKeyGlobalFlags = (layeredInHUD ? keyBuilder.withLayer(render::hifi::LAYER_3D_HUD) : keyBuilder.withoutLayer()); - updateRenderItemsKey(scene); - } -} - -bool Model::isLayeredInHUD() const { - return _renderItemKeyGlobalFlags.isLayer(render::hifi::LAYER_3D_HUD); -} - void Model::setTagMask(uint8_t mask, const render::ScenePointer& scene) { if (Model::getTagMask() != mask) { auto keyBuilder = render::ItemKey::Builder(_renderItemKeyGlobalFlags); @@ -920,6 +908,7 @@ void Model::setGroupCulled(bool groupCulled, const render::ScenePointer& scene) updateRenderItemsKey(scene); } } + bool Model::isGroupCulled() const { return _renderItemKeyGlobalFlags.isSubMetaCulled(); } @@ -1486,44 +1475,71 @@ bool Model::isRenderable() const { return !_meshStates.empty() || (isLoaded() && _renderGeometry->getMeshes().empty()); } -std::vector<unsigned int> 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<unsigned int> 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<unsigned int> Model::getMeshIDsFromMaterialID(QString parentMaterialName) { + std::set<unsigned int> 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<unsigned int> shapeIDs = getMeshIDsFromMaterialID(QString(parentMaterialName.c_str())); + std::set<unsigned int> shapeIDs = getMeshIDsFromMaterialID(QString(parentMaterialName.c_str())); render::Transaction transaction; for (auto shapeID : shapeIDs) { if (shapeID < _modelMeshRenderItemIDs.size()) { auto itemID = _modelMeshRenderItemIDs[shapeID]; auto renderItemsKey = _renderItemKeyGlobalFlags; - bool wireframe = isWireframe(); + PrimitiveMode primitiveMode = getPrimitiveMode(); auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex; bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex); bool useDualQuaternionSkinning = _useDualQuaternionSkinning; transaction.updateItem<ModelMeshPartPayload>(itemID, [material, renderItemsKey, - invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning](ModelMeshPartPayload& data) { + invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning](ModelMeshPartPayload& data) { data.addMaterial(material); // if the material changed, we might need to update our item key or shape key data.updateKey(renderItemsKey); - data.setShapeKey(invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning); + data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning); }); } } @@ -1531,22 +1547,22 @@ void Model::addMaterial(graphics::MaterialLayer material, const std::string& par } void Model::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { - std::vector<unsigned int> shapeIDs = getMeshIDsFromMaterialID(QString(parentMaterialName.c_str())); + std::set<unsigned int> shapeIDs = getMeshIDsFromMaterialID(QString(parentMaterialName.c_str())); render::Transaction transaction; for (auto shapeID : shapeIDs) { if (shapeID < _modelMeshRenderItemIDs.size()) { auto itemID = _modelMeshRenderItemIDs[shapeID]; auto renderItemsKey = _renderItemKeyGlobalFlags; - bool wireframe = isWireframe(); + PrimitiveMode primitiveMode = getPrimitiveMode(); auto meshIndex = _modelMeshRenderItemShapes[shapeID].meshIndex; bool invalidatePayloadShapeKey = shouldInvalidatePayloadShapeKey(meshIndex); bool useDualQuaternionSkinning = _useDualQuaternionSkinning; transaction.updateItem<ModelMeshPartPayload>(itemID, [material, renderItemsKey, - invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning](ModelMeshPartPayload& data) { + invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning](ModelMeshPartPayload& data) { data.removeMaterial(material); // if the material changed, we might need to update our item key or shape key data.updateKey(renderItemsKey); - data.setShapeKey(invalidatePayloadShapeKey, wireframe, useDualQuaternionSkinning); + data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning); }); } } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 0f8eb782c3..16e08c2b23 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -37,6 +37,7 @@ #include "GeometryCache.h" #include "TextureCache.h" #include "Rig.h" +#include "PrimitiveMode.h" // Use dual quaternion skinning! // Must match define in Skinning.slh @@ -123,11 +124,7 @@ public: bool canCastShadow() const; void setCanCastShadow(bool canCastShadow, const render::ScenePointer& scene = nullptr); - void setLayeredInFront(bool isLayeredInFront, const render::ScenePointer& scene = nullptr); - void setLayeredInHUD(bool isLayeredInHUD, const render::ScenePointer& scene = nullptr); - - bool isLayeredInFront() const; - bool isLayeredInHUD() const; + void setHifiRenderLayer(render::hifi::Layer layer, const render::ScenePointer& scene = nullptr); // Access the current RenderItemKey Global Flags used by the model and applied to the render items representing the parts of the model. const render::ItemKey getRenderItemKeyGlobalFlags() const; @@ -166,8 +163,8 @@ public: bool isLoaded() const { return (bool)_renderGeometry && _renderGeometry->isHFMModelLoaded(); } bool isAddedToScene() const { return _addedToScene; } - void setIsWireframe(bool isWireframe) { _isWireframe = isWireframe; } - bool isWireframe() const { return _isWireframe; } + void setPrimitiveMode(PrimitiveMode primitiveMode); + PrimitiveMode getPrimitiveMode() const { return _primitiveMode; } void reset(); @@ -455,7 +452,7 @@ protected: virtual void createRenderItemSet(); - bool _isWireframe; + PrimitiveMode _primitiveMode { PrimitiveMode::SOLID }; bool _useDualQuaternionSkinning { false }; // debug rendering support @@ -513,7 +510,7 @@ private: void calculateTextureInfo(); - std::vector<unsigned int> getMeshIDsFromMaterialID(QString parentMaterialName); + std::set<unsigned int> getMeshIDsFromMaterialID(QString parentMaterialName); }; Q_DECLARE_METATYPE(ModelPointer) 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/shared/src/BillboardMode.cpp b/libraries/shared/src/BillboardMode.cpp index 56251f53f2..4b6af5db33 100644 --- a/libraries/shared/src/BillboardMode.cpp +++ b/libraries/shared/src/BillboardMode.cpp @@ -14,10 +14,10 @@ const char* billboardModeNames[] = { "full" }; -static const size_t MATERIAL_MODE_NAMES = (sizeof(billboardModeNames) / sizeof((billboardModeNames)[0])); +static const size_t BILLBOARD_MODE_NAMES = (sizeof(billboardModeNames) / sizeof(billboardModeNames[0])); QString BillboardModeHelpers::getNameForBillboardMode(BillboardMode mode) { - if (((int)mode <= 0) || ((int)mode >= (int)MATERIAL_MODE_NAMES)) { + if (((int)mode <= 0) || ((int)mode >= (int)BILLBOARD_MODE_NAMES)) { mode = (BillboardMode)0; } 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/MaterialMappingMode.cpp b/libraries/shared/src/MaterialMappingMode.cpp index 1ddad178a2..09a7cfd6d9 100644 --- a/libraries/shared/src/MaterialMappingMode.cpp +++ b/libraries/shared/src/MaterialMappingMode.cpp @@ -13,7 +13,7 @@ const char* materialMappingModeNames[] = { "projected" }; -static const size_t MATERIAL_MODE_NAMES = (sizeof(materialMappingModeNames) / sizeof((materialMappingModeNames)[0])); +static const size_t MATERIAL_MODE_NAMES = (sizeof(materialMappingModeNames) / sizeof(materialMappingModeNames[0])); QString MaterialMappingModeHelpers::getNameForMaterialMappingMode(MaterialMappingMode mode) { if (((int)mode <= 0) || ((int)mode >= (int)MATERIAL_MODE_NAMES)) { diff --git a/libraries/shared/src/PrimitiveMode.cpp b/libraries/shared/src/PrimitiveMode.cpp new file mode 100644 index 0000000000..c426f896b9 --- /dev/null +++ b/libraries/shared/src/PrimitiveMode.cpp @@ -0,0 +1,24 @@ +// +// Created by Sam Gondelman on 1/7/19 +// Copyright 2019 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 "PrimitiveMode.h" + +const char* primitiveModeNames[] = { + "solid", + "lines" +}; + +static const size_t PRIMITIVE_MODE_NAMES = (sizeof(primitiveModeNames) / sizeof(primitiveModeNames[0])); + +QString PrimitiveModeHelpers::getNameForPrimitiveMode(PrimitiveMode mode) { + if (((int)mode <= 0) || ((int)mode >= (int)PRIMITIVE_MODE_NAMES)) { + mode = (PrimitiveMode)0; + } + + return primitiveModeNames[(int)mode]; +} \ No newline at end of file diff --git a/libraries/shared/src/PrimitiveMode.h b/libraries/shared/src/PrimitiveMode.h new file mode 100644 index 0000000000..6072f24fb9 --- /dev/null +++ b/libraries/shared/src/PrimitiveMode.h @@ -0,0 +1,39 @@ +// +// Created by Sam Gondelman on 1/7/19. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_PrimitiveMode_h +#define hifi_PrimitiveMode_h + +#include "QString" + +/**jsdoc + * <p>How the geometry of the entity is rendered.</p> + * <table> + * <thead> + * <tr><th>Value</th><th>Description</th></tr> + * </thead> + * <tbody> + * <tr><td><code>solid</code></td><td>The entity will be drawn as a solid shape.</td></tr> + * <tr><td><code>lines</code></td><td>The entity will be drawn as wireframe.</td></tr> + * </tbody> + * </table> + * @typedef {string} PrimitiveMode + */ + +enum class PrimitiveMode { + SOLID = 0, + LINES +}; + +class PrimitiveModeHelpers { +public: + static QString getNameForPrimitiveMode(PrimitiveMode mode); +}; + +#endif // hifi_PrimitiveMode_h + diff --git a/libraries/shared/src/RenderLayer.cpp b/libraries/shared/src/RenderLayer.cpp new file mode 100644 index 0000000000..7e072631e0 --- /dev/null +++ b/libraries/shared/src/RenderLayer.cpp @@ -0,0 +1,25 @@ +// +// Created by Sam Gondelman on 1/3/19 +// Copyright 2019 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 "RenderLayer.h" + +const char* renderLayerNames[] = { + "world", + "front", + "hud" +}; + +static const size_t RENDER_LAYER_NAMES = (sizeof(renderLayerNames) / sizeof(renderLayerNames[0])); + +QString RenderLayerHelpers::getNameForRenderLayer(RenderLayer mode) { + if (((int)mode <= 0) || ((int)mode >= (int)RENDER_LAYER_NAMES)) { + mode = (RenderLayer)0; + } + + return renderLayerNames[(int)mode]; +} \ No newline at end of file diff --git a/libraries/shared/src/RenderLayer.h b/libraries/shared/src/RenderLayer.h new file mode 100644 index 0000000000..b5bf885616 --- /dev/null +++ b/libraries/shared/src/RenderLayer.h @@ -0,0 +1,41 @@ +// +// Created by Sam Gondelman on 1/3/19. +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_RenderLayer_h +#define hifi_RenderLayer_h + +#include "QString" + +/**jsdoc + * <p>In which layer an entity is rendered.</p> + * <table> + * <thead> + * <tr><th>Value</th><th>Description</th></tr> + * </thead> + * <tbody> + * <tr><td><code>world</code></td><td>The entity will be drawn in the world with everything else.</td></tr> + * <tr><td><code>front</code></td><td>The entity will be drawn on top of the world layer, but behind the HUD sphere.</td></tr> + * <tr><td><code>hud</code></td><td>The entity will be drawn on top of other layers and the HUD sphere.</td></tr> + * </tbody> + * </table> + * @typedef {string} RenderLayer + */ + +enum class RenderLayer { + WORLD = 0, + FRONT, + HUD +}; + +class RenderLayerHelpers { +public: + static QString getNameForRenderLayer(RenderLayer mode); +}; + +#endif // hifi_RenderLayer_h + diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index 62ac8fad52..2693b9ee7e 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/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 cb194c9d66..bf3ff3f324 100644 --- a/scripts/system/assets/data/createAppTooltips.json +++ b/scripts/system/assets/data/createAppTooltips.json @@ -39,6 +39,14 @@ "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." }, @@ -133,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/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 8b7264eeb1..0bec77aa41 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -184,6 +184,20 @@ const GROUPS = [ 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", @@ -1345,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", @@ -1454,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' ], @@ -3317,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 + '"'; @@ -3437,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/tools/dissectors/3-hf-avatar.lua b/tools/dissectors/3-hf-avatar.lua index f4172b01cb..bc449770f5 100644 --- a/tools/dissectors/3-hf-avatar.lua +++ b/tools/dissectors/3-hf-avatar.lua @@ -379,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/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](<https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-v1.2.exe>) +1. Download the installer by browsing to [here](<https://hifi-qa.s3.amazonaws.com/nitpick/Windows/nitpick-installer-vXXXX.exe>) 1. Double click on the installer and install to a convenient location  @@ -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](<https://hifi-qa.s3.amazonaws.com/nitpick/Mac/nitpick-installer-v1.2.dmg>). +1. Download the installer by browsing to [here](<https://hifi-qa.s3.amazonaws.com/nitpick/Mac/nitpick-installer-vXXXX.dmg>). 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