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 01d805b077..a466245eda 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/apps/interface/src/main/cpp/native.cpp @@ -149,7 +149,7 @@ void unpackAndroidAssets() { extern "C" { -JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCreate(JNIEnv* env, jobject obj, jobject instance, jobject asset_mgr) { +JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCreate(JNIEnv* env, jobject instance, jobject asset_mgr) { g_assetManager = AAssetManager_fromJava(env, asset_mgr); qRegisterMetaType("QAndroidJniObject"); __interfaceActivity = QAndroidJniObject(instance); diff --git a/android/app/src/main/java/io/highfidelity/gvrinterface/InterfaceActivity.java b/android/apps/interface/src/main/java/io/highfidelity/gvrinterface/InterfaceActivity.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/gvrinterface/InterfaceActivity.java rename to android/apps/interface/src/main/java/io/highfidelity/gvrinterface/InterfaceActivity.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/BreakpadUploaderService.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/BreakpadUploaderService.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/BreakpadUploaderService.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/BreakpadUploaderService.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java similarity index 98% rename from android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java index 50aea59663..b7d2157737 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -61,7 +61,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW private HeadsetStateReceiver headsetStateReceiver; //public static native void handleHifiURL(String hifiURLString); - private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager); + private native void nativeOnCreate(AssetManager assetManager); private native void nativeOnDestroy(); private native void nativeGotoUrl(String url); private native void nativeGoToUser(String username); @@ -114,7 +114,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW assetManager = getResources().getAssets(); //nativeGvrApi = - nativeOnCreate(this, assetManager /*, gvrApi.getNativeGvrContext()*/); + nativeOnCreate(assetManager /*, gvrApi.getNativeGvrContext()*/); final View rootView = getWindow().getDecorView().findViewById(android.R.id.content); diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/LoginMenuActivity.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/LoginMenuActivity.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/LoginMenuActivity.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/LoginMenuActivity.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/MainActivity.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/MainActivity.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/WebViewActivity.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/WebViewActivity.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/WebViewActivity.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/WebViewActivity.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/FriendsFragment.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/HomeFragment.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/LoginFragment.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/LoginFragment.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/fragment/LoginFragment.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/LoginFragment.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/OnBackPressedListener.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/OnBackPressedListener.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/fragment/OnBackPressedListener.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/OnBackPressedListener.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/PolicyFragment.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/PolicyFragment.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/fragment/PolicyFragment.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/PolicyFragment.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/SettingsFragment.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SignupFragment.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/SignupFragment.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SignupFragment.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/SignupFragment.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/StartMenuFragment.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/StartMenuFragment.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/fragment/StartMenuFragment.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/StartMenuFragment.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/WebViewFragment.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/WebViewFragment.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/fragment/WebViewFragment.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/WebViewFragment.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/Callback.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/Callback.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/provider/Callback.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/Callback.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/DomainProvider.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/DomainProvider.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/provider/DomainProvider.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/DomainProvider.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/EndpointUsersProvider.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/UsersProvider.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/receiver/HeadsetStateReceiver.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/task/DownloadProfileImageTask.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/task/DownloadProfileImageTask.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/task/DownloadProfileImageTask.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/task/DownloadProfileImageTask.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java similarity index 100% rename from android/app/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/view/UserListAdapter.java diff --git a/android/app/src/main/res/drawable/default_profile_avatar.xml b/android/apps/interface/src/main/res/drawable/default_profile_avatar.xml similarity index 100% rename from android/app/src/main/res/drawable/default_profile_avatar.xml rename to android/apps/interface/src/main/res/drawable/default_profile_avatar.xml diff --git a/android/app/src/main/res/drawable/domain_placeholder.png b/android/apps/interface/src/main/res/drawable/domain_placeholder.png similarity index 100% rename from android/app/src/main/res/drawable/domain_placeholder.png rename to android/apps/interface/src/main/res/drawable/domain_placeholder.png diff --git a/android/app/src/main/res/drawable/encourage_login_background.jpg b/android/apps/interface/src/main/res/drawable/encourage_login_background.jpg similarity index 100% rename from android/app/src/main/res/drawable/encourage_login_background.jpg rename to android/apps/interface/src/main/res/drawable/encourage_login_background.jpg diff --git a/android/app/src/main/res/drawable/hifi_header.xml b/android/apps/interface/src/main/res/drawable/hifi_header.xml similarity index 100% rename from android/app/src/main/res/drawable/hifi_header.xml rename to android/apps/interface/src/main/res/drawable/hifi_header.xml diff --git a/android/app/src/main/res/drawable/hifi_logo_header.xml b/android/apps/interface/src/main/res/drawable/hifi_logo_header.xml similarity index 100% rename from android/app/src/main/res/drawable/hifi_logo_header.xml rename to android/apps/interface/src/main/res/drawable/hifi_logo_header.xml diff --git a/android/app/src/main/res/drawable/hifi_logo_splash.xml b/android/apps/interface/src/main/res/drawable/hifi_logo_splash.xml similarity index 100% rename from android/app/src/main/res/drawable/hifi_logo_splash.xml rename to android/apps/interface/src/main/res/drawable/hifi_logo_splash.xml diff --git a/android/app/src/main/res/drawable/ic_bookmark.xml b/android/apps/interface/src/main/res/drawable/ic_bookmark.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_bookmark.xml rename to android/apps/interface/src/main/res/drawable/ic_bookmark.xml diff --git a/android/app/src/main/res/drawable/ic_clear.xml b/android/apps/interface/src/main/res/drawable/ic_clear.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_clear.xml rename to android/apps/interface/src/main/res/drawable/ic_clear.xml diff --git a/android/app/src/main/res/drawable/ic_close.xml b/android/apps/interface/src/main/res/drawable/ic_close.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_close.xml rename to android/apps/interface/src/main/res/drawable/ic_close.xml diff --git a/android/app/src/main/res/drawable/ic_close_black_24dp.xml b/android/apps/interface/src/main/res/drawable/ic_close_black_24dp.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_close_black_24dp.xml rename to android/apps/interface/src/main/res/drawable/ic_close_black_24dp.xml diff --git a/android/app/src/main/res/drawable/ic_delete_black_24dp.xml b/android/apps/interface/src/main/res/drawable/ic_delete_black_24dp.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_delete_black_24dp.xml rename to android/apps/interface/src/main/res/drawable/ic_delete_black_24dp.xml diff --git a/android/app/src/main/res/drawable/ic_expand.xml b/android/apps/interface/src/main/res/drawable/ic_expand.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_expand.xml rename to android/apps/interface/src/main/res/drawable/ic_expand.xml diff --git a/android/app/src/main/res/drawable/ic_eye_noshow.xml b/android/apps/interface/src/main/res/drawable/ic_eye_noshow.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_eye_noshow.xml rename to android/apps/interface/src/main/res/drawable/ic_eye_noshow.xml diff --git a/android/app/src/main/res/drawable/ic_eye_show.xml b/android/apps/interface/src/main/res/drawable/ic_eye_show.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_eye_show.xml rename to android/apps/interface/src/main/res/drawable/ic_eye_show.xml diff --git a/android/app/src/main/res/drawable/ic_launcher.xml b/android/apps/interface/src/main/res/drawable/ic_launcher.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_launcher.xml rename to android/apps/interface/src/main/res/drawable/ic_launcher.xml diff --git a/android/app/src/main/res/drawable/ic_menu.xml b/android/apps/interface/src/main/res/drawable/ic_menu.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_menu.xml rename to android/apps/interface/src/main/res/drawable/ic_menu.xml diff --git a/android/app/src/main/res/drawable/ic_person.xml b/android/apps/interface/src/main/res/drawable/ic_person.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_person.xml rename to android/apps/interface/src/main/res/drawable/ic_person.xml diff --git a/android/app/src/main/res/drawable/ic_right_arrow.xml b/android/apps/interface/src/main/res/drawable/ic_right_arrow.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_right_arrow.xml rename to android/apps/interface/src/main/res/drawable/ic_right_arrow.xml diff --git a/android/app/src/main/res/drawable/ic_search.xml b/android/apps/interface/src/main/res/drawable/ic_search.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_search.xml rename to android/apps/interface/src/main/res/drawable/ic_search.xml diff --git a/android/app/src/main/res/drawable/ic_share.xml b/android/apps/interface/src/main/res/drawable/ic_share.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_share.xml rename to android/apps/interface/src/main/res/drawable/ic_share.xml diff --git a/android/app/src/main/res/drawable/ic_star.xml b/android/apps/interface/src/main/res/drawable/ic_star.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_star.xml rename to android/apps/interface/src/main/res/drawable/ic_star.xml diff --git a/android/app/src/main/res/drawable/ic_steam.xml b/android/apps/interface/src/main/res/drawable/ic_steam.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_steam.xml rename to android/apps/interface/src/main/res/drawable/ic_steam.xml diff --git a/android/app/src/main/res/drawable/ic_teleporticon.xml b/android/apps/interface/src/main/res/drawable/ic_teleporticon.xml similarity index 100% rename from android/app/src/main/res/drawable/ic_teleporticon.xml rename to android/apps/interface/src/main/res/drawable/ic_teleporticon.xml diff --git a/android/app/src/main/res/drawable/launch_screen.xml b/android/apps/interface/src/main/res/drawable/launch_screen.xml similarity index 100% rename from android/app/src/main/res/drawable/launch_screen.xml rename to android/apps/interface/src/main/res/drawable/launch_screen.xml diff --git a/android/app/src/main/res/drawable/rounded_button_color1.xml b/android/apps/interface/src/main/res/drawable/rounded_button_color1.xml similarity index 100% rename from android/app/src/main/res/drawable/rounded_button_color1.xml rename to android/apps/interface/src/main/res/drawable/rounded_button_color1.xml diff --git a/android/app/src/main/res/drawable/rounded_button_color3.xml b/android/apps/interface/src/main/res/drawable/rounded_button_color3.xml similarity index 100% rename from android/app/src/main/res/drawable/rounded_button_color3.xml rename to android/apps/interface/src/main/res/drawable/rounded_button_color3.xml diff --git a/android/app/src/main/res/drawable/rounded_button_color4.xml b/android/apps/interface/src/main/res/drawable/rounded_button_color4.xml similarity index 100% rename from android/app/src/main/res/drawable/rounded_button_color4.xml rename to android/apps/interface/src/main/res/drawable/rounded_button_color4.xml diff --git a/android/app/src/main/res/drawable/rounded_secondary_button.xml b/android/apps/interface/src/main/res/drawable/rounded_secondary_button.xml similarity index 100% rename from android/app/src/main/res/drawable/rounded_secondary_button.xml rename to android/apps/interface/src/main/res/drawable/rounded_secondary_button.xml diff --git a/android/app/src/main/res/drawable/search_bg.xml b/android/apps/interface/src/main/res/drawable/search_bg.xml similarity index 100% rename from android/app/src/main/res/drawable/search_bg.xml rename to android/apps/interface/src/main/res/drawable/search_bg.xml diff --git a/android/app/src/main/res/drawable/selector_show_password.xml b/android/apps/interface/src/main/res/drawable/selector_show_password.xml similarity index 100% rename from android/app/src/main/res/drawable/selector_show_password.xml rename to android/apps/interface/src/main/res/drawable/selector_show_password.xml diff --git a/android/app/src/main/res/font/raleway.ttf b/android/apps/interface/src/main/res/font/raleway.ttf similarity index 100% rename from android/app/src/main/res/font/raleway.ttf rename to android/apps/interface/src/main/res/font/raleway.ttf diff --git a/android/app/src/main/res/font/raleway_bold.xml b/android/apps/interface/src/main/res/font/raleway_bold.xml similarity index 100% rename from android/app/src/main/res/font/raleway_bold.xml rename to android/apps/interface/src/main/res/font/raleway_bold.xml diff --git a/android/app/src/main/res/font/raleway_italic.xml b/android/apps/interface/src/main/res/font/raleway_italic.xml similarity index 100% rename from android/app/src/main/res/font/raleway_italic.xml rename to android/apps/interface/src/main/res/font/raleway_italic.xml diff --git a/android/app/src/main/res/font/raleway_light_italic.xml b/android/apps/interface/src/main/res/font/raleway_light_italic.xml similarity index 100% rename from android/app/src/main/res/font/raleway_light_italic.xml rename to android/apps/interface/src/main/res/font/raleway_light_italic.xml diff --git a/android/app/src/main/res/font/raleway_medium.xml b/android/apps/interface/src/main/res/font/raleway_medium.xml similarity index 100% rename from android/app/src/main/res/font/raleway_medium.xml rename to android/apps/interface/src/main/res/font/raleway_medium.xml diff --git a/android/app/src/main/res/font/raleway_semibold.xml b/android/apps/interface/src/main/res/font/raleway_semibold.xml similarity index 100% rename from android/app/src/main/res/font/raleway_semibold.xml rename to android/apps/interface/src/main/res/font/raleway_semibold.xml diff --git a/android/app/src/main/res/layout/activity_encourage_login.xml b/android/apps/interface/src/main/res/layout/activity_encourage_login.xml similarity index 100% rename from android/app/src/main/res/layout/activity_encourage_login.xml rename to android/apps/interface/src/main/res/layout/activity_encourage_login.xml diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/apps/interface/src/main/res/layout/activity_main.xml similarity index 100% rename from android/app/src/main/res/layout/activity_main.xml rename to android/apps/interface/src/main/res/layout/activity_main.xml diff --git a/android/app/src/main/res/layout/activity_splash.xml b/android/apps/interface/src/main/res/layout/activity_splash.xml similarity index 100% rename from android/app/src/main/res/layout/activity_splash.xml rename to android/apps/interface/src/main/res/layout/activity_splash.xml diff --git a/android/app/src/main/res/layout/activity_web_view.xml b/android/apps/interface/src/main/res/layout/activity_web_view.xml similarity index 100% rename from android/app/src/main/res/layout/activity_web_view.xml rename to android/apps/interface/src/main/res/layout/activity_web_view.xml diff --git a/android/app/src/main/res/layout/domain_view.xml b/android/apps/interface/src/main/res/layout/domain_view.xml similarity index 100% rename from android/app/src/main/res/layout/domain_view.xml rename to android/apps/interface/src/main/res/layout/domain_view.xml diff --git a/android/app/src/main/res/layout/fragment_friends.xml b/android/apps/interface/src/main/res/layout/fragment_friends.xml similarity index 100% rename from android/app/src/main/res/layout/fragment_friends.xml rename to android/apps/interface/src/main/res/layout/fragment_friends.xml diff --git a/android/app/src/main/res/layout/fragment_home.xml b/android/apps/interface/src/main/res/layout/fragment_home.xml similarity index 100% rename from android/app/src/main/res/layout/fragment_home.xml rename to android/apps/interface/src/main/res/layout/fragment_home.xml diff --git a/android/app/src/main/res/layout/fragment_login.xml b/android/apps/interface/src/main/res/layout/fragment_login.xml similarity index 100% rename from android/app/src/main/res/layout/fragment_login.xml rename to android/apps/interface/src/main/res/layout/fragment_login.xml diff --git a/android/app/src/main/res/layout/fragment_login_menu.xml b/android/apps/interface/src/main/res/layout/fragment_login_menu.xml similarity index 100% rename from android/app/src/main/res/layout/fragment_login_menu.xml rename to android/apps/interface/src/main/res/layout/fragment_login_menu.xml diff --git a/android/app/src/main/res/layout/fragment_policy.xml b/android/apps/interface/src/main/res/layout/fragment_policy.xml similarity index 100% rename from android/app/src/main/res/layout/fragment_policy.xml rename to android/apps/interface/src/main/res/layout/fragment_policy.xml diff --git a/android/app/src/main/res/layout/fragment_signup.xml b/android/apps/interface/src/main/res/layout/fragment_signup.xml similarity index 100% rename from android/app/src/main/res/layout/fragment_signup.xml rename to android/apps/interface/src/main/res/layout/fragment_signup.xml diff --git a/android/app/src/main/res/layout/fragment_web_view.xml b/android/apps/interface/src/main/res/layout/fragment_web_view.xml similarity index 100% rename from android/app/src/main/res/layout/fragment_web_view.xml rename to android/apps/interface/src/main/res/layout/fragment_web_view.xml diff --git a/android/app/src/main/res/layout/navigation_header.xml b/android/apps/interface/src/main/res/layout/navigation_header.xml similarity index 100% rename from android/app/src/main/res/layout/navigation_header.xml rename to android/apps/interface/src/main/res/layout/navigation_header.xml diff --git a/android/app/src/main/res/layout/user_item.xml b/android/apps/interface/src/main/res/layout/user_item.xml similarity index 100% rename from android/app/src/main/res/layout/user_item.xml rename to android/apps/interface/src/main/res/layout/user_item.xml diff --git a/android/app/src/main/res/layout/web_drawer.xml b/android/apps/interface/src/main/res/layout/web_drawer.xml similarity index 100% rename from android/app/src/main/res/layout/web_drawer.xml rename to android/apps/interface/src/main/res/layout/web_drawer.xml diff --git a/android/app/src/main/res/menu/menu_navigation.xml b/android/apps/interface/src/main/res/menu/menu_navigation.xml similarity index 100% rename from android/app/src/main/res/menu/menu_navigation.xml rename to android/apps/interface/src/main/res/menu/menu_navigation.xml diff --git a/android/app/src/main/res/menu/web_view_menu.xml b/android/apps/interface/src/main/res/menu/web_view_menu.xml similarity index 100% rename from android/app/src/main/res/menu/web_view_menu.xml rename to android/apps/interface/src/main/res/menu/web_view_menu.xml diff --git a/android/app/src/main/res/values-w385dp/dimens.xml b/android/apps/interface/src/main/res/values-w385dp/dimens.xml similarity index 100% rename from android/app/src/main/res/values-w385dp/dimens.xml rename to android/apps/interface/src/main/res/values-w385dp/dimens.xml diff --git a/android/app/src/main/res/values/colors.xml b/android/apps/interface/src/main/res/values/colors.xml similarity index 100% rename from android/app/src/main/res/values/colors.xml rename to android/apps/interface/src/main/res/values/colors.xml diff --git a/android/app/src/main/res/values/dimens.xml b/android/apps/interface/src/main/res/values/dimens.xml similarity index 100% rename from android/app/src/main/res/values/dimens.xml rename to android/apps/interface/src/main/res/values/dimens.xml diff --git a/android/app/src/main/res/values/font_certs.xml b/android/apps/interface/src/main/res/values/font_certs.xml similarity index 100% rename from android/app/src/main/res/values/font_certs.xml rename to android/apps/interface/src/main/res/values/font_certs.xml diff --git a/android/app/src/main/res/values/preloaded_fonts.xml b/android/apps/interface/src/main/res/values/preloaded_fonts.xml similarity index 100% rename from android/app/src/main/res/values/preloaded_fonts.xml rename to android/apps/interface/src/main/res/values/preloaded_fonts.xml diff --git a/android/app/src/main/res/values/strings.xml b/android/apps/interface/src/main/res/values/strings.xml similarity index 100% rename from android/app/src/main/res/values/strings.xml rename to android/apps/interface/src/main/res/values/strings.xml diff --git a/android/app/src/main/res/values/styles.xml b/android/apps/interface/src/main/res/values/styles.xml similarity index 100% rename from android/app/src/main/res/values/styles.xml rename to android/apps/interface/src/main/res/values/styles.xml diff --git a/android/app/src/main/res/xml/settings.xml b/android/apps/interface/src/main/res/xml/settings.xml similarity index 100% rename from android/app/src/main/res/xml/settings.xml rename to android/apps/interface/src/main/res/xml/settings.xml diff --git a/android/build.gradle b/android/build.gradle index 8d03b9f6b3..ed2ca1c47e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -10,8 +10,8 @@ import java.util.regex.Pattern buildscript { repositories { - jcenter() google() + jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.2.1' @@ -26,8 +26,8 @@ plugins { allprojects { repositories { - jcenter() google() + jcenter() mavenCentral() } } @@ -42,378 +42,13 @@ ext { RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV' STABLE_BUILD = project.hasProperty('STABLE_BUILD') ? project.getProperty('STABLE_BUILD') : '0' EXEC_SUFFIX = Os.isFamily(Os.FAMILY_WINDOWS) ? '.exe' : '' - QT5_DEPS = [ - 'Qt5Concurrent', - 'Qt5Core', - 'Qt5Gui', - 'Qt5Multimedia', - 'Qt5Network', - 'Qt5OpenGL', - 'Qt5Qml', - 'Qt5Quick', - 'Qt5QuickControls2', - 'Qt5QuickTemplates2', - 'Qt5Script', - 'Qt5ScriptTools', - 'Qt5Svg', - 'Qt5WebChannel', - 'Qt5WebSockets', - 'Qt5Widgets', - 'Qt5XmlPatterns', - // Android specific - 'Qt5AndroidExtras', - 'Qt5WebView', - ] } -def baseFolder = new File(HIFI_ANDROID_PRECOMPILED) -def appDir = new File(projectDir, 'app') +def appDir = new File(projectDir, 'apps/interface') def jniFolder = new File(appDir, 'src/main/jniLibs/arm64-v8a') def baseUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/android/' def breakpadDumpSymsDir = new File("${appDir}/build/tmp/breakpadDumpSyms") -def qtFile='qt-5.11.1_linux_armv8-libcpp_openssl_patched.tgz' -def qtChecksum='aa449d4bfa963f3bc9a9dfe558ba29df' -def qtVersionId='3S97HBM5G5Xw9EfE52sikmgdN3t6C2MN' -if (Os.isFamily(Os.FAMILY_MAC)) { - qtFile = 'qt-5.11.1_osx_armv8-libcpp_openssl_patched.tgz' - qtChecksum='c83cc477c08a892e00c71764dca051a0' - qtVersionId='OxBD7iKINv1HbyOXmAmDrBb8AF3N.Kup' -} else if (Os.isFamily(Os.FAMILY_WINDOWS)) { - qtFile = 'qt-5.11.1_win_armv8-libcpp_openssl_patched.tgz' - qtChecksum='0582191cc55431aa4f660848a542883e' - qtVersionId='JfWM0P_Mz5Qp0LwpzhrsRwN3fqlLSFeT' -} - -def packages = [ - qt: [ - file: qtFile, - versionId: qtVersionId, - checksum: qtChecksum, - ], - bullet: [ - file: 'bullet-2.88_armv8-libcpp.tgz', - versionId: 'S8YaoED0Cl8sSb8fSV7Q2G1lQJSNDxqg', - checksum: '81642779ccb110f8c7338e8739ac38a0', - ], - draco: [ - file: 'draco_armv8-libcpp.tgz', - versionId: '3.B.uBj31kWlgND3_R2xwQzT_TP6Dz_8', - checksum: '617a80d213a5ec69fbfa21a1f2f738cd', - ], - glad: [ - file: 'glad_armv8-libcpp.zip', - versionId: 'r5Zran.JSCtvrrB6Q4KaqfIoALPw3lYY', - checksum: 'a8ee8584cf1ccd34766c7ddd9d5e5449', - ], - gvr: [ - file: 'gvrsdk_v1.101.0.tgz', - versionId: 'nqBV_j81Uc31rC7bKIrlya_Hah4v3y5r', - checksum: '57fd02baa069176ba18597a29b6b4fc7', - ], - nvtt: [ - file: 'nvtt_armv8-libcpp.zip', - versionId: 'lmkBVR5t4UF1UUwMwEirnk9H_8Nt90IO', - checksum: 'eb46d0b683e66987190ed124aabf8910', - sharedLibFolder: 'lib', - includeLibs: ['libnvtt.so', 'libnvmath.so', 'libnvimage.so', 'libnvcore.so'], - ], - openssl: [ - file: 'openssl-1.1.0g_armv8.tgz', - versionId: 'AiiPjmgUZTgNj7YV1EEx2lL47aDvvvAW', - checksum: 'cabb681fbccd79594f65fcc266e02f32', - ], - polyvox: [ - file: 'polyvox_armv8-libcpp.tgz', - versionId: 'A2kbKiNhpIenGq23bKRRzg7IMAI5BI92', - checksum: 'dba88b3a098747af4bb169e9eb9af57e', - sharedLibFolder: 'lib', - includeLibs: ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so'], - ], - tbb: [ - file: 'tbb-2018_U1_armv8_libcpp.tgz', - versionId: 'mrRbWnv4O4evcM1quRH43RJqimlRtaKB', - checksum: '20768f298f53b195e71b414b0ae240c4', - sharedLibFolder: 'lib/release', - includeLibs: ['libtbb.so', 'libtbbmalloc.so'], - ], - hifiAC: [ - baseUrl: 'http://s3.amazonaws.com/hifi-public/dependencies/', - file: 'codecSDK-android_armv8-2.0.zip', - checksum: '1cbef929675818fc64c4101b72f84a6a' - ], - etc2comp: [ - file: 'etc2comp-patched-armv8-libcpp.tgz', - versionId: 'bHhGECRAQR1vkpshBcK6ByNc1BQIM8gU', - checksum: '14b02795d774457a33bbc60e00a786bc' - ], - breakpad: [ - file: 'breakpad.tgz', - versionId: '8VrYXz7oyc.QBxNia0BVJOUBvrFO61jI', - checksum: 'ddcb23df336b08017042ba4786db1d9e', - sharedLibFolder: 'lib', - includeLibs: ['libbreakpad_client.a'] - ] -] - -def options = [ - files: new TreeSet(), - features: new HashSet(), - permissions: new HashSet() -] - -def qmlRoot = new File(HIFI_ANDROID_PRECOMPILED, 'qt') - -def captureOutput = { String command, List commandArgs -> - def result - new ByteArrayOutputStream().withStream { os -> - def execResult = exec { - executable = command - args = commandArgs - standardOutput = os - errorOutput = new ByteArrayOutputStream() - } - result = os.toString() - } - return result; -} - -def relativize = { File root, File absolute -> - def relativeURI = root.toURI().relativize(absolute.toURI()) - return new File(relativeURI.toString()) -} - -def scanQmlImports = { File qmlRootPath -> - def qmlImportCommandFile = new File(qmlRoot, 'bin/qmlimportscanner' + EXEC_SUFFIX) - if (!qmlImportCommandFile.exists()) { - throw new GradleException('Unable to find required qmlimportscanner executable at ' + qmlImportCommandFile.parent.toString()) - } - - def command = qmlImportCommandFile.absolutePath - def args = [ - '-rootPath', qmlRootPath.absolutePath, - '-importPath', "${qmlRoot.absolutePath}/qml" - ] - - def commandResult = captureOutput(command, args) - new JsonSlurper().parseText(commandResult).each { - if (!it.containsKey('path')) { - println "Warning: QML import could not be resolved in any of the import paths: ${it.name}" - return - } - def file = new File(it.path) - // Ignore non-existent files - if (!file.exists()) { - return - } - // Ignore files in the import path - if (file.canonicalPath.startsWith(qmlRootPath.canonicalPath)) { - return - } - if (file.isFile()) { - options.files.add(file) - } else { - file.eachFileRecurse(FileType.FILES, { - options.files.add(it) - }) - } - } -} - -def parseQtDependencies = { List qtLibs -> - qtLibs.each({ - def libFile = new File(qmlRoot, "lib/lib${it}.so") - options.files.add(libFile) - - def androidDeps = new File(qmlRoot, "lib/${it}-android-dependencies.xml") - if (!libFile.exists()) return - if (!androidDeps.exists()) return - - new XmlSlurper().parse(androidDeps).dependencies.lib.depends.'*'.each{ node -> - switch (node.name()) { - case 'lib': - case 'bundled': - def relativeFilename = node.@file.toString() - - // Special case, since this is handled by qmlimportscanner instead - if (relativeFilename.startsWith('qml')) - return - - def file = new File(qmlRoot, relativeFilename) - - if (!file.exists()) - return - - if (file.isFile()) { - options.files.add(file) - } else { - file.eachFileRecurse(FileType.FILES, { options.files.add(it) }) - } - break - - - case 'jar': - if (node.@bundling == "1") { - def jar = new File(qmlRoot, node.@file.toString()) - if (!jar.exists()) { - throw new GradleException('Unable to find required JAR ' + jar.path) - } - options.files.add(jar) - } - break - - case 'permission': - options.permissions.add(node.@name) - break - - case 'feature': - options.features.add(node.@name) - break - - default: - throw new GradleException('Unhandled Android Dependency node ' + node.name()) - } - } - }) -} - -def generateLibsXml = { - def libDestinationDirectory = jniFolder - def jarDestinationDirectory = new File(appDir, 'libs') - def assetDestinationDirectory = new File(appDir, 'src/main/assets/--Added-by-androiddeployqt--'); - def libsXmlFile = new File(appDir, 'src/main/res/values/libs.xml') - def libPrefix = 'lib' + File.separator - def jarPrefix = 'jar' + File.separator - - def xmlParser = new XmlParser() - def libsXmlRoot = xmlParser.parseText('') - def qtLibsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'qt_libs']) - def bundledLibsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'bundled_in_lib']) - def bundledAssetsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'bundled_in_assets']) - - options.files.each { - def sourceFile = it - if (!sourceFile.exists()) { - throw new GradleException("Unable to find dependency file " + sourceFile.toString()) - } - - def relativePath = relativize( qmlRoot, sourceFile ).toString() - def destinationFile - if (relativePath.endsWith('.so')) { - def garbledFileName - if (relativePath.startsWith(libPrefix)) { - garbledFileName = relativePath.substring(libPrefix.size()) - Pattern p = ~/lib(Qt5.*).so/ - Matcher m = p.matcher(garbledFileName) - assert m.matches() - def libName = m.group(1) - xmlParser.createNode(qtLibsNode, 'item', [:]).setValue(libName) - } else { - garbledFileName = 'lib' + relativePath.replace(File.separator, '_'[0]) - xmlParser.createNode(bundledLibsNode, 'item', [:]).setValue("${garbledFileName}:${relativePath}".replace(File.separator, '/')) - } - destinationFile = new File(libDestinationDirectory, garbledFileName) - } else if (relativePath.startsWith('jar')) { - destinationFile = new File(jarDestinationDirectory, relativePath.substring(jarPrefix.size())) - } else { - xmlParser.createNode(bundledAssetsNode, 'item', [:]).setValue("--Added-by-androiddeployqt--/${relativePath}:${relativePath}".replace(File.separator, '/')) - destinationFile = new File(assetDestinationDirectory, relativePath) - } - - copy { from sourceFile; into destinationFile.parent; rename(sourceFile.name, destinationFile.name) } - assert destinationFile.exists() && destinationFile.isFile() - } - def xml = XmlUtil.serialize(libsXmlRoot) - new FileWriter(libsXmlFile).withPrintWriter { writer -> - writer.write(xml) - } -} - -task downloadDependencies { - doLast { - packages.each { entry -> - def filename = entry.value['file']; - def dependencyBaseUrl = entry.value['baseUrl'] - def url = (dependencyBaseUrl?.trim() ? dependencyBaseUrl : baseUrl) + filename; - if (entry.value.containsKey('versionId')) { - url = url + '?versionId=' + entry.value['versionId'] - } - download { - src url - dest new File(baseFolder, filename) - onlyIfNewer true - } - } - } -} - -task verifyQt(type: Verify) { def p = packages['qt']; src new File(baseFolder, p['file']); checksum p['checksum']; } -task verifyBullet(type: Verify) { def p = packages['bullet']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyDraco(type: Verify) { def p = packages['draco']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyGvr(type: Verify) { def p = packages['gvr']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyOpenSSL(type: Verify) { def p = packages['openssl']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyPolyvox(type: Verify) { def p = packages['polyvox']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyTBB(type: Verify) { def p = packages['tbb']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyHifiAC(type: Verify) { def p = packages['hifiAC']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyEtc2Comp(type: Verify) { def p = packages['etc2comp']; src new File(baseFolder, p['file']); checksum p['checksum'] } -task verifyBreakpad(type: Verify) { def p = packages['breakpad']; src new File(baseFolder, p['file']); checksum p['checksum'] } - -task verifyDependencyDownloads(dependsOn: downloadDependencies) { } -verifyDependencyDownloads.dependsOn verifyQt -verifyDependencyDownloads.dependsOn verifyBullet -verifyDependencyDownloads.dependsOn verifyDraco -verifyDependencyDownloads.dependsOn verifyGvr -verifyDependencyDownloads.dependsOn verifyOpenSSL -verifyDependencyDownloads.dependsOn verifyPolyvox -verifyDependencyDownloads.dependsOn verifyTBB -verifyDependencyDownloads.dependsOn verifyHifiAC -verifyDependencyDownloads.dependsOn verifyEtc2Comp -verifyDependencyDownloads.dependsOn verifyBreakpad - -task extractDependencies(dependsOn: verifyDependencyDownloads) { - doLast { - packages.each { entry -> - def folder = entry.key - def filename = entry.value['file'] - def localFile = new File(HIFI_ANDROID_PRECOMPILED, filename) - def localFolder = new File(HIFI_ANDROID_PRECOMPILED, folder) - def fileTree; - if (filename.endsWith('zip')) { - fileTree = zipTree(localFile) - } else { - fileTree = tarTree(resources.gzip(localFile)) - } - copy { - from fileTree - into localFolder - } - } - } -} - -// Copies the non Qt dependencies. Qt dependencies (primary libraries and plugins) are handled by the qtBundle task -task copyDependencies() { - doLast { - packages.each { entry -> - def packageName = entry.key - def currentPackage = entry.value; - if (currentPackage.containsKey('sharedLibFolder')) { - def localFolder = new File(baseFolder, packageName + '/' + currentPackage['sharedLibFolder']) - def tree = fileTree(localFolder); - if (currentPackage.containsKey('includeLibs')) { - currentPackage['includeLibs'].each { includeSpec -> tree.include includeSpec } - } - tree.visit { element -> - if (!element.file.isDirectory()) { - println "Copying " + element.file + " to " + jniFolder - copy { from element.file; into jniFolder } - } - } - } - } - } -} - task extractGvrBinaries() { doLast { def gvrLibFolder = new File(HIFI_ANDROID_PRECOMPILED, 'gvr/gvr-android-sdk-1.101.0/libraries'); @@ -500,13 +135,11 @@ task qtBundle { } } -task setupDependencies(dependsOn: [copyDependencies, extractGvrBinaries, qtBundle]) { } +task setupDependencies() { + // migrated to python +} task cleanDependencies(type: Delete) { - delete HIFI_ANDROID_PRECOMPILED - delete 'app/src/main/jniLibs/arm64-v8a' - delete 'app/src/main/assets/--Added-by-androiddeployqt--' - delete 'app/src/main/res/values/libs.xml' } def runBreakpadDumpSyms = { buildType -> diff --git a/android/build_android.sh b/android/build_android.sh index 189e6099a8..9c68b8969b 100755 --- a/android/build_android.sh +++ b/android/build_android.sh @@ -1,4 +1,11 @@ #!/usr/bin/env bash set -xeuo pipefail -./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} setupDependencies -./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} app:${ANDROID_BUILD_TARGET} \ No newline at end of file +./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} ${ANDROID_APP}:${ANDROID_BUILD_TARGET} + +# This is the actual output from gradle, which no longer attempts to muck with the naming of the APK +OUTPUT_APK=./apps/${ANDROID_APP}/build/outputs/apk/${ANDROID_BUILD_DIR}/${ANDROID_APP}-${ANDROID_BUILD_DIR}.apk +# This is the APK name requested by Jenkins +TARGET_APK=./${ANDROID_APK_NAME} +# Make sure this matches up with the new ARTIFACT_EXPRESSION for jenkins builds, which should be "android/*.apk" +cp ${OUTPUT_APK} ${TARGET_APK} + diff --git a/android/containerized_build.sh b/android/containerized_build.sh index e5ec895146..42118a8e38 100755 --- a/android/containerized_build.sh +++ b/android/containerized_build.sh @@ -5,12 +5,21 @@ DOCKER_IMAGE_NAME="hifi_androidbuild" docker build --build-arg BUILD_UID=`id -u` -t "${DOCKER_IMAGE_NAME}" -f docker/Dockerfile docker +# The Jenkins PR builds use VERSION_CODE, but the release builds use VERSION +# So make sure we use VERSION_CODE consistently +if [-z "$VERSION_CODE"]; then + export VERSION_CODE=$VERSION +fi + docker run \ --rm \ - --security-opt seccomp:unconfined \ + --security-opt seccomp:unconfined \ -v "${WORKSPACE}":/home/jenkins/hifi \ + -v /home/jenkins/.gradle:/home/jenkins/.gradle \ -e RELEASE_NUMBER \ -e RELEASE_TYPE \ + -e ANDROID_APP \ + -e ANDROID_APK_NAME \ -e ANDROID_BUILD_TARGET \ -e ANDROID_BUILD_DIR \ -e CMAKE_BACKTRACE_URL \ diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 2a6943cbc2..c37f73cb2a 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -26,10 +26,9 @@ RUN mkdir -p "$ANDROID_HOME" "$ANDROID_SDK_HOME" && \ curl -s -S -o sdk.zip -L "${SDK_URL}" && \ unzip sdk.zip && \ rm sdk.zip && \ - yes | $ANDROID_HOME/tools/bin/sdkmanager --licenses + yes | $ANDROID_HOME/tools/bin/sdkmanager --licenses && yes | $ANDROID_HOME/tools/bin/sdkmanager --update # Install Android Build Tool and Libraries -RUN $ANDROID_HOME/tools/bin/sdkmanager --update RUN $ANDROID_HOME/tools/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" \ "platforms;android-${ANDROID_VERSION}" \ "platform-tools" @@ -52,11 +51,14 @@ ENV PATH ${PATH}:${ANDROID_NDK_HOME} RUN apt-get -y install \ g++ \ gcc \ + sudo \ + emacs-nox \ - # --- Gradle ARG BUILD_UID=1001 RUN useradd -ms /bin/bash -u $BUILD_UID jenkins +RUN echo "jenkins ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers USER jenkins WORKDIR /home/jenkins @@ -71,22 +73,21 @@ RUN mkdir "$HIFI_BASE" && \ RUN git clone https://github.com/jherico/hifi.git && \ cd ~/hifi && \ - git checkout feature/build/gradle-wrapper - + git checkout feature/quest_move_interface WORKDIR /home/jenkins/hifi -RUN touch .test4 && \ - git fetch && git reset origin/feature/build/gradle-wrapper --hard +RUN touch .test6 && \ + git fetch && git reset origin/feature/quest_move_interface --hard RUN mkdir build # Pre-cache the vcpkg managed dependencies WORKDIR /home/jenkins/hifi/build -RUN python3 ../prebuild.py --build-root `pwd` --android +RUN python3 ../prebuild.py --build-root `pwd` --android interface # Pre-cache the gradle dependencies WORKDIR /home/jenkins/hifi/android RUN ./gradlew -m tasks -PHIFI_ANDROID_PRECOMPILED=$HIFI_ANDROID_PRECOMPILED -RUN ./gradlew extractDependencies -PHIFI_ANDROID_PRECOMPILED=$HIFI_ANDROID_PRECOMPILED +#RUN ./gradlew extractDependencies -PHIFI_ANDROID_PRECOMPILED=$HIFI_ANDROID_PRECOMPILED diff --git a/android/docker/update.txt b/android/docker/update.txt new file mode 100644 index 0000000000..a12c215a06 --- /dev/null +++ b/android/docker/update.txt @@ -0,0 +1,13 @@ +git fetch +git checkout feature/quest_move_interface +export VERSION_CODE=1 +export RELEASE_NUMBER=1 +export RELEASE_TYPE=DEV +export ANDROID_APP=interface +touch ~/.gradle/gradle.properties +echo HIFI_ANDROID_KEYSTORE=/home/jenkins/keystore.jks > ~/.gradle/gradle.properties +echo HIFI_ANDROID_KEYSTORE_PASSWORD=password >> ~/.gradle/gradle.properties +echo HIFI_ANDROID_KEY_ALIAS=key0 >> ~/.gradle/gradle.properties +echo HIFI_ANDROID_KEY_PASSWORD=password >> ~/.gradle/gradle.properties +./build_android.sh +cp ./apps/${ANDROID_APP}/build/outputs/apk/release/${ANDROID_APP}-release.apk ${ANDROID_APP}.apk \ No newline at end of file diff --git a/android/libraries/qt/build.gradle b/android/libraries/qt/build.gradle new file mode 100644 index 0000000000..e6141f4cdf --- /dev/null +++ b/android/libraries/qt/build.gradle @@ -0,0 +1,22 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 28 + + defaultConfig { + minSdkVersion 24 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + api 'com.google.guava:guava:23.0' +} diff --git a/android/libraries/qt/src/main/AndroidManifest.xml b/android/libraries/qt/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..c6638c09e8 --- /dev/null +++ b/android/libraries/qt/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/android/libraries/qt/src/main/java/io/highfidelity/utils/HifiUtils.java b/android/libraries/qt/src/main/java/io/highfidelity/utils/HifiUtils.java new file mode 100644 index 0000000000..e8e9f04d9f --- /dev/null +++ b/android/libraries/qt/src/main/java/io/highfidelity/utils/HifiUtils.java @@ -0,0 +1,69 @@ + +package io.highfidelity.utils; + +import android.content.res.AssetManager; + +import com.google.common.io.ByteStreams; +import com.google.common.io.Files; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.LinkedList; + +public class HifiUtils { + + private static LinkedList readAssetLines(AssetManager assetManager, String asset) throws IOException { + LinkedList assets = new LinkedList<>(); + InputStream is = assetManager.open(asset); + BufferedReader in = new BufferedReader(new InputStreamReader(is, "UTF-8")); + String line; + while ((line=in.readLine()) != null) { + assets.add(line); + } + in.close(); + return assets; + } + + private static void copyAsset(AssetManager assetManager, String asset, String destFileName) throws IOException { + try (InputStream is = assetManager.open(asset)) { + try (OutputStream os = Files.asByteSink(new File(destFileName)).openStream()) { + ByteStreams.copy(is, os); + } + } + } + + public static void upackAssets(AssetManager assetManager, String destDir) { + try { + if (!destDir.endsWith("/")) + destDir = destDir + "/"; + LinkedList assets = readAssetLines(assetManager, "cache_assets.txt"); + String dateStamp = assets.poll(); + String dateStampFilename = destDir + dateStamp; + File dateStampFile = new File(dateStampFilename); + if (dateStampFile.exists()) { + return; + } + for (String fileToCopy : assets) { + String destFileName = destDir + fileToCopy; + { + File destFile = new File(destFileName); + File destFolder = destFile.getParentFile(); + if (!destFolder.exists()) { + destFolder.mkdirs(); + } + if (destFile.exists()) { + destFile.delete(); + } + } + copyAsset(assetManager, fileToCopy, destFileName); + } + Files.write("touch".getBytes(), dateStampFile); + } catch (IOException e){ + throw new RuntimeException(e); + } + } +} diff --git a/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java b/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java similarity index 100% rename from android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java rename to android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivity.java diff --git a/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivityLoader.java b/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivityLoader.java similarity index 100% rename from android/app/src/main/java/org/qtproject/qt5/android/bindings/QtActivityLoader.java rename to android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtActivityLoader.java diff --git a/android/app/src/main/java/org/qtproject/qt5/android/bindings/QtApplication.java b/android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtApplication.java similarity index 100% rename from android/app/src/main/java/org/qtproject/qt5/android/bindings/QtApplication.java rename to android/libraries/qt/src/main/java/org/qtproject/qt5/android/bindings/QtApplication.java diff --git a/android/settings.gradle b/android/settings.gradle index e7b4def49c..40b5eb44bf 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1 +1,5 @@ -include ':app' +include ':qt' +project(':qt').projectDir = new File(settingsDir, 'libraries/qt') + +include ':interface' +project(':interface').projectDir = new File(settingsDir, 'apps/interface') diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index cc2973f61d..5c644cb132 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -196,7 +196,8 @@ void Agent::run() { connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &Agent::nodeKilled); nodeList->addSetOfNodeTypesToNodeInterestSet({ - NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::EntityServer, NodeType::MessagesMixer, NodeType::AssetServer + NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::EntityServer, + NodeType::MessagesMixer, NodeType::AssetServer, NodeType::EntityScriptServer }); } @@ -505,16 +506,6 @@ void Agent::executeScript() { DependencyManager::set(_entityViewer.getTree()); - // Agents should run at 45hz - static const int AVATAR_DATA_HZ = 45; - static const int AVATAR_DATA_IN_MSECS = MSECS_PER_SECOND / AVATAR_DATA_HZ; - QTimer* avatarDataTimer = new QTimer(this); - connect(avatarDataTimer, &QTimer::timeout, this, &Agent::processAgentAvatar); - avatarDataTimer->setSingleShot(false); - avatarDataTimer->setInterval(AVATAR_DATA_IN_MSECS); - avatarDataTimer->setTimerType(Qt::PreciseTimer); - avatarDataTimer->start(); - _scriptEngine->run(); Frame::clearFrameHandler(AUDIO_FRAME_TYPE); @@ -528,8 +519,6 @@ void Agent::executeScript() { recordingInterface->stopRecording(); } - avatarDataTimer->stop(); - setIsAvatar(false); // will stop timers for sending identity packets } @@ -584,20 +573,16 @@ void Agent::setIsAvatar(bool isAvatar) { auto scriptableAvatar = DependencyManager::get(); if (_isAvatar) { - if (!_avatarIdentityTimer) { + if (!_avatarQueryTimer) { // set up the avatar timers - _avatarIdentityTimer = new QTimer(this); _avatarQueryTimer = new QTimer(this); // connect our slot - connect(_avatarIdentityTimer, &QTimer::timeout, this, &Agent::sendAvatarIdentityPacket); connect(_avatarQueryTimer, &QTimer::timeout, this, &Agent::queryAvatars); - static const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000; static const int AVATAR_VIEW_PACKET_SEND_INTERVAL_MSECS = 1000; - // start the timers - _avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); // FIXME - we shouldn't really need to constantly send identity packets + // start the timer _avatarQueryTimer->start(AVATAR_VIEW_PACKET_SEND_INTERVAL_MSECS); connect(_scriptEngine.data(), &ScriptEngine::update, @@ -609,11 +594,7 @@ void Agent::setIsAvatar(bool isAvatar) { _entityEditSender.setMyAvatar(scriptableAvatar.data()); } else { - if (_avatarIdentityTimer) { - _avatarIdentityTimer->stop(); - delete _avatarIdentityTimer; - _avatarIdentityTimer = nullptr; - + if (_avatarQueryTimer) { _avatarQueryTimer->stop(); delete _avatarQueryTimer; _avatarQueryTimer = nullptr; @@ -646,14 +627,6 @@ void Agent::setIsAvatar(bool isAvatar) { } } -void Agent::sendAvatarIdentityPacket() { - if (_isAvatar) { - auto scriptedAvatar = DependencyManager::get(); - scriptedAvatar->markIdentityDataChanged(); - scriptedAvatar->sendIdentityPacket(); - } -} - void Agent::queryAvatars() { auto scriptedAvatar = DependencyManager::get(); @@ -681,44 +654,6 @@ void Agent::queryAvatars() { { NodeType::AvatarMixer }); } -void Agent::processAgentAvatar() { - if (!_scriptEngine->isFinished() && _isAvatar) { - auto scriptedAvatar = DependencyManager::get(); - - AvatarData::AvatarDataDetail dataDetail = (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO) ? AvatarData::SendAllData : AvatarData::CullSmallData; - QByteArray avatarByteArray = scriptedAvatar->toByteArrayStateful(dataDetail); - - int maximumByteArraySize = NLPacket::maxPayloadSize(PacketType::AvatarData) - sizeof(AvatarDataSequenceNumber); - - if (avatarByteArray.size() > maximumByteArraySize) { - qWarning() << " scriptedAvatar->toByteArrayStateful() resulted in very large buffer:" << avatarByteArray.size() << "... attempt to drop facial data"; - avatarByteArray = scriptedAvatar->toByteArrayStateful(dataDetail, true); - - if (avatarByteArray.size() > maximumByteArraySize) { - qWarning() << " scriptedAvatar->toByteArrayStateful() without facial data resulted in very large buffer:" << avatarByteArray.size() << "... reduce to MinimumData"; - avatarByteArray = scriptedAvatar->toByteArrayStateful(AvatarData::MinimumData, true); - - if (avatarByteArray.size() > maximumByteArraySize) { - qWarning() << " scriptedAvatar->toByteArrayStateful() MinimumData resulted in very large buffer:" << avatarByteArray.size() << "... FAIL!!"; - return; - } - } - } - - scriptedAvatar->doneEncoding(true); - - static AvatarDataSequenceNumber sequenceNumber = 0; - auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size() + sizeof(sequenceNumber)); - avatarPacket->writePrimitive(sequenceNumber++); - - avatarPacket->write(avatarByteArray); - - auto nodeList = DependencyManager::get(); - - nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); - } -} - void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) { _flushEncoder = false; static const QByteArray zeros(AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL, 0); diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 244f72e624..b8e7652cca 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -81,7 +81,6 @@ private slots: void nodeActivated(SharedNodePointer activatedNode); void nodeKilled(SharedNodePointer killedNode); - void processAgentAvatar(); void processAgentAvatarAudio(); private: @@ -99,7 +98,6 @@ private: void setAvatarSound(SharedSoundPointer avatarSound) { _avatarSound = avatarSound; } - void sendAvatarIdentityPacket(); void queryAvatars(); QString _scriptContents; @@ -110,7 +108,6 @@ private: bool _shouldMuteRecordingAudio { false }; int _numAvatarSoundSentBytes = 0; bool _isAvatar = false; - QTimer* _avatarIdentityTimer = nullptr; QTimer* _avatarQueryTimer = nullptr; QHash _outgoingScriptAudioSequenceNumbers; diff --git a/assignment-client/src/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 mes _handleAvatarIdentityPacketElapsedTime += (end - start); } -void AvatarMixer::handleAvatarIdentityRequestPacket(QSharedPointer message, SharedNodePointer senderNode) { - if (message->getSize() < NUM_BYTES_RFC4122_UUID) { - qCDebug(avatars) << "Malformed AvatarIdentityRequest received from" << message->getSenderSockAddr().toString(); - return; - } - - QUuid avatarID(QUuid::fromRfc4122(message->getMessage()) ); - if (!avatarID.isNull()) { - auto nodeList = DependencyManager::get(); - auto requestedNode = nodeList->nodeWithUUID(avatarID); - - if (requestedNode) { - AvatarMixerClientData* avatarClientData = static_cast(requestedNode->getLinkedData()); - if (avatarClientData) { - const AvatarData& avatarData = avatarClientData->getAvatar(); - QByteArray serializedAvatar = avatarData.identityByteArray(); - auto identityPackets = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); - identityPackets->write(serializedAvatar); - nodeList->sendPacketList(std::move(identityPackets), *senderNode); - ++_sumIdentityPackets; - } - - AvatarMixerClientData* senderData = static_cast(senderNode->getLinkedData()); - if (senderData) { - senderData->resetSentTraitData(requestedNode->getLocalID()); - } - } - } -} - void AvatarMixer::handleKillAvatarPacket(QSharedPointer message, SharedNodePointer node) { auto start = usecTimestampNow(); handleAvatarKilled(node); diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 8ae7fc9931..764656a2d5 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -54,7 +54,6 @@ private slots: void handleRequestsDomainListDataPacket(QSharedPointer message, SharedNodePointer senderNode); void handleReplicatedPacket(QSharedPointer message); void handleReplicatedBulkAvatarPacket(QSharedPointer message); - void handleAvatarIdentityRequestPacket(QSharedPointer message, SharedNodePointer senderNode); void domainSettingsRequestComplete(); void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); void start(); diff --git a/assignment-client/src/avatars/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> 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/CompleteProfileBody.qml b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml index 6859b7ab3d..144b91063f 100644 --- a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml +++ b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml @@ -29,6 +29,8 @@ Item { readonly property bool withSteam: withSteam property string errorString: errorString + readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() + QtObject { id: d readonly property int minWidth: 480 @@ -123,6 +125,13 @@ Item { fontSize: completeProfileBody.fontSize fontBold: completeProfileBody.fontBold onClicked: { + if (completeProfileBody.loginDialogPoppedUp) { + var data = { + "action": "user clicked cancel on the complete profile screen" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader }); } } @@ -142,6 +151,12 @@ Item { fontSize: completeProfileBody.fontSize fontBold: completeProfileBody.fontBold onClicked: { + if (completeProfileBody.loginDialogPoppedUp) { + var data = { + "action": "user clicked create profile" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } loginErrorMessage.visible = false; loginDialog.createAccountFromSteam(); } @@ -253,13 +268,29 @@ Item { onHandleCreateCompleted: { console.log("Create Succeeded") - loginDialog.loginThroughSteam(); - bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": true, "linkSteam": false }); + if (completeProfileBody.withSteam) { + if (completeProfileBody.loginDialogPoppedUp) { + var data = { + "action": "user created a profile with Steam successfully from the complete profile screen" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + loginDialog.loginThroughSteam(); + } + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam, "linkSteam": false }); } onHandleCreateFailed: { console.log("Create Failed: " + error); + if (completeProfileBody.withSteam) { + if (completeProfileBody.loginDialogPoppedUp) { + var data = { + "action": "user failed to create a profile with Steam from the complete profile screen" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + } - bodyLoader.setSource("UsernameCollisionBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader }); + bodyLoader.setSource("UsernameCollisionBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": completeProfileBody.withSteam }); } } diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index 987c5b08e4..5048bf0278 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -42,6 +42,8 @@ Item { property string errorString: errorString property bool lostFocus: false + readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() + QtObject { id: d readonly property int minWidth: 480 @@ -68,6 +70,20 @@ Item { function login() { loginDialog.login(emailField.text, passwordField.text); + if (linkAccountBody.loginDialogPoppedUp) { + var data; + if (linkAccountBody.linkSteam) { + data = { + "action": "user linking hifi account with Steam" + }; + } else { + data = { + "action": "user logging in" + }; + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam }); } @@ -294,6 +310,14 @@ Item { topMargin: hifi.dimensions.contentSpacing.y } onClicked: { + if (linkAccountBody.loginDialogPoppedUp) { + var data = { + "action": "user clicked cancel at link account screen" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + loginDialog.dismissLoginDialog(); + } + bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "errorString": "" }); } } @@ -310,7 +334,7 @@ Item { topMargin: hifi.dimensions.contentSpacing.y } onClicked: { - linkAccountBody.login() + linkAccountBody.login(); } } TextMetrics { @@ -373,7 +397,12 @@ Item { lightboxPopup.visible = false; } lightboxPopup.visible = true; - // bodyLoader.setSource("CantAccessBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader }); + if (linkAccountBody.loginDialogPoppedUp) { + var data = { + "action": "user clicked can't access account" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } } } HifiControlsUit.Button { @@ -402,6 +431,19 @@ Item { linkAccountBody.withSteam = true; loginDialog.loginThroughSteam(); } + if (linkAccountBody.loginDialogPoppedUp) { + var data; + if (linkAccountBody.withOculus) { + data = { + "action": "user clicked login through Oculus" + }; + } else if (linkAccountBody.withSteam) { + data = { + "action": "user clicked login through Steam" + }; + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam }); @@ -470,6 +512,12 @@ Item { linkColor: hifi.colors.blueAccent onLinkActivated: { Tablet.playSound(TabletEnums.ButtonClick); + if (linkAccountBody.loginDialogPoppedUp) { + var data = { + "action": "user clicked sign up button" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } bodyLoader.setSource("SignUpBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": "", "linkSteam": linkAccountBody.linkSteam }); } @@ -495,10 +543,9 @@ Item { fontFamily: linkAccountBody.fontFamily fontSize: linkAccountBody.fontSize fontBold: linkAccountBody.fontBold - visible: loginDialog.getLoginDialogPoppedUp() && !linkAccountBody.linkSteam; + visible: linkAccountBody.loginDialogPoppedUp && !linkAccountBody.linkSteam; onClicked: { - if (loginDialog.getLoginDialogPoppedUp()) { - console.log("[ENCOURAGELOGINDIALOG]: user dismissed login screen") + if (linkAccountBody.loginDialogPoppedUp) { var data = { "action": "user dismissed login screen" }; diff --git a/interface/resources/qml/LoginDialog/LoggingInBody.qml b/interface/resources/qml/LoginDialog/LoggingInBody.qml index 355f89667b..5e4a6c4cb3 100644 --- a/interface/resources/qml/LoginDialog/LoggingInBody.qml +++ b/interface/resources/qml/LoginDialog/LoggingInBody.qml @@ -30,6 +30,8 @@ Item { property bool withOculus: withOculus property bool linkSteam: linkSteam + readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() + QtObject { id: d readonly property int minWidth: 480 @@ -62,8 +64,12 @@ Item { running: false; repeat: false; onTriggered: { - if (loginDialog.getLoginDialogPoppedUp()) { + if (loggingInBody.loginDialogPoppedUp) { loginDialog.dismissLoginDialog(); + var data = { + "action": "user logged in successfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); } root.tryDestroy(); } @@ -107,6 +113,12 @@ Item { loggingInText.x = 0; loggingInText.anchors.centerIn = loggingInHeader; loggedInGlyph.visible = true; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user logged in with Steam successfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } } else if (loggingInBody.withOculus) { // reset the flag. loggingInGlyph.visible = false; @@ -115,8 +127,21 @@ Item { loggingInText.anchors.centerIn = loggingInHeader; loggedInGlyph.text = hifi.glyphs.oculus; loggedInGlyph.visible = true; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user logged in with Oculus successfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } } else { loggingInText.text = "You are now logged in!"; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user logged in successfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } + } successTimer.start(); } @@ -234,11 +259,28 @@ Item { target: loginDialog onHandleLinkCompleted: { console.log("Link Succeeded"); - loggingInBody.linkSteam = false; - loggingInBody.loadingSuccess(); + if (loggingInBody.linkSteam) { + loggingInBody.linkSteam = false; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user linked Steam with their hifi account credentials successfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } + + loggingInBody.loadingSuccess(); + } } onHandleLinkFailed: { console.log("Link Failed: " + error); + if (loggingInBody.linkSteam) { + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user linked Steam unsuccessfully" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } + } bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": true, "errorString": error }); } @@ -253,18 +295,43 @@ Item { var errorString = ""; if (loggingInBody.linkSteam && loggingInBody.withSteam) { errorString = "Username or password is incorrect."; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user failed to link Steam with their hifi account credentials" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, "linkSteam": loggingInBody.linkSteam, "errorString": errorString }); } else if (loggingInBody.withSteam) { loggingInGlyph.visible = false; errorString = "Your Steam authentication has failed. Please make sure you are logged into Steam and try again."; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user failed to authenticate with Steam to log in" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } bodyLoader.setSource("CompleteProfileBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": loggingInBody.withSteam, "errorString": errorString }); } else if (loggingInBody.withOculus) { loggingInGlyph.visible = false; errorString = "Your Oculus authentication has failed. Please make sure you are logged into Oculus and try again." + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user failed to authenticate with Oculus to log in" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": errorString }); } else { errorString = "Username or password is incorrect."; + if (loggingInBody.loginDialogPoppedUp) { + var data = { + "action": "user failed at logging in" + }; + UserActivityLogger.logAction("encourageLoginDialog", data); + } + bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "errorString": errorString }); } } diff --git a/interface/resources/qml/LoginDialog/SignUpBody.qml b/interface/resources/qml/LoginDialog/SignUpBody.qml index 5e0e955330..3ba66391e6 100644 --- a/interface/resources/qml/LoginDialog/SignUpBody.qml +++ b/interface/resources/qml/LoginDialog/SignUpBody.qml @@ -40,6 +40,8 @@ Item { property bool linkSteam: linkSteam property bool lostFocus: false + readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() + QtObject { id: d readonly property int minWidth: 480 @@ -345,6 +347,12 @@ Item { fontSize: signUpBody.fontSize fontBold: signUpBody.fontBold onClicked: { + if (signUpBody.loginDialogPoppedUp) { + var data = { + "action": "user clicked cancel button at sign up screen" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } bodyLoader.setSource("LinkAccountBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "linkSteam": signUpBody.linkSteam }); } } @@ -363,23 +371,18 @@ Item { } onClicked: { + if (signUpBody.loginDialogPoppedUp) { + var data = { + "action": "user clicked sign up button" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } signUpBody.signup(); } } } } - MouseArea { - z: -2 - anchors.fill: parent - acceptedButtons: Qt.LeftButton - onClicked: { - if (!usernameField.focus && !emailField.focus && !passwordField.focus) { - usernameField.focus = true; - } - } - } - Component.onCompleted: { //but rise Tablet's one instead for Tablet interface root.keyboardEnabled = HMD.active; @@ -408,12 +411,26 @@ Item { onHandleSignupCompleted: { console.log("Sign Up Completed"); + if (signUpBody.loginDialogPoppedUp) { + var data = { + "action": "user signed up successfully" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + loginDialog.login(usernameField.text, passwordField.text); bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": false, "linkSteam": false }); } onHandleSignupFailed: { console.log("Sign Up Failed") + if (signUpBody.loginDialogPoppedUp) { + var data = { + "action": "user signed up unsuccessfully" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + if (errorString !== "") { loginErrorMessage.visible = true; var errorStringEdited = errorString.replace(/[\n\r]+/g, "\n"); diff --git a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml index d44f5b733c..af46fc0223 100644 --- a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml +++ b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml @@ -26,6 +26,10 @@ Item { readonly property int textFieldFontSize: 18 readonly property bool fontBold: true + readonly property bool withSteam: withSteam + + readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() + function create() { mainTextContainer.visible = false loginDialog.createAccountFromSteam(textField.text); @@ -197,18 +201,40 @@ Item { target: loginDialog onHandleCreateCompleted: { console.log("Create Succeeded"); - loginDialog.loginThroughSteam(); - bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": true, "linkSteam": false }) + if (usernameCollisionBody.withSteam) { + if (usernameCollisionBody.loginDialogPoppedUp) { + var data = { + "action": "user created a profile with Steam successfully in the username collision screen" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + + loginDialog.loginThroughSteam(); + } + + bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": usernameCollisionBody.withSteam, "linkSteam": false }) } onHandleCreateFailed: { console.log("Create Failed: " + error) + if (usernameCollisionBody.loginDialogPoppedUp) { + var data = { + "action": "user failed to create account from the username collision screen" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + mainTextContainer.visible = true mainTextContainer.text = "\"" + textField.text + qsTr("\" is invalid or already taken."); } onHandleLoginCompleted: { console.log("Login Succeeded"); - if (loginDialog.getLoginDialogPoppedUp()) { + if (usernameCollisionBody.loginDialogPoppedUp) { + var data = { + "action": "user logged in successfully from the username collision screen" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + loginDialog.dismissLoginDialog(); } root.tryDestroy(); @@ -216,6 +242,13 @@ Item { onHandleLoginFailed: { console.log("Login Failed") + if (usernameCollisionBody.loginDialogPoppedUp) { + var data = { + "action": "user failed to log in from the username collision screen" + } + UserActivityLogger.logAction("encourageLoginDialog", data); + } + mainTextContainer.text = "Login Failed"; } diff --git a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml index a537c65b23..136d535b3f 100644 --- a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml +++ b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml @@ -177,8 +177,9 @@ Rectangle { repeat: true onTriggered: { var currentWearable = getCurrentWearable(); - var soft = currentWearable ? currentWearable.relayParentJoints : false; - var softEnabled = currentWearable ? entityHasAvatarJoints(currentWearable.id) : false; + var hasSoft = currentWearable && currentWearable.relayParentJoints !== undefined; + var soft = hasSoft ? currentWearable.relayParentJoints : false; + var softEnabled = hasSoft ? entityHasAvatarJoints(currentWearable.id) : false; isSoft.set(soft); isSoft.enabled = softEnabled; } @@ -511,7 +512,7 @@ Rectangle { function set(value) { notify = false; - checked = value + checked = value; notify = true; } diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index 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/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 1954e373f7..7b79215cd2 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2201,7 +2201,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo || ((rightHandPose.valid || lastRightHandPose.valid) && (rightHandPose != lastRightHandPose)); lastLeftHandPose = leftHandPose; lastRightHandPose = rightHandPose; - properties["avatar_identity_requests_sent"] = DependencyManager::get()->getIdentityRequestsSent(); UserActivityLogger::getInstance().logAction("stats", properties); }); @@ -2867,7 +2866,7 @@ void Application::showLoginScreen() { dialogsManager->showLoginDialog(); emit loginDialogFocusEnabled(); QJsonObject loginData = {}; - loginData["action"] = "login dialog shown"; + loginData["action"] = "login dialog popped up"; UserActivityLogger::getInstance().logAction("encourageLoginDialog", loginData); _window->setWindowTitle("High Fidelity Interface"); } else { @@ -6274,7 +6273,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(); @@ -6757,7 +6756,7 @@ void Application::updateWindowTitle() const { DependencyManager::get< MessagesClient >()->sendLocalMessage("Toolbar-DomainChanged", ""); } -void Application::clearDomainOctreeDetails() { +void Application::clearDomainOctreeDetails(bool clearAll) { // before we delete all entities get MyAvatar's AvatarEntityData ready getMyAvatar()->prepareAvatarEntityDataForReload(); @@ -6776,7 +6775,7 @@ void Application::clearDomainOctreeDetails() { }); // reset the model renderer - getEntities()->clear(); + clearAll ? getEntities()->clear() : getEntities()->clearNonLocalEntities(); auto skyStage = DependencyManager::get()->getSkyStage(); @@ -6814,7 +6813,7 @@ void Application::goToErrorDomainURL(QUrl errorDomainURL) { void Application::resettingDomain() { _notifiedPacketVersionMismatchThisDomain = false; - clearDomainOctreeDetails(); + clearDomainOctreeDetails(false); } void Application::nodeAdded(SharedNodePointer node) const { @@ -6900,7 +6899,7 @@ void Application::nodeKilled(SharedNodePointer node) { QMetaObject::invokeMethod(DependencyManager::get().data(), "audioMixerKilled"); } else if (node->getType() == NodeType::EntityServer) { // we lost an entity server, clear all of the domain octree details - clearDomainOctreeDetails(); + clearDomainOctreeDetails(false); } else if (node->getType() == NodeType::AssetServer) { // asset server going away - check if we have the asset browser showing diff --git a/interface/src/Application.h b/interface/src/Application.h index 4c6d45b8c3..05d6135a93 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -469,7 +469,7 @@ private slots: void onDesktopRootItemCreated(QQuickItem* qmlContext); void onDesktopRootContextCreated(QQmlContext* qmlContext); void showDesktop(); - void clearDomainOctreeDetails(); + void clearDomainOctreeDetails(bool clearAll = true); void onAboutToQuit(); void onPresent(quint32 frameCount); diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index 1d003f19c1..5fe35bd23f 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -55,7 +55,7 @@ void addAvatarEntities(const QVariantList& avatarEntities) { QVariantMap asMap = variantProperties.toMap(); QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine); EntityItemProperties entityProperties; - EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, entityProperties); + EntityItemPropertiesFromScriptValueIgnoreReadOnly(scriptProperties, entityProperties); entityProperties.setParentID(myNodeID); entityProperties.setEntityHostType(entity::HostType::AVATAR); @@ -151,8 +151,29 @@ void AvatarBookmarks::removeBookmark(const QString& bookmarkName) { void AvatarBookmarks::updateAvatarEntities(const QVariantList &avatarEntities) { auto myAvatar = DependencyManager::get()->getMyAvatar(); - myAvatar->removeWearableAvatarEntities(); - addAvatarEntities(avatarEntities); + auto currentAvatarEntities = myAvatar->getAvatarEntityData(); + std::set newAvatarEntities; + + // Update or add all the new avatar entities + for (auto& avatarEntityVariant : avatarEntities) { + auto avatarEntityVariantMap = avatarEntityVariant.toMap(); + auto idItr = avatarEntityVariantMap.find("id"); + if (idItr != avatarEntityVariantMap.end()) { + auto propertiesItr = avatarEntityVariantMap.find("properties"); + if (propertiesItr != avatarEntityVariantMap.end()) { + EntityItemID id = idItr.value().toUuid(); + newAvatarEntities.insert(id); + myAvatar->updateAvatarEntity(id, QJsonDocument::fromVariant(propertiesItr.value()).toBinaryData()); + } + } + } + + // Remove any old entities not in the new list + for (auto& avatarEntityID : currentAvatarEntities.keys()) { + if (newAvatarEntities.find(avatarEntityID) == newAvatarEntities.end()) { + myAvatar->removeWornAvatarEntity(avatarEntityID); + } + } } void AvatarBookmarks::loadBookmark(const QString& bookmarkName) { @@ -176,7 +197,7 @@ void AvatarBookmarks::loadBookmark(const QString& bookmarkName) { auto myAvatar = DependencyManager::get()->getMyAvatar(); auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; - myAvatar->removeWearableAvatarEntities(); + myAvatar->clearWornAvatarEntities(); const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString(); myAvatar->useFullAvatarURL(avatarUrl); qCDebug(interfaceapp) << "Avatar On"; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 53c16c8a61..b9c7dc729d 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -48,8 +48,6 @@ // 50 times per second - target is 45hz, but this helps account for any small deviations // in the update loop - this also results in ~30hz when in desktop mode which is essentially // what we want -const int CLIENT_TO_AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 50; -static const quint64 MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS = USECS_PER_SECOND / CLIENT_TO_AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND; // We add _myAvatar into the hash with all the other AvatarData, and we use the default NULL QUid as the key. const QUuid MY_AVATAR_KEY; // NULL key @@ -67,6 +65,11 @@ AvatarManager::AvatarManager(QObject* parent) : connect(nodeList.data(), &NodeList::ignoredNode, this, [this](const QUuid& nodeID, bool enabled) { if (enabled) { removeAvatar(nodeID, KillAvatarReason::AvatarIgnored); + } else { + auto avatar = std::static_pointer_cast(getAvatarBySessionID(nodeID)); + if (avatar) { + avatar->createOrb(); + } } }); @@ -347,25 +350,6 @@ void AvatarManager::postUpdate(float deltaTime, const render::ScenePointer& scen } } -void AvatarManager::sendIdentityRequest(const QUuid& avatarID) const { - auto nodeList = DependencyManager::get(); - QWeakPointer nodeListWeak = nodeList; - nodeList->eachMatchingNode( - [](const SharedNodePointer& node)->bool { - return node->getType() == NodeType::AvatarMixer && node->getActiveSocket(); - }, - [this, avatarID, nodeListWeak](const SharedNodePointer& node) { - auto nodeList = nodeListWeak.lock(); - if (nodeList) { - auto packet = NLPacket::create(PacketType::AvatarIdentityRequest, NUM_BYTES_RFC4122_UUID, true); - packet->write(avatarID.toRfc4122()); - nodeList->sendPacket(std::move(packet), *node); - ++_identityRequestsSent; - } - } - ); -} - void AvatarManager::simulateAvatarFades(float deltaTime) { if (_avatarsToFadeOut.empty()) { return; @@ -391,8 +375,15 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { scene->enqueueTransaction(transaction); } -AvatarSharedPointer AvatarManager::newSharedAvatar() { - return AvatarSharedPointer(new OtherAvatar(qApp->thread()), [](OtherAvatar* ptr) { ptr->deleteLater(); }); +AvatarSharedPointer AvatarManager::newSharedAvatar(const QUuid& sessionUUID) { + + auto otherAvatar = new OtherAvatar(qApp->thread()); + otherAvatar->setSessionUUID(sessionUUID); + auto nodeList = DependencyManager::get(); + if (!nodeList || !nodeList->isIgnoringNode(sessionUUID)) { + otherAvatar->createOrb(); + } + return AvatarSharedPointer(otherAvatar, [](OtherAvatar* ptr) { ptr->deleteLater(); }); } void AvatarManager::queuePhysicsChange(const OtherAvatarPointer& avatar) { diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 359af8e361..7bd4e8236a 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -92,7 +92,6 @@ public: void updateMyAvatar(float deltaTime); void updateOtherAvatars(float deltaTime); - void sendIdentityRequest(const QUuid& avatarID) const; void setMyAvatarDataPacketsPaused(bool puase); @@ -191,7 +190,6 @@ public: Q_INVOKABLE QVariantMap getPalData(const QStringList& specificAvatarIdentifiers = QStringList()); float getMyAvatarSendRate() const { return _myAvatarSendRate.rate(); } - int getIdentityRequestsSent() const { return _identityRequestsSent; } void queuePhysicsChange(const OtherAvatarPointer& avatar); void buildPhysicsTransaction(PhysicsEngine::Transaction& transaction); @@ -216,7 +214,7 @@ private: void simulateAvatarFades(float deltaTime); - AvatarSharedPointer newSharedAvatar() override; + AvatarSharedPointer newSharedAvatar(const QUuid& sessionUUID) override; // called only from the AvatarHashMap thread - cannot be called while this thread holds the // hash lock, since handleRemovedAvatar needs a write lock on the entity tree and the entity tree @@ -241,7 +239,6 @@ private: float _avatarSimulationTime { 0.0f }; bool _shouldRender { true }; bool _myAvatarDataPacketsPaused { false }; - mutable int _identityRequestsSent { 0 }; mutable std::mutex _spaceLock; workload::SpacePointer _space; diff --git a/interface/src/avatar/GrabManager.cpp b/interface/src/avatar/GrabManager.cpp index c41435d67e..db1337b64d 100644 --- a/interface/src/avatar/GrabManager.cpp +++ b/interface/src/avatar/GrabManager.cpp @@ -18,6 +18,8 @@ void GrabManager::simulateGrabs() { // Update grabbed objects auto entityTreeRenderer = DependencyManager::get(); auto entityTree = entityTreeRenderer->getTree(); + auto sessionID = DependencyManager::get()->getSessionUUID(); + EntityEditPacketSender* packetSender = entityTreeRenderer ? entityTreeRenderer->getPacketSender() : nullptr; entityTree->withReadLock([&] { PROFILE_RANGE(simulation, "Grabs"); @@ -33,6 +35,8 @@ void GrabManager::simulateGrabs() { glm::vec3 finalPosition = acc.finalizePosition(); glm::quat finalOrientation = acc.finalizeOrientation(); grabbedThing->setTransform(createMatFromQuatAndPos(finalOrientation, finalPosition)); + bool iShouldTellServer = grabbedThing->getEditSenderID() == sessionID; + entityTree->updateEntityQueryAACube(grabbedThing, packetSender, false, iShouldTellServer); } } }); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 4e5c143919..e4da94c699 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -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; @@ -1610,10 +1604,12 @@ void MyAvatar::handleChangedAvatarEntityData() { skip = true; } }); - sanitizeAvatarEntityProperties(properties); - entityTree->withWriteLock([&] { - entityTree->updateEntity(id, properties); - }); + if (!skip) { + sanitizeAvatarEntityProperties(properties); + entityTree->withWriteLock([&] { + entityTree->updateEntity(id, properties); + }); + } } // DELETE cached blobs @@ -1810,19 +1806,27 @@ void MyAvatar::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) { void MyAvatar::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) { // NOTE: this is an invokable Script call - // TODO: we should handle the case where entityData is corrupt or invalid - // BEFORE we store into _cachedAvatarEntityBlobs - _needToSaveAvatarEntitySettings = true; + bool changed = false; _avatarEntitiesLock.withWriteLock([&] { - AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.find(entityID); - if (itr != _cachedAvatarEntityBlobs.end()) { - _entitiesToUpdate.push_back(entityID); - itr.value() = entityData; + auto data = QJsonDocument::fromBinaryData(entityData); + if (data.isEmpty() || data.isNull() || !data.isObject() || data.object().isEmpty()) { + qDebug() << "ERROR! Trying to update with invalid avatar entity data. Skipping." << data; } else { - _entitiesToAdd.push_back(entityID); - _cachedAvatarEntityBlobs.insert(entityID, entityData); + auto itr = _cachedAvatarEntityBlobs.find(entityID); + if (itr == _cachedAvatarEntityBlobs.end()) { + _entitiesToAdd.push_back(entityID); + _cachedAvatarEntityBlobs.insert(entityID, entityData); + changed = true; + } else { + _entitiesToUpdate.push_back(entityID); + itr.value() = entityData; + changed = true; + } } }); + if (changed) { + _needToSaveAvatarEntitySettings = true; + } } void MyAvatar::avatarEntityDataToJson(QJsonObject& root) const { @@ -2362,50 +2366,36 @@ bool isWearableEntity(const EntityItemPointer& entity) { || entity->getParentID() == AVATAR_SELF_ID); } -void MyAvatar::clearAvatarEntities() { +void MyAvatar::removeWornAvatarEntity(const EntityItemID& entityID) { auto treeRenderer = DependencyManager::get(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; - QList avatarEntityIDs; - _avatarEntitiesLock.withReadLock([&] { - avatarEntityIDs = _packedAvatarEntityData.keys(); - }); - for (const auto& entityID : avatarEntityIDs) { - entityTree->withWriteLock([&entityID, &entityTree] { - // remove this entity first from the entity tree - entityTree->deleteEntity(entityID, true, true); - }); - - // remove the avatar entity from our internal list - // (but indicate it doesn't need to be pulled from the tree) - clearAvatarEntity(entityID, false); - } -} - -void MyAvatar::removeWearableAvatarEntities() { - auto treeRenderer = DependencyManager::get(); - EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; if (entityTree) { - QList avatarEntityIDs; - _avatarEntitiesLock.withReadLock([&] { - avatarEntityIDs = _packedAvatarEntityData.keys(); - }); - for (const auto& entityID : avatarEntityIDs) { - auto entity = entityTree->findEntityByID(entityID); - if (entity && isWearableEntity(entity)) { - entityTree->withWriteLock([&entityID, &entityTree] { - // remove this entity first from the entity tree - entityTree->deleteEntity(entityID, true, true); - }); + auto entity = entityTree->findEntityByID(entityID); + if (entity && isWearableEntity(entity)) { + entityTree->withWriteLock([&entityID, &entityTree] { + // remove this entity first from the entity tree + entityTree->deleteEntity(entityID, true, true); + }); - // remove the avatar entity from our internal list - // (but indicate it doesn't need to be pulled from the tree) - clearAvatarEntity(entityID, false); - } + // remove the avatar entity from our internal list + // (but indicate it doesn't need to be pulled from the tree) + clearAvatarEntity(entityID, false); } } } +void MyAvatar::clearWornAvatarEntities() { + QList avatarEntityIDs; + _avatarEntitiesLock.withReadLock([&] { + avatarEntityIDs = _packedAvatarEntityData.keys(); + }); + for (auto entityID : avatarEntityIDs) { + removeWornAvatarEntity(entityID); + } +} + + QVariantList MyAvatar::getAvatarEntitiesVariant() { // NOTE: this method is NOT efficient QVariantList avatarEntitiesData; @@ -2428,6 +2418,7 @@ QVariantList MyAvatar::getAvatarEntitiesVariant() { desiredProperties += PROP_LOCAL_ROTATION; EntityItemProperties entityProperties = entity->getProperties(desiredProperties); QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(_myScriptEngine, entityProperties); + avatarEntityData["id"] = entityID; avatarEntityData["properties"] = scriptProperties.toVariant(); avatarEntitiesData.append(QVariant(avatarEntityData)); } @@ -2806,8 +2797,8 @@ void MyAvatar::setAttachmentData(const QVector& attachmentData) newEntitiesProperties.push_back(properties); } - // clear any existing avatar entities - clearAvatarEntities(); + // clear any existing wearables + clearWornAvatarEntities(); for (auto& properties : newEntitiesProperties) { DependencyManager::get()->addEntity(properties, true); @@ -3106,6 +3097,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 { diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index da7f7c47f9..20dc2ae42b 100644 --- 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, @@ -970,8 +973,8 @@ public: * @returns {object[]} */ Q_INVOKABLE QVariantList getAvatarEntitiesVariant(); - void clearAvatarEntities(); - void removeWearableAvatarEntities(); + void removeWornAvatarEntity(const EntityItemID& entityID); + void clearWornAvatarEntities(); /**jsdoc * Check whether your avatar is flying or not. @@ -1213,6 +1216,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: @@ -1937,6 +1941,8 @@ private: bool _skeletonModelLoaded { false }; bool _reloadAvatarEntityDataFromSettings { true }; + TimePoint _nextTraitsSendWindow; + Setting::Handle _dominantHandSetting; Setting::Handle _headPitchSetting; Setting::Handle _scaleSetting; diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 0dfc349e18..1f4fdc5e52 100644 --- 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() { diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index a6185d7e79..9426776983 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1375,7 +1375,7 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { AvatarData::setSkeletonModelURL(skeletonModelURL); if (QThread::currentThread() == thread()) { - if (!isMyAvatar()) { + if (!isMyAvatar() && !DependencyManager::get()->isIgnoringNode(getSessionUUID())) { createOrb(); } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 21e0a6aba2..bbd461907f 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -2157,7 +2157,7 @@ void AvatarData::detachAll(const QString& modelURL, const QString& jointName) { setAttachmentData(attachmentData); } -void AvatarData::sendAvatarDataPacket(bool sendAll) { +int AvatarData::sendAvatarDataPacket(bool sendAll) { auto nodeList = DependencyManager::get(); // about 2% of the time, we send a full update (meaning, we transmit all the joint data), even if nothing has changed. @@ -2170,16 +2170,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 +2189,20 @@ void AvatarData::sendAvatarDataPacket(bool sendAll) { auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size() + sizeof(sequenceNumber)); avatarPacket->writePrimitive(sequenceNumber++); avatarPacket->write(avatarByteArray); + auto packetSize = avatarPacket->getWireSize(); nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); + + return packetSize; } -void AvatarData::sendIdentityPacket() { +int AvatarData::sendIdentityPacket() { auto nodeList = DependencyManager::get(); if (_identityDataChanged) { // if the identity data has changed, push the sequence number forwards ++_identitySequenceNumber; } - QByteArray identityData = identityByteArray(); auto packetList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); @@ -2216,6 +2216,7 @@ void AvatarData::sendIdentityPacket() { }); _identityDataChanged = false; + return identityData.size(); } static const QString JSON_ATTACHMENT_URL = QStringLiteral("modelUrl"); @@ -2744,23 +2745,28 @@ void AvatarData::setAttachmentsVariant(const QVariantList& variant) { } void AvatarData::storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& data) { + bool changed = false; _avatarEntitiesLock.withWriteLock([&] { - PackedAvatarEntityMap::iterator itr = _packedAvatarEntityData.find(entityID); + auto itr = _packedAvatarEntityData.find(entityID); if (itr == _packedAvatarEntityData.end()) { if (_packedAvatarEntityData.size() < MAX_NUM_AVATAR_ENTITIES) { _packedAvatarEntityData.insert(entityID, data); + changed = true; } } else { itr.value() = data; + changed = true; } }); - _avatarEntityDataChanged = true; + if (changed) { + _avatarEntityDataChanged = true; - if (_clientTraitsHandler) { - // we have a client traits handler, so we need to mark this instanced trait as changed - // so that changes will be sent next frame - _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); + if (_clientTraitsHandler) { + // we have a client traits handler, so we need to mark this instanced trait as changed + // so that changes will be sent next frame + _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); + } } } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index ec5ea4a5d0..0e7c1f47bd 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -465,8 +465,6 @@ public: static const QUrl& defaultFullAvatarModelUrl(); - virtual bool isMyAvatar() const { return false; } - const QUuid getSessionUUID() const { return getID(); } glm::vec3 getHandPosition() const; @@ -1273,12 +1271,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(); +AvatarSharedPointer AvatarHashMap::newSharedAvatar(const QUuid& sessionUUID) { + auto avatarData = std::make_shared(); + avatarData->setSessionUUID(sessionUUID); + return avatarData; } AvatarSharedPointer AvatarHashMap::addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { qCDebug(avatars) << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarHashMap."; - auto avatar = newSharedAvatar(); + auto avatar = newSharedAvatar(sessionUUID); avatar->setSessionUUID(sessionUUID); avatar->setOwningAvatarMixer(mixerWeakPointer); // addAvatar is only called from newOrExistingAvatar, which already locks _hashLock _avatarHash.insert(sessionUUID, avatar); emit avatarAddedEvent(sessionUUID); - return avatar; } diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 3bb38dd081..8395651d6b 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -32,6 +32,9 @@ #include "AvatarData.h" #include "AssociatedTraitValues.h" +const int CLIENT_TO_AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 50; +const quint64 MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS = USECS_PER_SECOND / CLIENT_TO_AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND; + /**jsdoc * Note: An AvatarList API is also provided for Interface and client entity scripts: it is a * synonym for the {@link AvatarManager} API. @@ -179,7 +182,7 @@ protected: AvatarHashMap(); virtual AvatarSharedPointer parseAvatarData(QSharedPointer message, SharedNodePointer sendingNode); - virtual AvatarSharedPointer newSharedAvatar(); + virtual AvatarSharedPointer newSharedAvatar(const QUuid& sessionUUID); virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer); AvatarSharedPointer newOrExistingAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer, bool& isNew); diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp index 3e24c1f9ad..bcbe5308c7 100644 --- a/libraries/avatars/src/ClientTraitsHandler.cpp +++ b/libraries/avatars/src/ClientTraitsHandler.cpp @@ -65,8 +65,9 @@ void ClientTraitsHandler::resetForNewMixer() { _owningAvatar->prepareResetTraitInstances(); } -void ClientTraitsHandler::sendChangedTraitsToMixer() { +int ClientTraitsHandler::sendChangedTraitsToMixer() { std::unique_lock lock(_traitLock); + int bytesWritten = 0; if (hasChangedTraits() || _shouldPerformInitialSend) { // we have at least one changed trait to send @@ -75,7 +76,7 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { auto avatarMixer = nodeList->soloNodeOfType(NodeType::AvatarMixer); if (!avatarMixer || !avatarMixer->getActiveSocket()) { // we don't have an avatar mixer with an active socket, we can't send changed traits at this time - return; + return 0; } // we have a mixer to send to, setup our set traits packet @@ -106,7 +107,7 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { if (initialSend || *simpleIt == Updated) { if (traitType == AvatarTraits::SkeletonModelURL) { - _owningAvatar->packTrait(traitType, *traitsPacketList); + bytesWritten += _owningAvatar->packTrait(traitType, *traitsPacketList); // keep track of our skeleton version in case we get an override back _currentSkeletonVersion = _currentTraitVersion; @@ -123,10 +124,10 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { || instanceIDValuePair.value == Updated) { // this is a changed trait we need to send or we haven't send out trait information yet // ask the owning avatar to pack it - _owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList); + bytesWritten += _owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList); } else if (!initialSend && instanceIDValuePair.value == Deleted) { // pack delete for this trait instance - AvatarTraits::packInstancedTraitDelete(instancedIt->traitType, instanceIDValuePair.id, + bytesWritten += AvatarTraits::packInstancedTraitDelete(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList); } } @@ -136,6 +137,8 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() { nodeList->sendPacketList(std::move(traitsPacketList), *avatarMixer); } + + return bytesWritten; } void ClientTraitsHandler::processTraitOverride(QSharedPointer message, SharedNodePointer sendingNode) { diff --git a/libraries/avatars/src/ClientTraitsHandler.h b/libraries/avatars/src/ClientTraitsHandler.h index 3900268101..35499fd2cf 100644 --- a/libraries/avatars/src/ClientTraitsHandler.h +++ b/libraries/avatars/src/ClientTraitsHandler.h @@ -24,7 +24,7 @@ class ClientTraitsHandler : public QObject { public: ClientTraitsHandler(AvatarData* owningAvatar); - void sendChangedTraitsToMixer(); + int sendChangedTraitsToMixer(); bool hasChangedTraits() const { return _hasChangedTraits; } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 380998321f..c71b296a74 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -197,9 +197,57 @@ void EntityTreeRenderer::resetEntitiesScriptEngine() { }); } +void EntityTreeRenderer::stopNonLocalEntityScripts() { + leaveNonLocalEntities(); + // unload and stop the engine + if (_entitiesScriptEngine) { + QList entitiesWithEntityScripts = _entitiesScriptEngine->getListOfEntityScriptIDs(); + + foreach (const EntityItemID& entityID, entitiesWithEntityScripts) { + EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID); + + if (entityItem) { + if (!entityItem->isLocalEntity()) { + _entitiesScriptEngine->unloadEntityScript(entityID, true); + } + } + } + } +} + +void EntityTreeRenderer::clearNonLocalEntities() { + stopNonLocalEntityScripts(); + + std::unordered_map savedEntities; + // remove all entities from the scene + _space->clear(); + auto scene = _viewState->getMain3DScene(); + if (scene) { + render::Transaction transaction; + for (const auto& entry : _entitiesInScene) { + const auto& renderer = entry.second; + const EntityItemPointer& entityItem = renderer->getEntity(); + if (!entityItem->isLocalEntity()) { + renderer->removeFromScene(scene, transaction); + } else { + savedEntities[entry.first] = entry.second; + } + } + scene->enqueueTransaction(transaction); + } else { + qCWarning(entitiesrenderer) << "EntitityTreeRenderer::clear(), Unexpected null scene, possibly during application shutdown"; + } + + _renderablesToUpdate = savedEntities; + _entitiesInScene = savedEntities; + + _layeredZones.clearNonLocalLayeredZones(); + + OctreeProcessor::clearNonLocalEntities(); +} + void EntityTreeRenderer::clear() { leaveAllEntities(); - // unload and stop the engine if (_entitiesScriptEngine) { // do this here (instead of in deleter) to avoid marshalling unload signals back to this thread @@ -211,8 +259,8 @@ void EntityTreeRenderer::clear() { if (_wantScripts && !_shuttingDown) { resetEntitiesScriptEngine(); } - // remove all entities from the scene + _space->clear(); auto scene = _viewState->getMain3DScene(); if (scene) { @@ -507,8 +555,7 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QVector(_tree); // FIXME - if EntityTree had a findEntitiesContainingPoint() this could theoretically be a little faster - entityTree->evalEntitiesInSphere(_avatarPosition, radius, - PickFilter(PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES) | PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES)), entityIDs); + entityTree->evalEntitiesInSphere(_avatarPosition, radius, PickFilter(), entityIDs); LayeredZones oldLayeredZones(std::move(_layeredZones)); _layeredZones.clear(); @@ -614,6 +661,26 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { return didUpdate; } +void EntityTreeRenderer::leaveNonLocalEntities() { + if (_tree && !_shuttingDown) { + QVector currentLocalEntitiesInside; + foreach (const EntityItemID& entityID, _currentEntitiesInside) { + EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID); + if (!entityItem->isLocalEntity()) { + emit leaveEntity(entityID); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); + } + } else { + currentLocalEntitiesInside.push_back(entityID); + } + } + + _currentEntitiesInside = currentLocalEntitiesInside; + forceRecheckEntities(); + } +} + void EntityTreeRenderer::leaveAllEntities() { if (_tree && !_shuttingDown) { @@ -1136,6 +1203,29 @@ EntityTreeRenderer::LayeredZones::LayeredZones(LayeredZones&& other) { } } +void EntityTreeRenderer::LayeredZones::clearNonLocalLayeredZones() { + std::set localLayeredZones; + std::map newMap; + + for (auto iter = begin(); iter != end(); iter++) { + LayeredZone layeredZone = *iter; + + if (layeredZone.zone->isLocalEntity()) { + bool success; + iterator it; + std::tie(it, success) = localLayeredZones.insert(layeredZone); + + if (success) { + newMap.emplace(layeredZone.id, it); + } + } + } + + std::set::operator=(localLayeredZones); + _map = newMap; + _skyboxLayer = empty() ? end() : begin(); +} + void EntityTreeRenderer::LayeredZones::clear() { std::set::clear(); _map.clear(); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index b4f0bda703..d9f594a20b 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -86,6 +86,7 @@ public: virtual void init() override; /// clears the tree + virtual void clearNonLocalEntities() override; virtual void clear() override; /// reloads the entity scripts, calling unload and preload @@ -161,6 +162,7 @@ private: bool findBestZoneAndMaybeContainingEntities(QVector* entitiesContainingAvatar = nullptr); bool applyLayeredZones(); + void stopNonLocalEntityScripts(); void checkAndCallPreload(const EntityItemID& entityID, bool reload = false, bool unloadFirst = false); @@ -169,6 +171,7 @@ private: QScriptValueList createEntityArgs(const EntityItemID& entityID); bool checkEnterLeaveEntities(); + void leaveNonLocalEntities(); void leaveAllEntities(); void forceRecheckEntities(); @@ -219,6 +222,7 @@ private: LayeredZones& operator=(LayeredZones&&) = delete; void clear(); + void clearNonLocalLayeredZones(); std::pair insert(const LayeredZone& layer); void update(std::shared_ptr zone); bool contains(const LayeredZones& other); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 498f0ff066..8b6595d8c0 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -770,7 +770,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); }; @@ -3349,7 +3352,8 @@ void EntityItem::prepareForSimulationOwnershipBid(EntityItemProperties& properti } bool EntityItem::isWearable() const { - return isVisible() && (getParentID() == DependencyManager::get()->getSessionUUID() || getParentID() == AVATAR_SELF_ID); + return isVisible() && + (getParentID() == DependencyManager::get()->getSessionUUID() || getParentID() == AVATAR_SELF_ID); } void EntityItem::addGrab(GrabPointer grab) { @@ -3368,7 +3372,8 @@ void EntityItem::addGrab(GrabPointer grab) { EntityDynamicType dynamicType; QVariantMap arguments; int grabParentJointIndex =grab->getParentJointIndex(); - if (grabParentJointIndex == FARGRAB_RIGHTHAND_INDEX || grabParentJointIndex == FARGRAB_LEFTHAND_INDEX) { + if (grabParentJointIndex == FARGRAB_RIGHTHAND_INDEX || grabParentJointIndex == FARGRAB_LEFTHAND_INDEX || + grabParentJointIndex == FARGRAB_MOUSE_INDEX) { // add a far-grab action dynamicType = DYNAMIC_TYPE_FAR_GRAB; arguments["otherID"] = grab->getOwnerID(); diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 3b1e9a3e2b..1fdff19e38 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -4616,14 +4616,14 @@ bool EntityItemProperties::blobToProperties(QScriptEngine& scriptEngine, const Q // DANGER: this method is NOT efficient. // begin recipe for converting unfortunately-formatted-binary-blob to EntityItemProperties QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(blob); - if (!jsonProperties.isObject()) { + if (jsonProperties.isEmpty() || jsonProperties.isNull() || !jsonProperties.isObject() || jsonProperties.object().isEmpty()) { qCDebug(entities) << "bad avatarEntityData json" << QString(blob.toHex()); return false; } QVariant variant = jsonProperties.toVariant(); QVariantMap variantMap = variant.toMap(); QScriptValue scriptValue = variantMapToScriptValue(variantMap, scriptEngine); - EntityItemPropertiesFromScriptValueHonorReadOnly(scriptValue, properties); + EntityItemPropertiesFromScriptValueIgnoreReadOnly(scriptValue, properties); // end recipe return true; } diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 697d583de7..286f0dd650 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -986,7 +986,7 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { shouldSendDeleteToServer = false; } else { // only delete local entities, server entities will round trip through the server filters - if (entity->isAvatarEntity() || _entityTree->isServerlessMode()) { + if (!entity->isDomainEntity() || _entityTree->isServerlessMode()) { shouldSendDeleteToServer = false; _entityTree->deleteEntity(entityID); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index fb1a11d43f..b251579b81 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -70,6 +70,49 @@ OctreeElementPointer EntityTree::createNewElement(unsigned char* octalCode) { return std::static_pointer_cast(newElement); } +void EntityTree::eraseNonLocalEntities() { + emit clearingEntities(); + + if (_simulation) { + // This will clear all entities host types including local entities, because local entities + // are not in the physics simulation + _simulation->clearEntities(); + } + _staleProxies.clear(); + QHash localMap; + localMap.swap(_entityMap); + QHash savedEntities; + this->withWriteLock([&] { + foreach(EntityItemPointer entity, localMap) { + EntityTreeElementPointer element = entity->getElement(); + if (element) { + element->cleanupNonLocalEntities(); + } + + if (entity->isLocalEntity()) { + savedEntities[entity->getEntityItemID()] = entity; + } + } + }); + localMap.clear(); + _entityMap = savedEntities; + + resetClientEditStats(); + clearDeletedEntities(); + + { + QWriteLocker locker(&_needsParentFixupLock); + QVector localEntitiesNeedsParentFixup; + + foreach (EntityItemWeakPointer entityItem, _needsParentFixup) { + if (!entityItem.expired() && entityItem.lock()->isLocalEntity()) { + localEntitiesNeedsParentFixup.push_back(entityItem); + } + } + + _needsParentFixup = localEntitiesNeedsParentFixup; + } +} void EntityTree::eraseAllOctreeElements(bool createNewRoot) { emit clearingEntities(); @@ -2982,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/EntityTree.h b/libraries/entities/src/EntityTree.h index 9181a4851c..f9b7b8d67f 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -74,6 +74,8 @@ public: return std::static_pointer_cast(_rootElement); } + + virtual void eraseNonLocalEntities() override; virtual void eraseAllOctreeElements(bool createNewRoot = true) override; virtual void readBitstreamToTree(const unsigned char* bitstream, diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 2ece6835ea..d51ffcd10a 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -683,6 +683,23 @@ EntityItemPointer EntityTreeElement::getEntityWithEntityItemID(const EntityItemI return foundEntity; } +void EntityTreeElement::cleanupNonLocalEntities() { + withWriteLock([&] { + EntityItems savedEntities; + foreach(EntityItemPointer entity, _entityItems) { + if (!entity->isLocalEntity()) { + entity->preDelete(); + entity->_element = NULL; + } else { + savedEntities.push_back(entity); + } + } + + _entityItems = savedEntities; + }); + bumpChangedContent(); +} + void EntityTreeElement::cleanupEntities() { withWriteLock([&] { foreach(EntityItemPointer entity, _entityItems) { diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index aed19eed15..3f1fda57bd 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -189,6 +189,7 @@ public: EntityItemPointer getEntityWithEntityItemID(const EntityItemID& id) const; void getEntitiesInside(const AACube& box, QVector& foundEntities); + void cleanupNonLocalEntities(); void cleanupEntities(); /// called by EntityTree on cleanup this will free all entities bool removeEntityItem(EntityItemPointer entity, bool deletion = false); diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index 418dc8f417..7ab2296935 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -31,6 +31,7 @@ using namespace udt; using namespace std::chrono; Connection::Connection(Socket* parentSocket, HifiSockAddr destination, std::unique_ptr congestionControl) : + QObject(parentSocket), _parentSocket(parentSocket), _destination(destination), _congestionControl(move(congestionControl)) diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 642914cd56..ee9f784b8c 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -93,8 +93,6 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(PingVersion::IncludeConnectionID); case PacketType::AvatarQuery: return static_cast(AvatarQueryVersion::ConicalFrustums); - case PacketType::AvatarIdentityRequest: - return 22; case PacketType::EntityQueryInitialResultsComplete: return static_cast(EntityVersion::ParticleSpin); case PacketType::BulkAvatarTraitsAck: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index f53a287d71..37b8a3b1c7 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, diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index eef23493f6..aac29201f1 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -149,6 +149,7 @@ public: OctreeElementPointer getRoot() { return _rootElement; } + virtual void eraseNonLocalEntities() { _isDirty = true; }; virtual void eraseAllOctreeElements(bool createNewRoot = true); virtual void readBitstreamToTree(const unsigned char* bitstream, uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args); diff --git a/libraries/octree/src/OctreeProcessor.cpp b/libraries/octree/src/OctreeProcessor.cpp index 206ff399d9..18c8630391 100644 --- a/libraries/octree/src/OctreeProcessor.cpp +++ b/libraries/octree/src/OctreeProcessor.cpp @@ -51,15 +51,15 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe bool showTimingDetails = false; // Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showTimingDetails, "OctreeProcessor::processDatagram()", showTimingDetails); - + if (message.getType() == getExpectedPacketType()) { PerformanceWarning warn(showTimingDetails, "OctreeProcessor::processDatagram expected PacketType", showTimingDetails); // if we are getting inbound packets, then our tree is also viewing, and we should remember that fact. _tree->setIsViewing(true); - + OCTREE_PACKET_FLAGS flags; message.readPrimitive(&flags); - + OCTREE_PACKET_SEQUENCE sequence; message.readPrimitive(&sequence); @@ -68,7 +68,7 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe bool packetIsColored = oneAtBit(flags, PACKET_IS_COLOR_BIT); bool packetIsCompressed = oneAtBit(flags, PACKET_IS_COMPRESSED_BIT); - + OCTREE_PACKET_SENT_TIME arrivedAt = usecTimestampNow(); qint64 clockSkew = sourceNode ? sourceNode->getClockSkewUsec() : 0; qint64 flightTime = arrivedAt - sentAt + clockSkew; @@ -79,27 +79,27 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe qCDebug(octree) << "OctreeProcessor::processDatagram() ... " "Got Packet Section color:" << packetIsColored << "compressed:" << packetIsCompressed << - "sequence: " << sequence << + "sequence: " << sequence << "flight: " << flightTime << " usec" << "size:" << message.getSize() << "data:" << message.getBytesLeftToRead(); } - + _packetsInLastWindow++; - + int elementsPerPacket = 0; int entitiesPerPacket = 0; - + quint64 totalWaitingForLock = 0; quint64 totalUncompress = 0; quint64 totalReadBitsteam = 0; const QUuid& sourceUUID = sourceNode->getUUID(); - + int subsection = 1; - + bool error = false; - + while (message.getBytesLeftToRead() > 0 && !error) { if (packetIsCompressed) { if (message.getBytesLeftToRead() > (qint64) sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE)) { @@ -111,7 +111,7 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe } else { sectionLength = message.getBytesLeftToRead(); } - + if (sectionLength) { // ask the VoxelTree to read the bitstream into the tree ReadBitstreamToTreeParams args(WANT_EXISTS_BITS, NULL, @@ -149,7 +149,7 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe qCDebug(octree) << "OctreeProcessor::processDatagram() ******* END _tree->readBitstreamToTree()..."; } }); - + // seek forwards in packet message.seek(message.getPosition() + sectionLength); @@ -172,13 +172,13 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe _waitLockPerPacket.updateAverage(totalWaitingForLock); _uncompressPerPacket.updateAverage(totalUncompress); _readBitstreamPerPacket.updateAverage(totalReadBitsteam); - + quint64 now = usecTimestampNow(); if (_lastWindowAt == 0) { _lastWindowAt = now; } quint64 sinceLastWindow = now - _lastWindowAt; - + if (sinceLastWindow > USECS_PER_SECOND) { float packetsPerSecondInWindow = (float)_packetsInLastWindow / (float)(sinceLastWindow / USECS_PER_SECOND); float elementsPerSecondInWindow = (float)_elementsInLastWindow / (float)(sinceLastWindow / USECS_PER_SECOND); @@ -197,6 +197,14 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe } } + +void OctreeProcessor::clearNonLocalEntities() { + if (_tree) { + _tree->withWriteLock([&] { + _tree->eraseNonLocalEntities(); + }); + } +} void OctreeProcessor::clear() { if (_tree) { _tree->withWriteLock([&] { diff --git a/libraries/octree/src/OctreeProcessor.h b/libraries/octree/src/OctreeProcessor.h index 1bc3bd10f9..bc5618e657 100644 --- a/libraries/octree/src/OctreeProcessor.h +++ b/libraries/octree/src/OctreeProcessor.h @@ -43,6 +43,7 @@ public: virtual void init(); /// clears the tree + virtual void clearNonLocalEntities(); virtual void clear(); float getAverageElementsPerPacket() const { return _elementsPerPacket.getAverage(); } diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 47ce29f9a3..fa7a8e1114 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -2407,6 +2407,11 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldR } } +QList ScriptEngine::getListOfEntityScriptIDs() { + QReadLocker locker{ &_entityScriptsLock }; + return _entityScripts.keys(); +} + void ScriptEngine::unloadAllEntityScripts() { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 0d6d45e594..8753010089 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -145,6 +145,9 @@ public: QString getFilename() const; + + QList getListOfEntityScriptIDs(); + /**jsdoc * Stop the current script. * @function Script.stop diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 8805205361..c524e3183b 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -108,7 +108,8 @@ SpatiallyNestablePointer SpatiallyNestable::getParentPointer(bool& success) cons return nullptr; } - if (parent && parent->getID() == parentID) { + if (parent && (parent->getID() == parentID || + (parentID == AVATAR_SELF_ID && parent->isMyAvatar()))) { // parent pointer is up-to-date if (!_parentKnowsMe) { SpatialParentTree* parentTree = parent->getParentTree(); diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index a55f32896b..319f07236b 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -47,6 +47,8 @@ public: virtual const QUuid getParentID() const; virtual void setParentID(const QUuid& parentID); + virtual bool isMyAvatar() const { return false; } + virtual quint16 getParentJointIndex() const { return _parentJointIndex; } virtual void setParentJointIndex(quint16 parentJointIndex); diff --git a/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/avatarapp.js b/scripts/system/avatarapp.js index 2b9a738202..631c0e03e8 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -70,9 +70,9 @@ function getMyAvatarSettings() { } } -function updateAvatarWearables(avatar, callback) { +function updateAvatarWearables(avatar, callback, wearablesOverride) { executeLater(function() { - var wearables = getMyAvatarWearables(); + var wearables = wearablesOverride ? wearablesOverride : getMyAvatarWearables(); avatar[ENTRY_AVATAR_ENTITIES] = wearables; sendToQml({'method' : 'wearablesUpdated', 'wearables' : wearables}) @@ -210,7 +210,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See break; case 'adjustWearable': if(message.properties.localRotationAngles) { - message.properties.localRotation = Quat.fromVec3Degrees(message.properties.localRotationAngles) + message.properties.localRotation = Quat.fromVec3Degrees(message.properties.localRotationAngles); } Entities.editEntity(message.entityID, message.properties); @@ -235,7 +235,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See // revert changes using snapshot of wearables if(currentAvatarWearablesBackup !== null) { AvatarBookmarks.updateAvatarEntities(currentAvatarWearablesBackup); - updateAvatarWearables(currentAvatar); + updateAvatarWearables(currentAvatar, null, currentAvatarWearablesBackup); } } else { sendToQml({'method' : 'updateAvatarInBookmarks'}); diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index c61e46c8eb..b1c1bc7765 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -6,11 +6,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, Camera, print, - getControllerJointIndex, enableDispatcherModule, disableDispatcherModule, entityIsFarGrabbedByOther, - Messages, makeDispatcherModuleParameters, makeRunningValues, Settings, entityHasActions, - Vec3, Overlays, flatten, Xform, getControllerWorldLocation, ensureDynamic, entityIsCloneable, - cloneEntity, DISPATCHER_PROPERTIES, Uuid, unhighlightTargetEntity, isInEditMode, getGrabbableData +/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, Camera, print, getControllerJointIndex, + enableDispatcherModule, disableDispatcherModule, entityIsFarGrabbedByOther, Messages, makeDispatcherModuleParameters, + makeRunningValues, Settings, entityHasActions, Vec3, Overlays, flatten, Xform, getControllerWorldLocation, ensureDynamic, + entityIsCloneable, cloneEntity, DISPATCHER_PROPERTIES, Uuid, unhighlightTargetEntity, isInEditMode, getGrabbableData, + entityIsEquippable */ Script.include("/~/system/libraries/Xform.js"); @@ -767,7 +767,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var entityProperties = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); entityProperties.id = entityID; var hasEquipData = getWearableData(entityProperties); - if (hasEquipData && entityProperties.parentID === EMPTY_PARENT_ID && !entityIsFarGrabbedByOther(entityID)) { + if (hasEquipData && entityIsEquippable(entityProperties)) { entityProperties.id = entityID; var rightHandPosition = MyAvatar.getJointPosition("RightHand"); var leftHandPosition = MyAvatar.getJointPosition("LeftHand"); diff --git a/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js b/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js deleted file mode 100644 index 0ba3dd6e6b..0000000000 --- a/scripts/system/controllers/controllerModules/farActionGrabEntityDynOnly.js +++ /dev/null @@ -1,572 +0,0 @@ -"use strict"; - -// farActionGrabEntity.js -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html - -/* jslint bitwise: true */ - -/* global Script, Controller, RIGHT_HAND, LEFT_HAND, Mat4, MyAvatar, Vec3, Camera, Quat, getEnabledModuleByName, - makeRunningValues, Entities, enableDispatcherModule, disableDispatcherModule, entityIsGrabbable, - makeDispatcherModuleParameters, MSECS_PER_SEC, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, - TRIGGER_ON_VALUE, ZERO_VEC, getControllerWorldLocation, projectOntoEntityXYPlane, ContextOverlay, HMD, - Picks, makeLaserLockInfo, makeLaserParams, AddressManager, getEntityParents, Selection, DISPATCHER_HOVERING_LIST, - Uuid, worldPositionToRegistrationFrameMatrix, DISPATCHER_PROPERTIES -*/ - -Script.include("/~/system/libraries/controllerDispatcherUtils.js"); -Script.include("/~/system/libraries/controllers.js"); - -(function() { - - var MARGIN = 25; - - function TargetObject(entityID, entityProps) { - this.entityID = entityID; - this.entityProps = entityProps; - this.targetEntityID = null; - this.targetEntityProps = null; - - this.getTargetEntity = function() { - var parentPropsLength = this.parentProps.length; - if (parentPropsLength !== 0) { - var targetEntity = { - id: this.parentProps[parentPropsLength - 1].id, - props: this.parentProps[parentPropsLength - 1]}; - this.targetEntityID = targetEntity.id; - this.targetEntityProps = targetEntity.props; - return targetEntity; - } - this.targetEntityID = this.entityID; - this.targetEntityProps = this.entityProps; - return { - id: this.entityID, - props: this.entityProps}; - }; - } - - function FarActionGrabEntity(hand) { - this.hand = hand; - this.grabbing = false; - this.grabbedThingID = null; - this.targetObject = null; - this.actionID = null; // action this script created... - this.entityToLockOnto = null; - this.potentialEntityWithContextOverlay = false; - this.entityWithContextOverlay = false; - this.contextOverlayTimer = false; - this.locked = false; - this.highlightedEntity = null; - this.reticleMinX = MARGIN; - this.reticleMaxX = 0; - this.reticleMinY = MARGIN; - this.reticleMaxY = 0; - - var ACTION_TTL = 15; // seconds - - var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object - var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position - var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified - var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified - - this.parameters = makeDispatcherModuleParameters( - 550, - this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], - [], - 100, - makeLaserParams(this.hand, false)); - - - this.handToController = function() { - return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - }; - - this.distanceGrabTimescale = function(mass, distance) { - var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass / - DISTANCE_HOLDING_UNITY_MASS * distance / - DISTANCE_HOLDING_UNITY_DISTANCE; - if (timeScale < DISTANCE_HOLDING_ACTION_TIMEFRAME) { - timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME; - } - return timeScale; - }; - - this.getMass = function(dimensions, density) { - return (dimensions.x * dimensions.y * dimensions.z) * density; - }; - - this.startFarGrabAction = function (controllerData, grabbedProperties) { - var controllerLocation = controllerData.controllerLocations[this.hand]; - var worldControllerPosition = controllerLocation.position; - var worldControllerRotation = controllerLocation.orientation; - - // transform the position into room space - var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); - var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); - - var now = Date.now(); - - // add the action and initialize some variables - this.currentObjectPosition = grabbedProperties.position; - this.currentObjectRotation = grabbedProperties.rotation; - this.currentObjectTime = now; - this.currentCameraOrientation = Camera.orientation; - - this.grabRadius = this.grabbedDistance; - this.grabRadialVelocity = 0.0; - - // offset between controller vector at the grab radius and the entity position - var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); - targetPosition = Vec3.sum(targetPosition, worldControllerPosition); - this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); - - // compute a constant based on the initial conditions which we use below to exaggerate hand motion - // onto the held object - this.radiusScalar = Math.log(this.grabRadius + 1.0); - if (this.radiusScalar < 1.0) { - this.radiusScalar = 1.0; - } - - // compute the mass for the purpose of energy and how quickly to move object - this.mass = this.getMass(grabbedProperties.dimensions, grabbedProperties.density); - var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, grabbedProperties.position)); - var timeScale = this.distanceGrabTimescale(this.mass, distanceToObject); - this.linearTimeScale = timeScale; - this.actionID = Entities.addAction("far-grab", this.grabbedThingID, { - targetPosition: this.currentObjectPosition, - linearTimeScale: timeScale, - targetRotation: this.currentObjectRotation, - angularTimeScale: timeScale, - tag: "far-grab-" + MyAvatar.sessionUUID, - ttl: ACTION_TTL - }); - if (this.actionID === Uuid.NULL) { - this.actionID = null; - } - - if (this.actionID !== null) { - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.grabbedThingID, "startDistanceGrab", args); - } - - Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); - this.previousRoomControllerPosition = roomControllerPosition; - this.grabbing = true; - }; - - this.continueDistanceHolding = function(controllerData) { - var controllerLocation = controllerData.controllerLocations[this.hand]; - var worldControllerPosition = controllerLocation.position; - var worldControllerRotation = controllerLocation.orientation; - - // also transform the position into room space - var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); - var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); - - var grabbedProperties = Entities.getEntityProperties(this.grabbedThingID, DISPATCHER_PROPERTIES); - var now = Date.now(); - var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds - this.currentObjectTime = now; - - // the action was set up when this.distanceHolding was called. update the targets. - var radius = Vec3.distance(this.currentObjectPosition, worldControllerPosition) * - this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; - if (radius < 1.0) { - radius = 1.0; - } - - var roomHandDelta = Vec3.subtract(roomControllerPosition, this.previousRoomControllerPosition); - var worldHandDelta = Mat4.transformVector(MyAvatar.getSensorToWorldMatrix(), roomHandDelta); - var handMoved = Vec3.multiply(worldHandDelta, radius); - this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved); - - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.grabbedThingID, "continueDistanceGrab", args); - - // Update radialVelocity - var lastVelocity = Vec3.multiply(worldHandDelta, 1.0 / deltaObjectTime); - var delta = Vec3.normalize(Vec3.subtract(grabbedProperties.position, worldControllerPosition)); - var newRadialVelocity = Vec3.dot(lastVelocity, delta); - - var VELOCITY_AVERAGING_TIME = 0.016; - var blendFactor = deltaObjectTime / VELOCITY_AVERAGING_TIME; - if (blendFactor < 0.0) { - blendFactor = 0.0; - } else if (blendFactor > 1.0) { - blendFactor = 1.0; - } - this.grabRadialVelocity = blendFactor * newRadialVelocity + (1.0 - blendFactor) * this.grabRadialVelocity; - - var RADIAL_GRAB_AMPLIFIER = 10.0; - if (Math.abs(this.grabRadialVelocity) > 0.0) { - this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime * - this.grabRadius * RADIAL_GRAB_AMPLIFIER); - } - - // don't let grabRadius go all the way to zero, because it can't come back from that - var MINIMUM_GRAB_RADIUS = 0.1; - if (this.grabRadius < MINIMUM_GRAB_RADIUS) { - this.grabRadius = MINIMUM_GRAB_RADIUS; - } - var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); - newTargetPosition = Vec3.sum(newTargetPosition, worldControllerPosition); - newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition); - - // XXX - // this.maybeScale(grabbedProperties); - - var distanceToObject = Vec3.length(Vec3.subtract(MyAvatar.position, this.currentObjectPosition)); - - this.linearTimeScale = (this.linearTimeScale / 2); - if (this.linearTimeScale <= DISTANCE_HOLDING_ACTION_TIMEFRAME) { - this.linearTimeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME; - } - var success = Entities.updateAction(this.grabbedThingID, this.actionID, { - targetPosition: newTargetPosition, - linearTimeScale: this.linearTimeScale, - targetRotation: this.currentObjectRotation, - angularTimeScale: this.distanceGrabTimescale(this.mass, distanceToObject), - ttl: ACTION_TTL - }); - if (!success) { - print("farActionGrabEntity continueDistanceHolding -- updateAction failed: " + this.actionID); - this.actionID = null; - } - - this.previousRoomControllerPosition = roomControllerPosition; - }; - - this.endFarGrabAction = function () { - this.distanceHolding = false; - this.distanceRotating = false; - Entities.deleteAction(this.grabbedThingID, this.actionID); - - var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; - Entities.callEntityMethod(this.grabbedThingID, "releaseGrab", args); - this.actionID = null; - this.grabbedThingID = null; - this.targetObject = null; - this.potentialEntityWithContextOverlay = false; - this.grabbing = false; - }; - - this.updateRecommendedArea = function() { - var dims = Controller.getViewportDimensions(); - this.reticleMaxX = dims.x - MARGIN; - this.reticleMaxY = dims.y - MARGIN; - }; - - this.calculateNewReticlePosition = function(intersection) { - this.updateRecommendedArea(); - var point2d = HMD.overlayFromWorldPoint(intersection); - point2d.x = Math.max(this.reticleMinX, Math.min(point2d.x, this.reticleMaxX)); - point2d.y = Math.max(this.reticleMinY, Math.min(point2d.y, this.reticleMaxY)); - return point2d; - }; - - this.notPointingAtEntity = function(controllerData) { - var intersection = controllerData.rayPicks[this.hand]; - var entityProperty = Entities.getEntityProperties(intersection.objectID, DISPATCHER_PROPERTIES); - var entityType = entityProperty.type; - var hudRayPick = controllerData.hudRayPicks[this.hand]; - var point2d = this.calculateNewReticlePosition(hudRayPick.intersection); - if ((intersection.type === Picks.INTERSECTED_ENTITY && entityType === "Web") || - intersection.type === Picks.INTERSECTED_OVERLAY || Window.isPointOnDesktopWindow(point2d)) { - return true; - } - return false; - }; - - this.distanceRotate = function(otherFarGrabModule) { - this.distanceRotating = true; - this.distanceHolding = false; - - var worldControllerRotation = getControllerWorldLocation(this.handToController(), true).orientation; - var controllerRotationDelta = - Quat.multiply(worldControllerRotation, Quat.inverse(this.previousWorldControllerRotation)); - // Rotate entity by twice the delta rotation. - controllerRotationDelta = Quat.multiply(controllerRotationDelta, controllerRotationDelta); - - // Perform the rotation in the translation controller's action update. - otherFarGrabModule.currentObjectRotation = Quat.multiply(controllerRotationDelta, - otherFarGrabModule.currentObjectRotation); - - this.previousWorldControllerRotation = worldControllerRotation; - }; - - this.prepareDistanceRotatingData = function(controllerData) { - var intersection = controllerData.rayPicks[this.hand]; - - var controllerLocation = getControllerWorldLocation(this.handToController(), true); - var worldControllerPosition = controllerLocation.position; - var worldControllerRotation = controllerLocation.orientation; - - var grabbedProperties = Entities.getEntityProperties(intersection.objectID, DISPATCHER_PROPERTIES); - this.currentObjectPosition = grabbedProperties.position; - this.grabRadius = intersection.distance; - - // Offset between controller vector at the grab radius and the entity position. - var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); - targetPosition = Vec3.sum(targetPosition, worldControllerPosition); - this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); - - // Initial controller rotation. - this.previousWorldControllerRotation = worldControllerRotation; - }; - - this.destroyContextOverlay = function(controllerData) { - if (this.entityWithContextOverlay) { - ContextOverlay.destroyContextOverlay(this.entityWithContextOverlay); - this.entityWithContextOverlay = false; - this.potentialEntityWithContextOverlay = false; - } - }; - - this.targetIsNull = function() { - var properties = Entities.getEntityProperties(this.grabbedThingID, DISPATCHER_PROPERTIES); - if (Object.keys(properties).length === 0 && this.distanceHolding) { - return true; - } - return false; - }; - - this.getTargetProps = function (controllerData) { - var targetEntityID = controllerData.rayPicks[this.hand].objectID; - if (targetEntityID) { - return Entities.getEntityProperties(targetEntityID, DISPATCHER_PROPERTIES); - } - return null; - }; - - this.isReady = function (controllerData) { - if (HMD.active) { - if (this.notPointingAtEntity(controllerData)) { - return makeRunningValues(false, [], []); - } - - this.distanceHolding = false; - this.distanceRotating = false; - - if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { - this.prepareDistanceRotatingData(controllerData); - return makeRunningValues(true, [], []); - } else { - this.destroyContextOverlay(); - return makeRunningValues(false, [], []); - } - } - return makeRunningValues(false, [], []); - }; - - this.run = function (controllerData) { - if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.targetIsNull()) { - this.endFarGrabAction(); - Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", - this.highlightedEntity); - this.highlightedEntity = null; - return makeRunningValues(false, [], []); - } - this.intersectionDistance = controllerData.rayPicks[this.hand].distance; - - var otherModuleName = this.hand === RIGHT_HAND ? "LeftFarActionGrabEntity" : "RightFarActionGrabEntity"; - var otherFarGrabModule = getEnabledModuleByName(otherModuleName); - - // gather up the readiness of the near-grab modules - var nearGrabNames = [ - this.hand === RIGHT_HAND ? "RightScaleAvatar" : "LeftScaleAvatar", - this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity", - this.hand === RIGHT_HAND ? "RightNearActionGrabEntity" : "LeftNearActionGrabEntity", - this.hand === RIGHT_HAND ? "RightNearParentingGrabEntity" : "LeftNearParentingGrabEntity" - ]; - if (!this.grabbing) { - nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"); - nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight"); - } - - var nearGrabReadiness = []; - for (var i = 0; i < nearGrabNames.length; i++) { - var nearGrabModule = getEnabledModuleByName(nearGrabNames[i]); - var ready = nearGrabModule ? nearGrabModule.isReady(controllerData) : makeRunningValues(false, [], []); - nearGrabReadiness.push(ready); - } - - if (this.actionID) { - // if we are doing a distance grab and the object or tablet gets close enough to the controller, - // stop the far-grab so the near-grab or equip can take over. - for (var k = 0; k < nearGrabReadiness.length; k++) { - if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.grabbedThingID || - HMD.tabletID && nearGrabReadiness[k].targets[0] === HMD.tabletID)) { - this.endFarGrabAction(); - return makeRunningValues(false, [], []); - } - } - - this.continueDistanceHolding(controllerData); - } else { - // if we are doing a distance search and this controller moves into a position - // where it could near-grab something, stop searching. - for (var j = 0; j < nearGrabReadiness.length; j++) { - if (nearGrabReadiness[j].active) { - this.endFarGrabAction(); - return makeRunningValues(false, [], []); - } - } - - var rayPickInfo = controllerData.rayPicks[this.hand]; - if (rayPickInfo.type === Picks.INTERSECTED_ENTITY) { - if (controllerData.triggerClicks[this.hand]) { - var entityID = rayPickInfo.objectID; - Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", - this.highlightedEntity); - this.highlightedEntity = null; - var targetProps = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); - if (targetProps.href !== "") { - AddressManager.handleLookupString(targetProps.href); - return makeRunningValues(false, [], []); - } - - this.targetObject = new TargetObject(entityID, targetProps); - this.targetObject.parentProps = getEntityParents(targetProps); - - if (this.contextOverlayTimer) { - Script.clearTimeout(this.contextOverlayTimer); - } - this.contextOverlayTimer = false; - if (entityID === this.entityWithContextOverlay) { - this.destroyContextOverlay(); - } else { - Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID); - } - - var targetEntity = this.targetObject.getTargetEntity(); - entityID = targetEntity.id; - targetProps = targetEntity.props; - - if (!targetProps.dynamic && !this.targetObject.entityProps.dynamic) { - // let farParentGrabEntity handle it - return makeRunningValues(false, [], []); - } - - if (entityIsGrabbable(targetProps) || entityIsGrabbable(this.targetObject.entityProps)) { - if (!this.distanceRotating) { - this.grabbedThingID = entityID; - this.grabbedDistance = rayPickInfo.distance; - } - - if (otherFarGrabModule.grabbedThingID === this.grabbedThingID && - otherFarGrabModule.distanceHolding) { - this.prepareDistanceRotatingData(controllerData); - this.distanceRotate(otherFarGrabModule); - } else { - this.distanceHolding = true; - this.distanceRotating = false; - this.startFarGrabAction(controllerData, targetProps); - } - } - } else { - var targetEntityID = rayPickInfo.objectID; - if (this.highlightedEntity !== targetEntityID) { - Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", - this.highlightedEntity); - var selectionTargetProps = Entities.getEntityProperties(targetEntityID, DISPATCHER_PROPERTIES); - - var selectionTargetObject = new TargetObject(targetEntityID, selectionTargetProps); - selectionTargetObject.parentProps = getEntityParents(selectionTargetProps); - var selectionTargetEntity = selectionTargetObject.getTargetEntity(); - - if (entityIsGrabbable(selectionTargetEntity.props) || - entityIsGrabbable(selectionTargetObject.entityProps)) { - - Selection.addToSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", rayPickInfo.objectID); - } - this.highlightedEntity = rayPickInfo.objectID; - } - - if (!this.entityWithContextOverlay) { - var _this = this; - - if (_this.potentialEntityWithContextOverlay !== rayPickInfo.objectID) { - if (_this.contextOverlayTimer) { - Script.clearTimeout(_this.contextOverlayTimer); - } - _this.contextOverlayTimer = false; - _this.potentialEntityWithContextOverlay = rayPickInfo.objectID; - } - - if (!_this.contextOverlayTimer) { - _this.contextOverlayTimer = Script.setTimeout(function () { - if (!_this.entityWithContextOverlay && - _this.contextOverlayTimer && - _this.potentialEntityWithContextOverlay === rayPickInfo.objectID) { - var pEvProps = Entities.getEntityProperties(rayPickInfo.objectID, - DISPATCHER_PROPERTIES); - var pointerEvent = { - type: "Move", - id: _this.hand + 1, // 0 is reserved for hardware mouse - pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID, - rayPickInfo.intersection, pEvProps), - pos3D: rayPickInfo.intersection, - normal: rayPickInfo.surfaceNormal, - direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal), - button: "Secondary" - }; - if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) { - _this.entityWithContextOverlay = rayPickInfo.objectID; - } - } - _this.contextOverlayTimer = false; - }, 500); - } - } - } - } else if (this.distanceRotating) { - this.distanceRotate(otherFarGrabModule); - } else if (this.highlightedEntity) { - Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); - this.highlightedEntity = null; - } - } - return this.exitIfDisabled(controllerData); - }; - - this.exitIfDisabled = function(controllerData) { - var moduleName = this.hand === RIGHT_HAND ? "RightDisableModules" : "LeftDisableModules"; - var disableModule = getEnabledModuleByName(moduleName); - if (disableModule) { - if (disableModule.disableModules) { - this.endFarGrabAction(); - Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", - this.highlightedEntity); - this.highlightedEntity = null; - return makeRunningValues(false, [], []); - } - } - var grabbedThing = (this.distanceHolding || this.distanceRotating) ? this.targetObject.entityID : null; - var offset = this.calculateOffset(controllerData); - var laserLockInfo = makeLaserLockInfo(grabbedThing, false, this.hand, offset); - return makeRunningValues(true, [], [], laserLockInfo); - }; - - this.calculateOffset = function(controllerData) { - if (this.distanceHolding || this.distanceRotating) { - var targetProps = Entities.getEntityProperties(this.targetObject.entityID, - [ "position", "rotation", "registrationPoint", "dimensions" ]); - return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection); - } - return undefined; - }; - } - - var leftFarActionGrabEntity = new FarActionGrabEntity(LEFT_HAND); - var rightFarActionGrabEntity = new FarActionGrabEntity(RIGHT_HAND); - - enableDispatcherModule("LeftFarActionGrabEntity", leftFarActionGrabEntity); - enableDispatcherModule("RightFarActionGrabEntity", rightFarActionGrabEntity); - - function cleanup() { - disableDispatcherModule("LeftFarActionGrabEntity"); - disableDispatcherModule("RightFarActionGrabEntity"); - } - Script.scriptEnding.connect(cleanup); -}()); diff --git a/scripts/system/controllers/controllerModules/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/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/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 221af07474..78c50a2318 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, @@ -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 || @@ -561,27 +573,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); @@ -620,6 +611,7 @@ if (typeof module !== 'undefined') { 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,