diff --git a/CMakeGraphvizOptions.cmake b/CMakeGraphvizOptions.cmake new file mode 100644 index 0000000000..f1b73f1cae --- /dev/null +++ b/CMakeGraphvizOptions.cmake @@ -0,0 +1 @@ +set(GRAPHVIZ_EXTERNAL_LIBS FALSE) \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 5cee3357a0..59c787ce4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,20 +30,28 @@ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG") if (WIN32) add_definitions(-DNOMINMAX -D_CRT_SECURE_NO_WARNINGS) - # set path for Microsoft SDKs - # if get build error about missing 'glu32' this path is likely wrong - # Uncomment the line with 8.1 if running Windows 8.1 + + if (NOT WINDOW_SDK_PATH) + set(DEBUG_DISCOVERED_SDK_PATH TRUE) + endif() + + # sets path for Microsoft SDKs + # if you get build error about missing 'glu32' this path is likely wrong if (MSVC10) - set(WINDOW_SDK_PATH "C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1 ") + set(WINDOW_SDK_PATH "C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1 " CACHE PATH "Windows SDK PATH") elseif (MSVC12) if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") set(WINDOW_SDK_FOLDER "x64") else() set(WINDOW_SDK_FOLDER "x86") endif() - set(WINDOW_SDK_PATH "C:\\Program Files (x86)\\Windows Kits\\8.1\\Lib\\winv6.3\\um\\${WINDOW_SDK_FOLDER}") + set(WINDOW_SDK_PATH "C:\\Program Files (x86)\\Windows Kits\\8.1\\Lib\\winv6.3\\um\\${WINDOW_SDK_FOLDER}" CACHE PATH "Windows SDK PATH") endif () - message (WINDOW_SDK_PATH= ${WINDOW_SDK_PATH}) + + if (DEBUG_DISCOVERED_SDK_PATH) + message(STATUS "The discovered Windows SDK path is ${WINDOW_SDK_PATH}") + endif () + set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ${WINDOW_SDK_PATH}) # /wd4351 disables warning C4351: new behavior: elements of array will be default initialized set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4351") @@ -201,6 +209,8 @@ if (NOT DEFINED SERVER_ONLY) set(SERVER_ONLY 0) endif() +set_packaging_parameters() + # add subdirectories for all targets if (NOT ANDROID) add_subdirectory(assignment-client) @@ -232,5 +242,4 @@ if (HIFI_MEMORY_DEBUGGING) endif (UNIX) endif () -include_application_version() generate_installers() diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 3064e97d70..a473a7427c 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -3,12 +3,11 @@ set(TARGET_NAME assignment-client) setup_hifi_project(Core Gui Network Script Quick Widgets WebSockets) # link in the shared libraries -link_hifi_libraries( - audio avatars octree environment gpu model fbx entities +link_hifi_libraries( + audio avatars octree environment gpu model fbx entities networking animation recording shared script-engine embedded-webserver controllers physics ) -include_application_version() package_libraries_for_deployment() -consolidate_stack_components() \ No newline at end of file +consolidate_installer_components() diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index bbee597797..0d62b8dcc7 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include "AssignmentFactory.h" #include "AssignmentActionFactory.h" @@ -61,6 +62,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri DependencyManager::registerInheritance(); auto actionFactory = DependencyManager::set(); + DependencyManager::set(); // setup a thread for the NodeList and its PacketReceiver QThread* nodeThread = new QThread(this); diff --git a/assignment-client/src/entities/AssignmentParentFinder.cpp b/assignment-client/src/entities/AssignmentParentFinder.cpp index 5fe4742b90..cc5c7557dc 100644 --- a/assignment-client/src/entities/AssignmentParentFinder.cpp +++ b/assignment-client/src/entities/AssignmentParentFinder.cpp @@ -11,9 +11,14 @@ #include "AssignmentParentFinder.h" -SpatiallyNestableWeakPointer AssignmentParentFinder::find(QUuid parentID) const { +SpatiallyNestableWeakPointer AssignmentParentFinder::find(QUuid parentID, bool& success) const { SpatiallyNestableWeakPointer parent; // search entities parent = _tree->findEntityByEntityItemID(parentID); + if (parent.expired()) { + success = false; + } else { + success = true; + } return parent; } diff --git a/assignment-client/src/entities/AssignmentParentFinder.h b/assignment-client/src/entities/AssignmentParentFinder.h index 99fa58ffed..9a776bc7dd 100644 --- a/assignment-client/src/entities/AssignmentParentFinder.h +++ b/assignment-client/src/entities/AssignmentParentFinder.h @@ -25,7 +25,7 @@ class AssignmentParentFinder : public SpatialParentFinder { public: AssignmentParentFinder(EntityTreePointer tree) : _tree(tree) { } virtual ~AssignmentParentFinder() { } - virtual SpatiallyNestableWeakPointer find(QUuid parentID) const; + virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success) const; protected: EntityTreePointer _tree; diff --git a/cmake/externals/zlib/CMakeLists.txt b/cmake/externals/zlib/CMakeLists.txt index 06b6b564ba..3bbda322a1 100644 --- a/cmake/externals/zlib/CMakeLists.txt +++ b/cmake/externals/zlib/CMakeLists.txt @@ -5,14 +5,14 @@ include(ExternalProject) ExternalProject_Add( ${EXTERNAL_NAME} - URL http://zlib.net/zlib128.zip + URL http://hifi-public.s3.amazonaws.com/dependencies/zlib128.zip CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build LOG_DOWNLOAD 1 LOG_CONFIGURE 1 LOG_BUILD 1 ) - + # Hide this external target (for ide users) set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") @@ -29,4 +29,4 @@ select_library_configurations(${EXTERNAL_NAME_UPPER}) # Force selected libraries into the cache set(${EXTERNAL_NAME_UPPER}_LIBRARY ${${EXTERNAL_NAME_UPPER}_LIBRARY} CACHE FILEPATH "Location of zlib libraries") -set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE FILEPATH "Location of zlib libraries") +set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE FILEPATH "Location of zlib libraries") diff --git a/cmake/macros/ConsolidateInstallerComponents.cmake b/cmake/macros/ConsolidateInstallerComponents.cmake new file mode 100644 index 0000000000..ea60fb599a --- /dev/null +++ b/cmake/macros/ConsolidateInstallerComponents.cmake @@ -0,0 +1,33 @@ +macro(CONSOLIDATE_INSTALLER_COMPONENTS) + + if (DEPLOY_PACKAGE AND WIN32) + # Copy icon files for interface and stack manager + if (TARGET_NAME STREQUAL "interface") + set (ICON_FILE_PATH "${PROJECT_SOURCE_DIR}/icon/${INTERFACE_ICON}") + set (ICON_DESTINATION_NAME "interface.ico") + + add_custom_command( + TARGET ${TARGET_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy ${ICON_FILE_PATH} ${CMAKE_BINARY_DIR}/package-bundle/${ICON_DESTINATION_NAME} + COMMAND "${CMAKE_COMMAND}" -E copy_directory $ ${CMAKE_BINARY_DIR}/package-bundle + ) + elseif (TARGET_NAME STREQUAL "package-console") + set (ICON_FILE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/resources/${CONSOLE_ICON}") + set (ICON_DESTINATION_NAME "console.ico") + + # add a command to copy the folder produced by electron-packager to package-bundle + add_custom_command( + TARGET ${TARGET_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy ${ICON_FILE_PATH} ${CMAKE_BINARY_DIR}/package-bundle/${ICON_DESTINATION_NAME} + COMMAND "${CMAKE_COMMAND}" -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/${PACKAGED_CONSOLE_FOLDER} ${CMAKE_BINARY_DIR}/package-bundle + ) + else () + # add a command to copy the fixed up binary and libraries to package-bundle + add_custom_command( + TARGET ${TARGET_NAME} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_directory $ ${CMAKE_BINARY_DIR}/package-bundle + ) + endif () + endif () + +endmacro() diff --git a/cmake/macros/ConsolidateStackComponents.cmake b/cmake/macros/ConsolidateStackComponents.cmake deleted file mode 100644 index 2fc20f9506..0000000000 --- a/cmake/macros/ConsolidateStackComponents.cmake +++ /dev/null @@ -1,28 +0,0 @@ -macro(CONSOLIDATE_STACK_COMPONENTS) - - if (DEFINED DEPLOY_PACKAGE AND DEPLOY_PACKAGE) - if (WIN32) - # Copy icon files for interface and stack manager - if (TARGET_NAME STREQUAL "interface" OR TARGET_NAME STREQUAL "stack-manager") - if (TARGET_NAME STREQUAL "interface") - set (ICON_FILE_PATH "${PROJECT_SOURCE_DIR}/icon/${INTERFACE_ICON}") - set (ICON_DESTINATION_NAME "interface.ico") - else () - set (ICON_FILE_PATH "${PROJECT_SOURCE_DIR}/assets/${STACK_MANAGER_ICON}") - set (ICON_DESTINATION_NAME "stack-manager.ico") - endif () - add_custom_command( - TARGET ${TARGET_NAME} POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E copy ${ICON_FILE_PATH} ${CMAKE_BINARY_DIR}/package-bundle/${ICON_DESTINATION_NAME} - COMMAND "${CMAKE_COMMAND}" -E copy_directory $ ${CMAKE_BINARY_DIR}/package-bundle - ) - else () - add_custom_command( - TARGET ${TARGET_NAME} POST_BUILD - COMMAND "${CMAKE_COMMAND}" -E copy_directory $ ${CMAKE_BINARY_DIR}/package-bundle - ) - endif () - endif () - endif () - -endmacro() \ No newline at end of file diff --git a/cmake/macros/GenerateInstallers.cmake b/cmake/macros/GenerateInstallers.cmake index 9cb53c5367..18e5c190a6 100644 --- a/cmake/macros/GenerateInstallers.cmake +++ b/cmake/macros/GenerateInstallers.cmake @@ -1,4 +1,4 @@ -# +# # GenerateInstallers.cmake # cmake/macros # @@ -7,10 +7,10 @@ # # Distributed under the Apache License, Version 2.0. # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -# +# macro(GENERATE_INSTALLERS) - if (DEFINED DEPLOY_PACKAGE AND DEPLOY_PACKAGE AND WIN32) + if (DEPLOY_PACKAGE AND WIN32) file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/package-bundle") find_program(MAKENSIS_COMMAND makensis PATHS [HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\NSIS]) if (NOT MAKENSIS_COMMAND) @@ -26,5 +26,7 @@ macro(GENERATE_INSTALLERS) COMMAND set INSTALLER_DIRECTORY=${INSTALLER_DIRECTORY} COMMAND CMD /C "\"${MAKENSIS_COMMAND}\" ${CMAKE_SOURCE_DIR}/tools/nsis/release.nsi" ) + + set_target_properties(build-package PROPERTIES EXCLUDE_FROM_ALL TRUE FOLDER "Installer") endif () -endmacro() \ No newline at end of file +endmacro() diff --git a/cmake/macros/IncludeApplicationVersion.cmake b/cmake/macros/SetPackagingParameters.cmake similarity index 60% rename from cmake/macros/IncludeApplicationVersion.cmake rename to cmake/macros/SetPackagingParameters.cmake index a91f30f6cf..7f071f4c87 100644 --- a/cmake/macros/IncludeApplicationVersion.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -1,5 +1,5 @@ -# -# IncludeApplicationVersion.cmake +# +# SetPackagingParameters.cmake # cmake/macros # # Created by Leonardo Murillo on 07/14/2015. @@ -7,36 +7,42 @@ # # Distributed under the Apache License, Version 2.0. # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -# -macro(INCLUDE_APPLICATION_VERSION) - # - # We are relying on Jenkins defined environment variables to determine the origin of this build - # and will only package if this is a PR or Release build +# This macro checks some Jenkins defined environment variables to determine the origin of this build +# and decides how targets should be packaged. + +macro(SET_PACKAGING_PARAMETERS) + if (DEFINED ENV{JOB_ID}) set(DEPLOY_PACKAGE 1) set(BUILD_SEQ $ENV{JOB_ID}) + set(BUILD_TAGGED_BETA FALSE) set(INSTALLER_COMPANY "High Fidelity") set(INSTALLER_DIRECTORY "${INSTALLER_COMPANY}") set(INSTALLER_NAME "interface-win64-${BUILD_SEQ}.exe") set(INTERFACE_ICON "interface.ico") - set(STACK_MANAGER_ICON "icon.ico") + set(CONSOLE_ICON "console.ico") elseif (DEFINED ENV{ghprbPullId}) set(DEPLOY_PACKAGE 1) + set(BUILD_TAGGED_BETA TRUE) set(BUILD_SEQ "PR-$ENV{ghprbPullId}") set(INSTALLER_COMPANY "High Fidelity - PR") set(INSTALLER_DIRECTORY "${INSTALLER_COMPANY}\\${BUILD_SEQ}") set(INSTALLER_NAME "pr-interface-win64-${BUILD_SEQ}.exe") set(INTERFACE_ICON "interface-beta.ico") - set(STACK_MANAGER_ICON "icon-beta.ico") + set(CONSOLE_ICON "console-beta.ico") else () set(BUILD_SEQ "dev") + set(BUILD_TAGGED_BETA TRUE) set(INSTALLER_COMPANY "High Fidelity - Dev") set(INSTALLER_DIRECTORY "${INSTALLER_COMPANY}") set(INSTALLER_NAME "dev-interface-win64.exe") set(INTERFACE_ICON "interface-beta.ico") - set(STACK_MANAGER_ICON "icon-beta.ico") + set(CONSOLE_ICON "console-beta.ico") endif () - configure_file("${MACRO_DIR}/ApplicationVersion.h.in" "${PROJECT_BINARY_DIR}/includes/ApplicationVersion.h") - include_directories("${PROJECT_BINARY_DIR}/includes") -endmacro(INCLUDE_APPLICATION_VERSION) + + # create a header file our targets can use to find out the application version + file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/includes") + configure_file("${MACRO_DIR}/ApplicationVersion.h.in" "${CMAKE_BINARY_DIR}/includes/ApplicationVersion.h") + +endmacro(SET_PACKAGING_PARAMETERS) diff --git a/cmake/macros/SetupHifiProject.cmake b/cmake/macros/SetupHifiProject.cmake index 77df280d1a..212dbe6317 100644 --- a/cmake/macros/SetupHifiProject.cmake +++ b/cmake/macros/SetupHifiProject.cmake @@ -1,36 +1,39 @@ -# +# # SetupHifiProject.cmake -# +# # Copyright 2013 High Fidelity, Inc. # # Distributed under the Apache License, Version 2.0. # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -# +# macro(SETUP_HIFI_PROJECT) project(${TARGET_NAME}) - + # grab the implemenation and header files file(GLOB TARGET_SRCS src/*) - + file(GLOB SRC_SUBDIRS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/src/*) - + foreach(DIR ${SRC_SUBDIRS}) if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src/${DIR}") file(GLOB DIR_CONTENTS "src/${DIR}/*") set(TARGET_SRCS ${TARGET_SRCS} "${DIR_CONTENTS}") endif () endforeach() - + if (DEFINED BUILD_BUNDLE AND BUILD_BUNDLE AND APPLE) add_executable(${TARGET_NAME} MACOSX_BUNDLE ${TARGET_SRCS} ${AUTOMTC_SRC} ${AUTOSCRIBE_SHADER_LIB_SRC}) else () add_executable(${TARGET_NAME} ${TARGET_SRCS} ${AUTOMTC_SRC} ${AUTOSCRIBE_SHADER_LIB_SRC}) endif() + # include the generated application version header + target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/includes") + set(${TARGET_NAME}_DEPENDENCY_QT_MODULES ${ARGN}) list(APPEND ${TARGET_NAME}_DEPENDENCY_QT_MODULES Core) - + # find these Qt modules and link them to our own target find_package(Qt5 COMPONENTS ${${TARGET_NAME}_DEPENDENCY_QT_MODULES} REQUIRED) @@ -44,7 +47,7 @@ macro(SETUP_HIFI_PROJECT) foreach(QT_MODULE ${${TARGET_NAME}_DEPENDENCY_QT_MODULES}) target_link_libraries(${TARGET_NAME} Qt5::${QT_MODULE}) endforeach() - + target_glm() - + endmacro() diff --git a/cmake/macros/TargetNsight.cmake b/cmake/macros/TargetNsight.cmake index 4b7e87e9d3..09b056d07a 100644 --- a/cmake/macros/TargetNsight.cmake +++ b/cmake/macros/TargetNsight.cmake @@ -1,20 +1,28 @@ -# +# # Copyright 2015 High Fidelity, Inc. # Created by Bradley Austin Davis on 2015/10/10 # # Distributed under the Apache License, Version 2.0. # See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -# +# macro(TARGET_NSIGHT) - if (WIN32) - if (USE_NSIGHT) + if (WIN32 AND USE_NSIGHT) + + # grab the global CHECKED_FOR_NSIGHT_ONCE property + get_property(NSIGHT_CHECKED GLOBAL PROPERTY CHECKED_FOR_NSIGHT_ONCE) + + if (NOT NSIGHT_CHECKED) # try to find the Nsight package and add it to the build if we find it find_package(NSIGHT) - if (NSIGHT_FOUND) - include_directories(${NSIGHT_INCLUDE_DIRS}) - add_definitions(-DNSIGHT_FOUND) - target_link_libraries(${TARGET_NAME} "${NSIGHT_LIBRARIES}") - endif () - endif() - endif (WIN32) -endmacro() \ No newline at end of file + + # set the global CHECKED_FOR_NSIGHT_ONCE property so that we only debug that we couldn't find it once + set_property(GLOBAL PROPERTY CHECKED_FOR_NSIGHT_ONCE TRUE) + endif () + + if (NSIGHT_FOUND) + include_directories(${NSIGHT_INCLUDE_DIRS}) + add_definitions(-DNSIGHT_FOUND) + target_link_libraries(${TARGET_NAME} "${NSIGHT_LIBRARIES}") + endif () + endif () +endmacro() diff --git a/console/.gitignore b/console/.gitignore index 984bdbd6a5..de75c10412 100644 --- a/console/.gitignore +++ b/console/.gitignore @@ -1,3 +1,4 @@ -High\ Fidelity-*/ +Server\ Console-*/ +server-console-*/ npm-debug.log logs/ diff --git a/console/CMakeLists.txt b/console/CMakeLists.txt index 2641c685e6..490f59acc6 100644 --- a/console/CMakeLists.txt +++ b/console/CMakeLists.txt @@ -1,15 +1,24 @@ -set(PACKAGING_TARGET_NAME package-console) +set(TARGET_NAME package-console) -# add a target that when built will produce an executable of console for this platform -if (APPLE) - set(PACKAGE_COMMAND package-darwin) -elseif (WIN32) - set(PACKAGE_COMMAND package-win) -elseif (UNIX) - set(PACKAGE_COMMAND package-linux) -endif () +if (BUILD_TAGGED_BETA) + set(BETA_OPTION "--beta") +endif() -add_custom_target(${PACKAGING_TARGET_NAME} - COMMAND npm run-script ${PACKAGE_COMMAND} +# add a target that will package the console +add_custom_target(${TARGET_NAME} + COMMAND npm run packager -- --out ${CMAKE_CURRENT_BINARY_DIR} ${BETA_OPTION} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) + +# set the packaged console folder depending on platform, so we can copy it +if (APPLE) + set(PACKAGED_CONSOLE_FOLDER "Server\\ Console-darwin-x64") +elseif (WIN32) + set(PACKAGED_CONSOLE_FOLDER "server-console-win32-x64") +elseif (UNIX) + set(PACKAGED_CONSOLE_FOLDER "server-console-linux-x64") +endif () + +set_target_properties(${TARGET_NAME} PROPERTIES EXCLUDE_FROM_ALL TRUE FOLDER "Installer") + +consolidate_installer_components() diff --git a/console/package.json b/console/package.json index 71a2443984..32cbfb58eb 100644 --- a/console/package.json +++ b/console/package.json @@ -20,9 +20,7 @@ "scripts": { "start": "electron . --local-debug-builds --debug", "local-release": "electron . --local-release-builds --debug", - "package-darwin": "electron-packager . High\\ Fidelity --overwrite --platform=darwin --arch=x64 --version=0.35.4 --icon=resources/console.icns", - "package-win": "electron-packager . High\\ Fidelity --overwrite --platform=win32 --arch=x64 --version=0.35.4 --icon=resources/console.ico", - "package-linux": "electron-packager . High\\ Fidelity --overwrite --platform=linux --arch=x64 --version=0.35.4" + "packager": "node packager.js" }, "dependencies": { "extend": "^3.0.0", diff --git a/console/packager.js b/console/packager.js new file mode 100644 index 0000000000..7f52542fe2 --- /dev/null +++ b/console/packager.js @@ -0,0 +1,57 @@ +var packager = require('electron-packager') +var osType = require('os').type(); + +var platform = null; +if (osType == "Darwin" || osType == "Linux") { + platform = osType.toLowerCase(); +} else if (osType == "Windows_NT") { + platform = "win32" +} + +var argv = require('yargs').argv; + +// check which icon we should use, beta or regular +var iconName = argv.beta ? "console-beta" : "console" + +// setup the common options for the packager +var options = { + dir: __dirname, + name: "server-console", + version: "0.35.4", + overwrite: true, + prune: true, + arch: "x64", + platform: platform, + icon: "resources/" + iconName +} + +const EXEC_NAME = "server-console"; +const SHORT_NAME = "Server Console"; +const FULL_NAME = "High Fidelity Server Console"; + +// setup per OS options +if (osType == "Darwin") { + options["name"] = SHORT_NAME +} else if (osType == "Windows_NT") { + options["version-string"] = { + CompanyName: "High Fidelity, Inc.", + FileDescription: SHORT_NAME, + ProductName: FULL_NAME, + OriginalFilename: EXEC_NAME + ".exe" + } +} + +// check if we were passed a custom out directory, pass it along if so +if (argv.out) { + options.out = argv.out +} + +// call the packager to produce the executable +packager(options, function(error, appPath) { + if (error) { + console.error("There was an error writing the packaged console: " + error.message); + process.exit(1); + } else { + console.log("Wrote new app to " + appPath); + } +}); diff --git a/console/resources/console-beta.icns b/console/resources/console-beta.icns new file mode 100644 index 0000000000..a0c2af881b Binary files /dev/null and b/console/resources/console-beta.icns differ diff --git a/console/resources/console-beta.ico b/console/resources/console-beta.ico new file mode 100644 index 0000000000..90962c4871 Binary files /dev/null and b/console/resources/console-beta.ico differ diff --git a/console/resources/console-beta.png b/console/resources/console-beta.png new file mode 100644 index 0000000000..4fa8e1ce80 Binary files /dev/null and b/console/resources/console-beta.png differ diff --git a/console/src/main.js b/console/src/main.js index d605119ef2..a3fbd9a62d 100644 --- a/console/src/main.js +++ b/console/src/main.js @@ -22,6 +22,8 @@ var progress = require('request-progress'); var Config = require('./modules/config').Config; +const dialog = require('electron').dialog; + var hfprocess = require('./modules/hf-process.js'); var Process = hfprocess.Process; var ACMonitorProcess = hfprocess.ACMonitorProcess; @@ -95,7 +97,6 @@ userConfig.load(configPath); const TRAY_FILENAME = (osType == "Darwin" ? "console-tray-Template.png" : "console-tray.png"); const TRAY_ICON = path.join(__dirname, '../resources/' + TRAY_FILENAME); -const APP_ICON = path.join(__dirname, '../resources/console.png'); // print out uncaught exceptions in the console process.on('uncaughtException', function(err) { @@ -130,8 +131,40 @@ if (argv.localDebugBuilds || argv.localReleaseBuilds) { acPath = pathFinder.discoveredPath("assignment-client", argv.localReleaseBuilds); } +function binaryMissingMessage(displayName, executableName, required) { + var message = "The " + displayName + " executable was not found.\n"; + + if (required) { + message += "It is required for the Server Console to run.\n\n"; + } else { + message += "\n"; + } + + if (debug) { + message += "Please ensure there is a compiled " + displayName + " in a folder named build in this checkout.\n\n"; + message += "It was expected to be found at one of the following paths:\n"; + + var paths = pathFinder.searchPaths(executableName, argv.localReleaseBuilds); + message += paths.join("\n"); + } else { + message += "It is expected to be found beside this executable.\n" + message += "You may need to re-install the Server Console."; + } + + return message; +} + // if at this point any of the paths are null, we're missing something we wanted to find -// TODO: show an error for the binaries that couldn't be found + +if (!dsPath) { + dialog.showErrorBox("Domain Server Not Found", binaryMissingMessage("domain-server", "domain-server", true)); + app.quit(); +} + +if (!acPath) { + dialog.showErrorBox("Assignment Client Not Found", binaryMissingMessage("assignment-client", "assignment-client", true)) + app.quit(); +} function openFileBrowser(path) { // Add quotes around path @@ -207,6 +240,15 @@ LogWindow.prototype = { } }; +function goHomeClicked() { + if (interfacePath) { + startInterface('hifi://localhost'); + } else { + // show an error to say that we can't go home without an interface instance + dialog.showErrorBox("Client Not Found", binaryMissingMessage("High Fidelity Client", "Interface", false)); + } +} + var logWindow = null; function buildMenuArray(serverState) { @@ -222,7 +264,7 @@ function buildMenuArray(serverState) { menuArray = [ { label: 'Go Home', - click: function() { startInterface('hifi://localhost'); }, + click: goHomeClicked, enabled: false }, { @@ -446,18 +488,22 @@ app.on('ready', function() { // Create tray icon tray = new Tray(TRAY_ICON); - tray.setToolTip('High Fidelity'); + tray.setToolTip('High Fidelity Server Console'); + + tray.on('click', function() { + tray.popUpContextMenu(tray.menu); + }); updateTrayMenu(ProcessGroupStates.STOPPED); maybeInstallDefaultContentSet(function() { maybeShowSplash(); - if (interfacePath && dsPath && acPath) { + if (dsPath && acPath) { domainServer = new Process('domain-server', dsPath, [], logPath); - acMonitor = new ACMonitorProcess('ac-monitor', acPath, ['-n4', - '--log-directory', logPath, - '--http-status-port', httpStatusPort], httpStatusPort, logPath); + acMonitor = new ACMonitorProcess('ac-monitor', acPath, ['-n6', + '--log-directory', logPath, + '--http-status-port', httpStatusPort], httpStatusPort, logPath); homeServer = new ProcessGroup('home', [domainServer, acMonitor]); logWindow = new LogWindow(acMonitor, domainServer); diff --git a/console/src/modules/path-finder.js b/console/src/modules/path-finder.js index aed09b3fd3..690389f5f2 100644 --- a/console/src/modules/path-finder.js +++ b/console/src/modules/path-finder.js @@ -1,54 +1,49 @@ var fs = require('fs'); -exports.discoveredPath = function (name, preferRelease) { - var path = "../build/" + name + "/"; - - function binaryFromPath(name, path) { - function platformExtension(name) { - if (name == "Interface") { - if (process.platform == "darwin") { - return ".app/Contents/MacOS/" + name - } else if (process.platform == "win32") { - return ".exe" - } else { - return "" - } +exports.searchPaths = function(name, preferRelease) { + function platformExtension(name) { + if (name == "Interface") { + if (process.platform == "darwin") { + return ".app/Contents/MacOS/" + name + } else if (process.platform == "win32") { + return ".exe" } else { - return process.platform == "win32" ? ".exe" : "" + return "" } + } else { + return process.platform == "win32" ? ".exe" : "" } + } - var extension = platformExtension(name); - var fullPath = path + name + extension; + var extension = platformExtension(name); + var basePath = "../build/" + name + "/"; - try { - var stats = fs.lstatSync(fullPath); + return [ + basePath + name + extension, + basePath + (preferRelease ? "Release/" : "Debug/") + name + extension + ]; +} - if (stats.isFile() || (stats.isDirectory() && extension == ".app")) { - console.log("Found " + name + " at " + fullPath); - return fullPath; +exports.discoveredPath = function (name, preferRelease) { + function binaryFromPaths(name, paths) { + for (var i = 0; i < paths.length; i++) { + var path = paths[i]; + + try { + var stats = fs.lstatSync(path); + + if (stats.isFile() || (stats.isDirectory() && extension == ".app")) { + console.log("Found " + name + " at " + path); + return path; + } + } catch (e) { + console.warn("Executable with name " + name + " not found at path " + path); } - } catch (e) { - console.warn("Executable with name " + name + " not found at path " + fullPath); } return null; } - // does the executable exist at this path already? - // if so assume we're on a platform that doesn't have Debug/Release - // folders and just use the discovered executable - var matchingBinary = binaryFromPath(name, path); - - if (matchingBinary == null) { - if (preferRelease) { - // check if we can find the executable in a Release folder below this path - matchingBinary = binaryFromPath(name, path + "Release/"); - } else { - // check if we can find the executable in a Debug folder below this path - matchingBinary = binaryFromPath(name, path + "Debug/"); - } - } - - return matchingBinary; + // attempt to find a binary at the usual paths, return null if it doesn't exist + return binaryFromPaths(name, this.searchPaths(name, preferRelease)); } diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index 2200916765..6bcda2de4a 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -36,6 +36,5 @@ if (UNIX) target_link_libraries(${TARGET_NAME} ${CMAKE_DL_LIBS}) endif (UNIX) -include_application_version() package_libraries_for_deployment() -consolidate_stack_components() \ No newline at end of file +consolidate_installer_components() diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 8af8731c8d..bab356ee2c 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -23,13 +23,15 @@ var WANT_DEBUG = false; // these tune time-averaging and "on" value for analog trigger // -var TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing -var TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grab -var TRIGGER_GRAB_VALUE = 0.85; // Squeezed far enough to complete distant grab +var TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing +var TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grab +var TRIGGER_GRAB_VALUE = 0.85; // Squeezed far enough to complete distant grab var TRIGGER_OFF_VALUE = 0.15; var BUMPER_ON_VALUE = 0.5; +var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only use head for search/move. + // // distant manipulation // @@ -38,6 +40,7 @@ var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position var DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR = 2.0; // object rotates this much more than hand did var MOVE_WITH_HEAD = true; // experimental head-controll of distantly held objects +var FAR_TO_NEAR_GRAB_PADDING_FACTOR = 1.2; var NO_INTERSECT_COLOR = { red: 10, @@ -57,7 +60,6 @@ var LINE_ENTITY_DIMENSIONS = { var LINE_LENGTH = 500; var PICK_MAX_DISTANCE = 500; // max length of pick-ray - // // near grabbing // @@ -130,6 +132,7 @@ var USE_PARTICLE_BEAM_FOR_SEARCHING = false; var USE_ENTITY_LINES_FOR_MOVING = false; var USE_OVERLAY_LINES_FOR_MOVING = false; var USE_PARTICLE_BEAM_FOR_MOVING = true; +var TEMPORARY_PARTICLE_BEAM_LIFETIME = 30; var USE_SPOTLIGHT = false; var USE_POINTLIGHT = false; @@ -419,7 +422,7 @@ function MyController(hand) { this.searchSphereOn = function(location, size, color) { if (this.searchSphere === null) { var sphereProperties = { - position: location, + position: location, size: size, color: color, alpha: SEARCH_SPHERE_ALPHA, @@ -427,10 +430,15 @@ function MyController(hand) { visible: true } this.searchSphere = Overlays.addOverlay("sphere", sphereProperties); - } else { - Overlays.editOverlay(this.searchSphere, { position: location, size: size, color: color, visible: true }); + } else { + Overlays.editOverlay(this.searchSphere, { + position: location, + size: size, + color: color, + visible: true + }); } - } + } this.overlayLineOn = function(closePoint, farPoint, color) { if (this.overlayLine === null) { @@ -572,6 +580,13 @@ function MyController(hand) { }; + this.renewParticleBeamLifetime = function() { + var props = Entities.getEntityProperties(this.particleBeam, "age"); + Entities.editEntity(this.particleBeam, { + lifetime: TEMPORARY_PARTICLE_BEAM_LIFETIME + props.age // renew lifetime + }) + } + this.evalLightWorldTransform = function(modelPos, modelRot) { var MODEL_LIGHT_POSITION = { @@ -763,7 +778,7 @@ function MyController(hand) { if (this.triggerSmoothedSqueezed() || this.bumperSqueezed()) { this.lastPickTime = 0; var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - this.startingHandRotation = Controller.getPoseValue(controllerHandInput).rotation; + this.startingHandRotation = Controller.getPoseValue(controllerHandInput).rotation; if (this.triggerSmoothedSqueezed()) { this.setState(STATE_SEARCHING); } else { @@ -786,10 +801,16 @@ function MyController(hand) { var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; var currentHandRotation = Controller.getPoseValue(controllerHandInput).rotation; var handDeltaRotation = Quat.multiply(currentHandRotation, Quat.inverse(this.startingHandRotation)); - + var distantPickRay = { - origin: Camera.position, - direction: Quat.getFront(Quat.multiply(Camera.orientation, handDeltaRotation)), + origin: Camera.position, + direction: Vec3.mix(Quat.getUp(this.getHandRotation()), Quat.getFront(Camera.orientation), HAND_HEAD_MIX_RATIO), + length: PICK_MAX_DISTANCE + }; + + var searchVisualizationPickRay = { + origin: handPosition, + direction: Quat.getUp(this.getHandRotation()), length: PICK_MAX_DISTANCE }; @@ -884,11 +905,19 @@ function MyController(hand) { if (intersection.properties.collisionsWillMove && !intersection.properties.locked) { // the hand is far from the intersected object. go into distance-holding mode this.grabbedEntity = intersection.entityID; - if (typeof grabbableData.spatialKey !== 'undefined' && this.state == STATE_EQUIP_SEARCHING) { + if (this.state == STATE_EQUIP_SEARCHING) { // if a distance pick in equip mode hits something with a spatialKey, equip it // TODO use STATE_EQUIP_SPRING here once it works right. // this.setState(STATE_EQUIP_SPRING); + if (typeof grabbableData.spatialKey === 'undefined') { + // We want to give a temporary position offset to this object so it is pulled close to hand + var intersectionPointToCenterDistance = Vec3.length(Vec3.subtract(intersection.intersection, intersection.properties.position)); + this.temporaryPositionOffset = Vec3.normalize(Vec3.subtract(intersection.properties.position, handPosition)); + this.temporaryPositionOffset = Vec3.multiply(this.temporaryPositionOffset, intersectionPointToCenterDistance * FAR_TO_NEAR_GRAB_PADDING_FACTOR); + + } this.setState(STATE_EQUIP); + this.turnOffVisualizations(); return; } else if ((this.state == STATE_SEARCHING) && this.triggerSmoothedGrab()) { this.setState(STATE_DISTANCE_HOLDING); @@ -1013,6 +1042,11 @@ function MyController(hand) { if (USE_PARTICLE_BEAM_FOR_SEARCHING === true) { this.handleParticleBeam(distantPickRay.origin, this.getHandRotation(), NO_INTERSECT_COLOR); } + + if (USE_OVERLAY_LINES_FOR_SEARCHING === true) { + this.overlayLineOn(searchVisualizationPickRay.origin, Vec3.sum(searchVisualizationPickRay.origin, Vec3.multiply(searchVisualizationPickRay.direction, LINE_LENGTH)), NO_INTERSECT_COLOR); + } + if (this.intersectionDistance > 0) { var SPHERE_INTERSECTION_SIZE = 0.011; var SEARCH_SPHERE_FOLLOW_RATE = 0.50; @@ -1022,9 +1056,7 @@ function MyController(hand) { searchSphereLocation.y -= ((this.intersectionDistance - this.searchSphereDistance) / this.intersectionDistance) * SEARCH_SPHERE_CHASE_DROP; this.searchSphereOn(searchSphereLocation, SPHERE_INTERSECTION_SIZE * this.intersectionDistance, this.triggerSmoothedGrab() ? INTERSECT_COLOR : NO_INTERSECT_COLOR); if (USE_OVERLAY_LINES_FOR_SEARCHING === true) { - var OVERLAY_BEAM_SETBACK = 0.9; - var startBeam = Vec3.sum(handPosition, Vec3.multiply(Vec3.subtract(searchSphereLocation, handPosition), OVERLAY_BEAM_SETBACK)); - this.overlayLineOn(startBeam, searchSphereLocation, this.triggerSmoothedGrab() ? INTERSECT_COLOR : NO_INTERSECT_COLOR); + this.overlayLineOn(handPosition, searchSphereLocation, this.triggerSmoothedGrab() ? INTERSECT_COLOR : NO_INTERSECT_COLOR); } } }; @@ -1189,15 +1221,12 @@ function MyController(hand) { y: 0.0, z: objDistance }); - var change = Vec3.subtract(before, after); + var change = Vec3.multiply(Vec3.subtract(before, after), HAND_HEAD_MIX_RATIO); this.currentCameraOrientation = Camera.orientation; this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); } - } else { - // print('should not head move!'); } - var defaultConstraintData = { axisStart: false, axisEnd: false, @@ -1237,131 +1266,144 @@ function MyController(hand) { this.handleSpotlight(this.grabbedEntity); } - Entities.updateAction(this.grabbedEntity, this.actionID, { + var success = Entities.updateAction(this.grabbedEntity, this.actionID, { targetPosition: targetPosition, linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, targetRotation: this.currentObjectRotation, angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, ttl: ACTION_TTL }); + if (success) { + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + } else { + print("continueDistanceHolding -- updateAction failed"); + } + }; + + this.setupHoldAction = function() { + this.actionID = Entities.addAction("hold", this.grabbedEntity, { + hand: this.hand === RIGHT_HAND ? "right" : "left", + timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, + relativePosition: this.offsetPosition, + relativeRotation: this.offsetRotation, + ttl: ACTION_TTL, + kinematic: NEAR_GRABBING_KINEMATIC, + kinematicSetVelocity: true, + ignoreIK: this.ignoreIK + }); + if (this.actionID === NULL_ACTION_ID) { + this.actionID = null; + return false; + } + var now = Date.now(); this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); - + return true; }; this.projectVectorAlongAxis = function(position, axisStart, axisEnd) { - var aPrime = Vec3.subtract(position, axisStart); + var aPrime = Vec3.subtract(position, axisStart); - var bPrime = Vec3.subtract(axisEnd, axisStart); + var bPrime = Vec3.subtract(axisEnd, axisStart); - var bPrimeMagnitude = Vec3.length(bPrime); + var bPrimeMagnitude = Vec3.length(bPrime); - var dotProduct = Vec3.dot(aPrime, bPrime); + var dotProduct = Vec3.dot(aPrime, bPrime); - var scalar = dotProduct / bPrimeMagnitude; + var scalar = dotProduct / bPrimeMagnitude; - if (scalar < 0) { - scalar = 0; - } + if (scalar < 0) { + scalar = 0; + } - if (scalar > 1) { - scalar = 1; - } + if (scalar > 1) { + scalar = 1; + } - var projection = Vec3.sum(axisStart, Vec3.multiply(scalar, Vec3.normalize(bPrime))); + var projection = Vec3.sum(axisStart, Vec3.multiply(scalar, Vec3.normalize(bPrime))); - return projection + return projection - }, + }; - this.nearGrabbing = function() { - var now = Date.now(); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + this.nearGrabbing = function() { + var now = Date.now(); + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); - return; - } + if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + return; + } - this.lineOff(); - this.overlayLineOff(); + this.lineOff(); + this.overlayLineOff(); - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - this.activateEntity(this.grabbedEntity, grabbedProperties); - if (grabbedProperties.collisionsWillMove && NEAR_GRABBING_KINEMATIC) { - Entities.editEntity(this.grabbedEntity, { - collisionsWillMove: false - }); - } - - var handRotation = this.getHandRotation(); - var handPosition = this.getHandPosition(); - - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - - if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) { - // if an object is "equipped" and has a spatialKey, use it. - this.ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; - this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); - this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); - } else { - this.ignoreIK = false; - - var objectRotation = grabbedProperties.rotation; - this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); - - var currentObjectPosition = grabbedProperties.position; - var offset = Vec3.subtract(currentObjectPosition, handPosition); - this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); - } - - this.actionID = NULL_ACTION_ID; - this.actionID = Entities.addAction("hold", this.grabbedEntity, { - hand: this.hand === RIGHT_HAND ? "right" : "left", - timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, - relativePosition: this.offsetPosition, - relativeRotation: this.offsetRotation, - ttl: ACTION_TTL, - kinematic: NEAR_GRABBING_KINEMATIC, - kinematicSetVelocity: true, - ignoreIK: this.ignoreIK + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + this.activateEntity(this.grabbedEntity, grabbedProperties); + if (grabbedProperties.collisionsWillMove && NEAR_GRABBING_KINEMATIC) { + Entities.editEntity(this.grabbedEntity, { + collisionsWillMove: false }); - if (this.actionID === NULL_ACTION_ID) { - this.actionID = null; - } else { - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); - if (this.state == STATE_NEAR_GRABBING) { - this.setState(STATE_CONTINUE_NEAR_GRABBING); - } else { - // equipping - Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); - this.startHandGrasp(); + } - this.setState(STATE_CONTINUE_EQUIP_BD); - } + var handRotation = this.getHandRotation(); + var handPosition = this.getHandPosition(); - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) { + // if an object is "equipped" and has a spatialKey, use it. + this.ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; + this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); + this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); + } else { + this.ignoreIK = false; - Entities.callEntityMethod(this.grabbedEntity, "startNearGrab"); + var objectRotation = grabbedProperties.rotation; + this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); + var currentObjectPosition = grabbedProperties.position; + var offset = Vec3.subtract(currentObjectPosition, handPosition); + this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); + if (this.temporaryPositionOffset && this.state != STATE_NEAR_GRABBING) { + this.offsetPosition = this.temporaryPositionOffset; } + } - this.currentHandControllerTipPosition = - (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; - this.currentObjectTime = Date.now(); - }; + if (!this.setupHoldAction()) { + return; + } + + if (this.state == STATE_NEAR_GRABBING) { + this.setState(STATE_CONTINUE_NEAR_GRABBING); + } else { + // equipping + Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); + this.startHandGrasp(); + + this.setState(STATE_CONTINUE_EQUIP_BD); + } + + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + Entities.callEntityMethod(this.grabbedEntity, "startNearGrab"); + + this.currentHandControllerTipPosition = + (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; + + this.currentObjectTime = Date.now(); + }; this.continueNearGrabbing = function() { if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) { @@ -1406,7 +1448,7 @@ function MyController(hand) { if (this.actionTimeout - now < ACTION_TTL_REFRESH * MSEC_PER_SEC) { // if less than a 5 seconds left, refresh the actions ttl - Entities.updateAction(this.grabbedEntity, this.actionID, { + var success = Entities.updateAction(this.grabbedEntity, this.actionID, { hand: this.hand === RIGHT_HAND ? "right" : "left", timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, relativePosition: this.offsetPosition, @@ -1416,7 +1458,13 @@ function MyController(hand) { kinematicSetVelocity: true, ignoreIK: this.ignoreIK }); - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + if (success) { + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + } else { + print("continueNearGrabbing -- updateAction failed"); + Entities.deleteAction(this.grabbedEntity, this.actionID); + this.setupHoldAction(); + } } }; @@ -1463,7 +1511,7 @@ function MyController(hand) { return; } } else { - Entities.updateAction(this.grabbedEntity, this.equipSpringID, { + var success = Entities.updateAction(this.grabbedEntity, this.equipSpringID, { targetPosition: targetPosition, linearTimeScale: EQUIP_SPRING_TIMEFRAME, targetRotation: targetRotation, @@ -1471,6 +1519,9 @@ function MyController(hand) { ttl: ACTION_TTL, ignoreIK: ignoreIK }); + if (!success) { + print("pullTowardEquipPosition -- updateActionfailed"); + } } if (Vec3.distance(grabbedProperties.position, targetPosition) < EQUIP_SPRING_SHUTOFF_DISTANCE) { @@ -1849,7 +1900,31 @@ function cleanup() { rightController.cleanup(); leftController.cleanup(); Controller.disableMapping(MAPPING_NAME); + if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { + Script.update.disconnect(renewParticleBeamLifetimes); + } +} +Script.scriptEnding.connect(cleanup); +Script.update.connect(update); + +// particle systems can end up hanging around if a user crashes or something else causes controller cleanup not to get called. +// we can't create the search system on-demand since it takes some time for the particles to reach their entire length. +// thus the system cannot have a fixed lifetime. this loop updates the lifetimes and will stop updating if a user crashes. + +if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { + Script.update.connect(renewParticleBeamLifetimes) } -Script.scriptEnding.connect(cleanup); -Script.update.connect(update); \ No newline at end of file +var sinceLastParticleLifetimeUpdate = 0; + +function renewParticleBeamLifetimes(deltaTime) { + //debounce this call since we don't want it 60x a second + sinceLastParticleLifetimeUpdate = sinceLastParticleLifetimeUpdate + deltaTime; + if (sinceLastParticleLifetimeUpdate > TEMPORARY_PARTICLE_BEAM_LIFETIME - 2) { + sinceLastParticleLifetimeUpdate = 0; + } else { + return; + } + rightController.renewParticleBeamLifetime(); + leftController.renewParticleBeamLifetime(); +} \ No newline at end of file diff --git a/examples/actionInspector.js b/examples/debugging/actionInspector.js similarity index 98% rename from examples/actionInspector.js rename to examples/debugging/actionInspector.js index 934120ddf6..303dcbebbd 100644 --- a/examples/actionInspector.js +++ b/examples/debugging/actionInspector.js @@ -1,6 +1,6 @@ // // actionInspector.js -// examples +// examples/debugging/ // // Created by Seth Alves on 2015-9-30. // Copyright 2015 High Fidelity, Inc. @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("libraries/utils.js"); +Script.include("../libraries/utils.js"); var INSPECT_RADIUS = 10; diff --git a/examples/grabInspector.js b/examples/debugging/grabInspector.js similarity index 98% rename from examples/grabInspector.js rename to examples/debugging/grabInspector.js index 8a027f819a..aeea2c20b4 100644 --- a/examples/grabInspector.js +++ b/examples/debugging/grabInspector.js @@ -1,6 +1,6 @@ // // grabInspector.js -// examples +// examples/debugging/ // // Created by Seth Alves on 2015-9-30. // Copyright 2015 High Fidelity, Inc. @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("libraries/utils.js"); +Script.include("../libraries/utils.js"); var INSPECT_RADIUS = 10; var overlays = {}; diff --git a/examples/debugging/queryAACubeInspector.js b/examples/debugging/queryAACubeInspector.js new file mode 100644 index 0000000000..2735617bc1 --- /dev/null +++ b/examples/debugging/queryAACubeInspector.js @@ -0,0 +1,58 @@ +// +// grabInspector.js +// examples/debugging/ +// +// Created by Seth Alves on 2015-12-19. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// This script draws an overlay cube around nearby entities to show their queryAABoxes. + + +Script.include("../libraries/utils.js"); + +var INSPECT_RADIUS = 10; +var overlays = {}; + +function updateOverlay(entityID, queryAACube) { + var cubeCenter = {x: queryAACube.x + queryAACube.scale / 2.0, + y: queryAACube.y + queryAACube.scale / 2.0, + z: queryAACube.z + queryAACube.scale / 2.0}; + + if (entityID in overlays) { + var overlay = overlays[entityID]; + Overlays.editOverlay(overlay, { + position: cubeCenter, + size: queryAACube.scale + }); + } else { + overlays[entityID] = Overlays.addOverlay("cube", { + position: cubeCenter, + size: queryAACube.scale, + color: { red: 0, green: 0, blue: 255}, + alpha: 1, + // borderSize: ..., + solid: false + }); + } +} + +Script.setInterval(function() { + var nearbyEntities = Entities.findEntities(MyAvatar.position, INSPECT_RADIUS); + for (var entityIndex = 0; entityIndex < nearbyEntities.length; entityIndex++) { + var entityID = nearbyEntities[entityIndex]; + var queryAACube = Entities.getEntityProperties(entityID, ["queryAACube"]).queryAACube; + updateOverlay(entityID, queryAACube); + } +}, 100); + +function cleanup() { + for (var entityID in overlays) { + Overlays.deleteOverlay(overlays[entityID]); + } +} + +Script.scriptEnding.connect(cleanup); diff --git a/examples/directory.js b/examples/directory.js index d2a3768051..8d9993ffda 100644 --- a/examples/directory.js +++ b/examples/directory.js @@ -62,8 +62,13 @@ var directory = (function () { function setUp() { viewport = Controller.getViewportDimensions(); - directoryWindow = new OverlayWebWindow('Directory', DIRECTORY_URL, 900, 700, false); - directoryWindow.setVisible(false); + directoryWindow = new OverlayWebWindow({ + title: 'Directory', + source: DIRECTORY_URL, + width: 900, + height: 700, + visible: false + }); directoryButton = Overlays.addOverlay("image", { imageURL: DIRECTORY_BUTTON_URL, diff --git a/examples/edit.js b/examples/edit.js index 074b43c8c1..99219fcaa2 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -140,8 +140,13 @@ var importingSVOTextOverlay = Overlays.addOverlay("text", { }); var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace"; -var marketplaceWindow = new OverlayWebWindow('Marketplace', "about:blank", 900, 700, false); -marketplaceWindow.setVisible(false); +var marketplaceWindow = new OverlayWebWindow({ + title: 'Marketplace', + source: "about:blank", + width: 900, + height: 700, + visible: false +}); function showMarketplace(marketplaceID) { var url = MARKETPLACE_URL; diff --git a/examples/entityScripts/changeColorOnCollision.js b/examples/entityScripts/changeColorOnCollision.js index 69a14f4b52..f0fff4e7a3 100644 --- a/examples/entityScripts/changeColorOnCollision.js +++ b/examples/entityScripts/changeColorOnCollision.js @@ -16,5 +16,12 @@ this.collisionWithEntity = function(myID, otherID, collisionInfo) { Entities.editEntity(myID, { color: { red: getRandomInt(128,255), green: getRandomInt(128,255), blue: getRandomInt(128,255)} }); + print("collisionWithEntity() myID:" + myID + ", otherID:" + otherID); + print(" collisionInfo.type:" + collisionInfo.type); + print(" collisionInfo.idA:" + collisionInfo.idA); + print(" collisionInfo.idB:" + collisionInfo.idB); + Vec3.print(" collisionInfo.penetration:", collisionInfo.penetration); + Vec3.print(" collisionInfo.contactPoint:", collisionInfo.contactPoint); + Vec3.print(" collisionInfo.velocityChange:", collisionInfo.velocityChange); }; }) \ No newline at end of file diff --git a/examples/example/avatarcontrol/doppelganger.js b/examples/example/avatarcontrol/doppelganger.js new file mode 100644 index 0000000000..ffd24f09b2 --- /dev/null +++ b/examples/example/avatarcontrol/doppelganger.js @@ -0,0 +1,121 @@ +// +// doppelganger.js +// +// Created by James B. Pollack @imgntn on 12/28/2015 +// Copyright 2015 High Fidelity, Inc. +// +// This script shows how to hook up a model entity to your avatar to act as a doppelganger. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +// To-Do: mirror joints, rotate avatar fully, automatically get avatar fbx, make sure dimensions for avatar are right when u bring it in + +var TEST_MODEL_URL = 'https://s3.amazonaws.com/hifi-public/ozan/avatars/albert/albert/albert.fbx'; + +var doppelgangers = []; + +function Doppelganger(avatar) { + this.initialProperties = { + name: 'Hifi-Doppelganger', + type: 'Model', + modelURL: TEST_MODEL_URL, + // dimensions: getAvatarDimensions(avatar), + position: putDoppelgangerAcrossFromAvatar(this, avatar), + rotation: rotateDoppelgangerTowardAvatar(this, avatar), + }; + + this.id = createDoppelgangerEntity(this); + this.avatar = avatar; + return this; +} + +function getJointData(avatar) { + var allJointData = []; + var jointNames = MyAvatar.jointNames; + jointNames.forEach(function(joint, index) { + var translation = MyAvatar.getJointTranslation(index); + var rotation = MyAvatar.getJointRotation(index) + allJointData.push({ + joint: joint, + index: index, + translation: translation, + rotation: rotation + }) + }); + + return allJointData; +} + +function setJointData(doppelganger, allJointData) { + var jointRotations = []; + allJointData.forEach(function(jointData, index) { + jointRotations.push(jointData.rotation); + }); + Entities.setAbsoluteJointRotationsInObjectFrame(doppelganger.id, jointRotations); + + return true; +} + +function mirrorJointData() { + return mirroredJointData; +} + +function createDoppelganger(avatar) { + return new Doppelganger(avatar); +} + +function createDoppelgangerEntity(doppelganger) { + return Entities.addEntity(doppelganger.initialProperties); +} + +function putDoppelgangerAcrossFromAvatar(doppelganger, avatar) { + var avatarRot = Quat.fromPitchYawRollDegrees(0, avatar.bodyYaw, 0.0); + var basePosition = Vec3.sum(avatar.position, Vec3.multiply(1.5, Quat.getFront(avatarRot))); + return basePosition; +} + +function getAvatarDimensions(avatar) { + return dimensions; +} + +function rotateDoppelgangerTowardAvatar(doppelganger, avatar) { + var avatarRot = Quat.fromPitchYawRollDegrees(0, avatar.bodyYaw, 0.0); + avatarRot = Vec3.multiply(-1, avatarRot); + return avatarRot; +} + +function connectDoppelgangerUpdates() { + // Script.update.connect(updateDoppelganger); + Script.setInterval(updateDoppelganger, 100); +} + +function disconnectDoppelgangerUpdates() { + Script.update.disconnect(updateDoppelganger); +} + +function updateDoppelganger() { + doppelgangers.forEach(function(doppelganger) { + var joints = getJointData(MyAvatar); + //var mirroredJoints = mirrorJointData(joints); + setJointData(doppelganger, joints); + }); +} + +function makeDoppelgangerForMyAvatar() { + var doppelganger = createDoppelganger(MyAvatar); + doppelgangers.push(doppelganger); + connectDoppelgangerUpdates(); +} + +makeDoppelgangerForMyAvatar(); + +function cleanup() { + disconnectDoppelgangerUpdates(); + + doppelgangers.forEach(function(doppelganger) { + Entities.deleteEntity(doppelganger.id); + }); +} + +Script.scriptEnding.connect(cleanup); diff --git a/examples/example/entities/particlesTest.js b/examples/example/entities/particlesTest.js index e7f88ce468..2ebbe6489e 100644 --- a/examples/example/entities/particlesTest.js +++ b/examples/example/entities/particlesTest.js @@ -71,7 +71,7 @@ Entities.editEntity(particles, { radiusSpread: 0.0, radiusStart: 0.0, - particleRadius: 2 * PARTICLE_RADIUS, // Bezier interpolation used means that middle value isn't intersected + particleRadius: PARTICLE_RADIUS, radiusFinish: 0.0 }); break; diff --git a/examples/flowArts/raveStick/raveStick.js b/examples/flowArts/raveStick/raveStick.js index 5fb019bf97..d1180e2b34 100644 --- a/examples/flowArts/raveStick/raveStick.js +++ b/examples/flowArts/raveStick/raveStick.js @@ -24,6 +24,9 @@ RaveStick = function(spawnPosition) { green: 10, blue: 40 }]; + + + var stick = Entities.addEntity({ type: "Model", name: "raveStick", @@ -40,23 +43,25 @@ RaveStick = function(spawnPosition) { userData: JSON.stringify({ grabbableKey: { spatialKey: { - rightRelativePosition: { - x: 0.02, - y: 0, - z: 0 - }, - leftRelativePosition: { - x: -0.02, - y: 0, - z: 0 - }, - relativeRotation: Quat.fromPitchYawRollDegrees(90, 90, 0) + rightRelativePosition: { + x: 0.02, + y: 0, + z: 0 }, + leftRelativePosition: { + x: -0.02, + y: 0, + z: 0 + }, + relativeRotation: Quat.fromPitchYawRollDegrees(90, 90, 0) + }, invertSolidWhileHeld: true } }) }); + var glowEmitter = createGlowEmitter(); + var light = Entities.addEntity({ type: 'Light', name: "raveLight", @@ -82,14 +87,76 @@ RaveStick = function(spawnPosition) { green: 200, blue: 40 }; - function cleanup() { Entities.deleteEntity(stick); Entities.deleteEntity(light); + Entities.deleteEntity(glowEmitter); } this.cleanup = cleanup; -} \ No newline at end of file + + function createGlowEmitter() { + var props = Entities.getEntityProperties(stick, ["position", "rotation"]); + var forwardVec = Quat.getFront(props.rotation); + var forwardQuat = Quat.rotationBetween(Vec3.UNIT_Z, forwardVec); + var position = props.position; + var color = { + red: 150, + green: 20, + blue: 100 + } + var props = { + type: "ParticleEffect", + name: "Rave Stick Glow Emitter", + position: position, + parentID: stick, + isEmitting: true, + colorStart: color, + color: { + red: 200, + green: 200, + blue: 255 + }, + colorFinish: color, + maxParticles: 100000, + lifespan: 0.8, + emitRate: 1000, + emitOrientation: forwardQuat, + emitSpeed: 0.2, + speedSpread: 0.0, + emitDimensions: { + x: 0, + y: 0, + z: 0 + }, + polarStart: 0, + polarFinish: 0, + azimuthStart: 0.1, + azimuthFinish: 0.01, + emitAcceleration: { + x: 0, + y: 0, + z: 0 + }, + accelerationSpread: { + x: 0.00, + y: 0.00, + z: 0.00 + }, + radiusStart: 0.01, + radiusFinish: 0.005, + alpha: 0.7, + alphaSpread: 0.1, + alphaStart: 0.1, + alphaFinish: 0.1, + textures: "https://s3.amazonaws.com/hifi-public/eric/textures/particleSprites/beamParticle.png", + emitterShouldTrail: false + } + var glowEmitter = Entities.addEntity(props); + return glowEmitter; + + } +} diff --git a/examples/grab.js b/examples/grab.js index 1637e1bcf2..cb0723b8eb 100644 --- a/examples/grab.js +++ b/examples/grab.js @@ -309,7 +309,7 @@ Grabber.prototype.computeNewGrabPlane = function() { } Grabber.prototype.pressEvent = function(event) { - if (!event.isLeftButton) { + if (event.isLeftButton!==true ||event.isRightButton===true || event.isMiddleButton===true) { return; } @@ -374,7 +374,11 @@ Grabber.prototype.pressEvent = function(event) { //Audio.playSound(grabSound, { position: entityProperties.position, volume: VOLUME }); } -Grabber.prototype.releaseEvent = function() { +Grabber.prototype.releaseEvent = function(event) { + if (event.isLeftButton!==true ||event.isRightButton===true || event.isMiddleButton===true) { + return; + } + if (this.isGrabbing) { this.deactivateEntity(this.entityID); this.isGrabbing = false diff --git a/examples/tests/qmlTest.js b/examples/tests/qmlTest.js new file mode 100644 index 0000000000..c891b6a1b7 --- /dev/null +++ b/examples/tests/qmlTest.js @@ -0,0 +1,13 @@ +print("Launching web window"); +qmlWindow = new OverlayWindow({ + title: 'Test Qml', + source: "https://s3.amazonaws.com/DreamingContent/qml/content.qml", + height: 240, + width: 320, + toolWindow: false, + visible: true +}); + +Script.setInterval(function() { + qmlWindow.raise(); +}, 2 * 1000); diff --git a/examples/tests/qmlWebTest.js b/examples/tests/qmlWebTest.js index f905e494dc..5faa68668d 100644 --- a/examples/tests/qmlWebTest.js +++ b/examples/tests/qmlWebTest.js @@ -1,6 +1,7 @@ print("Launching web window"); -webWindow = new OverlayWebWindow('Test Event Bridge', "file:///C:/Users/bdavis/Git/hifi/examples/html/qmlWebTest.html", 320, 240, false); +var htmlUrl = Script.resolvePath("..//html/qmlWebTest.html") +webWindow = new OverlayWebWindow('Test Event Bridge', htmlUrl, 320, 240, false); print("JS Side window: " + webWindow); print("JS Side bridge: " + webWindow.eventBridge); webWindow.eventBridge.webEventReceived.connect(function(data) { diff --git a/examples/toybox/ping_pong_gun/wallTarget.js b/examples/toybox/ping_pong_gun/wallTarget.js index 26e8d320a8..5aa46b1dc7 100644 --- a/examples/toybox/ping_pong_gun/wallTarget.js +++ b/examples/toybox/ping_pong_gun/wallTarget.js @@ -17,34 +17,34 @@ } Target.prototype = { - hasPlayedSound: false, + hasBecomeActive: false, preload: function(entityID) { this.entityID = entityID; var SOUND_URL = "http://hifi-public.s3.amazonaws.com/sounds/Clay_Pigeon_02.L.wav"; this.hitSound = SoundCache.getSound(SOUND_URL); }, collisionWithEntity: function(me, otherEntity) { - var position = Entities.getEntityProperties(me, "position").position; - Entities.editEntity(me, { - gravity: { - x: 0, - y: -9.8, - z: 0 - }, - velocity: { - x: 0, - y: -0.01, - z: 0 - } - }); + if (this.hasBecomeActive === false) { + var position = Entities.getEntityProperties(me, "position").position; + Entities.editEntity(me, { + gravity: { + x: 0, + y: -9.8, + z: 0 + }, + velocity: { + x: 0, + y: -0.01, + z: 0 + } + }); - if (this.hasPlayedSound === false) { this.audioInjector = Audio.playSound(this.hitSound, { position: position, volume: 0.5 }); - this.hasPlayedSound = true; + this.hasBecomeActive = true; } diff --git a/examples/toybox/pistol/pistol.js b/examples/toybox/pistol/pistol.js index 7fb05d992f..e0f063d463 100644 --- a/examples/toybox/pistol/pistol.js +++ b/examples/toybox/pistol/pistol.js @@ -37,6 +37,13 @@ this.bulletForce = 10; this.showLaser = false; + this.laserOffsets = { + y: 0.095 + }; + this.firingOffsets = { + z: 0.16 + } + }; Pistol.prototype = { @@ -272,46 +279,12 @@ }); }, 100); - Entities.editEntity(this.flash, { - isEmitting: true - }); - Script.setTimeout(function() { - Entities.editEntity(_this.flash, { - isEmitting: false - }); - }, 100) - - }, - - preload: function(entityID) { - this.entityID = entityID; - this.laser = Overlays.addOverlay("line3d", { - start: ZERO_VECTOR, - end: ZERO_VECTOR, - color: COLORS.RED, - alpha: 1, - visible: true, - lineWidth: 2 - }); - this.laserOffsets = { - y: 0.095 - }; - this.firingOffsets = { - z: 0.16 - } - var gunProps = Entities.getEntityProperties(this.entityID, ['position', 'rotation']); - var position = gunProps.position; - var rotation = Quat.fromPitchYawRollDegrees(0, 0, 0); - this.firingDirection = Quat.getFront(rotation); - var upVec = Quat.getUp(rotation); - this.barrelPoint = Vec3.sum(position, Vec3.multiply(upVec, this.laserOffsets.y)); - this.barrelPoint = Vec3.sum(this.barrelPoint, Vec3.multiply(this.firingDirection, this.firingOffsets.z)) - - this.flash = Entities.addEntity({ + var flash = Entities.addEntity({ type: "ParticleEffect", position: this.barrelPoint, "name": "Muzzle Flash", - isEmitting: false, + lifetime: 4, + parentID: this.entityID, "color": { red: 228, green: 128, @@ -363,14 +336,27 @@ "additiveBlending": true, "textures": "http://ericrius1.github.io/PartiArt/assets/star.png" }); + Script.setTimeout(function() { + Entities.editEntity(flash, { + isEmitting: false + }); + }, 100) - Script.setTimeout(function() { - Entities.editEntity(_this.flash, {parentID: _this.entityID}); - }, 500) + }, + preload: function(entityID) { + this.entityID = entityID; + this.laser = Overlays.addOverlay("line3d", { + start: ZERO_VECTOR, + end: ZERO_VECTOR, + color: COLORS.RED, + alpha: 1, + visible: true, + lineWidth: 2 + }); }, }; // entity scripts always need to return a newly constructed object of our type return new Pistol(); -}); +}); \ No newline at end of file diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 638f27f367..d20e7dd71f 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -20,12 +20,10 @@ find_package(Qt5LinguistTools REQUIRED) find_package(Qt5LinguistToolsMacros) if (WIN32) - add_definitions(-D_USE_MATH_DEFINES) # apparently needed to get M_PI and other defines from cmath/math.h - add_definitions(-DWINDOWS_LEAN_AND_MEAN) # needed to make sure windows doesn't go to crazy with its defines + add_definitions(-D_USE_MATH_DEFINES) # apparently needed to get M_PI and other defines from cmath/math.h + add_definitions(-DWINDOWS_LEAN_AND_MEAN) # needed to make sure windows doesn't go to crazy with its defines endif() -include_application_version() - # grab the implementation and header files from src dirs file(GLOB_RECURSE INTERFACE_SRCS "src/*.cpp" "src/*.h") GroupSources("src") @@ -45,8 +43,8 @@ else () list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP}) endif () -find_package(Qt5 COMPONENTS - Gui Multimedia Network OpenGL Qml Quick Script Svg +find_package(Qt5 COMPONENTS + Gui Multimedia Network OpenGL Qml Quick Script Svg WebChannel WebEngine WebEngineWidgets WebKitWidgets WebSockets) # grab the ui files in resources/ui @@ -69,19 +67,19 @@ if (APPLE) set(MACOSX_BUNDLE_BUNDLE_NAME Interface) set(MACOSX_BUNDLE_GUI_IDENTIFIER io.highfidelity.Interface) - + if (UPPER_CMAKE_BUILD_TYPE MATCHES RELEASE OR UPPER_CMAKE_BUILD_TYPE MATCHES RELWITHDEBINFO) set(ICON_FILENAME "interface.icns") else () set(ICON_FILENAME "interface-beta.icns") endif () - + # set how the icon shows up in the Info.plist file SET(MACOSX_BUNDLE_ICON_FILE "${ICON_FILENAME}") # set where in the bundle to put the resources file SET_SOURCE_FILES_PROPERTIES(${CMAKE_CURRENT_SOURCE_DIR}/icon/${ICON_FILENAME} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) - + set(DISCOVERED_RESOURCES "") # use the add_resources_to_os_x_bundle macro to recurse into resources @@ -89,7 +87,7 @@ if (APPLE) # append the discovered resources to our list of interface sources list(APPEND INTERFACE_SRCS ${DISCOVERED_RESOURCES}) - + set(INTERFACE_SRCS ${INTERFACE_SRCS} "${CMAKE_CURRENT_SOURCE_DIR}/icon/${ICON_FILENAME}") endif() @@ -102,7 +100,9 @@ else() add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM}) endif() -# These are external plugins, but we need to do the 'add dependency' here so that their +target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/includes") + +# These are external plugins, but we need to do the 'add dependency' here so that their # binary directories get added to the fixup path add_dependency_external_projects(sixense) add_dependency_external_projects(sdl2) @@ -121,11 +121,14 @@ if (WIN32) endif() # link required hifi libraries -link_hifi_libraries(shared octree environment gpu gl procedural model render - recording fbx networking model-networking entities avatars - audio audio-client animation script-engine physics +link_hifi_libraries(shared octree environment gpu gl procedural model render + recording fbx networking model-networking entities avatars + audio audio-client animation script-engine physics render-utils entities-renderer ui auto-updater - controllers plugins display-plugins input-plugins ) + controllers plugins display-plugins input-plugins) + +# include the binary directory of render-utils for shader includes +target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries/render-utils") #fixme find a way to express faceshift as a plugin target_bullet() @@ -138,34 +141,34 @@ endif() # perform standard include and linking for found externals foreach(EXTERNAL ${OPTIONAL_EXTERNALS}) - + if (${${EXTERNAL}_UPPERCASE}_REQUIRED) find_package(${EXTERNAL} REQUIRED) else () find_package(${EXTERNAL}) endif () - + if (${${EXTERNAL}_UPPERCASE}_FOUND AND NOT DISABLE_${${EXTERNAL}_UPPERCASE}) add_definitions(-DHAVE_${${EXTERNAL}_UPPERCASE}) - + # include the library directories (ignoring warnings) if (NOT ${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS) set(${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS ${${${EXTERNAL}_UPPERCASE}_INCLUDE_DIR}) endif () - + include_directories(SYSTEM ${${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS}) - + # perform the system include hack for OS X to ignore warnings if (APPLE) foreach(EXTERNAL_INCLUDE_DIR ${${${EXTERNAL}_UPPERCASE}_INCLUDE_DIRS}) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${EXTERNAL_INCLUDE_DIR}") endforeach() endif () - + if (NOT ${${EXTERNAL}_UPPERCASE}_LIBRARIES) set(${${EXTERNAL}_UPPERCASE}_LIBRARIES ${${${EXTERNAL}_UPPERCASE}_LIBRARY}) endif () - + if (NOT APPLE OR NOT ${${EXTERNAL}_UPPERCASE} MATCHES "SIXENSE") target_link_libraries(${TARGET_NAME} ${${${EXTERNAL}_UPPERCASE}_LIBRARIES}) elseif (APPLE AND NOT INSTALLER_BUILD) @@ -179,13 +182,13 @@ include_directories("${PROJECT_SOURCE_DIR}/src") target_link_libraries( ${TARGET_NAME} - Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL - Qt5::Qml Qt5::Quick Qt5::Script Qt5::Svg + Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL + Qt5::Qml Qt5::Quick Qt5::Script Qt5::Svg Qt5::WebChannel Qt5::WebEngine Qt5::WebEngineWidgets Qt5::WebKitWidgets ) -# Issue causes build failure unless we add this directory. -# See https://bugreports.qt.io/browse/QTBUG-43351 +# Issue causes build failure unless we add this directory. +# See https://bugreports.qt.io/browse/QTBUG-43351 if (WIN32) add_paths_to_fixup_libs(${Qt5_DIR}/../../../plugins/qtwebengine) endif() @@ -199,7 +202,7 @@ if (APPLE) find_library(AppKit AppKit) target_link_libraries(${TARGET_NAME} ${OpenGL} ${AppKit}) - + # install command for OS X bundle INSTALL(TARGETS ${TARGET_NAME} BUNDLE DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/install" COMPONENT Runtime @@ -229,4 +232,4 @@ if (WIN32) endif() package_libraries_for_deployment() -consolidate_stack_components() +consolidate_installer_components() diff --git a/interface/resources/controllers/standard_navigation.json b/interface/resources/controllers/standard_navigation.json new file mode 100644 index 0000000000..208970f030 --- /dev/null +++ b/interface/resources/controllers/standard_navigation.json @@ -0,0 +1,61 @@ +{ + "name": "Standard to Action", + "when": "Application.NavigationFocused", + "channels": [ + { "disabled_from": { "makeAxis" : [ "Standard.DD", "Standard.DU" ] }, "to": "Actions.UiNavVertical" }, + { "disabled_from": { "makeAxis" : [ "Standard.DL", "Standard.DR" ] }, "to": "Actions.UiNavLateral" }, + { "disabled_from": { "makeAxis" : [ "Standard.LB", "Standard.RB" ] }, "to": "Actions.UiNavGroup" }, + { "from": "Standard.DU", "to": "Actions.UiNavVertical" }, + { "from": "Standard.DD", "to": "Actions.UiNavVertical", "filters": "invert" }, + { "from": "Standard.DL", "to": "Actions.UiNavLateral", "filters": "invert" }, + { "from": "Standard.DR", "to": "Actions.UiNavLateral" }, + { "from": "Standard.LB", "to": "Actions.UiNavGroup","filters": "invert" }, + { "from": "Standard.RB", "to": "Actions.UiNavGroup" }, + { "from": [ "Standard.A", "Standard.X" ], "to": "Actions.UiNavSelect" }, + { "from": [ "Standard.B", "Standard.Y", "Standard.RightPrimaryThumb", "Standard.LeftPrimaryThumb" ], "to": "Actions.UiNavBack" }, + { + "from": [ "Standard.RT", "Standard.LT" ], + "to": "Actions.UiNavSelect", + "filters": [ + { "type": "deadZone", "min": 0.5 }, + "constrainToInteger" + ] + }, + { + "from": "Standard.LX", "to": "Actions.UiNavLateral", + "filters": [ + { "type": "deadZone", "min": 0.95 }, + "constrainToInteger", + { "type": "pulse", "interval": 0.4 } + ] + }, + { + "from": "Standard.LY", "to": "Actions.UiNavVertical", + "filters": [ + "invert", + { "type": "deadZone", "min": 0.95 }, + "constrainToInteger", + { "type": "pulse", "interval": 0.4 } + ] + }, + { + "from": "Standard.RX", "to": "Actions.UiNavLateral", + "filters": [ + { "type": "deadZone", "min": 0.95 }, + "constrainToInteger", + { "type": "pulse", "interval": 0.4 } + ] + }, + { + "from": "Standard.RY", "to": "Actions.UiNavVertical", + "filters": [ + "invert", + { "type": "deadZone", "min": 0.95 }, + "constrainToInteger", + { "type": "pulse", "interval": 0.4 } + ] + } + ] +} + + diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml index 3852ea3819..6c94f9d254 100644 --- a/interface/resources/qml/AddressBarDialog.qml +++ b/interface/resources/qml/AddressBarDialog.qml @@ -16,6 +16,7 @@ import "styles" DialogContainer { id: root HifiConstants { id: hifi } + z: 1000 objectName: "AddressBarDialog" @@ -150,6 +151,17 @@ DialogContainer { addressLine.forceActiveFocus() } } + + Timer { + running: root.enabled + interval: 500 + repeat: true + onTriggered: { + if (root.enabled && !addressLine.activeFocus) { + addressLine.forceActiveFocus() + } + } + } onVisibleChanged: { if (!visible) { @@ -169,8 +181,10 @@ DialogContainer { switch (event.key) { case Qt.Key_Escape: case Qt.Key_Back: - enabled = false - event.accepted = true + if (enabled) { + enabled = false + event.accepted = true + } break case Qt.Key_Enter: case Qt.Key_Return: diff --git a/interface/resources/qml/AvatarInputs.qml b/interface/resources/qml/AvatarInputs.qml index 7888ffd967..49aeee3074 100644 --- a/interface/resources/qml/AvatarInputs.qml +++ b/interface/resources/qml/AvatarInputs.qml @@ -84,6 +84,7 @@ Hifi.AvatarInputs { Item { width: root.mirrorWidth height: 44 + visible: !root.isHMD x: root.mirrorLeftPad y: root.mirrorVisible ? root.mirrorTopPad + root.mirrorHeight : 5 diff --git a/interface/resources/qml/ErrorDialog.qml b/interface/resources/qml/ErrorDialog.qml index 76d9111d97..4ace671c52 100644 --- a/interface/resources/qml/ErrorDialog.qml +++ b/interface/resources/qml/ErrorDialog.qml @@ -96,6 +96,9 @@ DialogContainer { } Keys.onPressed: { + if (!enabled) { + return + } switch (event.key) { case Qt.Key_Escape: case Qt.Key_Back: diff --git a/interface/resources/qml/Global.js b/interface/resources/qml/Global.js new file mode 100644 index 0000000000..d41dde3c61 --- /dev/null +++ b/interface/resources/qml/Global.js @@ -0,0 +1,165 @@ +var OFFSCREEN_ROOT_OBJECT_NAME = "desktopRoot" +var OFFSCREEN_WINDOW_OBJECT_NAME = "topLevelWindow" + +function findChild(item, name) { + for (var i = 0; i < item.children.length; ++i) { + if (item.children[i].objectName === name) { + return item.children[i]; + } + } + return null; +} + +function findParent(item, name) { + while (item) { + if (item.objectName === name) { + return item; + } + item = item.parent; + } + return null; +} + +function getDesktop(item) { + return findParent(item, OFFSCREEN_ROOT_OBJECT_NAME); +} + +function findRootMenu(item) { + item = getDesktop(item); + return item ? item.rootMenu : null; +} + + +function getTopLevelWindows(item) { + var desktop = getDesktop(item); + var currentWindows = []; + if (!desktop) { + console.log("Could not find desktop for " + item) + return currentWindows; + } + + for (var i = 0; i < desktop.children.length; ++i) { + var child = desktop.children[i]; + if (Global.OFFSCREEN_WINDOW_OBJECT_NAME === child.objectName) { + var windowId = child.toString(); + currentWindows.push(child) + } + } + return currentWindows; +} + + +function getDesktopWindow(item) { + item = findParent(item, OFFSCREEN_WINDOW_OBJECT_NAME); + return item; +} + +function closeWindow(item) { + item = findDialog(item); + if (item) { + item.enabled = false + } else { + console.warn("Could not find top level dialog") + } +} + +function findMenuChild(menu, childName) { + if (!menu) { + return null; + } + + if (menu.type !== 2) { + console.warn("Tried to find child of a non-menu"); + return null; + } + + var items = menu.items; + var count = items.length; + for (var i = 0; i < count; ++i) { + var child = items[i]; + var name; + switch (child.type) { + case 2: + name = child.title; + break; + case 1: + name = child.text; + break; + default: + break; + } + + if (name && name === childName) { + return child; + } + } +} + +function findMenu(rootMenu, path) { + if ('string' === typeof(path)) { + path = [ path ] + } + + var currentMenu = rootMenu; + for (var i = 0; currentMenu && i < path.length; ++i) { + currentMenu = findMenuChild(currentMenu, path[i]); + } + + return currentMenu; +} + +function findInRootMenu(item, path) { + return findMenu(findRootMenu(item), path); +} + + +function menuItemsToListModel(parent, items) { + var newListModel = Qt.createQmlObject('import QtQuick 2.5; ListModel {}', parent); + for (var i = 0; i < items.length; ++i) { + var item = items[i]; + switch (item.type) { + case 2: + newListModel.append({"type":item.type, "name": item.title, "item": item}) + break; + case 1: + newListModel.append({"type":item.type, "name": item.text, "item": item}) + break; + case 0: + newListModel.append({"type":item.type, "name": "-----", "item": item}) + break; + } + } + return newListModel; +} + +function raiseWindow(item) { + var targetWindow = getDesktopWindow(item); + if (!targetWindow) { + console.warn("Could not find top level window for " + item); + return; + } + + var desktop = getDesktop(targetWindow); + if (!desktop) { + //console.warn("Could not find desktop for window " + targetWindow); + return; + } + + var maxZ = 0; + var minZ = 100000; + var windows = desktop.windows; + windows.sort(function(a, b){ + return a.z - b.z; + }); + var lastZ = -1; + var lastTargetZ = -1; + for (var i = 0; i < windows.length; ++i) { + var window = windows[i]; + if (window.z > lastZ) { + lastZ = window.z; + ++lastTargetZ; + } + window.z = lastTargetZ; + } + targetWindow.z = lastTargetZ + 1; +} diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index 29264fa608..3ce5251771 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -343,8 +343,10 @@ DialogContainer { switch (event.key) { case Qt.Key_Escape: case Qt.Key_Back: - enabled = false - event.accepted = true + if (enabled) { + enabled = false + event.accepted = true + } break case Qt.Key_Enter: case Qt.Key_Return: diff --git a/interface/resources/qml/MessageDialog.qml b/interface/resources/qml/MessageDialog.qml index e8b01df9d0..73fb135ac4 100644 --- a/interface/resources/qml/MessageDialog.qml +++ b/interface/resources/qml/MessageDialog.qml @@ -300,6 +300,10 @@ VrDialog { Keys.onPressed: { + if (!enabled) { + return + } + if (event.modifiers === Qt.ControlModifier) switch (event.key) { case Qt.Key_A: diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index 6e9502edb2..008aaeccc3 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -1,9 +1,6 @@ - import QtQuick 2.3 import QtQuick.Controls 1.2 import QtWebEngine 1.1 -import QtWebChannel 1.0 -import QtWebSockets 1.0 import "controls" import "styles" @@ -13,6 +10,8 @@ VrDialog { HifiConstants { id: hifi } title: "WebWindow" resizable: true + enabled: false + visible: false // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer destroyOnCloseButton: false contentImplicitWidth: clientArea.implicitWidth @@ -24,23 +23,13 @@ VrDialog { function stop() { webview.stop(); } - Component.onCompleted: { - enabled = true - console.log("Web Window Created " + root); + // Ensure the JS from the web-engine makes it to our logging webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message); }); - webview.loadingChanged.connect(handleWebviewLoading) - } - - function handleWebviewLoading(loadRequest) { - if (WebEngineView.LoadStartedStatus == loadRequest.status) { - var newUrl = loadRequest.url.toString(); - root.navigating(newUrl) - } } Item { @@ -56,13 +45,38 @@ VrDialog { id: webview url: root.source anchors.fill: parent + focus: true + + property var originalUrl + property var lastFixupTime: 0 + onUrlChanged: { var currentUrl = url.toString(); - var newUrl = urlFixer.fixupUrl(currentUrl); + var newUrl = urlHandler.fixupUrl(currentUrl).toString(); if (newUrl != currentUrl) { + var now = new Date().valueOf(); + if (url === originalUrl && (now - lastFixupTime < 100)) { + console.warn("URL fixup loop detected") + return; + } + originalUrl = url + lastFixupTime = now url = newUrl; } } + + onLoadingChanged: { + // Required to support clicking on "hifi://" links + if (WebEngineView.LoadStartedStatus == loadRequest.status) { + var url = loadRequest.url.toString(); + if (urlHandler.canHandleUrl(url)) { + if (urlHandler.handleUrl(url)) { + webview.stop(); + } + } + } + } + profile: WebEngineProfile { id: webviewProfile httpUserAgent: "Mozilla/5.0 (HighFidelityInterface)" diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml new file mode 100644 index 0000000000..f8217371e7 --- /dev/null +++ b/interface/resources/qml/QmlWindow.qml @@ -0,0 +1,56 @@ + +import QtQuick 2.3 +import QtQuick.Controls 1.2 +import QtWebChannel 1.0 +import QtWebSockets 1.0 +import "qrc:///qtwebchannel/qwebchannel.js" as WebChannel + +import "Global.js" as Global + +import "controls" +import "styles" + +VrDialog { + id: root + HifiConstants { id: hifi } + title: "QmlWindow" + resizable: true + enabled: false + visible: false + focus: true + property var channel; + + // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer + destroyOnCloseButton: false + contentImplicitWidth: clientArea.implicitWidth + contentImplicitHeight: clientArea.implicitHeight + property alias source: pageLoader.source + + function raiseWindow() { + Global.raiseWindow(root) + } + + Item { + id: clientArea + implicitHeight: 600 + implicitWidth: 800 + x: root.clientX + y: root.clientY + width: root.clientWidth + height: root.clientHeight + focus: true + clip: true + + Loader { + id: pageLoader + objectName: "Loader" + anchors.fill: parent + focus: true + property var dialog: root + + Keys.onPressed: { + console.log("QmlWindow pageLoader keypress") + } + } + } // item +} // dialog diff --git a/interface/resources/qml/Root.qml b/interface/resources/qml/Root.qml index 1b0f09558f..49262cb7db 100644 --- a/interface/resources/qml/Root.qml +++ b/interface/resources/qml/Root.qml @@ -1,11 +1,23 @@ import Hifi 1.0 -import QtQuick 2.3 +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +import "Global.js" as Global // This is our primary 'window' object to which all dialogs and controls will // be childed. Root { id: root - anchors.fill: parent + objectName: Global.OFFSCREEN_ROOT_OBJECT_NAME + anchors.fill: parent; + property var windows: []; + property var rootMenu: Menu { + objectName: "rootMenu" + } + + onChildrenChanged: { + windows = Global.getTopLevelWindows(root); + } onParentChanged: { forceActiveFocus(); diff --git a/interface/resources/qml/RootMenu.qml b/interface/resources/qml/RootMenu.qml deleted file mode 100644 index b8c81a6589..0000000000 --- a/interface/resources/qml/RootMenu.qml +++ /dev/null @@ -1,9 +0,0 @@ -import QtQuick 2.4 -import QtQuick.Controls 1.3 - -Item { - Menu { - id: root - objectName: "rootMenu" - } -} diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 56d4f9c14b..9954cab063 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -258,14 +258,14 @@ Item { Text { color: root.fontColor; font.pixelSize: root.fontSize - visible: root.expanded + visible: root.showAcuity text: "LOD: " + root.lodStatus; } Text { color: root.fontColor; font.pixelSize: root.fontSize - visible: root.expanded - text: "Renderable avatars: " + root.avatarRenderableCount + " w/in " + root.avatarRenderDistance + "m"; + visible: root.expanded && !root.showAcuity + text: root.lodStatsRenderText; } } } diff --git a/interface/resources/qml/VrMenu.qml b/interface/resources/qml/VrMenu.qml index 14a4a449fd..738ec34a02 100644 --- a/interface/resources/qml/VrMenu.qml +++ b/interface/resources/qml/VrMenu.qml @@ -1,7 +1,9 @@ import Hifi 1.0 as Hifi + import QtQuick 2.4 import QtQuick.Controls 1.3 import QtQuick.Controls.Styles 1.3 + import "controls" import "styles" @@ -21,15 +23,12 @@ Hifi.VrMenu { property var models: [] property var columns: [] - onEnabledChanged: { if (enabled && columns.length == 0) { pushColumn(rootMenu.items); } opacity = enabled ? 1.0 : 0.0 - if (enabled) { - forceActiveFocus() - } + offscreenFlags.navigationFocused = enabled; } // The actual animator @@ -49,13 +48,12 @@ Hifi.VrMenu { } property var menuBuilder: Component { - Border { - HifiConstants { id: hifi } - property int menuDepth + VrMenuView { + property int menuDepth: root.models.length - 1 + model: root.models[menuDepth] Component.onCompleted: { - menuDepth = root.models.length - 1 - if (menuDepth == 0) { + if (menuDepth === 0) { x = lastMousePosition.x - 20 y = lastMousePosition.y - 20 } else { @@ -65,48 +63,8 @@ Hifi.VrMenu { } } - border.color: hifi.colors.hifiBlue - color: hifi.colors.window - implicitHeight: listView.implicitHeight + 16 - implicitWidth: listView.implicitWidth + 16 - - Column { - id: listView - property real minWidth: 0 - anchors { - top: parent.top - topMargin: 8 - left: parent.left - leftMargin: 8 - right: parent.right - rightMargin: 8 - } - - Repeater { - model: root.models[menuDepth] - delegate: Loader { - id: loader - source: "VrMenuItem.qml" - Binding { - target: loader.item - property: "menuContainer" - value: root - when: loader.status == Loader.Ready - } - Binding { - target: loader.item - property: "source" - value: modelData - when: loader.status == Loader.Ready - } - Binding { - target: loader.item - property: "listView" - value: listView - when: loader.status == Loader.Ready - } - } - } + onSelected: { + root.selectItem(menuDepth, item) } } } @@ -116,14 +74,14 @@ Hifi.VrMenu { } function pushColumn(items) { - models.push(items) + models.push(itemsToModel(items)) if (columns.length) { var oldColumn = lastColumn(); //oldColumn.enabled = false } var newColumn = menuBuilder.createObject(root); columns.push(newColumn); - newColumn.forceActiveFocus(); + forceActiveFocus(); } function popColumn() { @@ -145,13 +103,39 @@ Hifi.VrMenu { curColumn.forceActiveFocus(); } - function selectItem(source) { + function itemsToModel(items) { + var newListModel = Qt.createQmlObject('import QtQuick 2.2; ListModel {}', root); + for (var i = 0; i < items.length; ++i) { + var item = items[i]; + switch (item.type) { + case 2: + newListModel.append({"type":item.type, "name": item.title, "item": item}) + break; + case 1: + newListModel.append({"type":item.type, "name": item.text, "item": item}) + break; + case 0: + newListModel.append({"type":item.type, "name": "-----", "item": item}) + break; + } + } + return newListModel; + } + + function selectItem(depth, source) { + var popped = false; + while (depth + 1 < columns.length) { + popColumn() + popped = true + } + switch (source.type) { case 2: + lastColumn().enabled = false pushColumn(source.items) break; case 1: - source.trigger() + if (!popped) source.trigger() enabled = false break; case 0: @@ -165,14 +149,6 @@ Hifi.VrMenu { } } - Keys.onPressed: { - switch (event.key) { - case Qt.Key_Escape: - root.popColumn() - event.accepted = true; - } - } - MouseArea { anchors.fill: parent id: mouseArea @@ -206,4 +182,36 @@ Hifi.VrMenu { function removeItem(menu, menuItem) { menu.removeItem(menuItem); } + + function previousItem() { + if (columns.length) { + lastColumn().incrementCurrentIndex() + } + } + + function nextItem() { + if (columns.length) { + lastColumn().decrementCurrentIndex() + } + } + + function selectCurrentItem() { + if (columns.length) { + var depth = columns.length - 1; + var index = lastColumn().currentIndex; + if (index >= 0) { + var model = models[depth]; + var item = model.get(index).item; + selectItem(depth, item); + } + } + } + + Keys.onDownPressed: previousItem(); + Keys.onUpPressed: nextItem(); + Keys.onSpacePressed: selectCurrentItem(); + Keys.onReturnPressed: selectCurrentItem(); + Keys.onRightPressed: selectCurrentItem(); + Keys.onLeftPressed: popColumn(); + Keys.onEscapePressed: popColumn(); } diff --git a/interface/resources/qml/VrMenuItem.qml b/interface/resources/qml/VrMenuItem.qml index fbde35059d..2b1a4a3b5a 100644 --- a/interface/resources/qml/VrMenuItem.qml +++ b/interface/resources/qml/VrMenuItem.qml @@ -6,57 +6,18 @@ import "styles" Item { id: root - HifiConstants { - id: hifi + HifiConstants { + id: hifi } + // The model object + property alias text: label.text property var source - property var menuContainer - property var listView - - MouseArea { - anchors.left: parent.left - anchors.right: tag.right - anchors.rightMargin: -4 - anchors.top: parent.top - anchors.bottom: parent.bottom - acceptedButtons: Qt.LeftButton - hoverEnabled: true - - Rectangle { - id: highlight - visible: false - anchors.fill: parent - color: "#7f0e7077" - } - - onEntered: { - //if (source.type == 2 && enabled) { - // timer.start() - //} - highlight.visible = source.enabled - } - - onExited: { - timer.stop() - highlight.visible = false - } - - onClicked: { - select() - } - } implicitHeight: source.visible ? label.implicitHeight * 1.5 : 0 - implicitWidth: label.implicitWidth + label.height * 2.5 + implicitWidth: label.width + label.height * 2.5 visible: source.visible - - Timer { - id: timer - interval: 1000 - onTriggered: parent.select() - } - + width: parent.width FontAwesome { clip: true @@ -84,32 +45,18 @@ Item { Text { id: label - text: typedText() anchors.left: check.right anchors.leftMargin: 4 anchors.verticalCenter: parent.verticalCenter verticalAlignment: Text.AlignVCenter color: source.enabled ? hifi.colors.text : hifi.colors.disabledText - enabled: source.enabled && source.visible + enabled: source.visible && (source.type !== 0 ? source.enabled : false) visible: source.visible - function typedText() { - if (source) { - switch (source.type) { - case 2: - return source.title - case 1: - return source.text - case 0: - return "-----" - } - } - return "" - } } FontAwesome { id: tag - x: listView.width - width - 4 + x: root.parent.width - width size: label.height width: implicitWidth visible: source.visible && (source.type == 2) @@ -117,17 +64,4 @@ Item { anchors.verticalCenter: parent.verticalCenter color: label.color } - - function select() { - //timer.stop(); - var popped = false - while (columns.length - 1 > listView.parent.menuDepth) { - popColumn() - popped = true - } - - if (!popped || source.type != 1) { - root.menuContainer.selectItem(source) - } - } } diff --git a/interface/resources/qml/VrMenuView.qml b/interface/resources/qml/VrMenuView.qml new file mode 100644 index 0000000000..b00e21ba93 --- /dev/null +++ b/interface/resources/qml/VrMenuView.qml @@ -0,0 +1,77 @@ +import QtQuick 2.4 +import QtQuick.Controls 1.3 +import QtQuick.Controls.Styles 1.3 + +import "styles" + +ListView { + id: root + HifiConstants { id: hifi } + width: 128 + height: count * 32 + onEnabledChanged: recalcSize(); + onVisibleChanged: recalcSize(); + onCountChanged: recalcSize(); + + signal selected(var item) + + highlight: Rectangle { + width: root.currentItem ? root.currentItem.width : 0 + height: root.currentItem ? root.currentItem.height : 0 + color: "lightsteelblue"; radius: 3 + } + + delegate: VrMenuItem { + text: name + source: item + onImplicitHeightChanged: root.recalcSize() + onImplicitWidthChanged: root.recalcSize() + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onEntered: root.currentIndex = index + onClicked: root.selected(item) + } + } + + function recalcSize() { + if (model.count !== count || !visible) { + return; + } + + var originalIndex = currentIndex; + var maxWidth = width; + var newHeight = 0; + for (var i = 0; i < count; ++i) { + currentIndex = i; + if (!currentItem) { + continue; + } + if (currentItem && currentItem.implicitWidth > maxWidth) { + maxWidth = currentItem.implicitWidth + } + if (currentItem.visible) { + newHeight += currentItem.implicitHeight + } + } + if (maxWidth > width) { + width = maxWidth; + } + if (newHeight > height) { + height = newHeight + } + currentIndex = originalIndex; + } + + Border { + id: border + anchors.fill: parent + anchors.margins: -8 + z: parent.z - 1 + border.color: hifi.colors.hifiBlue + color: hifi.colors.window + } +} + + diff --git a/interface/resources/qml/controls/DialogBase.qml b/interface/resources/qml/controls/DialogBase.qml index a6616fc731..9fb2a47907 100644 --- a/interface/resources/qml/controls/DialogBase.qml +++ b/interface/resources/qml/controls/DialogBase.qml @@ -5,6 +5,7 @@ import "../styles" Item { id: root + objectName: "topLevelWindow" HifiConstants { id: hifi } implicitHeight: contentImplicitHeight + titleBorder.height + hifi.styles.borderWidth implicitWidth: contentImplicitWidth + hifi.styles.borderWidth * 2 diff --git a/interface/resources/qml/controls/VrDialog.qml b/interface/resources/qml/controls/VrDialog.qml index aa14e2fcba..18cab04533 100644 --- a/interface/resources/qml/controls/VrDialog.qml +++ b/interface/resources/qml/controls/VrDialog.qml @@ -3,6 +3,8 @@ import QtQuick.Controls 1.2 import "." import "../styles" +import "../Global.js" as Global + /* * FIXME Need to create a client property here so that objects can be * placed in it without having to think about positioning within the outer @@ -41,8 +43,22 @@ DialogBase { // modify the visibility onEnabledChanged: { opacity = enabled ? 1.0 : 0.0 + // If the dialog is initially invisible, setting opacity doesn't + // trigger making it visible. + if (enabled) { + visible = true; + if (root.parent) { + Global.raiseWindow(root); + } + } } + onParentChanged: { + if (enabled && parent) { + Global.raiseWindow(root); + } + } + // The actual animator Behavior on opacity { NumberAnimation { @@ -55,6 +71,12 @@ DialogBase { onOpacityChanged: { visible = (opacity != 0.0); } + + Component.onCompleted: { + if (visible) { + Global.raiseWindow(root); + } + } // Some dialogs should be destroyed when they become invisible, // so handle that @@ -113,6 +135,7 @@ DialogBase { y: root.titleY width: root.titleWidth height: root.titleHeight + onClicked: Global.raiseWindow(root) drag { target: root diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 232ed1c2c5..46b4d4172d 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -11,6 +11,8 @@ #include "Application.h" +#include + #include #include #include @@ -18,6 +20,8 @@ #include #include +#include +#include #include #include #include @@ -28,7 +32,6 @@ #include #include #include -#include #include #include #include @@ -36,6 +39,10 @@ #include #include +#include +#include +#include + #include #include #include @@ -50,6 +57,7 @@ #include #include +#include #include #include #include @@ -83,6 +91,7 @@ #include #include #include +#include #include #include #include @@ -181,6 +190,7 @@ static const QString JS_EXTENSION = ".js"; static const QString FST_EXTENSION = ".fst"; static const QString FBX_EXTENSION = ".fbx"; static const QString OBJ_EXTENSION = ".obj"; +static const QString AVA_JSON_EXTENSION = ".ava.json"; static const int MIRROR_VIEW_TOP_PADDING = 5; static const int MIRROR_VIEW_LEFT_PADDING = 10; @@ -216,7 +226,8 @@ const QHash Application::_acceptedExtensi { SVO_EXTENSION, &Application::importSVOFromURL }, { SVO_JSON_EXTENSION, &Application::importSVOFromURL }, { JS_EXTENSION, &Application::askToLoadScript }, - { FST_EXTENSION, &Application::askToSetAvatarUrl } + { FST_EXTENSION, &Application::askToSetAvatarUrl }, + { AVA_JSON_EXTENSION, &Application::askToWearAvatarAttachmentUrl } }; #ifdef Q_OS_WIN @@ -340,6 +351,8 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); + #if defined(Q_OS_MAC) || defined(Q_OS_WIN) DependencyManager::set(); @@ -370,7 +383,7 @@ PluginContainer* _pluginContainer; // FIXME hack access to the internal share context for the Chromium helper -// Normally we'd want to use QWebEngine::initialize(), but we can't because +// Normally we'd want to use QWebEngine::initialize(), but we can't because // our primary context is a QGLWidget, which can't easily be initialized to share // from a QOpenGLContext. // @@ -685,6 +698,76 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // Setup the userInputMapper with the actions auto userInputMapper = DependencyManager::get(); connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) { + using namespace controller; + auto offscreenUi = DependencyManager::get(); + if (offscreenUi->navigationFocused()) { + auto actionEnum = static_cast(action); + int key = Qt::Key_unknown; + static int lastKey = Qt::Key_unknown; + bool navAxis = false; + switch (actionEnum) { + case Action::UI_NAV_VERTICAL: + navAxis = true; + if (state > 0.0f) { + key = Qt::Key_Up; + } else if (state < 0.0f) { + key = Qt::Key_Down; + } + break; + + case Action::UI_NAV_LATERAL: + navAxis = true; + if (state > 0.0f) { + key = Qt::Key_Right; + } else if (state < 0.0f) { + key = Qt::Key_Left; + } + break; + + case Action::UI_NAV_GROUP: + navAxis = true; + if (state > 0.0f) { + key = Qt::Key_Tab; + } else if (state < 0.0f) { + key = Qt::Key_Backtab; + } + break; + + case Action::UI_NAV_BACK: + key = Qt::Key_Escape; + break; + + case Action::UI_NAV_SELECT: + key = Qt::Key_Return; + break; + default: + break; + } + + if (navAxis) { + if (lastKey != Qt::Key_unknown) { + QKeyEvent event(QEvent::KeyRelease, lastKey, Qt::NoModifier); + sendEvent(offscreenUi->getWindow(), &event); + lastKey = Qt::Key_unknown; + } + + if (key != Qt::Key_unknown) { + QKeyEvent event(QEvent::KeyPress, key, Qt::NoModifier); + sendEvent(offscreenUi->getWindow(), &event); + lastKey = key; + } + } else if (key != Qt::Key_unknown) { + if (state) { + QKeyEvent event(QEvent::KeyPress, key, Qt::NoModifier); + sendEvent(offscreenUi->getWindow(), &event); + } else { + QKeyEvent event(QEvent::KeyRelease, key, Qt::NoModifier); + sendEvent(offscreenUi->getWindow(), &event); + } + return; + } + } + if (action == controller::toInt(controller::Action::RETICLE_CLICK)) { auto globalPos = QCursor::pos(); auto localPos = _glWidget->mapFromGlobal(globalPos); @@ -755,6 +838,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : _applicationStateDevice->addInputVariant(QString("Grounded"), controller::StateController::ReadLambda([]() -> float { return (float)qApp->getMyAvatar()->getCharacterController()->onGround(); })); + _applicationStateDevice->addInputVariant(QString("NavigationFocused"), controller::StateController::ReadLambda([]() -> float { + auto offscreenUi = DependencyManager::get(); + return offscreenUi->navigationFocused() ? 1.0 : 0.0; + })); userInputMapper->registerDevice(_applicationStateDevice); @@ -955,7 +1042,7 @@ void Application::cleanupBeforeQuit() { // destroy the AudioClient so it and its thread have a chance to go down safely DependencyManager::destroy(); - + // destroy the AudioInjectorManager so it and its thread have a chance to go down safely // this will also stop any ongoing network injectors DependencyManager::destroy(); @@ -967,6 +1054,8 @@ void Application::cleanupBeforeQuit() { #ifdef HAVE_IVIEWHMD DependencyManager::destroy(); #endif + + DependencyManager::destroy(); } void Application::emptyLocalCache() { @@ -1000,8 +1089,8 @@ Application::~Application() { // remove avatars from physics engine DependencyManager::get()->clearOtherAvatars(); VectorOfMotionStates motionStates; - DependencyManager::get()->getObjectsToDelete(motionStates); - _physicsEngine->deleteObjects(motionStates); + DependencyManager::get()->getObjectsToRemoveFromPhysics(motionStates); + _physicsEngine->removeObjects(motionStates); DependencyManager::destroy(); DependencyManager::destroy(); @@ -1096,10 +1185,59 @@ void Application::initializeUi() { offscreenUi->setProxyWindow(_window->windowHandle()); offscreenUi->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/")); offscreenUi->load("Root.qml"); - offscreenUi->load("RootMenu.qml"); - auto scriptingInterface = DependencyManager::get(); - offscreenUi->getRootContext()->setContextProperty("Controller", scriptingInterface.data()); - offscreenUi->getRootContext()->setContextProperty("MyAvatar", getMyAvatar()); + // FIXME either expose so that dialogs can set this themselves or + // do better detection in the offscreen UI of what has focus + offscreenUi->setNavigationFocused(false); + + auto rootContext = offscreenUi->getRootContext(); + auto engine = rootContext->engine(); + connect(engine, &QQmlEngine::quit, [] { + qApp->quit(); + }); + rootContext->setContextProperty("Audio", &AudioScriptingInterface::getInstance()); + rootContext->setContextProperty("AnimationCache", DependencyManager::get().data()); + rootContext->setContextProperty("Controller", DependencyManager::get().data()); + rootContext->setContextProperty("Entities", DependencyManager::get().data()); + rootContext->setContextProperty("MyAvatar", getMyAvatar()); + rootContext->setContextProperty("Messages", DependencyManager::get().data()); + rootContext->setContextProperty("Recording", DependencyManager::get().data()); + + rootContext->setContextProperty("TREE_SCALE", TREE_SCALE); + rootContext->setContextProperty("Quat", new Quat()); + rootContext->setContextProperty("Vec3", new Vec3()); + rootContext->setContextProperty("Uuid", new ScriptUUID()); + + rootContext->setContextProperty("AvatarList", DependencyManager::get().data()); + + rootContext->setContextProperty("Camera", &_myCamera); + +#if defined(Q_OS_MAC) || defined(Q_OS_WIN) + rootContext->setContextProperty("SpeechRecognizer", DependencyManager::get().data()); +#endif + + rootContext->setContextProperty("Overlays", &_overlays); + rootContext->setContextProperty("Desktop", DependencyManager::get().data()); + + rootContext->setContextProperty("Window", DependencyManager::get().data()); + rootContext->setContextProperty("Menu", MenuScriptingInterface::getInstance()); + rootContext->setContextProperty("Stats", Stats::getInstance()); + rootContext->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); + rootContext->setContextProperty("AudioDevice", AudioDeviceScriptingInterface::getInstance()); + rootContext->setContextProperty("AnimationCache", DependencyManager::get().data()); + rootContext->setContextProperty("SoundCache", DependencyManager::get().data()); + rootContext->setContextProperty("Account", AccountScriptingInterface::getInstance()); + rootContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface); + rootContext->setContextProperty("GlobalServices", GlobalServicesScriptingInterface::getInstance()); + rootContext->setContextProperty("FaceTracker", DependencyManager::get().data()); + rootContext->setContextProperty("AvatarManager", DependencyManager::get().data()); + rootContext->setContextProperty("UndoStack", &_undoStackScriptingInterface); + rootContext->setContextProperty("LODManager", DependencyManager::get().data()); + rootContext->setContextProperty("Paths", DependencyManager::get().data()); + rootContext->setContextProperty("HMD", DependencyManager::get().data()); + rootContext->setContextProperty("Scene", DependencyManager::get().data()); + rootContext->setContextProperty("Render", DependencyManager::get().data()); + rootContext->setContextProperty("ScriptDiscoveryService", this->getRunningScriptsWidget()); + _glWidget->installEventFilter(offscreenUi.data()); VrMenu::load(); VrMenu::executeQueuedLambdas(); @@ -1135,7 +1273,7 @@ void Application::initializeUi() { } void Application::paintGL() { - // paintGL uses a queued connection, so we can get messages from the queue even after we've quit + // paintGL uses a queued connection, so we can get messages from the queue even after we've quit // and the plugins have shutdown if (_aboutToQuit) { return; @@ -1205,7 +1343,7 @@ void Application::paintGL() { renderArgs._context->syncCache(); } - if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { + if (Menu::getInstance()->isOptionChecked(MenuOption::MiniMirror)) { PerformanceTimer perfTimer("Mirror"); auto primaryFbo = DependencyManager::get()->getPrimaryFramebuffer(); @@ -1494,7 +1632,7 @@ void Application::aboutApp() { InfoView::show(INFO_HELP_PATH); } -void Application::showEditEntitiesHelp() { +void Application::showHelp() { InfoView::show(INFO_EDIT_ENTITIES_PATH); } @@ -1852,7 +1990,7 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_H: if (isShifted) { - Menu::getInstance()->triggerOption(MenuOption::Mirror); + Menu::getInstance()->triggerOption(MenuOption::MiniMirror); } else { Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, !Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)); if (!Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) { @@ -2863,7 +3001,11 @@ void Application::update(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::update()"); - updateLOD(); + if (DependencyManager::get()->getUseAcuity()) { + updateLOD(); + } else { + DependencyManager::get()->updatePIDRenderDistance(getTargetFrameRate(), getLastInstanteousFps(), deltaTime, isThrottleRendering()); + } { PerformanceTimer perfTimer("devices"); @@ -2948,11 +3090,11 @@ void Application::update(float deltaTime) { PerformanceTimer perfTimer("physics"); static VectorOfMotionStates motionStates; - _entitySimulation.getObjectsToDelete(motionStates); - _physicsEngine->deleteObjects(motionStates); + _entitySimulation.getObjectsToRemoveFromPhysics(motionStates); + _physicsEngine->removeObjects(motionStates); getEntities()->getTree()->withWriteLock([&] { - _entitySimulation.getObjectsToAdd(motionStates); + _entitySimulation.getObjectsToAddToPhysics(motionStates); _physicsEngine->addObjects(motionStates); }); @@ -2965,9 +3107,9 @@ void Application::update(float deltaTime) { _entitySimulation.applyActionChanges(); AvatarManager* avatarManager = DependencyManager::get().data(); - avatarManager->getObjectsToDelete(motionStates); - _physicsEngine->deleteObjects(motionStates); - avatarManager->getObjectsToAdd(motionStates); + avatarManager->getObjectsToRemoveFromPhysics(motionStates); + _physicsEngine->removeObjects(motionStates); + avatarManager->getObjectsToAddToPhysics(motionStates); _physicsEngine->addObjects(motionStates); avatarManager->getObjectsToChange(motionStates); _physicsEngine->changeObjects(motionStates); @@ -3002,8 +3144,7 @@ void Application::update(float deltaTime) { getEntities()->update(); // update the models... } - myAvatar->harvestResultsFromPhysicsSimulation(); - myAvatar->simulateAttachments(deltaTime); + myAvatar->harvestResultsFromPhysicsSimulation(deltaTime); } } @@ -3605,7 +3746,7 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se auto backgroundRenderData = make_shared(&_environment); auto backgroundRenderPayload = make_shared(backgroundRenderData); BackgroundRenderData::_item = _main3DScene->allocateID(); - pendingChanges.resetItem(WorldBoxRenderData::_item, backgroundRenderPayload); + pendingChanges.resetItem(BackgroundRenderData::_item, backgroundRenderPayload); } else { } @@ -3947,7 +4088,7 @@ bool Application::nearbyEntitiesAreReadyForPhysics() { }); foreach (EntityItemPointer entity, entities) { - if (!entity->isReadyToComputeShape()) { + if (entity->shouldBePhysical() && !entity->isReadyToComputeShape()) { static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("Physics disabled until entity loads: .*"); qCDebug(interfaceapp) << "Physics disabled until entity loads: " << entity->getID() << entity->getName(); @@ -4114,6 +4255,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri scriptEngine->registerFunction("WebWindow", WebWindowClass::constructor, 1); scriptEngine->registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor); + scriptEngine->registerFunction("OverlayWindow", QmlWindowClass::constructor); scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Stats", Stats::getInstance()); @@ -4246,7 +4388,7 @@ bool Application::askToSetAvatarUrl(const QString& url) { bool Application::askToLoadScript(const QString& scriptFilenameOrURL) { QMessageBox::StandardButton reply; QString message = "Would you like to run this script:\n" + scriptFilenameOrURL; - reply = QMessageBox::question(getWindow(), "Run Script", message, QMessageBox::Yes|QMessageBox::No); + reply = OffscreenUi::question(getWindow(), "Run Script", message, QMessageBox::Yes | QMessageBox::No); if (reply == QMessageBox::Yes) { qCDebug(interfaceapp) << "Chose to run the script: " << scriptFilenameOrURL; @@ -4259,7 +4401,7 @@ bool Application::askToLoadScript(const QString& scriptFilenameOrURL) { bool Application::askToUploadAsset(const QString& filename) { if (!DependencyManager::get()->getThisNodeCanRez()) { - QMessageBox::warning(_window, "Failed Upload", + OffscreenUi::warning(_window, "Failed Upload", QString("You don't have upload rights on that domain.\n\n")); return false; } @@ -4303,7 +4445,7 @@ bool Application::askToUploadAsset(const QString& filename) { } // display a message box with the error - QMessageBox::warning(_window, "Failed Upload", QString("Failed to upload %1.\n\n").arg(filename)); + OffscreenUi::warning(_window, "Failed Upload", QString("Failed to upload %1.\n\n").arg(filename)); return false; } @@ -4330,6 +4472,99 @@ void Application::modelUploadFinished(AssetUpload* upload, const QString& hash) } } +bool Application::askToWearAvatarAttachmentUrl(const QString& url) { + + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest networkRequest = QNetworkRequest(url); + networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + QNetworkReply* reply = networkAccessManager.get(networkRequest); + int requestNumber = ++_avatarAttachmentRequest; + connect(reply, &QNetworkReply::finished, [this, reply, url, requestNumber]() { + + if (requestNumber != _avatarAttachmentRequest) { + // this request has been superseded by another more recent request + reply->deleteLater(); + return; + } + + QNetworkReply::NetworkError networkError = reply->error(); + if (networkError == QNetworkReply::NoError) { + // download success + QByteArray contents = reply->readAll(); + + QJsonParseError jsonError; + auto doc = QJsonDocument::fromJson(contents, &jsonError); + if (jsonError.error == QJsonParseError::NoError) { + + auto jsonObject = doc.object(); + + // retrieve optional name field from JSON + QString name = tr("Unnamed Attachment"); + auto nameValue = jsonObject.value("name"); + if (nameValue.isString()) { + name = nameValue.toString(); + } + + // display confirmation dialog + if (displayAvatarAttachmentConfirmationDialog(name)) { + + // add attachment to avatar + auto myAvatar = getMyAvatar(); + assert(myAvatar); + auto attachmentDataVec = myAvatar->getAttachmentData(); + AttachmentData attachmentData; + attachmentData.fromJson(jsonObject); + attachmentDataVec.push_back(attachmentData); + myAvatar->setAttachmentData(attachmentDataVec); + + } else { + qCDebug(interfaceapp) << "User declined to wear the avatar attachment: " << url; + } + + } else { + // json parse error + auto avatarAttachmentParseErrorString = tr("Error parsing attachment JSON from url: \"%1\""); + displayAvatarAttachmentWarning(avatarAttachmentParseErrorString.arg(url)); + } + } else { + // download failure + auto avatarAttachmentDownloadErrorString = tr("Error downloading attachment JSON from url: \"%1\""); + displayAvatarAttachmentWarning(avatarAttachmentDownloadErrorString.arg(url)); + } + reply->deleteLater(); + }); + return true; +} + +void Application::displayAvatarAttachmentWarning(const QString& message) const { + auto avatarAttachmentWarningTitle = tr("Avatar Attachment Failure"); + QMessageBox msgBox; + msgBox.setIcon(QMessageBox::Warning); + msgBox.setWindowTitle(avatarAttachmentWarningTitle); + msgBox.setText(message); + msgBox.exec(); + msgBox.addButton(QMessageBox::Ok); + msgBox.exec(); +} + +bool Application::displayAvatarAttachmentConfirmationDialog(const QString& name) const { + auto avatarAttachmentConfirmationTitle = tr("Avatar Attachment Confirmation"); + auto avatarAttachmentConfirmationMessage = tr("Would you like to wear '%1' on your avatar?"); + QMessageBox msgBox; + msgBox.setIcon(QMessageBox::Question); + msgBox.setWindowTitle(avatarAttachmentConfirmationTitle); + QPushButton* button = msgBox.addButton(tr("Yes"), QMessageBox::ActionRole); + QString message = avatarAttachmentConfirmationMessage.arg(name); + msgBox.setText(message); + msgBox.addButton(QMessageBox::Cancel); + msgBox.exec(); + if (msgBox.clickedButton() == button) { + return true; + } else { + return false; + } +} + ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUserLoaded, bool loadScriptFromEditor, bool activateMainWindow, bool reload) { @@ -4401,7 +4636,7 @@ void Application::handleScriptEngineLoaded(const QString& scriptFilename) { // FIXME - change to new version of ScriptCache loading notification void Application::handleScriptLoadError(const QString& scriptFilename) { qCDebug(interfaceapp) << "Application::loadScript(), script failed to load..."; - QMessageBox::warning(getWindow(), "Error Loading Script", scriptFilename + " failed to load."); + OffscreenUi::warning(getWindow(), "Error Loading Script", scriptFilename + " failed to load."); } QStringList Application::getRunningScripts() { @@ -4857,7 +5092,7 @@ void Application::updateDisplayMode() { foreach(auto displayPlugin, displayPlugins) { addDisplayPluginToMenu(displayPlugin, first); // This must be a queued connection to avoid a deadlock - QObject::connect(displayPlugin.get(), &DisplayPlugin::requestRender, + QObject::connect(displayPlugin.get(), &DisplayPlugin::requestRender, this, &Application::paintGL, Qt::QueuedConnection); QObject::connect(displayPlugin.get(), &DisplayPlugin::recommendedFramebufferSizeChanged, [this](const QSize & size) { diff --git a/interface/src/Application.h b/interface/src/Application.h index 9b1cffc659..19fe49ffed 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -298,7 +298,7 @@ public slots: #endif void aboutApp(); - void showEditEntitiesHelp(); + void showHelp(); void cycleCamera(); void cameraMenuChanged(); @@ -343,7 +343,11 @@ private slots: bool askToLoadScript(const QString& scriptFilenameOrURL); bool askToUploadAsset(const QString& asset); void modelUploadFinished(AssetUpload* upload, const QString& hash); - + + bool askToWearAvatarAttachmentUrl(const QString& url); + void displayAvatarAttachmentWarning(const QString& message) const; + bool displayAvatarAttachmentConfirmationDialog(const QString& name) const; + void setSessionUUID(const QUuid& sessionUUID); void domainChanged(const QString& domainHostname); void updateWindowTitle(); @@ -553,6 +557,8 @@ private: bool _physicsEnabled { false }; bool _reticleClickPressed { false }; + + int _avatarAttachmentRequest = 0; }; #endif // hifi_Application_h diff --git a/interface/src/InterfaceParentFinder.cpp b/interface/src/InterfaceParentFinder.cpp index 112bae5bb8..de4b0ce38c 100644 --- a/interface/src/InterfaceParentFinder.cpp +++ b/interface/src/InterfaceParentFinder.cpp @@ -16,22 +16,31 @@ #include "InterfaceParentFinder.h" -SpatiallyNestableWeakPointer InterfaceParentFinder::find(QUuid parentID) const { +SpatiallyNestableWeakPointer InterfaceParentFinder::find(QUuid parentID, bool& success) const { SpatiallyNestableWeakPointer parent; if (parentID.isNull()) { + success = true; return parent; } // search entities EntityTreeRenderer* treeRenderer = qApp->getEntities(); - EntityTreePointer tree = treeRenderer->getTree(); - parent = tree->findEntityByEntityItemID(parentID); + EntityTreePointer tree = treeRenderer ? treeRenderer->getTree() : nullptr; + parent = tree ? tree->findEntityByEntityItemID(parentID) : nullptr; if (!parent.expired()) { + success = true; return parent; } // search avatars QSharedPointer avatarManager = DependencyManager::get(); - return avatarManager->getAvatarBySessionID(parentID); + parent = avatarManager->getAvatarBySessionID(parentID); + if (!parent.expired()) { + success = true; + return parent; + } + + success = false; + return parent; } diff --git a/interface/src/InterfaceParentFinder.h b/interface/src/InterfaceParentFinder.h index c8e8d4ed9f..db579c2144 100644 --- a/interface/src/InterfaceParentFinder.h +++ b/interface/src/InterfaceParentFinder.h @@ -21,7 +21,7 @@ class InterfaceParentFinder : public SpatialParentFinder { public: InterfaceParentFinder() { } virtual ~InterfaceParentFinder() { } - virtual SpatiallyNestableWeakPointer find(QUuid parentID) const; + virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success) const; }; #endif // hifi_InterfaceParentFinder_h diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 368143e36e..13bedb128a 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include @@ -20,9 +21,30 @@ Setting::Handle desktopLODDecreaseFPS("desktopLODDecreaseFPS", DEFAULT_DESKTOP_LOD_DOWN_FPS); Setting::Handle hmdLODDecreaseFPS("hmdLODDecreaseFPS", DEFAULT_HMD_LOD_DOWN_FPS); +// There are two different systems in use, based on lodPreference: +// pid: renderDistance is adjusted by a PID such that frame rate targets are met. +// acuity: a pseudo-acuity target is held, or adjusted to match minimum frame rates (and a PID controlls avatar rendering distance) +// If unspecified, acuity is used only if user has specified non-default minumum frame rates. +Setting::Handle lodPreference("lodPreference", (int)LODManager::LODPreference::unspecified); +const float SMALLEST_REASONABLE_HORIZON = 50.0f; // meters +Setting::Handle renderDistanceInverseHighLimit("renderDistanceInverseHighLimit", 1.0f / SMALLEST_REASONABLE_HORIZON); +void LODManager::setRenderDistanceInverseHighLimit(float newValue) { + renderDistanceInverseHighLimit.set(newValue); // persist it, and tell all the controllers that use it + _renderDistanceController.setControlledValueHighLimit(newValue); +} LODManager::LODManager() { calculateAvatarLODDistanceMultiplier(); + + setRenderDistanceInverseHighLimit(renderDistanceInverseHighLimit.get()); + setRenderDistanceInverseLowLimit(1.0f / (float)TREE_SCALE); + // Advice for tuning parameters: + // See PIDController.h. There's a section on tuning in the reference. + // Turn on logging with the following (or from js with LODManager.setRenderDistanceControllerHistory("render pid", 240)) + //setRenderDistanceControllerHistory("render pid", 60 * 4); + // Note that extra logging/hysteresis is turned off in Avatar.cpp when the above logging is on. + setRenderDistanceKP(0.000012f); // Usually about 0.6 of largest that doesn't oscillate when other parameters 0. + setRenderDistanceKI(0.00002f); // Big enough to bring us to target with the above KP. } float LODManager::getLODDecreaseFPS() { @@ -39,7 +61,6 @@ float LODManager::getLODIncreaseFPS() { return getDesktopLODIncreaseFPS(); } - void LODManager::autoAdjustLOD(float currentFPS) { // NOTE: our first ~100 samples at app startup are completely all over the place, and we don't @@ -217,15 +238,58 @@ QString LODManager::getLODFeedbackText() { return result; } +static float renderDistance = (float)TREE_SCALE; +static int renderedCount = 0; +static int lastRenderedCount = 0; +bool LODManager::getUseAcuity() { return lodPreference.get() == (int)LODManager::LODPreference::acuity; } +void LODManager::setUseAcuity(bool newValue) { lodPreference.set(newValue ? (int)LODManager::LODPreference::acuity : (int)LODManager::LODPreference::pid); } +float LODManager::getRenderDistance() { + return renderDistance; +} +int LODManager::getRenderedCount() { + return lastRenderedCount; +} +QString LODManager::getLODStatsRenderText() { + const QString label = "Rendered objects: "; + return label + QString::number(getRenderedCount()) + " w/in " + QString::number((int)getRenderDistance()) + "m"; +} +// compare audoAdjustLOD() +void LODManager::updatePIDRenderDistance(float targetFps, float measuredFps, float deltaTime, bool isThrottled) { + float distance; + if (!isThrottled) { + _renderDistanceController.setMeasuredValueSetpoint(targetFps); // No problem updating in flight. + // The PID controller raises the controlled value when the measured value goes up. + // The measured value is frame rate. When the controlled value (1 / render cutoff distance) + // goes up, the render cutoff distance gets closer, the number of rendered avatars is less, and frame rate + // goes up. + distance = 1.0f / _renderDistanceController.update(measuredFps, deltaTime); + } else { + // Here we choose to just use the maximum render cutoff distance if throttled. + distance = 1.0f / _renderDistanceController.getControlledValueLowLimit(); + } + _renderDistanceAverage.updateAverage(distance); + renderDistance = _renderDistanceAverage.getAverage(); // average only once per cycle + lastRenderedCount = renderedCount; + renderedCount = 0; +} + bool LODManager::shouldRender(const RenderArgs* args, const AABox& bounds) { + float distanceToCamera = glm::length(bounds.calcCenter() - args->_viewFrustum->getPosition()); + float largestDimension = bounds.getLargestDimension(); + if (!getUseAcuity()) { + const float scenerySize = 300; // meters + bool isRendered = (largestDimension > scenerySize) || // render scenery regardless of distance + (distanceToCamera < renderDistance + largestDimension); + renderedCount += isRendered ? 1 : 0; + return isRendered; + } + const float maxScale = (float)TREE_SCALE; const float octreeToMeshRatio = 4.0f; // must be this many times closer to a mesh than a voxel to see it. float octreeSizeScale = args->_sizeScale; int boundaryLevelAdjust = args->_boundaryLevelAdjust; float visibleDistanceAtMaxScale = boundaryDistanceForRenderLevel(boundaryLevelAdjust, octreeSizeScale) / octreeToMeshRatio; - float distanceToCamera = glm::length(bounds.calcCenter() - args->_viewFrustum->getPosition()); - float largestDimension = bounds.getLargestDimension(); - + static bool shouldRenderTableNeedsBuilding = true; static QMap shouldRenderTable; if (shouldRenderTableNeedsBuilding) { @@ -315,6 +379,12 @@ void LODManager::setBoundaryLevelAdjust(int boundaryLevelAdjust) { void LODManager::loadSettings() { setDesktopLODDecreaseFPS(desktopLODDecreaseFPS.get()); setHMDLODDecreaseFPS(hmdLODDecreaseFPS.get()); + + if (lodPreference.get() == (int)LODManager::LODPreference::unspecified) { + setUseAcuity((getDesktopLODDecreaseFPS() != DEFAULT_DESKTOP_LOD_DOWN_FPS) || (getHMDLODDecreaseFPS() != DEFAULT_HMD_LOD_DOWN_FPS)); + } + Menu::getInstance()->getActionForOption(MenuOption::LodTools)->setEnabled(getUseAcuity()); + Menu::getInstance()->getSubMenuFromName(MenuOption::RenderResolution, Menu::getInstance()->getSubMenuFromName("Render", Menu::getInstance()->getMenu("Developer")))->setEnabled(getUseAcuity()); } void LODManager::saveSettings() { diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index b0185b528f..6b141004e5 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -15,6 +15,7 @@ #include #include #include +#include #include const float DEFAULT_DESKTOP_LOD_DOWN_FPS = 15.0; @@ -81,6 +82,27 @@ public: Q_INVOKABLE float getLODDecreaseFPS(); Q_INVOKABLE float getLODIncreaseFPS(); + enum class LODPreference { + pid = 0, + acuity, + unspecified + }; + static bool getUseAcuity(); + static void setUseAcuity(bool newValue); + Q_INVOKABLE void setRenderDistanceKP(float newValue) { _renderDistanceController.setKP(newValue); } + Q_INVOKABLE void setRenderDistanceKI(float newValue) { _renderDistanceController.setKI(newValue); } + Q_INVOKABLE void setRenderDistanceKD(float newValue) { _renderDistanceController.setKD(newValue); } + Q_INVOKABLE bool getRenderDistanceControllerIsLogging() { return _renderDistanceController.getIsLogging(); } + Q_INVOKABLE void setRenderDistanceControllerHistory(QString label, int size) { return _renderDistanceController.setHistorySize(label, size); } + Q_INVOKABLE float getRenderDistanceInverseLowLimit() { return _renderDistanceController.getControlledValueLowLimit(); } + Q_INVOKABLE void setRenderDistanceInverseLowLimit(float newValue) { _renderDistanceController.setControlledValueLowLimit(newValue); } + Q_INVOKABLE float getRenderDistanceInverseHighLimit() { return _renderDistanceController.getControlledValueHighLimit(); } + Q_INVOKABLE void setRenderDistanceInverseHighLimit(float newValue); + void updatePIDRenderDistance(float targetFps, float measuredFps, float deltaTime, bool isThrottled); + float getRenderDistance(); + int getRenderedCount(); + QString getLODStatsRenderText(); + static bool shouldRender(const RenderArgs* args, const AABox& bounds); bool shouldRenderMesh(float largestDimension, float distanceToCamera); void autoAdjustLOD(float currentFPS); @@ -116,6 +138,9 @@ private: bool _shouldRenderTableNeedsRebuilding = true; QMap _shouldRenderTable; + + PIDController _renderDistanceController{}; + SimpleMovingAverage _renderDistanceAverage{ 10 }; }; #endif // hifi_LODManager_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 85af912892..d57c395b48 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -51,13 +51,16 @@ Menu* Menu::getInstance() { } Menu::Menu() { - MenuWrapper * fileMenu = addMenu("File"); -#ifdef Q_OS_MAC - addActionToQMenuAndActionHash(fileMenu, MenuOption::AboutApp, 0, qApp, SLOT(aboutApp()), QAction::AboutRole); -#endif auto dialogsManager = DependencyManager::get(); AccountManager& accountManager = AccountManager::getInstance(); + // File/Application menu ---------------------------------- + MenuWrapper* fileMenu = addMenu("File"); + + // File > Quit + addActionToQMenuAndActionHash(fileMenu, MenuOption::Quit, Qt::CTRL | Qt::Key_Q, qApp,SLOT(quit()), QAction::QuitRole); + + // File > Login menu items { addActionToQMenuAndActionHash(fileMenu, MenuOption::Login); @@ -68,256 +71,269 @@ Menu::Menu() { dialogsManager.data(), &DialogsManager::toggleLoginDialog); } - // File Menu > Scripts section -- "Advanced" grouping - addDisabledActionAndSeparator(fileMenu, "Scripts", UNSPECIFIED_POSITION, "Advanced"); - addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, - qApp, SLOT(loadDialog()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScriptURL, - Qt::CTRL | Qt::SHIFT | Qt::Key_O, qApp, SLOT(loadScriptURLDialog()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - addActionToQMenuAndActionHash(fileMenu, MenuOption::StopAllScripts, 0, qApp, SLOT(stopAllScripts()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - addActionToQMenuAndActionHash(fileMenu, MenuOption::ReloadAllScripts, Qt::CTRL | Qt::Key_R, - qApp, SLOT(reloadAllScripts()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - addActionToQMenuAndActionHash(fileMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J, - qApp, SLOT(toggleRunningScriptsWidget()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + // File > Update -- FIXME: needs implementation + auto updateAction = addActionToQMenuAndActionHash(fileMenu, "Update"); + updateAction->setDisabled(true); - auto addressManager = DependencyManager::get(); + // File > Help + addActionToQMenuAndActionHash(fileMenu, MenuOption::Help, 0, qApp, SLOT(showHelp())); - addDisabledActionAndSeparator(fileMenu, "History"); + // File > Crash Reporter...-- FIXME: needs implementation + auto crashReporterAction = addActionToQMenuAndActionHash(fileMenu, "Crash Reporter..."); + crashReporterAction->setDisabled(true); - QAction* backAction = addActionToQMenuAndActionHash(fileMenu, - MenuOption::Back, - 0, - addressManager.data(), - SLOT(goBack())); - - QAction* forwardAction = addActionToQMenuAndActionHash(fileMenu, - MenuOption::Forward, - 0, - addressManager.data(), - SLOT(goForward())); - - // connect to the AddressManager signal to enable and disable the back and forward menu items - connect(addressManager.data(), &AddressManager::goBackPossible, backAction, &QAction::setEnabled); - connect(addressManager.data(), &AddressManager::goForwardPossible, forwardAction, &QAction::setEnabled); - - // set the two actions to start disabled since the stacks are clear on startup - backAction->setDisabled(true); - forwardAction->setDisabled(true); - - addDisabledActionAndSeparator(fileMenu, "Location"); - qApp->getBookmarks()->setupMenus(this, fileMenu); - - addActionToQMenuAndActionHash(fileMenu, - MenuOption::AddressBar, - Qt::CTRL | Qt::Key_L, - dialogsManager.data(), - SLOT(toggleAddressBar())); - addActionToQMenuAndActionHash(fileMenu, MenuOption::CopyAddress, 0, - addressManager.data(), SLOT(copyAddress()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - addActionToQMenuAndActionHash(fileMenu, MenuOption::CopyPath, 0, - addressManager.data(), SLOT(copyPath()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - - addActionToQMenuAndActionHash(fileMenu, - MenuOption::Quit, - Qt::CTRL | Qt::Key_Q, - qApp, - SLOT(quit()), - QAction::QuitRole); + // File > About + addActionToQMenuAndActionHash(fileMenu, MenuOption::AboutApp, 0, qApp, SLOT(aboutApp()), QAction::AboutRole); + // Audio menu ---------------------------------- + MenuWrapper* audioMenu = addMenu("Audio"); + auto audioIO = DependencyManager::get(); + + // Audio > Mute + addCheckableActionToQMenuAndActionHash(audioMenu, MenuOption::MuteAudio, Qt::CTRL | Qt::Key_M, false, + audioIO.data(), SLOT(toggleMute())); + + // Audio > Level Meter [advanced] -- FIXME: needs implementation + auto levelMeterAction = addCheckableActionToQMenuAndActionHash(audioMenu, "Level Meter", 0, false, NULL, NULL, UNSPECIFIED_POSITION, "Advanced"); + levelMeterAction->setDisabled(true); + + + // Avatar menu ---------------------------------- + MenuWrapper* avatarMenu = addMenu("Avatar"); + auto avatarManager = DependencyManager::get(); + QObject* avatar = avatarManager->getMyAvatar(); + + // Avatar > Attachments... + addActionToQMenuAndActionHash(avatarMenu, MenuOption::Attachments, 0, + dialogsManager.data(), SLOT(editAttachments())); + + // Avatar > Size + MenuWrapper* avatarSizeMenu = avatarMenu->addMenu("Size"); + + // Avatar > Size > Increase + addActionToQMenuAndActionHash(avatarSizeMenu, + MenuOption::IncreaseAvatarSize, + 0, // QML Qt::Key_Plus, + avatar, SLOT(increaseSize())); + + // Avatar > Size > Decrease + addActionToQMenuAndActionHash(avatarSizeMenu, + MenuOption::DecreaseAvatarSize, + 0, // QML Qt::Key_Minus, + avatar, SLOT(decreaseSize())); + + // Avatar > Size > Reset + addActionToQMenuAndActionHash(avatarSizeMenu, + MenuOption::ResetAvatarSize, + 0, // QML Qt::Key_Equal, + avatar, SLOT(resetSize())); + + // Avatar > Reset Sensors + addActionToQMenuAndActionHash(avatarMenu, + MenuOption::ResetSensors, + 0, // QML Qt::Key_Apostrophe, + qApp, SLOT(resetSensors())); + + + // Display menu ---------------------------------- + // FIXME - this is not yet matching Alan's spec because it doesn't have + // menus for "2D"/"3D" - we need to add support for detecting the appropriate + // default 3D display mode + addMenu(DisplayPlugin::MENU_PATH()); + MenuWrapper* displayModeMenu = addMenu(MenuOption::OutputMenu); + QActionGroup* displayModeGroup = new QActionGroup(displayModeMenu); + displayModeGroup->setExclusive(true); + + + // View menu ---------------------------------- + MenuWrapper* viewMenu = addMenu("View"); + QActionGroup* cameraModeGroup = new QActionGroup(viewMenu); + + // View > [camera group] + cameraModeGroup->setExclusive(true); + + // View > First Person + cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, + MenuOption::FirstPerson, 0, // QML Qt:: Key_P + false, qApp, SLOT(cameraMenuChanged()))); + + // View > Third Person + cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, + MenuOption::ThirdPerson, 0, + true, qApp, SLOT(cameraMenuChanged()))); + + // View > Mirror + cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, + MenuOption::FullscreenMirror, 0, // QML Qt::Key_H, + false, qApp, SLOT(cameraMenuChanged()))); + + // View > Independent [advanced] + cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, + MenuOption::IndependentMode, 0, + false, qApp, SLOT(cameraMenuChanged()), + UNSPECIFIED_POSITION, "Advanced")); + + // View > Entity Camera [advanced] + cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, + MenuOption::CameraEntityMode, 0, + false, qApp, SLOT(cameraMenuChanged()), + UNSPECIFIED_POSITION, "Advanced")); + + viewMenu->addSeparator(); + + // View > Mini Mirror + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::MiniMirror, 0, false); + + + // Edit menu ---------------------------------- MenuWrapper* editMenu = addMenu("Edit"); + // Edit > Undo QUndoStack* undoStack = qApp->getUndoStack(); QAction* undoAction = undoStack->createUndoAction(editMenu); undoAction->setShortcut(Qt::CTRL | Qt::Key_Z); addActionToQMenuAndActionHash(editMenu, undoAction); + // Edit > Redo QAction* redoAction = undoStack->createRedoAction(editMenu); redoAction->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Z); addActionToQMenuAndActionHash(editMenu, redoAction); - addActionToQMenuAndActionHash(editMenu, - MenuOption::Preferences, - Qt::CTRL | Qt::Key_Comma, - dialogsManager.data(), - SLOT(editPreferences()), - QAction::PreferencesRole); + // Edit > Running Sccripts + addActionToQMenuAndActionHash(editMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J, + qApp, SLOT(toggleRunningScriptsWidget())); - addActionToQMenuAndActionHash(editMenu, MenuOption::Attachments, 0, - dialogsManager.data(), SLOT(editAttachments()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + // Edit > Open and Run Script from File... [advanced] + addActionToQMenuAndActionHash(editMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, + qApp, SLOT(loadDialog()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - MenuWrapper* toolsMenu = addMenu("Tools"); - addActionToQMenuAndActionHash(toolsMenu, MenuOption::ScriptEditor, Qt::ALT | Qt::Key_S, - dialogsManager.data(), SLOT(showScriptEditor()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + // Edit > Open and Run Script from Url... [advanced] + addActionToQMenuAndActionHash(editMenu, MenuOption::LoadScriptURL, + Qt::CTRL | Qt::SHIFT | Qt::Key_O, qApp, SLOT(loadScriptURLDialog()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + // Edit > Stop All Scripts... [advanced] + addActionToQMenuAndActionHash(editMenu, MenuOption::StopAllScripts, 0, qApp, SLOT(stopAllScripts()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + + // Edit > Reload All Scripts... [advanced] + addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadAllScripts, Qt::CTRL | Qt::Key_R, + qApp, SLOT(reloadAllScripts()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + + // Edit > Scripts Editor... [advanced] + addActionToQMenuAndActionHash(editMenu, MenuOption::ScriptEditor, Qt::ALT | Qt::Key_S, + dialogsManager.data(), SLOT(showScriptEditor()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + + // Edit > Console... [advanced] + addActionToQMenuAndActionHash(editMenu, MenuOption::Console, Qt::CTRL | Qt::ALT | Qt::Key_J, + DependencyManager::get().data(), + SLOT(toggleConsole()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + + // Edit > Reload All Content [advanced] + addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + + + // Edit > Package Model... [advanced] + addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0, + qApp, SLOT(packageModel()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + + + // Navigate menu ---------------------------------- + MenuWrapper* navigateMenu = addMenu("Navigate"); + + // Navigate > Home -- FIXME: needs implementation + auto homeAction = addActionToQMenuAndActionHash(navigateMenu, "Home"); + homeAction->setDisabled(true); + + addActionToQMenuAndActionHash(navigateMenu, MenuOption::AddressBar, Qt::CTRL | Qt::Key_L, + dialogsManager.data(), SLOT(toggleAddressBar())); + + // Navigate > Directory -- FIXME: needs implementation + addActionToQMenuAndActionHash(navigateMenu, "Directory"); + + // Navigate > Bookmark related menus -- Note: the Bookmark class adds its own submenus here. + qApp->getBookmarks()->setupMenus(this, navigateMenu); + + // Navigate > Copy Address [advanced] + auto addressManager = DependencyManager::get(); + addActionToQMenuAndActionHash(navigateMenu, MenuOption::CopyAddress, 0, + addressManager.data(), SLOT(copyAddress()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + + // Navigate > Copy Path [advanced] + addActionToQMenuAndActionHash(navigateMenu, MenuOption::CopyPath, 0, + addressManager.data(), SLOT(copyPath()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + + + // Market menu ---------------------------------- + MenuWrapper* marketMenu = addMenu("Market"); + + // Market > Marketplace... -- FIXME: needs implementation + auto marketplaceAction = addActionToQMenuAndActionHash(marketMenu, "Marketplace..."); + marketplaceAction->setDisabled(true); + + + // Settings menu ---------------------------------- + MenuWrapper* settingsMenu = addMenu("Settings"); + + // Settings > Advance Menus + addCheckableActionToQMenuAndActionHash(settingsMenu, "Advanced Menus", 0, false, this, SLOT(toggleAdvancedMenus())); + + // Settings > Developer Menus + addCheckableActionToQMenuAndActionHash(settingsMenu, "Developer Menus", 0, false, this, SLOT(toggleDeveloperMenus())); + + // Settings > General... + addActionToQMenuAndActionHash(settingsMenu, MenuOption::Preferences, Qt::CTRL | Qt::Key_Comma, + dialogsManager.data(), SLOT(editPreferences()), QAction::PreferencesRole); + + // Settings > Avatar...-- FIXME: needs implementation + auto avatarAction = addActionToQMenuAndActionHash(settingsMenu, "Avatar..."); + avatarAction->setDisabled(true); + + // Settings > Audio...-- FIXME: needs implementation + auto audioAction = addActionToQMenuAndActionHash(settingsMenu, "Audio..."); + audioAction->setDisabled(true); + + // Settings > LOD...-- FIXME: needs implementation + auto lodAction = addActionToQMenuAndActionHash(settingsMenu, "LOD..."); + lodAction->setDisabled(true); + + // Settings > Control with Speech [advanced] #if defined(Q_OS_MAC) || defined(Q_OS_WIN) auto speechRecognizer = DependencyManager::get(); - QAction* speechRecognizerAction = addCheckableActionToQMenuAndActionHash(toolsMenu, MenuOption::ControlWithSpeech, - Qt::CTRL | Qt::SHIFT | Qt::Key_C, - speechRecognizer->getEnabled(), - speechRecognizer.data(), - SLOT(setEnabled(bool)), - UNSPECIFIED_POSITION, "Advanced"); + QAction* speechRecognizerAction = addCheckableActionToQMenuAndActionHash(settingsMenu, MenuOption::ControlWithSpeech, + Qt::CTRL | Qt::SHIFT | Qt::Key_C, + speechRecognizer->getEnabled(), + speechRecognizer.data(), + SLOT(setEnabled(bool)), + UNSPECIFIED_POSITION, "Advanced"); connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool))); #endif - addActionToQMenuAndActionHash(toolsMenu, MenuOption::Chat, - 0, // QML Qt::Key_Backslash, - dialogsManager.data(), SLOT(showIRCLink()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - - addActionToQMenuAndActionHash(toolsMenu, MenuOption::AddRemoveFriends, 0, - qApp, SLOT(showFriendsWindow())); - - MenuWrapper* visibilityMenu = toolsMenu->addMenu("I Am Visible To"); - { - QActionGroup* visibilityGroup = new QActionGroup(toolsMenu); - auto discoverabilityManager = DependencyManager::get(); - - QAction* visibleToEveryone = addCheckableActionToQMenuAndActionHash(visibilityMenu, MenuOption::VisibleToEveryone, - 0, discoverabilityManager->getDiscoverabilityMode() == Discoverability::All, - discoverabilityManager.data(), SLOT(setVisibility())); - visibilityGroup->addAction(visibleToEveryone); - - QAction* visibleToFriends = addCheckableActionToQMenuAndActionHash(visibilityMenu, MenuOption::VisibleToFriends, - 0, discoverabilityManager->getDiscoverabilityMode() == Discoverability::Friends, - discoverabilityManager.data(), SLOT(setVisibility())); - visibilityGroup->addAction(visibleToFriends); - - QAction* visibleToNoOne = addCheckableActionToQMenuAndActionHash(visibilityMenu, MenuOption::VisibleToNoOne, - 0, discoverabilityManager->getDiscoverabilityMode() == Discoverability::None, - discoverabilityManager.data(), SLOT(setVisibility())); - visibilityGroup->addAction(visibleToNoOne); - - connect(discoverabilityManager.data(), &DiscoverabilityManager::discoverabilityModeChanged, - discoverabilityManager.data(), &DiscoverabilityManager::visibilityChanged); - } - - addActionToQMenuAndActionHash(toolsMenu, - MenuOption::ToolWindow, - Qt::CTRL | Qt::ALT | Qt::Key_T, - dialogsManager.data(), - SLOT(toggleToolWindow()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - - addActionToQMenuAndActionHash(toolsMenu, - MenuOption::Console, - Qt::CTRL | Qt::ALT | Qt::Key_J, - DependencyManager::get().data(), - SLOT(toggleConsole()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - - addActionToQMenuAndActionHash(toolsMenu, - MenuOption::ResetSensors, - 0, // QML Qt::Key_Apostrophe, - qApp, - SLOT(resetSensors()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - - addActionToQMenuAndActionHash(toolsMenu, MenuOption::PackageModel, 0, - qApp, SLOT(packageModel()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - - addMenu(DisplayPlugin::MENU_PATH()); - { - MenuWrapper* displayModeMenu = addMenu(MenuOption::OutputMenu); - QActionGroup* displayModeGroup = new QActionGroup(displayModeMenu); - displayModeGroup->setExclusive(true); - } - - MenuWrapper* avatarMenu = addMenu("Avatar"); - QObject* avatar = DependencyManager::get()->getMyAvatar(); - + // Settings > Input Devices MenuWrapper* inputModeMenu = addMenu(MenuOption::InputMenu, "Advanced"); QActionGroup* inputModeGroup = new QActionGroup(inputModeMenu); inputModeGroup->setExclusive(false); - - MenuWrapper* avatarSizeMenu = avatarMenu->addMenu("Size"); - addActionToQMenuAndActionHash(avatarSizeMenu, - MenuOption::IncreaseAvatarSize, - 0, // QML Qt::Key_Plus, - avatar, - SLOT(increaseSize())); - addActionToQMenuAndActionHash(avatarSizeMenu, - MenuOption::DecreaseAvatarSize, - 0, // QML Qt::Key_Minus, - avatar, - SLOT(decreaseSize())); - addActionToQMenuAndActionHash(avatarSizeMenu, - MenuOption::ResetAvatarSize, - 0, // QML Qt::Key_Equal, - avatar, - SLOT(resetSize())); - addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::NamesAboveHeads, 0, true, - NULL, NULL, UNSPECIFIED_POSITION, "Advanced"); - - MenuWrapper* viewMenu = addMenu("View"); - addActionToQMenuAndActionHash(viewMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()), - QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); - - MenuWrapper* cameraModeMenu = viewMenu->addMenu("Camera Mode"); - QActionGroup* cameraModeGroup = new QActionGroup(cameraModeMenu); - cameraModeGroup->setExclusive(true); - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(cameraModeMenu, - MenuOption::FirstPerson, 0, // QML Qt:: Key_P - false, qApp, SLOT(cameraMenuChanged()))); - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(cameraModeMenu, - MenuOption::ThirdPerson, 0, - true, qApp, SLOT(cameraMenuChanged()))); - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(cameraModeMenu, - MenuOption::IndependentMode, 0, - false, qApp, SLOT(cameraMenuChanged()))); - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(cameraModeMenu, - MenuOption::CameraEntityMode, 0, - false, qApp, SLOT(cameraMenuChanged()))); - cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(cameraModeMenu, - MenuOption::FullscreenMirror, 0, // QML Qt::Key_H, - false, qApp, SLOT(cameraMenuChanged()))); - - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, - 0, //QML Qt::SHIFT | Qt::Key_H, - true); - - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::CenterPlayerInView, - 0, false, qApp, SLOT(rotationModeChanged()), - UNSPECIFIED_POSITION, "Advanced"); - - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::WorldAxes, 0, false, NULL, NULL, UNSPECIFIED_POSITION, "Developer"); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats, 0, false, NULL, NULL, UNSPECIFIED_POSITION, "Developer"); - addActionToQMenuAndActionHash(viewMenu, MenuOption::Log, - Qt::CTRL | Qt::SHIFT | Qt::Key_L, - qApp, SLOT(toggleLogDialog()), QAction::NoRole, UNSPECIFIED_POSITION, "Developer"); - - addActionToQMenuAndActionHash(viewMenu, MenuOption::AudioNetworkStats, 0, - dialogsManager.data(), SLOT(audioStatsDetails()), QAction::NoRole, UNSPECIFIED_POSITION, "Developer"); - - addActionToQMenuAndActionHash(viewMenu, MenuOption::BandwidthDetails, 0, - dialogsManager.data(), SLOT(bandwidthDetails()), QAction::NoRole, UNSPECIFIED_POSITION, "Developer"); - addActionToQMenuAndActionHash(viewMenu, MenuOption::OctreeStats, 0, - dialogsManager.data(), SLOT(octreeStatsDetails()), QAction::NoRole, UNSPECIFIED_POSITION, "Developer"); - - addCheckableActionToQMenuAndActionHash(viewMenu, "Advanced Menus", 0, false, this, SLOT(toggleAdvancedMenus())); - addCheckableActionToQMenuAndActionHash(viewMenu, "Developer Menus", 0, false, this, SLOT(toggleDeveloperMenus())); + // Developer menu ---------------------------------- MenuWrapper* developerMenu = addMenu("Developer", "Developer"); + // Developer > Render >>> MenuWrapper* renderOptionsMenu = developerMenu->addMenu("Render"); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, - 0, // QML Qt::SHIFT | Qt::Key_A, - true); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, 0, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DebugAmbientOcclusion); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Antialiasing); + // Developer > Render > Ambient Light MenuWrapper* ambientLightMenu = renderOptionsMenu->addMenu(MenuOption::RenderAmbientLight); QActionGroup* ambientLightGroup = new QActionGroup(ambientLightMenu); ambientLightGroup->setExclusive(true); @@ -333,8 +349,10 @@ Menu::Menu() { ambientLightGroup->addAction(addCheckableActionToQMenuAndActionHash(ambientLightMenu, MenuOption::RenderAmbientLight8, 0, false)); ambientLightGroup->addAction(addCheckableActionToQMenuAndActionHash(ambientLightMenu, MenuOption::RenderAmbientLight9, 0, false)); + // Developer > Render > Throttle FPS If Not Focus addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::ThrottleFPSIfNotFocus, 0, true); + // Developer > Render > Resolution MenuWrapper* resolutionMenu = renderOptionsMenu->addMenu(MenuOption::RenderResolution); QActionGroup* resolutionGroup = new QActionGroup(resolutionMenu); resolutionGroup->setExclusive(true); @@ -344,37 +362,40 @@ Menu::Menu() { resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionThird, 0, false)); resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionQuarter, 0, false)); + // Developer > Render > Stars addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, 0, // QML Qt::Key_Asterisk, true); + // Developer > Render > LOD Tools addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, 0, // QML Qt::SHIFT | Qt::Key_L, dialogsManager.data(), SLOT(lodTools())); - + + // Developer > Assets >>> MenuWrapper* assetDeveloperMenu = developerMenu->addMenu("Assets"); - auto& assetDialogFactory = AssetUploadDialogFactory::getInstance(); assetDialogFactory.setDialogParent(this); - QAction* assetUpload = addActionToQMenuAndActionHash(assetDeveloperMenu, - MenuOption::UploadAsset, - 0, - &assetDialogFactory, - SLOT(showDialog())); - + MenuOption::UploadAsset, + 0, + &assetDialogFactory, + SLOT(showDialog())); + // disable the asset upload action by default - it gets enabled only if asset server becomes present assetUpload->setEnabled(false); - + auto& atpMigrator = ATPAssetMigrator::getInstance(); atpMigrator.setDialogParent(this); - + addActionToQMenuAndActionHash(assetDeveloperMenu, MenuOption::AssetMigration, - 0, &atpMigrator, - SLOT(loadEntityServerFile())); - + 0, &atpMigrator, + SLOT(loadEntityServerFile())); + + // Developer > Avatar >>> MenuWrapper* avatarDebugMenu = developerMenu->addMenu("Avatar"); + // Developer > Avatar > Face Tracking MenuWrapper* faceTrackingMenu = avatarDebugMenu->addMenu("Face Tracking"); { QActionGroup* faceTrackerGroup = new QActionGroup(avatarDebugMenu); @@ -423,6 +444,7 @@ Menu::Menu() { #endif #ifdef HAVE_IVIEWHMD + // Developer > Avatar > Eye Tracking MenuWrapper* eyeTrackingMenu = avatarDebugMenu->addMenu("Eye Tracking"); addCheckableActionToQMenuAndActionHash(eyeTrackingMenu, MenuOption::SMIEyeTracking, 0, false, qApp, SLOT(setActiveEyeTracker())); @@ -439,9 +461,8 @@ Menu::Menu() { qApp, SLOT(setActiveEyeTracker())); #endif - auto avatarManager = DependencyManager::get(); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false, - avatarManager.data(), SLOT(setShouldShowReceiveStats(bool))); + avatarManager.data(), SLOT(setShouldShowReceiveStats(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderBoundingCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderLookAtVectors, 0, false); @@ -450,13 +471,13 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowWhosLookingAtMe, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawDefaultPose, 0, false, - avatar, SLOT(setEnableDebugDrawDefaultPose(bool))); + avatar, SLOT(setEnableDebugDrawDefaultPose(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawAnimPose, 0, false, - avatar, SLOT(setEnableDebugDrawAnimPose(bool))); + avatar, SLOT(setEnableDebugDrawAnimPose(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawPosition, 0, false, - avatar, SLOT(setEnableDebugDrawPosition(bool))); + avatar, SLOT(setEnableDebugDrawPosition(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::MeshVisible, 0, true, - avatar, SLOT(setEnableMeshVisible(bool))); + avatar, SLOT(setEnableMeshVisible(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisableEyelidAdjustment, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::TurnWithHead, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ComfortMode, 0, true); @@ -473,37 +494,37 @@ Menu::Menu() { avatar, SLOT(updateMotionBehaviorFromMenu()), UNSPECIFIED_POSITION, "Developer"); - - + // Developer > Hands >>> MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands"); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::LowVelocityFilter, 0, true, - qApp, SLOT(setLowVelocityFilter(bool))); + qApp, SLOT(setLowVelocityFilter(bool))); MenuWrapper* leapOptionsMenu = handOptionsMenu->addMenu("Leap Motion"); addCheckableActionToQMenuAndActionHash(leapOptionsMenu, MenuOption::LeapMotionOnHMD, 0, false); + // Developer > Network >>> MenuWrapper* networkMenu = developerMenu->addMenu("Network"); addActionToQMenuAndActionHash(networkMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches())); addCheckableActionToQMenuAndActionHash(networkMenu, MenuOption::DisableNackPackets, 0, false, - qApp->getEntityEditPacketSender(), - SLOT(toggleNackPackets())); + qApp->getEntityEditPacketSender(), + SLOT(toggleNackPackets())); addCheckableActionToQMenuAndActionHash(networkMenu, - MenuOption::DisableActivityLogger, - 0, - false, - &UserActivityLogger::getInstance(), - SLOT(disable(bool))); + MenuOption::DisableActivityLogger, + 0, + false, + &UserActivityLogger::getInstance(), + SLOT(disable(bool))); addActionToQMenuAndActionHash(networkMenu, MenuOption::CachesSize, 0, - dialogsManager.data(), SLOT(cachesSizeDialog())); + dialogsManager.data(), SLOT(cachesSizeDialog())); addActionToQMenuAndActionHash(networkMenu, MenuOption::DiskCacheEditor, 0, - dialogsManager.data(), SLOT(toggleDiskCacheEditor())); + dialogsManager.data(), SLOT(toggleDiskCacheEditor())); addActionToQMenuAndActionHash(networkMenu, MenuOption::ShowDSConnectTable, 0, - dialogsManager.data(), SLOT(showDomainConnectionDialog())); + dialogsManager.data(), SLOT(showDomainConnectionDialog())); + // Developer > Timing and Stats >>> MenuWrapper* timingMenu = developerMenu->addMenu("Timing and Stats"); - MenuWrapper* perfTimerMenu = timingMenu->addMenu("Performance Timer"); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayDebugTimingDetails, 0, false); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::OnlyDisplayTopTen, 0, true); @@ -520,60 +541,34 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::SuppressShortTimings); addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::ShowRealtimeEntityStats); - auto audioIO = DependencyManager::get(); + // Developer > Audio >>> MenuWrapper* audioDebugMenu = developerMenu->addMenu("Audio"); - addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction, - 0, - true, - audioIO.data(), - SLOT(toggleAudioNoiseReduction())); - + addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioNoiseReduction, 0, true, + audioIO.data(), SLOT(toggleAudioNoiseReduction())); addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoServerAudio, 0, false, - audioIO.data(), SLOT(toggleServerEcho())); + audioIO.data(), SLOT(toggleServerEcho())); addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoLocalAudio, 0, false, - audioIO.data(), SLOT(toggleLocalEcho())); - addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::MuteAudio, - Qt::CTRL | Qt::Key_M, - false, - audioIO.data(), - SLOT(toggleMute())); - addActionToQMenuAndActionHash(audioDebugMenu, - MenuOption::MuteEnvironment, - 0, - audioIO.data(), - SLOT(sendMuteEnvironmentPacket())); + audioIO.data(), SLOT(toggleLocalEcho())); + addActionToQMenuAndActionHash(audioDebugMenu, MenuOption::MuteEnvironment, 0, + audioIO.data(), SLOT(sendMuteEnvironmentPacket())); auto scope = DependencyManager::get(); - MenuWrapper* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope"); - addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, - Qt::CTRL | Qt::Key_P, false, - scope.data(), - SLOT(toggle())); - addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopePause, - Qt::CTRL | Qt::SHIFT | Qt::Key_P , - false, - scope.data(), - SLOT(togglePause())); + addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_P, false, + scope.data(), SLOT(toggle())); + addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopePause, Qt::CTRL | Qt::SHIFT | Qt::Key_P, false, + scope.data(), SLOT(togglePause())); + addDisabledActionAndSeparator(audioScopeMenu, "Display Frames"); { - QAction *fiveFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeFiveFrames, - 0, - true, - scope.data(), - SLOT(selectAudioScopeFiveFrames())); + QAction* fiveFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeFiveFrames, + 0, true, scope.data(), SLOT(selectAudioScopeFiveFrames())); - QAction *twentyFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeTwentyFrames, - 0, - false, - scope.data(), - SLOT(selectAudioScopeTwentyFrames())); + QAction* twentyFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeTwentyFrames, + 0, false, scope.data(), SLOT(selectAudioScopeTwentyFrames())); - QAction *fiftyFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeFiftyFrames, - 0, - false, - scope.data(), - SLOT(selectAudioScopeFiftyFrames())); + QAction* fiftyFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeFiftyFrames, + 0, false, scope.data(), SLOT(selectAudioScopeFiftyFrames())); QActionGroup* audioScopeFramesGroup = new QActionGroup(audioScopeMenu); audioScopeFramesGroup->addAction(fiveFrames); @@ -581,19 +576,69 @@ Menu::Menu() { audioScopeFramesGroup->addAction(fiftyFrames); } + // Developer > Physics >>> MenuWrapper* physicsOptionsMenu = developerMenu->addMenu("Physics"); addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowOwned); addCheckableActionToQMenuAndActionHash(physicsOptionsMenu, MenuOption::PhysicsShowHulls); + // Developer > Display Crash Options addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisplayCrashOptions, 0, true); + // Developer > Crash Application addActionToQMenuAndActionHash(developerMenu, MenuOption::CrashInterface, 0, qApp, SLOT(crashApplication())); - MenuWrapper* helpMenu = addMenu("Help"); - addActionToQMenuAndActionHash(helpMenu, MenuOption::EditEntitiesHelp, 0, qApp, SLOT(showEditEntitiesHelp())); + // Developer > Log... + addActionToQMenuAndActionHash(developerMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L, + qApp, SLOT(toggleLogDialog())); + + // Developer > Stats + addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats); + + // Developer > Audio Stats... + addActionToQMenuAndActionHash(developerMenu, MenuOption::AudioNetworkStats, 0, + dialogsManager.data(), SLOT(audioStatsDetails())); + + // Developer > Bandwidth Stats... + addActionToQMenuAndActionHash(developerMenu, MenuOption::BandwidthDetails, 0, + dialogsManager.data(), SLOT(bandwidthDetails())); + + // Developer > Entity Stats... + addActionToQMenuAndActionHash(developerMenu, MenuOption::OctreeStats, 0, + dialogsManager.data(), SLOT(octreeStatsDetails())); + + // Developer > World Axes + addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::WorldAxes); + + + +#if 0 /// -------------- REMOVED FOR NOW -------------- + addDisabledActionAndSeparator(navigateMenu, "History"); + QAction* backAction = addActionToQMenuAndActionHash(navigateMenu, MenuOption::Back, 0, addressManager.data(), SLOT(goBack())); + QAction* forwardAction = addActionToQMenuAndActionHash(navigateMenu, MenuOption::Forward, 0, addressManager.data(), SLOT(goForward())); + + // connect to the AddressManager signal to enable and disable the back and forward menu items + connect(addressManager.data(), &AddressManager::goBackPossible, backAction, &QAction::setEnabled); + connect(addressManager.data(), &AddressManager::goForwardPossible, forwardAction, &QAction::setEnabled); + + // set the two actions to start disabled since the stacks are clear on startup + backAction->setDisabled(true); + forwardAction->setDisabled(true); + + MenuWrapper* toolsMenu = addMenu("Tools"); + addActionToQMenuAndActionHash(toolsMenu, + MenuOption::ToolWindow, + Qt::CTRL | Qt::ALT | Qt::Key_T, + dialogsManager.data(), + SLOT(toggleToolWindow()), + QAction::NoRole, UNSPECIFIED_POSITION, "Advanced"); + + + addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::NamesAboveHeads, 0, true, + NULL, NULL, UNSPECIFIED_POSITION, "Advanced"); + + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::CenterPlayerInView, + 0, false, qApp, SLOT(rotationModeChanged()), + UNSPECIFIED_POSITION, "Advanced"); -#ifndef Q_OS_MAC - QAction* aboutAction = helpMenu->addAction(MenuOption::AboutApp); - connect(aboutAction, SIGNAL(triggered()), qApp, SLOT(aboutApp())); #endif } diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 4b77e96f79..085b349b8f 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -64,6 +64,7 @@ public: void saveSettings(); MenuWrapper* getMenu(const QString& menuName); + MenuWrapper* getSubMenuFromName(const QString& menuName, MenuWrapper* menu); void triggerOption(const QString& menuOption); QAction* getActionForOption(const QString& menuOption); @@ -130,7 +131,6 @@ private: const QString& grouping = QString()); QAction* getActionFromName(const QString& menuName, MenuWrapper* menu); - MenuWrapper* getSubMenuFromName(const QString& menuName, MenuWrapper* menu); MenuWrapper* getMenuParent(const QString& menuName, QString& finalMenuPart); QAction* getMenuAction(const QString& menuName); @@ -205,7 +205,6 @@ namespace MenuOption { const QString DontRenderEntitiesAsScene = "Don't Render Entities as Scene"; const QString EchoLocalAudio = "Echo Local Audio"; const QString EchoServerAudio = "Echo Server Audio"; - const QString EditEntitiesHelp = "Edit Entities Help..."; const QString Enable3DTVMode = "Enable 3DTV Mode"; const QString EnableCharacterController = "Enable avatar collisions"; const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation"; @@ -219,8 +218,9 @@ namespace MenuOption { const QString FixGaze = "Fix Gaze (no saccade)"; const QString Forward = "Forward"; const QString FrameTimer = "Show Timer"; - const QString FullscreenMirror = "Fullscreen Mirror"; + const QString FullscreenMirror = "Mirror"; const QString GlowWhenSpeaking = "Glow When Speaking"; + const QString Help = "Help..."; const QString IncreaseAvatarSize = "Increase Avatar Size"; const QString IndependentMode = "Independent Mode"; const QString InputMenu = "Avatar>Input Devices"; @@ -234,7 +234,7 @@ namespace MenuOption { const QString LogExtraTimings = "Log Extra Timing Details"; const QString LowVelocityFilter = "Low Velocity Filter"; const QString MeshVisible = "Draw Mesh"; - const QString Mirror = "Mirror"; + const QString MiniMirror = "Mini Mirror"; const QString MuteAudio = "Mute Microphone"; const QString MuteEnvironment = "Mute Environment"; const QString MuteFaceTracking = "Mute Face Tracking"; @@ -249,7 +249,7 @@ namespace MenuOption { const QString PhysicsShowOwned = "Highlight Simulation Ownership"; const QString PhysicsShowHulls = "Draw Collision Hulls"; const QString PipelineWarnings = "Log Render Pipeline Warnings"; - const QString Preferences = "Preferences..."; + const QString Preferences = "General..."; const QString Quit = "Quit"; const QString ReloadAllScripts = "Reload All Scripts"; const QString ReloadContent = "Reload Content (Clears all caches)"; @@ -277,7 +277,7 @@ namespace MenuOption { const QString RenderAmbientLight9 = "FUNSTON_BEACH_SUNSET"; const QString ResetAvatarSize = "Reset Avatar Size"; const QString ResetSensors = "Reset Sensors"; - const QString RunningScripts = "Running Scripts"; + const QString RunningScripts = "Running Scripts..."; const QString RunTimingTests = "Run Timing Tests"; const QString ScriptEditor = "Script Editor..."; const QString ScriptedMotorControl = "Enable Scripted Motor Control"; diff --git a/interface/src/ModelPackager.cpp b/interface/src/ModelPackager.cpp index 35be11f2bb..64ab76c64c 100644 --- a/interface/src/ModelPackager.cpp +++ b/interface/src/ModelPackager.cpp @@ -15,6 +15,7 @@ #include #include +#include #include "ModelSelector.h" #include "ModelPropertiesDialog.h" @@ -78,7 +79,7 @@ bool ModelPackager::loadModel() { if (_modelFile.completeSuffix().contains("fst")) { QFile fst(_modelFile.filePath()); if (!fst.open(QFile::ReadOnly | QFile::Text)) { - QMessageBox::warning(NULL, + OffscreenUi::warning(NULL, QString("ModelPackager::loadModel()"), QString("Could not open FST file %1").arg(_modelFile.filePath()), QMessageBox::Ok); @@ -97,7 +98,7 @@ bool ModelPackager::loadModel() { // open the fbx file QFile fbx(_fbxInfo.filePath()); if (!_fbxInfo.exists() || !_fbxInfo.isFile() || !fbx.open(QIODevice::ReadOnly)) { - QMessageBox::warning(NULL, + OffscreenUi::warning(NULL, QString("ModelPackager::loadModel()"), QString("Could not open FBX file %1").arg(_fbxInfo.filePath()), QMessageBox::Ok); @@ -402,7 +403,7 @@ bool ModelPackager::copyTextures(const QString& oldDir, const QDir& newDir) { } if (!errors.isEmpty()) { - QMessageBox::warning(nullptr, "ModelPackager::copyTextures()", + OffscreenUi::warning(nullptr, "ModelPackager::copyTextures()", "Missing textures:" + errors); qCDebug(interfaceapp) << "ModelPackager::copyTextures():" << errors; return false; diff --git a/interface/src/ModelPropertiesDialog.cpp b/interface/src/ModelPropertiesDialog.cpp index 8e0541518a..d41a913c95 100644 --- a/interface/src/ModelPropertiesDialog.cpp +++ b/interface/src/ModelPropertiesDialog.cpp @@ -21,6 +21,7 @@ #include #include +#include #include "ModelPropertiesDialog.h" @@ -200,7 +201,7 @@ void ModelPropertiesDialog::chooseTextureDirectory() { return; } if (!directory.startsWith(_basePath)) { - QMessageBox::warning(NULL, "Invalid texture directory", "Texture directory must be child of base path."); + OffscreenUi::warning(NULL, "Invalid texture directory", "Texture directory must be child of base path."); return; } _textureDirectory->setText(directory.length() == _basePath.length() ? "." : directory.mid(_basePath.length() + 1)); diff --git a/interface/src/Stars.cpp b/interface/src/Stars.cpp index c8fd5188e2..3305cc4f3b 100644 --- a/interface/src/Stars.cpp +++ b/interface/src/Stars.cpp @@ -22,11 +22,11 @@ #include #include -#include "../../libraries/render-utils/stars_vert.h" -#include "../../libraries/render-utils/stars_frag.h" +#include +#include -#include "../../libraries/render-utils/standardTransformPNTC_vert.h" -#include "../../libraries/render-utils/starsGrid_frag.h" +#include +#include //static const float TILT = 0.23f; static const float TILT = 0.0f; diff --git a/interface/src/assets/ATPAssetMigrator.cpp b/interface/src/assets/ATPAssetMigrator.cpp index fadf4ca7ad..f037cf9f91 100644 --- a/interface/src/assets/ATPAssetMigrator.cpp +++ b/interface/src/assets/ATPAssetMigrator.cpp @@ -25,6 +25,7 @@ #include #include +#include "OffscreenUi.h" #include "../ui/AssetUploadDialogFactory.h" Q_DECLARE_LOGGING_CATEGORY(asset_migrator); @@ -52,7 +53,7 @@ void ATPAssetMigrator::loadEntityServerFile() { " continue?\n\nMake sure you are connected to the right domain." }; - auto button = QMessageBox::question(_dialogParent, MESSAGE_BOX_TITLE, MIGRATION_CONFIRMATION_TEXT, + auto button = OffscreenUi::question(_dialogParent, MESSAGE_BOX_TITLE, MIGRATION_CONFIRMATION_TEXT, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (button == QMessageBox::No) { @@ -67,7 +68,7 @@ void ATPAssetMigrator::loadEntityServerFile() { QByteArray jsonData; if (!gunzip(compressedJsonData, jsonData)) { - QMessageBox::warning(_dialogParent, "Error", "The file at" + filename + "was not in gzip format."); + OffscreenUi::warning(_dialogParent, "Error", "The file at" + filename + "was not in gzip format."); } QJsonDocument modelsJSON = QJsonDocument::fromJson(jsonData); @@ -107,7 +108,7 @@ void ATPAssetMigrator::loadEntityServerFile() { if (request->getResult() == ResourceRequest::Success) { migrateResource(request); } else { - QMessageBox::warning(_dialogParent, "Error", + OffscreenUi::warning(_dialogParent, "Error", QString("Could not retrieve asset at %1").arg(modelURL.toString())); } request->deleteLater(); @@ -115,7 +116,7 @@ void ATPAssetMigrator::loadEntityServerFile() { request->send(); } else { - QMessageBox::warning(_dialogParent, "Error", + OffscreenUi::warning(_dialogParent, "Error", QString("Could not create request for asset at %1").arg(modelURL.toString())); } @@ -129,7 +130,7 @@ void ATPAssetMigrator::loadEntityServerFile() { _doneReading = true; } else { - QMessageBox::warning(_dialogParent, "Error", + OffscreenUi::warning(_dialogParent, "Error", "There was a problem loading that entity-server file for ATP asset migration. Please try again"); } } @@ -212,7 +213,7 @@ bool ATPAssetMigrator::wantsToMigrateResource(const QUrl& url) { "Select \"No\" to be prompted for each discovered asset." }; - auto button = QMessageBox::question(_dialogParent, MESSAGE_BOX_TITLE, COMPLETE_MIGRATION_TEXT, + auto button = OffscreenUi::question(_dialogParent, MESSAGE_BOX_TITLE, COMPLETE_MIGRATION_TEXT, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (button == QMessageBox::Yes) { @@ -226,7 +227,7 @@ bool ATPAssetMigrator::wantsToMigrateResource(const QUrl& url) { return true; } else { // present a dialog asking the user if they want to migrate this specific resource - auto button = QMessageBox::question(_dialogParent, MESSAGE_BOX_TITLE, + auto button = OffscreenUi::question(_dialogParent, MESSAGE_BOX_TITLE, "Would you like to migrate the following resource?\n" + url.toString(), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); return button == QMessageBox::Yes; @@ -254,11 +255,11 @@ void ATPAssetMigrator::saveEntityServerFile() { QMessageBox::information(_dialogParent, "Success", QString("Your new entities file has been saved at %1").arg(saveName)); } else { - QMessageBox::warning(_dialogParent, "Error", "Could not gzip JSON data for new entities file."); + OffscreenUi::warning(_dialogParent, "Error", "Could not gzip JSON data for new entities file."); } } else { - QMessageBox::warning(_dialogParent, "Error", + OffscreenUi::warning(_dialogParent, "Error", QString("Could not open file at %1 to write new entities file to.").arg(saveName)); } } diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 6094a0b9fe..c1eb173629 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -42,6 +42,7 @@ #include "Util.h" #include "world.h" #include "InterfaceLogging.h" +#include "SoftAttachmentModel.h" #include using namespace std; @@ -107,9 +108,10 @@ Avatar::Avatar(RigPointer rig) : } Avatar::~Avatar() { - assert(_motionState == nullptr); - for(auto attachment : _unusedAttachments) { - delete attachment; + assert(isDead()); // mark dead before calling the dtor + if (_motionState) { + delete _motionState; + _motionState = nullptr; } } @@ -186,26 +188,6 @@ void Avatar::simulate(float deltaTime) { qCDebug(interfaceapp) << "Billboarding" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for LOD" << getLODDistance(); } - const bool isControllerLogging = DependencyManager::get()->getRenderDistanceControllerIsLogging(); - float renderDistance = DependencyManager::get()->getRenderDistance(); - const float SKIP_HYSTERESIS_PROPORTION = isControllerLogging ? 0.0f : BILLBOARD_HYSTERESIS_PROPORTION; - float distance = glm::distance(qApp->getCamera()->getPosition(), getPosition()); - if (_shouldSkipRender) { - if (distance < renderDistance * (1.0f - SKIP_HYSTERESIS_PROPORTION)) { - _shouldSkipRender = false; - _skeletonModel.setVisibleInScene(true, qApp->getMain3DScene()); - if (!isControllerLogging) { // Test for isMyAvatar is prophylactic. Never occurs in current code. - qCDebug(interfaceapp) << "Rerendering" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for distance" << renderDistance; - } - } - } else if (distance > renderDistance * (1.0f + SKIP_HYSTERESIS_PROPORTION)) { - _shouldSkipRender = true; - _skeletonModel.setVisibleInScene(false, qApp->getMain3DScene()); - if (!isControllerLogging) { - qCDebug(interfaceapp) << "Unrendering" << (isMyAvatar() ? "myself" : getSessionUUID()) << "for distance" << renderDistance; - } - } - // simple frustum check float boundingRadius = getBillboardSize(); bool inViewFrustum = qApp->getViewFrustum()->sphereInFrustum(getPosition(), boundingRadius) != @@ -257,6 +239,9 @@ void Avatar::simulate(float deltaTime) { // until velocity is included in AvatarData update message. //_position += _velocity * deltaTime; measureMotionDerivatives(deltaTime); + + simulateAttachments(deltaTime); + updatePalms(); } bool Avatar::isLookingAtMe(AvatarSharedPointer avatar) const { @@ -324,7 +309,7 @@ bool Avatar::addToScene(AvatarSharedPointer self, std::shared_ptr _skeletonModel.addToScene(scene, pendingChanges); getHead()->getFaceModel().addToScene(scene, pendingChanges); - for (auto attachmentModel : _attachmentModels) { + for (auto& attachmentModel : _attachmentModels) { attachmentModel->addToScene(scene, pendingChanges); } @@ -335,7 +320,7 @@ void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptrgetFaceModel().removeFromScene(scene, pendingChanges); - for (auto attachmentModel : _attachmentModels) { + for (auto& attachmentModel : _attachmentModels) { attachmentModel->removeFromScene(scene, pendingChanges); } } @@ -565,15 +550,14 @@ void Avatar::fixupModelsInScene() { faceModel.removeFromScene(scene, pendingChanges); faceModel.addToScene(scene, pendingChanges); } - for (auto attachmentModel : _attachmentModels) { + for (auto& attachmentModel : _attachmentModels) { if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) { attachmentModel->removeFromScene(scene, pendingChanges); attachmentModel->addToScene(scene, pendingChanges); } } - for (auto attachmentModelToRemove : _attachmentsToRemove) { + for (auto& attachmentModelToRemove : _attachmentsToRemove) { attachmentModelToRemove->removeFromScene(scene, pendingChanges); - _unusedAttachments << attachmentModelToRemove; } _attachmentsToRemove.clear(); scene->enqueuePendingChanges(pendingChanges); @@ -603,21 +587,29 @@ bool Avatar::shouldRenderHead(const RenderArgs* renderArgs) const { return true; } +// virtual void Avatar::simulateAttachments(float deltaTime) { - for (int i = 0; i < _attachmentModels.size(); i++) { + for (int i = 0; i < (int)_attachmentModels.size(); i++) { const AttachmentData& attachment = _attachmentData.at(i); - Model* model = _attachmentModels.at(i); + auto& model = _attachmentModels.at(i); int jointIndex = getJointIndex(attachment.jointName); glm::vec3 jointPosition; glm::quat jointRotation; - if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) && - _skeletonModel.getJointRotationInWorldFrame(jointIndex, jointRotation)) { - model->setTranslation(jointPosition + jointRotation * attachment.translation * getUniformScale()); - model->setRotation(jointRotation * attachment.rotation); - model->setScaleToFit(true, getUniformScale() * attachment.scale, true); // hack to force rescale - model->setSnapModelToCenter(false); // hack to force resnap - model->setSnapModelToCenter(true); + if (attachment.isSoft) { + // soft attachments do not have transform offsets + model->setTranslation(getPosition()); + model->setRotation(getOrientation() * Quaternions::Y_180); model->simulate(deltaTime); + } else { + if (_skeletonModel.getJointPositionInWorldFrame(jointIndex, jointPosition) && + _skeletonModel.getJointRotationInWorldFrame(jointIndex, jointRotation)) { + model->setTranslation(jointPosition + jointRotation * attachment.translation * getUniformScale()); + model->setRotation(jointRotation * attachment.rotation); + model->setScaleToFit(true, getUniformScale() * attachment.scale, true); // hack to force rescale + model->setSnapModelToCenter(false); // hack to force resnap + model->setSnapModelToCenter(true); + model->simulate(deltaTime); + } } } } @@ -940,13 +932,48 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _skeletonModel.setURL(_skeletonModelURL); } +// create new model, can return an instance of a SoftAttachmentModel rather then Model +static std::shared_ptr allocateAttachmentModel(bool isSoft, RigPointer rigOverride) { + if (isSoft) { + // cast to std::shared_ptr + return std::dynamic_pointer_cast(std::make_shared(std::make_shared(), nullptr, rigOverride)); + } else { + return std::make_shared(std::make_shared()); + } +} + void Avatar::setAttachmentData(const QVector& attachmentData) { - AvatarData::setAttachmentData(attachmentData); if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "setAttachmentData", Qt::DirectConnection, Q_ARG(const QVector, attachmentData)); return; } + + auto oldAttachmentData = _attachmentData; + AvatarData::setAttachmentData(attachmentData); + + // if number of attachments has been reduced, remove excess models. + while ((int)_attachmentModels.size() > attachmentData.size()) { + auto attachmentModel = _attachmentModels.back(); + _attachmentModels.pop_back(); + _attachmentsToRemove.push_back(attachmentModel); + } + + for (int i = 0; i < attachmentData.size(); i++) { + if (i == (int)_attachmentModels.size()) { + // if number of attachments has been increased, we need to allocate a new model + _attachmentModels.push_back(allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel.getRig())); + } + else if (i < oldAttachmentData.size() && oldAttachmentData[i].isSoft != attachmentData[i].isSoft) { + // if the attachment has changed type, we need to re-allocate a new one. + _attachmentsToRemove.push_back(_attachmentModels[i]); + _attachmentModels[i] = allocateAttachmentModel(attachmentData[i].isSoft, _skeletonModel.getRig()); + } + _attachmentModels[i]->setURL(attachmentData[i].modelURL); + } + + // AJT: TODO REMOVE + /* // make sure we have as many models as attachments while (_attachmentModels.size() < attachmentData.size()) { Model* model = nullptr; @@ -959,16 +986,20 @@ void Avatar::setAttachmentData(const QVector& attachmentData) { _attachmentModels.append(model); } while (_attachmentModels.size() > attachmentData.size()) { - auto attachmentModel = _attachmentModels.takeLast(); - _attachmentsToRemove << attachmentModel; + auto attachmentModel = _attachmentModels.back(); + _attachmentModels.pop_back(); + _attachmentsToRemove.push_back(attachmentModel); } + */ + /* // update the urls for (int i = 0; i < attachmentData.size(); i++) { _attachmentModels[i]->setURL(attachmentData.at(i).modelURL); _attachmentModels[i]->setSnapModelToCenter(true); _attachmentModels[i]->setScaleToFit(true, getUniformScale() * _attachmentData.at(i).scale); } + */ } void Avatar::setBillboard(const QByteArray& billboard) { @@ -995,6 +1026,9 @@ int Avatar::parseDataFromBuffer(const QByteArray& buffer) { if (_moving && _motionState) { _motionState->addDirtyFlags(Simulation::DIRTY_POSITION); } + if (_moving || _hasNewJointRotations || _hasNewJointTranslations) { + locationChanged(); + } endUpdate(); return bytesRead; @@ -1126,34 +1160,24 @@ void Avatar::rebuildCollisionShape() { } } +// thread-safe glm::vec3 Avatar::getLeftPalmPosition() { - glm::vec3 leftHandPosition; - getSkeletonModel().getLeftHandPosition(leftHandPosition); - glm::quat leftRotation; - getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getLeftHandJointIndex(), leftRotation); - leftHandPosition += HAND_TO_PALM_OFFSET * glm::inverse(leftRotation); - return leftHandPosition; + return _leftPalmPositionCache.get(); } +// thread-safe glm::quat Avatar::getLeftPalmRotation() { - glm::quat leftRotation; - getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getLeftHandJointIndex(), leftRotation); - return leftRotation; + return _leftPalmRotationCache.get(); } +// thread-safe glm::vec3 Avatar::getRightPalmPosition() { - glm::vec3 rightHandPosition; - getSkeletonModel().getRightHandPosition(rightHandPosition); - glm::quat rightRotation; - getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation); - rightHandPosition += HAND_TO_PALM_OFFSET * glm::inverse(rightRotation); - return rightHandPosition; + return _rightPalmPositionCache.get(); } +// thread-safe glm::quat Avatar::getRightPalmRotation() { - glm::quat rightRotation; - getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation); - return rightRotation; + return _rightPalmRotationCache.get(); } void Avatar::setPosition(const glm::vec3& position) { @@ -1165,3 +1189,24 @@ void Avatar::setOrientation(const glm::quat& orientation) { AvatarData::setOrientation(orientation); updateAttitude(); } + +void Avatar::updatePalms() { + + // get palm rotations + glm::quat leftPalmRotation, rightPalmRotation; + getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getLeftHandJointIndex(), leftPalmRotation); + getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightPalmRotation); + + // get palm positions + glm::vec3 leftPalmPosition, rightPalmPosition; + getSkeletonModel().getLeftHandPosition(leftPalmPosition); + getSkeletonModel().getRightHandPosition(rightPalmPosition); + leftPalmPosition += HAND_TO_PALM_OFFSET * glm::inverse(leftPalmRotation); + rightPalmPosition += HAND_TO_PALM_OFFSET * glm::inverse(rightPalmRotation); + + // update thread-safe caches + _leftPalmRotationCache.set(leftPalmRotation); + _rightPalmRotationCache.set(rightPalmRotation); + _leftPalmPositionCache.set(leftPalmPosition); + _rightPalmPositionCache.set(rightPalmPosition); +} diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 4926212b4d..ddd1c612f1 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -27,6 +27,7 @@ #include "SkeletonModel.h" #include "world.h" #include "Rig.h" +#include namespace render { template <> const ItemKey payloadGetKey(const AvatarSharedPointer& avatar); @@ -68,7 +69,7 @@ public: void init(); void simulate(float deltaTime); - void simulateAttachments(float deltaTime); + virtual void simulateAttachments(float deltaTime); virtual void render(RenderArgs* renderArgs, const glm::vec3& cameraPosition); @@ -112,6 +113,8 @@ public: virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; + virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; } + virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; } virtual void setFaceModelURL(const QUrl& faceModelURL) override; virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; @@ -159,7 +162,9 @@ public: AvatarMotionState* getMotionState() { return _motionState; } + using SpatiallyNestable::setPosition; virtual void setPosition(const glm::vec3& position) override; + using SpatiallyNestable::setOrientation; virtual void setOrientation(const glm::quat& orientation) override; public slots: @@ -177,9 +182,9 @@ protected: SkeletonModel _skeletonModel; glm::vec3 _skeletonOffset; - QVector _attachmentModels; - QVector _attachmentsToRemove; - QVector _unusedAttachments; + std::vector> _attachmentModels; + std::vector> _attachmentsToRemove; + float _bodyYawDelta; // degrees/sec // These position histories and derivatives are in the world-frame. @@ -225,8 +230,15 @@ protected: virtual void updateJointMappings() override; + virtual void updatePalms(); + render::ItemID _renderItemID; + ThreadSafeValueCache _leftPalmPositionCache { glm::vec3() }; + ThreadSafeValueCache _leftPalmRotationCache { glm::quat() }; + ThreadSafeValueCache _rightPalmPositionCache { glm::vec3() }; + ThreadSafeValueCache _rightPalmRotationCache { glm::quat() }; + private: bool _initialized; NetworkTexturePointer _billboardTexture; diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 689d557c48..12c792e631 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -89,6 +89,8 @@ void AvatarActionHold::prepareForPhysicsSimulation() { // code here for future reference. // _palmRotationFromRigidBody = avatarRotationInverse * palmRotation; }); + + activateBody(true); } std::shared_ptr AvatarActionHold::getTarget(glm::quat& rotation, glm::vec3& position) { @@ -197,7 +199,6 @@ void AvatarActionHold::updateActionWorker(float deltaTimeStep) { if (_kinematic) { doKinematicUpdate(deltaTimeStep); } else { - activateBody(); forceBodyNonStatic(); ObjectActionSpring::updateActionWorker(deltaTimeStep); } @@ -247,7 +248,6 @@ void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) { _previousSet = true; }); - activateBody(); forceBodyNonStatic(); } @@ -344,7 +344,6 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) { ownerEntity->setActionDataNeedsTransmit(true); } }); - activateBody(); } return true; @@ -431,6 +430,5 @@ void AvatarActionHold::deserialize(QByteArray serializedArguments) { _active = true; }); - activateBody(); forceBodyNonStatic(); } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 217cd28e61..833ed26cc9 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -76,11 +76,8 @@ AvatarManager::AvatarManager(QObject* parent) : packetReceiver.registerListener(PacketType::AvatarBillboard, this, "processAvatarBillboardPacket"); } -const float SMALLEST_REASONABLE_HORIZON = 5.0f; // meters -Setting::Handle avatarRenderDistanceInverseHighLimit("avatarRenderDistanceHighLimit", 1.0f / SMALLEST_REASONABLE_HORIZON); -void AvatarManager::setRenderDistanceInverseHighLimit(float newValue) { - avatarRenderDistanceInverseHighLimit.set(newValue); - _renderDistanceController.setControlledValueHighLimit(newValue); +AvatarManager::~AvatarManager() { + _myAvatar->die(); } void AvatarManager::init() { @@ -98,19 +95,6 @@ void AvatarManager::init() { _myAvatar->addToScene(_myAvatar, scene, pendingChanges); } scene->enqueuePendingChanges(pendingChanges); - - const float target_fps = qApp->getTargetFrameRate(); - _renderDistanceController.setMeasuredValueSetpoint(target_fps); - _renderDistanceController.setControlledValueHighLimit(avatarRenderDistanceInverseHighLimit.get()); - _renderDistanceController.setControlledValueLowLimit(1.0f / (float) TREE_SCALE); - // Advice for tuning parameters: - // See PIDController.h. There's a section on tuning in the reference. - // Turn on logging with the following (or from js with AvatarList.setRenderDistanceControllerHistory("avatar render", 300)) - //_renderDistanceController.setHistorySize("avatar render", target_fps * 4); - // Note that extra logging/hysteresis is turned off in Avatar.cpp when the above logging is on. - _renderDistanceController.setKP(0.0008f); // Usually about 0.6 of largest that doesn't oscillate when other parameters 0. - _renderDistanceController.setKI(0.0006f); // Big enough to bring us to target with the above KP. - _renderDistanceController.setKD(0.000001f); // A touch of kd increases the speed by which we get there. } void AvatarManager::updateMyAvatar(float deltaTime) { @@ -133,38 +117,21 @@ void AvatarManager::updateMyAvatar(float deltaTime) { void AvatarManager::updateOtherAvatars(float deltaTime) { // lock the hash for read to check the size QReadLocker lock(&_hashLock); - + if (_avatarHash.size() < 2 && _avatarFades.isEmpty()) { return; } - + lock.unlock(); - + bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateAvatars()"); PerformanceTimer perfTimer("otherAvatars"); - - float distance; - if (!qApp->isThrottleRendering()) { - _renderDistanceController.setMeasuredValueSetpoint(qApp->getTargetFrameRate()); // No problem updating in flight. - // The PID controller raises the controlled value when the measured value goes up. - // The measured value is frame rate. When the controlled value (1 / render cutoff distance) - // goes up, the render cutoff distance gets closer, the number of rendered avatars is less, and frame rate - // goes up. - const float deduced = qApp->getLastUnsynchronizedFps(); - distance = 1.0f / _renderDistanceController.update(deduced, deltaTime); - } else { - // Here we choose to just use the maximum render cutoff distance if throttled. - distance = 1.0f / _renderDistanceController.getControlledValueLowLimit(); - } - _renderDistanceAverage.updateAverage(distance); - _renderDistance = _renderDistanceAverage.getAverage(); - int renderableCount = 0; // simulate avatars auto hashCopy = getHashCopy(); - + AvatarHash::iterator avatarIterator = hashCopy.begin(); while (avatarIterator != hashCopy.end()) { auto avatar = std::static_pointer_cast(avatarIterator.value()); @@ -179,14 +146,10 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { } else { avatar->startUpdate(); avatar->simulate(deltaTime); - if (avatar->getShouldRender()) { - renderableCount++; - } avatar->endUpdate(); ++avatarIterator; } } - _renderedAvatarCount = renderableCount; // simulate avatar fades simulateAvatarFades(deltaTime); @@ -206,7 +169,12 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { avatar->setTargetScale(avatar->getUniformScale() * SHRINK_RATE); if (avatar->getTargetScale() <= MIN_FADE_SCALE) { avatar->removeFromScene(*fadingIterator, scene, pendingChanges); - fadingIterator = _avatarFades.erase(fadingIterator); + // only remove from _avatarFades if we're sure its motionState has been removed from PhysicsEngine + if (_motionStatesToRemoveFromPhysics.empty()) { + fadingIterator = _avatarFades.erase(fadingIterator); + } else { + ++fadingIterator; + } } else { avatar->simulate(deltaTime); ++fadingIterator; @@ -234,20 +202,6 @@ AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWe return newAvatar; } -// protected -void AvatarManager::removeAvatarMotionState(AvatarSharedPointer avatar) { - auto rawPointer = std::static_pointer_cast(avatar); - AvatarMotionState* motionState = rawPointer->getMotionState(); - if (motionState) { - // clean up physics stuff - motionState->clearObjectBackPointer(); - rawPointer->setMotionState(nullptr); - _avatarMotionStates.remove(motionState); - _motionStatesToAdd.remove(motionState); - _motionStatesToDelete.push_back(motionState); - } -} - // virtual void AvatarManager::removeAvatar(const QUuid& sessionUUID) { QWriteLocker locker(&_hashLock); @@ -261,8 +215,18 @@ void AvatarManager::removeAvatar(const QUuid& sessionUUID) { void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar) { AvatarHashMap::handleRemovedAvatar(removedAvatar); - removedAvatar->die(); - removeAvatarMotionState(removedAvatar); + // removedAvatar is a shared pointer to an AvatarData but we need to get to the derived Avatar + // class in this context so we can call methods that don't exist at the base class. + Avatar* avatar = static_cast(removedAvatar.get()); + avatar->die(); + + AvatarMotionState* motionState = avatar->getMotionState(); + if (motionState) { + _motionStatesThatMightUpdate.remove(motionState); + _motionStatesToAddToPhysics.remove(motionState); + _motionStatesToRemoveFromPhysics.push_back(motionState); + } + _avatarFades.push_back(removedAvatar); } @@ -315,22 +279,22 @@ AvatarData* AvatarManager::getAvatar(QUuid avatarID) { } -void AvatarManager::getObjectsToDelete(VectorOfMotionStates& result) { +void AvatarManager::getObjectsToRemoveFromPhysics(VectorOfMotionStates& result) { result.clear(); - result.swap(_motionStatesToDelete); + result.swap(_motionStatesToRemoveFromPhysics); } -void AvatarManager::getObjectsToAdd(VectorOfMotionStates& result) { +void AvatarManager::getObjectsToAddToPhysics(VectorOfMotionStates& result) { result.clear(); - for (auto motionState : _motionStatesToAdd) { + for (auto motionState : _motionStatesToAddToPhysics) { result.push_back(motionState); } - _motionStatesToAdd.clear(); + _motionStatesToAddToPhysics.clear(); } void AvatarManager::getObjectsToChange(VectorOfMotionStates& result) { result.clear(); - for (auto state : _avatarMotionStates) { + for (auto state : _motionStatesThatMightUpdate) { if (state->_dirtyFlags > 0) { result.push_back(state); } @@ -385,8 +349,8 @@ void AvatarManager::addAvatarToSimulation(Avatar* avatar) { // we don't add to the simulation now, we put it on a list to be added later AvatarMotionState* motionState = new AvatarMotionState(avatar, shape); avatar->setMotionState(motionState); - _motionStatesToAdd.insert(motionState); - _avatarMotionStates.insert(motionState); + _motionStatesToAddToPhysics.insert(motionState); + _motionStatesThatMightUpdate.insert(motionState); } } diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 1b165495c3..72fcb3f862 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -34,6 +34,8 @@ public: /// Registers the script types associated with the avatar manager. static void registerMetaTypes(QScriptEngine* engine); + virtual ~AvatarManager(); + void init(); MyAvatar* getMyAvatar() { return _myAvatar.get(); } @@ -41,11 +43,10 @@ public: void updateMyAvatar(float deltaTime); void updateOtherAvatars(float deltaTime); - + void clearOtherAvatars(); - + bool shouldShowReceiveStats() const { return _shouldShowReceiveStats; } - PIDController& getRenderDistanceController() { return _renderDistanceController; } class LocalLight { public: @@ -60,27 +61,14 @@ public: Q_INVOKABLE AvatarData* getAvatar(QUuid avatarID); - void getObjectsToDelete(VectorOfMotionStates& motionStates); - void getObjectsToAdd(VectorOfMotionStates& motionStates); + void getObjectsToRemoveFromPhysics(VectorOfMotionStates& motionStates); + void getObjectsToAddToPhysics(VectorOfMotionStates& motionStates); void getObjectsToChange(VectorOfMotionStates& motionStates); void handleOutgoingChanges(const VectorOfMotionStates& motionStates); void handleCollisionEvents(const CollisionEvents& collisionEvents); void addAvatarToSimulation(Avatar* avatar); - // Expose results and parameter-tuning operations to other systems, such as stats and javascript. - Q_INVOKABLE float getRenderDistance() { return _renderDistance; } - Q_INVOKABLE float getRenderDistanceInverseLowLimit() { return _renderDistanceController.getControlledValueLowLimit(); } - Q_INVOKABLE float getRenderDistanceInverseHighLimit() { return _renderDistanceController.getControlledValueHighLimit(); } - Q_INVOKABLE int getNumberInRenderRange() { return _renderedAvatarCount; } - Q_INVOKABLE bool getRenderDistanceControllerIsLogging() { return _renderDistanceController.getIsLogging(); } - Q_INVOKABLE void setRenderDistanceControllerHistory(QString label, int size) { return _renderDistanceController.setHistorySize(label, size); } - Q_INVOKABLE void setRenderDistanceKP(float newValue) { _renderDistanceController.setKP(newValue); } - Q_INVOKABLE void setRenderDistanceKI(float newValue) { _renderDistanceController.setKI(newValue); } - Q_INVOKABLE void setRenderDistanceKD(float newValue) { _renderDistanceController.setKD(newValue); } - Q_INVOKABLE void setRenderDistanceInverseLowLimit(float newValue) { _renderDistanceController.setControlledValueLowLimit(newValue); } - Q_INVOKABLE void setRenderDistanceInverseHighLimit(float newValue); - public slots: void setShouldShowReceiveStats(bool shouldShowReceiveStats) { _shouldShowReceiveStats = shouldShowReceiveStats; } void updateAvatarRenderStatus(bool shouldRenderAvatars); @@ -94,7 +82,6 @@ private: // virtual overrides virtual AvatarSharedPointer newSharedAvatar(); virtual AvatarSharedPointer addAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer); - void removeAvatarMotionState(AvatarSharedPointer avatar); virtual void removeAvatar(const QUuid& sessionUUID); virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar); @@ -106,14 +93,10 @@ private: QVector _localLights; bool _shouldShowReceiveStats = false; - float _renderDistance { (float) TREE_SCALE }; - int _renderedAvatarCount { 0 }; - PIDController _renderDistanceController { }; - SimpleMovingAverage _renderDistanceAverage { 10 }; - SetOfAvatarMotionStates _avatarMotionStates; - SetOfMotionStates _motionStatesToAdd; - VectorOfMotionStates _motionStatesToDelete; + SetOfAvatarMotionStates _motionStatesThatMightUpdate; + SetOfMotionStates _motionStatesToAddToPhysics; + VectorOfMotionStates _motionStatesToRemoveFromPhysics; }; Q_DECLARE_METATYPE(AvatarManager::LocalLight) diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index acd9a45aab..9ce9594d45 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -25,20 +25,17 @@ AvatarMotionState::AvatarMotionState(Avatar* avatar, btCollisionShape* shape) : } AvatarMotionState::~AvatarMotionState() { + assert(_avatar); _avatar = nullptr; } // virtual uint32_t AvatarMotionState::getIncomingDirtyFlags() { - uint32_t dirtyFlags = 0; - if (_body && _avatar) { - dirtyFlags = _dirtyFlags; - } - return dirtyFlags; + return _body ? _dirtyFlags : 0; } void AvatarMotionState::clearIncomingDirtyFlags() { - if (_body && _avatar) { + if (_body) { _dirtyFlags = 0; } } @@ -50,12 +47,9 @@ MotionType AvatarMotionState::computeObjectMotionType() const { // virtual and protected btCollisionShape* AvatarMotionState::computeNewShape() { - if (_avatar) { - ShapeInfo shapeInfo; - _avatar->computeShapeInfo(shapeInfo); - return getShapeManager()->getShape(shapeInfo); - } - return nullptr; + ShapeInfo shapeInfo; + _avatar->computeShapeInfo(shapeInfo); + return getShapeManager()->getShape(shapeInfo); } // virtual @@ -65,9 +59,6 @@ bool AvatarMotionState::isMoving() const { // virtual void AvatarMotionState::getWorldTransform(btTransform& worldTrans) const { - if (!_avatar) { - return; - } worldTrans.setOrigin(glmToBullet(getObjectPosition())); worldTrans.setRotation(glmToBullet(getObjectRotation())); if (_body) { @@ -76,11 +67,8 @@ void AvatarMotionState::getWorldTransform(btTransform& worldTrans) const { } } -// virtual +// virtual void AvatarMotionState::setWorldTransform(const btTransform& worldTrans) { - if (!_avatar) { - return; - } // HACK: The PhysicsEngine does not actually move OTHER avatars -- instead it slaves their local RigidBody to the transform // as specified by a remote simulation. However, to give the remote simulation time to respond to our own objects we tie // the other avatar's body to its true position with a simple spring. This is a HACK that will have to be improved later. @@ -154,15 +142,8 @@ QUuid AvatarMotionState::getSimulatorID() const { return _avatar->getSessionUUID(); } -// virtual -int16_t AvatarMotionState::computeCollisionGroup() { +// virtual +int16_t AvatarMotionState::computeCollisionGroup() const { return COLLISION_GROUP_OTHER_AVATAR; } -// virtual -void AvatarMotionState::clearObjectBackPointer() { - ObjectMotionState::clearObjectBackPointer(); - _avatar = nullptr; -} - - diff --git a/interface/src/avatar/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h index b5101d2c70..0465ddf50b 100644 --- a/interface/src/avatar/AvatarMotionState.h +++ b/interface/src/avatar/AvatarMotionState.h @@ -21,55 +21,65 @@ class Avatar; class AvatarMotionState : public ObjectMotionState { public: AvatarMotionState(Avatar* avatar, btCollisionShape* shape); - ~AvatarMotionState(); - virtual MotionType getMotionType() const { return _motionType; } + virtual MotionType getMotionType() const override { return _motionType; } - virtual uint32_t getIncomingDirtyFlags(); - virtual void clearIncomingDirtyFlags(); + virtual uint32_t getIncomingDirtyFlags() override; + virtual void clearIncomingDirtyFlags() override; - virtual MotionType computeObjectMotionType() const; + virtual MotionType computeObjectMotionType() const override; - virtual bool isMoving() const; + virtual bool isMoving() const override; // this relays incoming position/rotation to the RigidBody - virtual void getWorldTransform(btTransform& worldTrans) const; + virtual void getWorldTransform(btTransform& worldTrans) const override; // this relays outgoing position/rotation to the EntityItem - virtual void setWorldTransform(const btTransform& worldTrans); + virtual void setWorldTransform(const btTransform& worldTrans) override; // These pure virtual methods must be implemented for each MotionState type // and make it possible to implement more complicated methods in this base class. - virtual float getObjectRestitution() const; - virtual float getObjectFriction() const; - virtual float getObjectLinearDamping() const; - virtual float getObjectAngularDamping() const; + // pure virtual overrides from ObjectMotionState + virtual float getObjectRestitution() const override; + virtual float getObjectFriction() const override; + virtual float getObjectLinearDamping() const override; + virtual float getObjectAngularDamping() const override; - virtual glm::vec3 getObjectPosition() const; - virtual glm::quat getObjectRotation() const; - virtual glm::vec3 getObjectLinearVelocity() const; - virtual glm::vec3 getObjectAngularVelocity() const; - virtual glm::vec3 getObjectGravity() const; + virtual glm::vec3 getObjectPosition() const override; + virtual glm::quat getObjectRotation() const override; + virtual glm::vec3 getObjectLinearVelocity() const override; + virtual glm::vec3 getObjectAngularVelocity() const override; + virtual glm::vec3 getObjectGravity() const override; - virtual const QUuid& getObjectID() const; + virtual const QUuid& getObjectID() const override; - virtual QUuid getSimulatorID() const; + virtual QUuid getSimulatorID() const override; void setBoundingBox(const glm::vec3& corner, const glm::vec3& diagonal); void addDirtyFlags(uint32_t flags) { _dirtyFlags |= flags; } - virtual int16_t computeCollisionGroup(); + virtual int16_t computeCollisionGroup() const override; friend class AvatarManager; + friend class Avatar; protected: - virtual bool isReadyToComputeShape() { return true; } + // the dtor had been made protected to force the compiler to verify that it is only + // ever called by the Avatar class dtor. + ~AvatarMotionState(); + + virtual bool isReadyToComputeShape() const override { return true; } virtual btCollisionShape* computeNewShape(); - virtual void clearObjectBackPointer(); - Avatar* _avatar; + + // The AvatarMotionState keeps a RAW backpointer to its Avatar because all AvatarMotionState + // instances are "owned" by their corresponding Avatar instance and are deleted in the Avatar dtor. + // In other words, it is impossible for the Avatar to be deleted out from under its MotionState. + // In conclusion: weak pointer shennanigans would be pure overhead. + Avatar* _avatar; // do NOT use smartpointer here, no need for weakpointer + uint32_t _dirtyFlags; }; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c989f1dc71..98b09d6c59 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -64,7 +64,7 @@ const float MIN_AVATAR_SPEED = 0.05f; // speed is set to zero below this // TODO: normalize avatar speed for standard avatar size, then scale all motion logic // to properly follow avatar size. -float MAX_AVATAR_SPEED = 300.0f; +float MAX_AVATAR_SPEED = 30.0f; float MAX_KEYBOARD_MOTOR_SPEED = MAX_AVATAR_SPEED; float DEFAULT_KEYBOARD_MOTOR_TIMESCALE = 0.25f; float MIN_SCRIPTED_MOTOR_TIMESCALE = 0.005f; @@ -201,6 +201,11 @@ MyAvatar::~MyAvatar() { _lookAtTargetAvatar.reset(); } +// virtual +void MyAvatar::simulateAttachments(float deltaTime) { + // don't update attachments here, do it in harvestResultsFromPhysicsSimulation() +} + QByteArray MyAvatar::toByteArray(bool cullSmallChanges, bool sendAll) { CameraMode mode = qApp->getCamera()->getMode(); _globalPosition = getPosition(); @@ -412,6 +417,8 @@ void MyAvatar::updateSensorToWorldMatrix() { // position when driven from the head. glm::mat4 desiredMat = createMatFromQuatAndPos(getOrientation(), getPosition()); _sensorToWorldMatrix = desiredMat * glm::inverse(_bodySensorMatrix); + + lateUpdatePalms(); } // Update avatar head rotation with sensor data @@ -621,6 +628,7 @@ void MyAvatar::saveData() { settings.setValue("rotation_y", eulers.y); settings.setValue("rotation_z", eulers.z); settings.setValue("scale", attachment.scale); + settings.setValue("isSoft", attachment.isSoft); } settings.endArray(); @@ -702,6 +710,7 @@ void MyAvatar::loadData() { eulers.z = loadSetting(settings, "rotation_z", 0.0f); attachment.rotation = glm::quat(eulers); attachment.scale = loadSetting(settings, "scale", 1.0f); + attachment.isSoft = settings.value("isSoft").toBool(); attachmentData.append(attachment); } settings.endArray(); @@ -1057,7 +1066,7 @@ void MyAvatar::prepareForPhysicsSimulation() { _characterController.setFollowVelocity(_followVelocity); } -void MyAvatar::harvestResultsFromPhysicsSimulation() { +void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaType) { glm::vec3 position = getPosition(); glm::quat orientation = getOrientation(); _characterController.getPositionAndOrientation(position, orientation); @@ -1068,6 +1077,9 @@ void MyAvatar::harvestResultsFromPhysicsSimulation() { } else { setVelocity(_characterController.getLinearVelocity()); } + + // now that physics has adjusted our position, we can update attachements. + Avatar::simulateAttachments(deltaType); } void MyAvatar::adjustSensorTransform() { @@ -1599,7 +1611,7 @@ void MyAvatar::maybeUpdateBillboard() { if (_billboardValid || !(_skeletonModel.isLoadedWithTextures() && getHead()->getFaceModel().isLoadedWithTextures())) { return; } - foreach (Model* model, _attachmentModels) { + for (auto& model : _attachmentModels) { if (!model->isLoadedWithTextures()) { return; } @@ -1829,3 +1841,8 @@ QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioList void audioListenModeFromScriptValue(const QScriptValue& object, AudioListenerMode& audioListenerMode) { audioListenerMode = (AudioListenerMode)object.toUInt16(); } + + +void MyAvatar::lateUpdatePalms() { + Avatar::updatePalms(); +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 019ba0f992..44823c9913 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -83,6 +83,8 @@ public: MyAvatar(RigPointer rig); ~MyAvatar(); + virtual void simulateAttachments(float deltaTime) override; + AudioListenerMode getAudioListenerModeHead() const { return FROM_HEAD; } AudioListenerMode getAudioListenerModeCamera() const { return FROM_CAMERA; } AudioListenerMode getAudioListenerModeCustom() const { return CUSTOM; } @@ -204,7 +206,7 @@ public: MyCharacterController* getCharacterController() { return &_characterController; } void prepareForPhysicsSimulation(); - void harvestResultsFromPhysicsSimulation(); + void harvestResultsFromPhysicsSimulation(float deltaTime); void adjustSensorTransform(); const QString& getCollisionSoundURL() { return _collisionSoundURL; } @@ -309,12 +311,14 @@ private: void setVisibleInSceneIfReady(Model* model, render::ScenePointer scene, bool visiblity); - PalmData getActivePalmData(int palmIndex) const; - // derive avatar body position and orientation from the current HMD Sensor location. // results are in HMD frame glm::mat4 deriveBodyFromHMDSensor() const; + virtual void updatePalms() override {} + void lateUpdatePalms(); + + float _driveKeys[MAX_DRIVE_KEYS]; bool _wasPushing; bool _isPushing; diff --git a/interface/src/avatar/SoftAttachmentModel.cpp b/interface/src/avatar/SoftAttachmentModel.cpp new file mode 100644 index 0000000000..92534f01ea --- /dev/null +++ b/interface/src/avatar/SoftAttachmentModel.cpp @@ -0,0 +1,84 @@ +// +// SoftAttachmentModel.cpp +// interface/src/avatar +// +// Created by Anthony J. Thibault on 12/17/15. +// Copyright 2013 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "SoftAttachmentModel.h" +#include "InterfaceLogging.h" + +SoftAttachmentModel::SoftAttachmentModel(RigPointer rig, QObject* parent, RigPointer rigOverride) : + Model(rig, parent), + _rigOverride(rigOverride) { + assert(_rig); + assert(_rigOverride); +} + +SoftAttachmentModel::~SoftAttachmentModel() { +} + +// virtual +void SoftAttachmentModel::updateRig(float deltaTime, glm::mat4 parentTransform) { + _needsUpdateClusterMatrices = true; +} + +int SoftAttachmentModel::getJointIndexOverride(int i) const { + QString name = _rig->nameOfJoint(i); + if (name.isEmpty()) { + return -1; + } + return _rigOverride->indexOfJoint(name); +} + +// virtual +// use the _rigOverride matrices instead of the Model::_rig +void SoftAttachmentModel::updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) { + if (!_needsUpdateClusterMatrices) { + return; + } + _needsUpdateClusterMatrices = false; + + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + + glm::mat4 modelToWorld = glm::mat4_cast(modelOrientation); + for (int i = 0; i < _meshStates.size(); i++) { + MeshState& state = _meshStates[i]; + const FBXMesh& mesh = geometry.meshes.at(i); + + for (int j = 0; j < mesh.clusters.size(); j++) { + const FBXCluster& cluster = mesh.clusters.at(j); + + // TODO: cache these look ups as an optimization + int jointIndexOverride = getJointIndexOverride(cluster.jointIndex); + glm::mat4 jointMatrix(glm::mat4::_null); + if (jointIndexOverride >= 0 && jointIndexOverride < _rigOverride->getJointStateCount()) { + jointMatrix = _rigOverride->getJointTransform(jointIndexOverride); + } else { + jointMatrix = _rig->getJointTransform(cluster.jointIndex); + } + state.clusterMatrices[j] = modelToWorld * jointMatrix * cluster.inverseBindMatrix; + } + + // Once computed the cluster matrices, update the buffer(s) + if (mesh.clusters.size() > 1) { + if (!state.clusterBuffer) { + state.clusterBuffer = std::make_shared(state.clusterMatrices.size() * sizeof(glm::mat4), + (const gpu::Byte*) state.clusterMatrices.constData()); + } else { + state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4), + (const gpu::Byte*) state.clusterMatrices.constData()); + } + } + } + + // post the blender if we're not currently waiting for one to finish + if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) { + _blendedBlendshapeCoefficients = _blendshapeCoefficients; + DependencyManager::get()->noteRequiresBlend(this); + } +} diff --git a/interface/src/avatar/SoftAttachmentModel.h b/interface/src/avatar/SoftAttachmentModel.h new file mode 100644 index 0000000000..84a8b4cc66 --- /dev/null +++ b/interface/src/avatar/SoftAttachmentModel.h @@ -0,0 +1,42 @@ +// +// SoftAttachmentModel.h +// interface/src/avatar +// +// Created by Anthony J. Thibault on 12/17/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_SoftAttachmentModel_h +#define hifi_SoftAttachmentModel_h + +#include + +// A model that allows the creator to specify a secondary rig instance. +// When the cluster matrices are created for rendering, the +// cluster matrices will use the secondary rig for the joint poses +// instead of the primary rig. +// +// This is used by Avatar instances to wear clothing that follows the same +// animated pose as the SkeletonModel. + +class SoftAttachmentModel : public Model { + Q_OBJECT + +public: + + SoftAttachmentModel(RigPointer rig, QObject* parent, RigPointer rigOverride); + ~SoftAttachmentModel(); + + virtual void updateRig(float deltaTime, glm::mat4 parentTransform) override; + virtual void updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) override; + +protected: + int getJointIndexOverride(int i) const; + + RigPointer _rigOverride; +}; + +#endif // hifi_SoftAttachmentModel_h diff --git a/interface/src/devices/EyeTracker.cpp b/interface/src/devices/EyeTracker.cpp index 2cfc2ecd75..367aa52aae 100644 --- a/interface/src/devices/EyeTracker.cpp +++ b/interface/src/devices/EyeTracker.cpp @@ -139,7 +139,7 @@ void EyeTracker::onStreamStarted() { qCWarning(interfaceapp) << "Eye Tracker: Error starting streaming:" << smiReturnValueToString(result); // Display error dialog unless SMI SDK has already displayed an error message. if (result != SMI_ERROR_HMD_NOT_SUPPORTED) { - QMessageBox::warning(nullptr, "Eye Tracker Error", smiReturnValueToString(result)); + OffscreenUi::warning(nullptr, "Eye Tracker Error", smiReturnValueToString(result)); } } else { qCDebug(interfaceapp) << "Eye Tracker: Started streaming"; @@ -152,7 +152,7 @@ void EyeTracker::onStreamStarted() { result = smi_loadCalibration(HIGH_FIDELITY_EYE_TRACKER_CALIBRATION); if (result != SMI_RET_SUCCESS) { qCWarning(interfaceapp) << "Eye Tracker: Error loading calibration:" << smiReturnValueToString(result); - QMessageBox::warning(nullptr, "Eye Tracker Error", "Error loading calibration" + OffscreenUi::warning(nullptr, "Eye Tracker Error", "Error loading calibration" + smiReturnValueToString(result)); } else { qCDebug(interfaceapp) << "Eye Tracker: Loaded calibration"; @@ -168,7 +168,7 @@ void EyeTracker::setEnabled(bool enabled, bool simulate) { int result = smi_setCallback(eyeTrackerCallback); if (result != SMI_RET_SUCCESS) { qCWarning(interfaceapp) << "Eye Tracker: Error setting callback:" << smiReturnValueToString(result); - QMessageBox::warning(nullptr, "Eye Tracker Error", smiReturnValueToString(result)); + OffscreenUi::warning(nullptr, "Eye Tracker Error", smiReturnValueToString(result)); } else { _isInitialized = true; } @@ -273,7 +273,7 @@ void EyeTracker::calibrate(int points) { } if (result != SMI_RET_SUCCESS) { - QMessageBox::warning(nullptr, "Eye Tracker Error", "Calibration error: " + smiReturnValueToString(result)); + OffscreenUi::warning(nullptr, "Eye Tracker Error", "Calibration error: " + smiReturnValueToString(result)); } } #endif diff --git a/interface/src/scripting/AccountScriptingInterface.cpp b/interface/src/scripting/AccountScriptingInterface.cpp index 87ea3220a4..126cd53003 100644 --- a/interface/src/scripting/AccountScriptingInterface.cpp +++ b/interface/src/scripting/AccountScriptingInterface.cpp @@ -17,7 +17,6 @@ AccountScriptingInterface::AccountScriptingInterface() { AccountManager& accountManager = AccountManager::getInstance(); connect(&accountManager, &AccountManager::balanceChanged, this, &AccountScriptingInterface::updateBalance); - } AccountScriptingInterface* AccountScriptingInterface::getInstance() { @@ -39,3 +38,12 @@ void AccountScriptingInterface::updateBalance() { AccountManager& accountManager = AccountManager::getInstance(); emit balanceChanged(accountManager.getAccountInfo().getBalanceInSatoshis()); } + +QString AccountScriptingInterface::getUsername() { + AccountManager& accountManager = AccountManager::getInstance(); + if (accountManager.isLoggedIn()) { + return accountManager.getAccountInfo().getUsername(); + } else { + return "Unknown user"; + } +} diff --git a/interface/src/scripting/AccountScriptingInterface.h b/interface/src/scripting/AccountScriptingInterface.h index e9cf0ede5f..578a9d6728 100644 --- a/interface/src/scripting/AccountScriptingInterface.h +++ b/interface/src/scripting/AccountScriptingInterface.h @@ -24,6 +24,7 @@ signals: public slots: static AccountScriptingInterface* getInstance(); float getBalance(); + QString getUsername(); bool isLoggedIn(); void updateBalance(); }; diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 0bf94f02a9..fe84f36158 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -21,6 +21,7 @@ #include "DomainHandler.h" #include "MainWindow.h" #include "Menu.h" +#include "OffscreenUi.h" #include "ui/ModelsBrowser.h" #include "WindowScriptingInterface.h" @@ -153,12 +154,11 @@ QScriptValue WindowScriptingInterface::peekNonBlockingFormResult(QScriptValue fo return retVal; } - /// Display an alert box /// \param const QString& message message to display /// \return QScriptValue::UndefinedValue QScriptValue WindowScriptingInterface::showAlert(const QString& message) { - QMessageBox::warning(qApp->getWindow(), "", message); + OffscreenUi::warning("", message); return QScriptValue::UndefinedValue; } @@ -166,8 +166,11 @@ QScriptValue WindowScriptingInterface::showAlert(const QString& message) { /// \param const QString& message message to display /// \return QScriptValue `true` if 'Yes' was clicked, `false` otherwise QScriptValue WindowScriptingInterface::showConfirm(const QString& message) { - QMessageBox::StandardButton response = QMessageBox::question(qApp->getWindow(), "", message); - return QScriptValue(response == QMessageBox::Yes); + bool confirm = false; + OffscreenUi::question("", message, [&](QMessageBox::StandardButton response){ + confirm = (response == QMessageBox::Yes); + }); + return QScriptValue(confirm); } void WindowScriptingInterface::chooseDirectory() { @@ -185,7 +188,7 @@ void WindowScriptingInterface::chooseDirectory() { } if (!validateAs.exactMatch(directory)) { - QMessageBox::warning(NULL, "Invalid Directory", errorMessage); + OffscreenUi::warning(NULL, "Invalid Directory", errorMessage); return; } @@ -597,7 +600,6 @@ QScriptValue WindowScriptingInterface::showPrompt(const QString& message, const if (promptDialog.exec() == QDialog::Accepted) { return QScriptValue(promptDialog.textValue()); } - return QScriptValue::NullValue; } diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 03d6432104..9259478d46 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -86,7 +86,7 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { // Now render the overlay components together into a single texture renderDomainConnectionStatusBorder(renderArgs); // renders the connected domain line - renderAudioScope(renderArgs); // audio scope in the very back + renderAudioScope(renderArgs); // audio scope in the very back - NOTE: this is the debug audio scope, not the VU meter renderRearView(renderArgs); // renders the mirror view selfie renderQmlUi(renderArgs); // renders a unit quad with the QML UI texture, and the text overlays from scripts renderOverlays(renderArgs); // renders Scripts Overlay and AudioScope @@ -158,7 +158,7 @@ void ApplicationOverlay::renderRearViewToFbo(RenderArgs* renderArgs) { } void ApplicationOverlay::renderRearView(RenderArgs* renderArgs) { - if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { + if (!qApp->isHMDMode() && Menu::getInstance()->isOptionChecked(MenuOption::MiniMirror)) { gpu::Batch& batch = *renderArgs->_batch; auto geometryCache = DependencyManager::get(); diff --git a/interface/src/ui/AssetUploadDialogFactory.cpp b/interface/src/ui/AssetUploadDialogFactory.cpp index 66f72e5b5c..9cd319815b 100644 --- a/interface/src/ui/AssetUploadDialogFactory.cpp +++ b/interface/src/ui/AssetUploadDialogFactory.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include AssetUploadDialogFactory& AssetUploadDialogFactory::getInstance() { @@ -146,5 +147,5 @@ void AssetUploadDialogFactory::showErrorDialog(AssetUpload* upload, QWidget* dia dialogMessage += errorMessage; - QMessageBox::warning(dialogParent, "Failed Upload", dialogMessage); + OffscreenUi::warning(dialogParent, "Failed Upload", dialogMessage); } diff --git a/interface/src/ui/AttachmentsDialog.cpp b/interface/src/ui/AttachmentsDialog.cpp index 1d53eb3871..d718b52d6d 100644 --- a/interface/src/ui/AttachmentsDialog.cpp +++ b/interface/src/ui/AttachmentsDialog.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -27,13 +28,13 @@ AttachmentsDialog::AttachmentsDialog(QWidget* parent) : QDialog(parent) { - + setWindowTitle("Edit Attachments"); setAttribute(Qt::WA_DeleteOnClose); - + QVBoxLayout* layout = new QVBoxLayout(); setLayout(layout); - + QScrollArea* area = new QScrollArea(); layout->addWidget(area); area->setWidgetResizable(true); @@ -42,26 +43,26 @@ AttachmentsDialog::AttachmentsDialog(QWidget* parent) : container->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); area->setWidget(container); _attachments->addStretch(1); - + foreach (const AttachmentData& data, DependencyManager::get()->getMyAvatar()->getAttachmentData()) { addAttachment(data); } - + QPushButton* newAttachment = new QPushButton("New Attachment"); connect(newAttachment, SIGNAL(clicked(bool)), SLOT(addAttachment())); layout->addWidget(newAttachment); - + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok); layout->addWidget(buttons); connect(buttons, SIGNAL(accepted()), SLOT(deleteLater())); _ok = buttons->button(QDialogButtonBox::Ok); - + setMinimumSize(600, 600); } void AttachmentsDialog::setVisible(bool visible) { QDialog::setVisible(visible); - + // un-default the OK button if (visible) { _ok->setDefault(false); @@ -104,11 +105,11 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData _dialog(dialog), _applying(false) { setFrameStyle(QFrame::StyledPanel); - + QFormLayout* layout = new QFormLayout(); layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); setLayout(layout); - + QHBoxLayout* urlBox = new QHBoxLayout(); layout->addRow("Model URL:", urlBox); urlBox->addWidget(_modelURL = new QLineEdit(data.modelURL.toString()), 1); @@ -117,7 +118,7 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData QPushButton* chooseURL = new QPushButton("Choose"); urlBox->addWidget(chooseURL); connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseModelURL())); - + layout->addRow("Joint:", _jointName = new QComboBox()); QSharedPointer geometry = DependencyManager::get()->getMyAvatar()->getSkeletonModel().getGeometry(); if (geometry && geometry->isLoaded()) { @@ -127,26 +128,30 @@ AttachmentPanel::AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData } _jointName->setCurrentText(data.jointName); connect(_jointName, SIGNAL(currentIndexChanged(int)), SLOT(jointNameChanged())); - + QHBoxLayout* translationBox = new QHBoxLayout(); translationBox->addWidget(_translationX = createTranslationBox(this, data.translation.x)); translationBox->addWidget(_translationY = createTranslationBox(this, data.translation.y)); translationBox->addWidget(_translationZ = createTranslationBox(this, data.translation.z)); layout->addRow("Translation:", translationBox); - + QHBoxLayout* rotationBox = new QHBoxLayout(); glm::vec3 eulers = glm::degrees(safeEulerAngles(data.rotation)); rotationBox->addWidget(_rotationX = createRotationBox(this, eulers.x)); rotationBox->addWidget(_rotationY = createRotationBox(this, eulers.y)); rotationBox->addWidget(_rotationZ = createRotationBox(this, eulers.z)); layout->addRow("Rotation:", rotationBox); - + layout->addRow("Scale:", _scale = new QDoubleSpinBox()); _scale->setSingleStep(0.01); _scale->setMaximum(FLT_MAX); _scale->setValue(data.scale); connect(_scale, SIGNAL(valueChanged(double)), SLOT(updateAttachmentData())); - + + layout->addRow("Is Soft:", _isSoft = new QCheckBox()); + _isSoft->setChecked(data.isSoft); + connect(_isSoft, SIGNAL(stateChanged(int)), SLOT(updateAttachmentData())); + QPushButton* remove = new QPushButton("Delete"); layout->addRow(remove); connect(remove, SIGNAL(clicked(bool)), SLOT(deleteLater())); @@ -160,6 +165,7 @@ AttachmentData AttachmentPanel::getAttachmentData() const { data.translation = glm::vec3(_translationX->value(), _translationY->value(), _translationZ->value()); data.rotation = glm::quat(glm::radians(glm::vec3(_rotationX->value(), _rotationY->value(), _rotationZ->value()))); data.scale = _scale->value(); + data.isSoft = _isSoft->isChecked(); return data; } @@ -227,6 +233,7 @@ void AttachmentPanel::applyAttachmentData(const AttachmentData& attachment) { _rotationY->setValue(eulers.y); _rotationZ->setValue(eulers.z); _scale->setValue(attachment.scale); + _isSoft->setChecked(attachment.isSoft); _applying = false; _dialog->updateAttachmentData(); } diff --git a/interface/src/ui/AttachmentsDialog.h b/interface/src/ui/AttachmentsDialog.h index d3b9219a16..43ba5f8f3e 100644 --- a/interface/src/ui/AttachmentsDialog.h +++ b/interface/src/ui/AttachmentsDialog.h @@ -36,11 +36,11 @@ public slots: void updateAttachmentData(); private slots: - + void addAttachment(const AttachmentData& data = AttachmentData()); private: - + QVBoxLayout* _attachments; QPushButton* _ok; }; @@ -50,7 +50,7 @@ class AttachmentPanel : public QFrame { Q_OBJECT public: - + AttachmentPanel(AttachmentsDialog* dialog, const AttachmentData& data = AttachmentData()); AttachmentData getAttachmentData() const; @@ -64,9 +64,9 @@ private slots: void updateAttachmentData(); private: - + void applyAttachmentData(const AttachmentData& attachment); - + AttachmentsDialog* _dialog; QLineEdit* _modelURL; QComboBox* _jointName; @@ -77,6 +77,7 @@ private: QDoubleSpinBox* _rotationY; QDoubleSpinBox* _rotationZ; QDoubleSpinBox* _scale; + QCheckBox* _isSoft; bool _applying; }; diff --git a/interface/src/ui/AvatarInputs.cpp b/interface/src/ui/AvatarInputs.cpp index a9827a23c2..fa6eaf072d 100644 --- a/interface/src/ui/AvatarInputs.cpp +++ b/interface/src/ui/AvatarInputs.cpp @@ -62,10 +62,11 @@ void AvatarInputs::update() { if (!Menu::getInstance()) { return; } - AI_UPDATE(mirrorVisible, Menu::getInstance()->isOptionChecked(MenuOption::Mirror) && !qApp->isHMDMode() + AI_UPDATE(mirrorVisible, Menu::getInstance()->isOptionChecked(MenuOption::MiniMirror) && !qApp->isHMDMode() && !Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)); AI_UPDATE(cameraEnabled, !Menu::getInstance()->isOptionChecked(MenuOption::NoFaceTracking)); AI_UPDATE(cameraMuted, Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking)); + AI_UPDATE(isHMD, qApp->isHMDMode()); auto audioIO = DependencyManager::get(); const float AUDIO_METER_AVERAGING = 0.5; @@ -128,7 +129,7 @@ void AvatarInputs::toggleZoom() { } void AvatarInputs::closeMirror() { - if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { - Menu::getInstance()->triggerOption(MenuOption::Mirror); + if (Menu::getInstance()->isOptionChecked(MenuOption::MiniMirror)) { + Menu::getInstance()->triggerOption(MenuOption::MiniMirror); } } diff --git a/interface/src/ui/AvatarInputs.h b/interface/src/ui/AvatarInputs.h index 8ed4e8f163..36d3027a42 100644 --- a/interface/src/ui/AvatarInputs.h +++ b/interface/src/ui/AvatarInputs.h @@ -30,6 +30,7 @@ class AvatarInputs : public QQuickItem { AI_PROPERTY(float, audioLevel, 0) AI_PROPERTY(bool, mirrorVisible, false) AI_PROPERTY(bool, mirrorZoomed, true) + AI_PROPERTY(bool, isHMD, false) public: static AvatarInputs* getInstance(); @@ -44,6 +45,7 @@ signals: void audioLevelChanged(); void mirrorVisibleChanged(); void mirrorZoomedChanged(); + void isHMDChanged(); protected: Q_INVOKABLE void resetSensors(); diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 1acbf3a595..155d41c575 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -176,20 +176,6 @@ void DialogsManager::showScriptEditor() { _scriptEditor->raise(); } -void DialogsManager::showIRCLink() { - if (!_ircInfoBox) { - _ircInfoBox = new QMessageBox(QMessageBox::NoIcon, - "High Fidelity IRC", - "High Fidelity has an IRC channel on irc.freenode.net at #highfidelity.

Web chat is available here.", - QMessageBox::Ok); - _ircInfoBox->setTextFormat(Qt::RichText); - _ircInfoBox->setAttribute(Qt::WA_DeleteOnClose); - _ircInfoBox->show(); - } - - _ircInfoBox->raise(); -} - void DialogsManager::showDomainConnectionDialog() { // if the dialog already exists we delete it so the connection data is refreshed if (_domainConnectionDialog) { diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index f6ad1d6f98..e0b841fa12 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -57,7 +57,6 @@ public slots: void lodTools(); void hmdTools(bool showTools); void showScriptEditor(); - void showIRCLink(); void showDomainConnectionDialog(); // Application Update diff --git a/interface/src/ui/DiskCacheEditor.cpp b/interface/src/ui/DiskCacheEditor.cpp index a33f28e240..62917007d3 100644 --- a/interface/src/ui/DiskCacheEditor.cpp +++ b/interface/src/ui/DiskCacheEditor.cpp @@ -22,6 +22,7 @@ #include #include "DiskCacheEditor.h" +#include "OffscreenUi.h" DiskCacheEditor::DiskCacheEditor(QWidget* parent) : QObject(parent) { @@ -136,7 +137,7 @@ void DiskCacheEditor::refresh() { void DiskCacheEditor::clear() { QMessageBox::StandardButton buttonClicked = - QMessageBox::question(_dialog, "Clearing disk cache", + OffscreenUi::question(_dialog, "Clearing disk cache", "You are about to erase all the content of the disk cache," "are you sure you want to do that?"); if (buttonClicked == QMessageBox::Yes) { diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index a38cc13100..216c11a05c 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -48,6 +48,7 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) : connect(ui.buttonChangeAppearance, &QPushButton::clicked, this, &PreferencesDialog::openFullAvatarModelBrowser); connect(ui.appearanceDescription, &QLineEdit::editingFinished, this, &PreferencesDialog::changeFullAvatarURL); + connect(ui.useAcuityCheckBox, &QCheckBox::clicked, this, &PreferencesDialog::changeUseAcuity); connect(qApp, &Application::fullAvatarURLChanged, this, &PreferencesDialog::fullAvatarURLChanged); @@ -58,6 +59,17 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) : UIUtil::scaleWidgetFontSizes(this); } +void PreferencesDialog::changeUseAcuity() { + bool useAcuity = ui.useAcuityCheckBox->isChecked(); + ui.label_desktopMinimumFPSSpin->setEnabled(useAcuity); + ui.desktopMinimumFPSSpin->setEnabled(useAcuity); + ui.label_hmdMinimumFPSSpin->setEnabled(useAcuity); + ui.hmdMinimumFPSSpin->setEnabled(useAcuity); + ui.label_smallestReasonableRenderHorizon->setEnabled(!useAcuity); + ui.smallestReasonableRenderHorizon->setEnabled(!useAcuity); + Menu::getInstance()->getActionForOption(MenuOption::LodTools)->setEnabled(useAcuity); + Menu::getInstance()->getSubMenuFromName(MenuOption::RenderResolution, Menu::getInstance()->getSubMenuFromName("Render", Menu::getInstance()->getMenu("Developer")))->setEnabled(useAcuity); +} void PreferencesDialog::changeFullAvatarURL() { DependencyManager::get()->getMyAvatar()->useFullAvatarURL(ui.appearanceDescription->text(), ""); this->fullAvatarURLChanged(ui.appearanceDescription->text(), ""); @@ -127,22 +139,22 @@ void PreferencesDialog::openFullAvatarModelBrowser() { } void PreferencesDialog::resizeEvent(QResizeEvent *resizeEvent) { - + // keep buttons panel at the bottom ui.buttonsPanel->setGeometry(0, size().height() - ui.buttonsPanel->height(), size().width(), ui.buttonsPanel->height()); - + // set width and height of srcollarea to match bottom panel and width ui.scrollArea->setGeometry(ui.scrollArea->geometry().x(), ui.scrollArea->geometry().y(), size().width(), size().height() - ui.buttonsPanel->height() - ui.scrollArea->geometry().y()); - + } void PreferencesDialog::loadPreferences() { - + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); Menu* menuInstance = Menu::getInstance(); @@ -163,14 +175,14 @@ void PreferencesDialog::loadPreferences() { ui.pupilDilationSlider->setValue(myAvatar->getHead()->getPupilDilation() * ui.pupilDilationSlider->maximum()); - + auto dde = DependencyManager::get(); - ui.ddeEyeClosingThresholdSlider->setValue(dde->getEyeClosingThreshold() * + ui.ddeEyeClosingThresholdSlider->setValue(dde->getEyeClosingThreshold() * ui.ddeEyeClosingThresholdSlider->maximum()); ui.faceTrackerEyeDeflectionSider->setValue(FaceTracker::getEyeDeflection() * ui.faceTrackerEyeDeflectionSider->maximum()); - + auto faceshift = DependencyManager::get(); ui.faceshiftHostnameEdit->setText(faceshift->getHostname()); @@ -196,12 +208,12 @@ void PreferencesDialog::loadPreferences() { ui.realWorldFieldOfViewSpin->setValue(myAvatar->getRealWorldFieldOfView()); ui.fieldOfViewSpin->setValue(qApp->getFieldOfView()); - + ui.leanScaleSpin->setValue(myAvatar->getLeanScale()); ui.avatarScaleSpin->setValue(myAvatar->getUniformScale()); ui.avatarAnimationEdit->setText(myAvatar->getAnimGraphUrl().toString()); - + ui.maxOctreePPSSpin->setValue(qApp->getMaxOctreePacketsPerSecond()); #if 0 @@ -212,17 +224,19 @@ void PreferencesDialog::loadPreferences() { // LOD items auto lodManager = DependencyManager::get(); + ui.useAcuityCheckBox->setChecked(lodManager->getUseAcuity()); ui.desktopMinimumFPSSpin->setValue(lodManager->getDesktopLODDecreaseFPS()); ui.hmdMinimumFPSSpin->setValue(lodManager->getHMDLODDecreaseFPS()); - ui.avatarRenderSmallestReasonableHorizon->setValue(1.0f / DependencyManager::get()->getRenderDistanceInverseHighLimit()); + ui.smallestReasonableRenderHorizon->setValue(1.0f / lodManager->getRenderDistanceInverseHighLimit()); + changeUseAcuity(); } void PreferencesDialog::savePreferences() { - + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); bool shouldDispatchIdentityPacket = false; - + QString displayNameStr(ui.displayNameEdit->text()); if (displayNameStr != _displayNameString) { myAvatar->setDisplayName(displayNameStr); @@ -233,7 +247,7 @@ void PreferencesDialog::savePreferences() { if (shouldDispatchIdentityPacket) { myAvatar->sendIdentityPacket(); } - + myAvatar->setCollisionSoundURL(ui.collisionSoundURLEdit->text()); // MyAvatar persists its own data. If it doesn't agree with what the user has explicitly accepted, set it back to old values. @@ -262,28 +276,28 @@ void PreferencesDialog::savePreferences() { } myAvatar->setRealWorldFieldOfView(ui.realWorldFieldOfViewSpin->value()); - + qApp->setFieldOfView(ui.fieldOfViewSpin->value()); - + auto dde = DependencyManager::get(); - dde->setEyeClosingThreshold(ui.ddeEyeClosingThresholdSlider->value() / + dde->setEyeClosingThreshold(ui.ddeEyeClosingThresholdSlider->value() / (float)ui.ddeEyeClosingThresholdSlider->maximum()); FaceTracker::setEyeDeflection(ui.faceTrackerEyeDeflectionSider->value() / (float)ui.faceTrackerEyeDeflectionSider->maximum()); - + auto faceshift = DependencyManager::get(); faceshift->setHostname(ui.faceshiftHostnameEdit->text()); - + qApp->setMaxOctreePacketsPerSecond(ui.maxOctreePPSSpin->value()); qApp->getApplicationCompositor().setHmdUIAngularSize(ui.oculusUIAngularSizeSpin->value()); - + controller::InputDevice::setReticleMoveSpeed(ui.sixenseReticleMoveSpeedSpin->value()); auto audio = DependencyManager::get(); MixedProcessedAudioStream& stream = audio->getReceivedAudioStream(); - + stream.setDynamicJitterBuffers(ui.dynamicJitterBuffersCheckBox->isChecked()); stream.setStaticDesiredJitterBufferFrames(ui.staticDesiredJitterBufferFramesSpin->value()); stream.setMaxFramesOverDesired(ui.maxFramesOverDesiredSpin->value()); @@ -303,7 +317,8 @@ void PreferencesDialog::savePreferences() { // LOD items auto lodManager = DependencyManager::get(); + lodManager->setUseAcuity(ui.useAcuityCheckBox->isChecked()); lodManager->setDesktopLODDecreaseFPS(ui.desktopMinimumFPSSpin->value()); lodManager->setHMDLODDecreaseFPS(ui.hmdMinimumFPSSpin->value()); - DependencyManager::get()->setRenderDistanceInverseHighLimit(1.0f / ui.avatarRenderSmallestReasonableHorizon->value()); + lodManager->setRenderDistanceInverseHighLimit(1.0f / ui.smallestReasonableRenderHorizon->value()); } diff --git a/interface/src/ui/PreferencesDialog.h b/interface/src/ui/PreferencesDialog.h index 1536eca3ee..a6c27dee08 100644 --- a/interface/src/ui/PreferencesDialog.h +++ b/interface/src/ui/PreferencesDialog.h @@ -51,6 +51,7 @@ private slots: void openScriptsLocationBrowser(); void changeFullAvatarURL(); void fullAvatarURLChanged(const QString& newValue, const QString& modelName); + void changeUseAcuity(); }; #endif // hifi_PreferencesDialog_h diff --git a/interface/src/ui/ScriptEditorWidget.cpp b/interface/src/ui/ScriptEditorWidget.cpp index 0ae13f9c24..380b645681 100644 --- a/interface/src/ui/ScriptEditorWidget.cpp +++ b/interface/src/ui/ScriptEditorWidget.cpp @@ -26,6 +26,7 @@ #include #include +#include #include "Application.h" #include "ScriptHighlighting.h" @@ -120,7 +121,7 @@ bool ScriptEditorWidget::setRunning(bool run) { bool ScriptEditorWidget::saveFile(const QString &scriptPath) { QFile file(scriptPath); if (!file.open(QFile::WriteOnly | QFile::Text)) { - QMessageBox::warning(this, tr("Interface"), tr("Cannot write script %1:\n%2.").arg(scriptPath) + OffscreenUi::warning(this, tr("Interface"), tr("Cannot write script %1:\n%2.").arg(scriptPath) .arg(file.errorString())); return false; } @@ -141,7 +142,7 @@ void ScriptEditorWidget::loadFile(const QString& scriptPath) { if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) { QFile file(scriptPath); if (!file.open(QFile::ReadOnly | QFile::Text)) { - QMessageBox::warning(this, tr("Interface"), tr("Cannot read script %1:\n%2.").arg(scriptPath) + OffscreenUi::warning(this, tr("Interface"), tr("Cannot read script %1:\n%2.").arg(scriptPath) .arg(file.errorString())); return; } @@ -208,7 +209,7 @@ void ScriptEditorWidget::setScriptFile(const QString& scriptPath) { bool ScriptEditorWidget::questionSave() { if (_scriptEditorWidgetUI->scriptEdit->document()->isModified()) { - QMessageBox::StandardButton button = QMessageBox::warning(this, tr("Interface"), + QMessageBox::StandardButton button = OffscreenUi::warning(this, tr("Interface"), tr("The script has been modified.\nDo you want to save your changes?"), QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Save); return button == QMessageBox::Save ? save() : (button == QMessageBox::Discard); @@ -222,7 +223,7 @@ void ScriptEditorWidget::onWindowActivated() { if (QFileInfo(_currentScript).lastModified() > _currentScriptModified) { if (static_cast(this->parent()->parent()->parent())->autoReloadScripts() - || QMessageBox::warning(this, _currentScript, + || OffscreenUi::warning(this, _currentScript, tr("This file has been modified outside of the Interface editor.") + "\n\n" + (isModified() ? tr("Do you want to reload it and lose the changes you've made in the Interface editor?") diff --git a/interface/src/ui/SnapshotShareDialog.cpp b/interface/src/ui/SnapshotShareDialog.cpp index 3053c3ee0a..ddebfe25fd 100644 --- a/interface/src/ui/SnapshotShareDialog.cpp +++ b/interface/src/ui/SnapshotShareDialog.cpp @@ -20,6 +20,7 @@ #include #include +#include const int NARROW_SNAPSHOT_DIALOG_SIZE = 500; const int WIDE_SNAPSHOT_DIALOG_WIDTH = 650; @@ -87,7 +88,7 @@ void SnapshotShareDialog::accept() { void SnapshotShareDialog::uploadSnapshot() { if (AccountManager::getInstance().getAccountInfo().getDiscourseApiKey().isEmpty()) { - QMessageBox::warning(this, "", + OffscreenUi::warning(this, "", "Your Discourse API key is missing, you cannot share snapshots. Please try to relog."); return; } @@ -178,7 +179,7 @@ void SnapshotShareDialog::postRequestFinished() { errorMessage = errorArray.first().toString(); } } - QMessageBox::warning(this, "", errorMessage); + OffscreenUi::warning(this, "", errorMessage); _ui.shareButton->setEnabled(true); _ui.shareButton->setStyleSheet(SHARE_BUTTON_STYLE + SHARE_BUTTON_ENABLED_STYLE); } @@ -193,7 +194,7 @@ void SnapshotShareDialog::uploadRequestFinished() { if (responseObject.contains("url")) { sendForumPost(responseObject["url"].toString()); } else { - QMessageBox::warning(this, "", SHARE_DEFAULT_ERROR); + OffscreenUi::warning(this, "", SHARE_DEFAULT_ERROR); _ui.shareButton->setEnabled(true); _ui.shareButton->setStyleSheet(SHARE_BUTTON_STYLE + SHARE_BUTTON_ENABLED_STYLE); } diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 6acacee41d..b82ab93068 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -116,8 +116,6 @@ void Stats::updateStats(bool force) { auto avatarManager = DependencyManager::get(); // we need to take one avatar out so we don't include ourselves STAT_UPDATE(avatarCount, avatarManager->size() - 1); - STAT_UPDATE(avatarRenderableCount, avatarManager->getNumberInRenderRange()); - STAT_UPDATE(avatarRenderDistance, (int) round(avatarManager->getRenderDistance())); // deliberately truncating STAT_UPDATE(serverCount, (int)nodeList->size()); STAT_UPDATE(renderrate, (int)qApp->getFps()); if (qApp->getActiveDisplayPlugin()) { @@ -285,7 +283,9 @@ void Stats::updateStats(bool force) { STAT_UPDATE(localLeaves, (int)OctreeElement::getLeafNodeCount()); // LOD Details STAT_UPDATE(lodStatus, "You can see " + DependencyManager::get()->getLODFeedbackText()); + STAT_UPDATE(lodStatsRenderText, DependencyManager::get()->getLODStatsRenderText()); } + STAT_UPDATE(showAcuity, (_expanded || force) && DependencyManager::get()->getUseAcuity()); bool performanceTimerIsActive = PerformanceTimer::isActive(); bool displayPerf = _expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayDebugTimingDetails); diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index eb28883001..5e948ce0f0 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -30,6 +30,7 @@ class Stats : public QQuickItem { Q_PROPERTY(QString monospaceFont READ monospaceFont CONSTANT) Q_PROPERTY(float audioPacketlossUpstream READ getAudioPacketLossUpstream) Q_PROPERTY(float audioPacketlossDownstream READ getAudioPacketLossDownstream) + Q_PROPERTY(bool showAcuity READ getShowAcuity WRITE setShowAcuity NOTIFY showAcuityChanged) STATS_PROPERTY(int, serverCount, 0) STATS_PROPERTY(int, renderrate, 0) @@ -37,8 +38,6 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, simrate, 0) STATS_PROPERTY(int, avatarSimrate, 0) STATS_PROPERTY(int, avatarCount, 0) - STATS_PROPERTY(int, avatarRenderableCount, 0) - STATS_PROPERTY(int, avatarRenderDistance, 0) STATS_PROPERTY(int, packetInCount, 0) STATS_PROPERTY(int, packetOutCount, 0) STATS_PROPERTY(float, mbpsIn, 0) @@ -77,6 +76,7 @@ class Stats : public QQuickItem { STATS_PROPERTY(QString, packetStats, QString()) STATS_PROPERTY(QString, lodStatus, QString()) STATS_PROPERTY(QString, timingStats, QString()) + STATS_PROPERTY(QString, lodStatsRenderText, QString()) STATS_PROPERTY(int, serverElements, 0) STATS_PROPERTY(int, serverInternal, 0) STATS_PROPERTY(int, serverLeaves, 0) @@ -108,12 +108,15 @@ public: emit expandedChanged(); } } + bool getShowAcuity() { return _showAcuity; } + void setShowAcuity(bool newValue) { _showAcuity = newValue; } public slots: void forceUpdateStats() { updateStats(true); } signals: void expandedChanged(); + void showAcuityChanged(); void timingExpandedChanged(); void serverCountChanged(); void renderrateChanged(); @@ -121,8 +124,7 @@ signals: void simrateChanged(); void avatarSimrateChanged(); void avatarCountChanged(); - void avatarRenderableCountChanged(); - void avatarRenderDistanceChanged(); + void lodStatsRenderTextChanged(); void packetInCountChanged(); void packetOutCountChanged(); void mbpsInChanged(); @@ -172,6 +174,7 @@ private: int _recentMaxPackets{ 0 } ; // recent max incoming voxel packets to process bool _resetRecentMaxPacketsSoon{ true }; bool _expanded{ false }; + bool _showAcuity{ false }; bool _timingExpanded{ false }; QString _monospaceFont; const AudioIOStats* _audioStats; diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui index e6a5e2228d..06ce0bd154 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -742,6 +742,43 @@ + + + + + 0 + 0 + + + + + 32 + 28 + + + + + 0 + 0 + + + + + Arial + + + + Render based on visual acuity + + + + 0 + 0 + + + + + @@ -757,7 +794,7 @@ 7 - + Arial @@ -842,7 +879,7 @@ 7 - + Arial @@ -927,7 +964,7 @@ 7 - + Arial @@ -937,7 +974,7 @@ - Minimum Avatar Display Distance + Minimum Display Distance 0 @@ -963,7 +1000,7 @@ - + 100 diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 4dd091f1d6..c6060d791a 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -245,6 +245,14 @@ int Rig::indexOfJoint(const QString& jointName) const { } } +QString Rig::nameOfJoint(int jointIndex) const { + if (_animSkeleton) { + return _animSkeleton->getJointName(jointIndex); + } else { + return ""; + } +} + void Rig::setModelOffset(const glm::mat4& modelOffsetMat) { AnimPose newModelOffset = AnimPose(modelOffsetMat); if (!isEqual(_modelOffset.trans, newModelOffset.trans) || diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index 9faf93e40b..669af2ea64 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -91,6 +91,7 @@ public: bool jointStatesEmpty(); int getJointStateCount() const; int indexOfJoint(const QString& jointName) const; + QString nameOfJoint(int jointIndex) const; void setModelOffset(const glm::mat4& modelOffsetMat); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 2ed9a47e02..0015a064fb 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -90,10 +90,18 @@ const QUrl& AvatarData::defaultFullAvatarModelUrl() { // There are a number of possible strategies for this set of tools through endRender, below. void AvatarData::nextAttitude(glm::vec3 position, glm::quat orientation) { avatarLock.lock(); - Transform trans = getTransform(); + bool success; + Transform trans = getTransform(success); + if (!success) { + qDebug() << "Warning -- AvatarData::nextAttitude failed"; + return; + } trans.setTranslation(position); trans.setRotation(orientation); - SpatiallyNestable::setTransform(trans); + SpatiallyNestable::setTransform(trans, success); + if (!success) { + qDebug() << "Warning -- AvatarData::nextAttitude failed"; + } avatarLock.unlock(); updateAttitude(); } @@ -208,8 +216,9 @@ QByteArray AvatarData::toByteArray(bool cullSmallChanges, bool sendAll) { setAtBit(bitItems, IS_EYE_TRACKER_CONNECTED); } // referential state - SpatiallyNestablePointer parent = getParentPointer(); - if (parent) { + bool success; + SpatiallyNestablePointer parent = getParentPointer(success); + if (parent && success) { setAtBit(bitItems, HAS_REFERENTIAL); } *destinationBuffer++ = bitItems; @@ -1202,7 +1211,7 @@ void AvatarData::setJointMappingsFromNetworkReply() { void AvatarData::sendAvatarDataPacket() { 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. // this is to guard against a joint moving once, the packet getting lost, and the joint never moving again. bool sendFullUpdate = randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO; @@ -1210,7 +1219,7 @@ void AvatarData::sendAvatarDataPacket() { doneEncoding(true); static AvatarDataSequenceNumber sequenceNumber = 0; - + auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size() + sizeof(sequenceNumber)); avatarPacket->writePrimitive(sequenceNumber++); avatarPacket->write(avatarByteArray); @@ -1232,13 +1241,13 @@ void AvatarData::sendIdentityPacket() { void AvatarData::sendBillboardPacket() { if (!_billboard.isEmpty()) { auto nodeList = DependencyManager::get(); - + // This makes sure the billboard won't be too large to send. // Once more protocol changes are done and we can send blocks of data we can support sending > MTU sized billboards. if (_billboard.size() <= NLPacket::maxPayloadSize(PacketType::AvatarBillboard)) { auto billboardPacket = NLPacket::create(PacketType::AvatarBillboard, _billboard.size()); billboardPacket->write(_billboard); - + nodeList->broadcastToNodes(std::move(billboardPacket), NodeSet() << NodeType::AvatarMixer); } } @@ -1260,6 +1269,7 @@ void AvatarData::updateJointMappings() { static const QString JSON_ATTACHMENT_URL = QStringLiteral("modelUrl"); static const QString JSON_ATTACHMENT_JOINT_NAME = QStringLiteral("jointName"); static const QString JSON_ATTACHMENT_TRANSFORM = QStringLiteral("transform"); +static const QString JSON_ATTACHMENT_IS_SOFT = QStringLiteral("isSoft"); QJsonObject AttachmentData::toJson() const { QJsonObject result; @@ -1278,6 +1288,7 @@ QJsonObject AttachmentData::toJson() const { if (!transform.isIdentity()) { result[JSON_ATTACHMENT_TRANSFORM] = Transform::toJson(transform); } + result[JSON_ATTACHMENT_IS_SOFT] = isSoft; return result; } @@ -1302,21 +1313,25 @@ void AttachmentData::fromJson(const QJsonObject& json) { rotation = transform.getRotation(); scale = transform.getScale().x; } + + if (json.contains(JSON_ATTACHMENT_IS_SOFT)) { + isSoft = json[JSON_ATTACHMENT_IS_SOFT].toBool(); + } } bool AttachmentData::operator==(const AttachmentData& other) const { return modelURL == other.modelURL && jointName == other.jointName && translation == other.translation && - rotation == other.rotation && scale == other.scale; + rotation == other.rotation && scale == other.scale && isSoft == other.isSoft; } QDataStream& operator<<(QDataStream& out, const AttachmentData& attachment) { return out << attachment.modelURL << attachment.jointName << - attachment.translation << attachment.rotation << attachment.scale; + attachment.translation << attachment.rotation << attachment.scale << attachment.isSoft; } QDataStream& operator>>(QDataStream& in, AttachmentData& attachment) { return in >> attachment.modelURL >> attachment.jointName >> - attachment.translation >> attachment.rotation >> attachment.scale; + attachment.translation >> attachment.rotation >> attachment.scale >> attachment.isSoft; } void AttachmentDataObject::setModelURL(const QString& modelURL) const { @@ -1440,7 +1455,11 @@ QJsonObject AvatarData::toJson() const { } auto recordingBasis = getRecordingBasis(); - Transform avatarTransform = getTransform(); + bool success; + Transform avatarTransform = getTransform(success); + if (!success) { + qDebug() << "Warning -- AvatarData::toJson couldn't get avatar transform"; + } avatarTransform.setScale(getTargetScale()); if (recordingBasis) { root[JSON_AVATAR_BASIS] = Transform::toJson(*recordingBasis); @@ -1476,7 +1495,7 @@ QJsonObject AvatarData::toJson() const { } void AvatarData::fromJson(const QJsonObject& json) { - // The head setOrientation likes to overwrite the avatar orientation, + // The head setOrientation likes to overwrite the avatar orientation, // so lets do the head first // Most head data is relative to the avatar, and needs no basis correction, // but the lookat vector does need correction @@ -1512,7 +1531,7 @@ void AvatarData::fromJson(const QJsonObject& json) { if (json.contains(JSON_AVATAR_RELATIVE)) { // During playback you can either have the recording basis set to the avatar current state // meaning that all playback is relative to this avatars starting position, or - // the basis can be loaded from the recording, meaning the playback is relative to the + // the basis can be loaded from the recording, meaning the playback is relative to the // original avatar location // The first is more useful for playing back recordings on your own avatar, while // the latter is more useful for playing back other avatars within your scene. @@ -1561,8 +1580,8 @@ void AvatarData::fromJson(const QJsonObject& json) { } // Every frame will store both a basis for the recording and a relative transform -// This allows the application to decide whether playback should be relative to an avatar's -// transform at the start of playback, or relative to the transform of the recorded +// This allows the application to decide whether playback should be relative to an avatar's +// transform at the start of playback, or relative to the transform of the recorded // avatar QByteArray AvatarData::toFrame(const AvatarData& avatar) { QJsonObject root = avatar.toJson(); @@ -1629,3 +1648,13 @@ void AvatarData::setPosition(const glm::vec3& position) { void AvatarData::setOrientation(const glm::quat& orientation) { SpatiallyNestable::setOrientation(orientation); } + +glm::quat AvatarData::getAbsoluteJointRotationInObjectFrame(int index) const { + assert(false); + return glm::quat(); +} + +glm::vec3 AvatarData::getAbsoluteJointTranslationInObjectFrame(int index) const { + assert(false); + return glm::vec3(); +} diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 64c215cee7..542b0598ce 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -201,7 +201,9 @@ public: float getBodyRoll() const; void setBodyRoll(float bodyRoll); + using SpatiallyNestable::setPosition; virtual void setPosition(const glm::vec3& position) override; + using SpatiallyNestable::setOrientation; virtual void setOrientation(const glm::quat& orientation) override; void nextAttitude(glm::vec3 position, glm::quat orientation); // Can be safely called at any time. @@ -265,9 +267,9 @@ public: Q_INVOKABLE virtual QVector getJointRotations() const; Q_INVOKABLE virtual void setJointRotations(QVector jointRotations); Q_INVOKABLE virtual void setJointTranslations(QVector jointTranslations); - + Q_INVOKABLE virtual void clearJointsData(); - + /// Returns the index of the joint with the specified name, or -1 if not found/unknown. Q_INVOKABLE virtual int getJointIndex(const QString& name) const { return _jointIndices.value(name) - 1; } @@ -286,42 +288,42 @@ public: bool hasIdentityChangedAfterParsing(const QByteArray& data); QByteArray identityByteArray(); - + bool hasBillboardChangedAfterParsing(const QByteArray& data); - + const QUrl& getFaceModelURL() const { return _faceModelURL; } QString getFaceModelURLString() const { return _faceModelURL.toString(); } const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } const QString& getDisplayName() const { return _displayName; } virtual void setFaceModelURL(const QUrl& faceModelURL); virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); - + virtual void setDisplayName(const QString& displayName); - + Q_INVOKABLE QVector getAttachmentData() const; Q_INVOKABLE virtual void setAttachmentData(const QVector& attachmentData); - + Q_INVOKABLE virtual void attach(const QString& modelURL, const QString& jointName = QString(), const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f, bool allowDuplicates = false, bool useSaved = true); - + Q_INVOKABLE void detachOne(const QString& modelURL, const QString& jointName = QString()); Q_INVOKABLE void detachAll(const QString& modelURL, const QString& jointName = QString()); - + virtual void setBillboard(const QByteArray& billboard); const QByteArray& getBillboard() const { return _billboard; } - + void setBillboardFromURL(const QString& billboardURL); const QString& getBillboardURL() { return _billboardURL; } - + QString getFaceModelURLFromScript() const { return _faceModelURL.toString(); } void setFaceModelURLFromScript(const QString& faceModelString) { setFaceModelURL(faceModelString); } - + QString getSkeletonModelURLFromScript() const { return _skeletonModelURL.toString(); } void setSkeletonModelURLFromScript(const QString& skeletonModelString) { setSkeletonModelURL(QUrl(skeletonModelString)); } - + void setOwningAvatarMixer(const QWeakPointer& owningAvatarMixer) { _owningAvatarMixer = owningAvatarMixer; } - + const AABox& getLocalAABox() const { return _localAABox; } int getUsecsSinceLastUpdate() const { return _averageBytesReceived.getUsecsSinceLastEvent(); } @@ -342,18 +344,20 @@ public: glm::vec3 getClientGlobalPosition() { return _globalPosition; } - void die() { _isDead = true; } - bool isDead() const { return _isDead; } - public slots: void sendAvatarDataPacket(); void sendIdentityPacket(); void sendBillboardPacket(); - + void setBillboardFromNetworkReply(); void setJointMappingsFromNetworkReply(); void setSessionUUID(const QUuid& sessionUUID) { setID(sessionUUID); } + virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; + virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; + virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; } + virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; } + protected: glm::vec3 _handPosition; @@ -387,14 +391,14 @@ protected: QByteArray _billboard; QString _billboardURL; - + QHash _jointIndices; ///< 1-based, since zero is returned for missing keys QStringList _jointNames; ///< in order of depth-first traversal quint64 _errorLogExpiry; ///< time in future when to log an error - + QWeakPointer _owningAvatarMixer; - + /// Loads the joint indices, names from the FST file (if any) virtual void updateJointMappings(); @@ -406,7 +410,7 @@ protected: SimpleMovingAverage _averageBytesReceived; QMutex avatarLock; // Name is redundant, but it aids searches. - + // During recording, this holds the starting position, orientation & scale of the recorded avatar // During playback, it holds the origin from which to play the relative positions in the clip TransformPointer _recordingBasis; @@ -416,8 +420,6 @@ protected: // updates about one avatar to another. glm::vec3 _globalPosition; - bool _isDead { false }; - private: friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar); static QUrl _defaultFullAvatarModelUrl; @@ -437,9 +439,10 @@ public: glm::vec3 translation; glm::quat rotation; float scale { 1.0f }; - + bool isSoft { false }; + bool isValid() const { return modelURL.isValid(); } - + bool operator==(const AttachmentData& other) const; QJsonObject toJson() const; @@ -462,19 +465,19 @@ class AttachmentDataObject : public QObject, protected QScriptable { Q_PROPERTY(float scale READ getScale WRITE setScale) public: - + Q_INVOKABLE void setModelURL(const QString& modelURL) const; Q_INVOKABLE QString getModelURL() const; - + Q_INVOKABLE void setJointName(const QString& jointName) const; Q_INVOKABLE QString getJointName() const; - + Q_INVOKABLE void setTranslation(const glm::vec3& translation) const; Q_INVOKABLE glm::vec3 getTranslation() const; - + Q_INVOKABLE void setRotation(const glm::quat& rotation) const; Q_INVOKABLE glm::quat getRotation() const; - + Q_INVOKABLE void setScale(float scale) const; Q_INVOKABLE float getScale() const; }; diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index 0bda52b237..7bee5101b1 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -70,6 +70,12 @@ namespace controller { makeAxisPair(Action::RETICLE_UP, "ReticleUp"), makeAxisPair(Action::RETICLE_DOWN, "ReticleDown"), + makeAxisPair(Action::UI_NAV_LATERAL, "UiNavLateral"), + makeAxisPair(Action::UI_NAV_VERTICAL, "UiNavVertical"), + makeAxisPair(Action::UI_NAV_GROUP, "UiNavGroup"), + makeAxisPair(Action::UI_NAV_SELECT, "UiNavSelect"), + makeAxisPair(Action::UI_NAV_BACK, "UiNavBack"), + // Aliases and bisected versions makeAxisPair(Action::LONGITUDINAL_BACKWARD, "Backward"), makeAxisPair(Action::LONGITUDINAL_FORWARD, "Forward"), diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index 56dd9660d9..812d3b8df3 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -55,6 +55,12 @@ enum class Action { SHIFT, + UI_NAV_LATERAL, + UI_NAV_VERTICAL, + UI_NAV_GROUP, + UI_NAV_SELECT, + UI_NAV_BACK, + // Pointer/Reticle control RETICLE_CLICK, RETICLE_X, @@ -90,6 +96,7 @@ enum class Action { BOOM_IN, BOOM_OUT, + NUM_ACTIONS, }; diff --git a/libraries/controllers/src/controllers/InputDevice.h b/libraries/controllers/src/controllers/InputDevice.h index fc3477b41a..3add7d236f 100644 --- a/libraries/controllers/src/controllers/InputDevice.h +++ b/libraries/controllers/src/controllers/InputDevice.h @@ -83,6 +83,7 @@ protected: friend class UserInputMapper; virtual Input::NamedVector getAvailableInputs() const = 0; + virtual QStringList getDefaultMappingConfigs() const { return QStringList() << getDefaultMappingConfig(); } virtual QString getDefaultMappingConfig() const { return QString(); } virtual EndpointPointer createEndpoint(const Input& input) const; diff --git a/libraries/controllers/src/controllers/StandardController.cpp b/libraries/controllers/src/controllers/StandardController.cpp index fadbeee326..e101c5f4ff 100644 --- a/libraries/controllers/src/controllers/StandardController.cpp +++ b/libraries/controllers/src/controllers/StandardController.cpp @@ -131,10 +131,10 @@ EndpointPointer StandardController::createEndpoint(const Input& input) const { return std::make_shared(input); } -QString StandardController::getDefaultMappingConfig() const { +QStringList StandardController::getDefaultMappingConfigs() const { static const QString DEFAULT_MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/standard.json"; - return DEFAULT_MAPPING_JSON; + static const QString DEFAULT_NAV_MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/standard_navigation.json"; + return QStringList() << DEFAULT_NAV_MAPPING_JSON << DEFAULT_MAPPING_JSON; } - } diff --git a/libraries/controllers/src/controllers/StandardController.h b/libraries/controllers/src/controllers/StandardController.h index 6c18c76371..57bd0faba5 100644 --- a/libraries/controllers/src/controllers/StandardController.h +++ b/libraries/controllers/src/controllers/StandardController.h @@ -27,7 +27,7 @@ class StandardController : public QObject, public InputDevice { public: virtual EndpointPointer createEndpoint(const Input& input) const override; virtual Input::NamedVector getAvailableInputs() const override; - virtual QString getDefaultMappingConfig() const override; + virtual QStringList getDefaultMappingConfigs() const override; virtual void update(float deltaTime, bool jointsCaptured) override; virtual void focusOutEvent() override; diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 9251a663ba..fe64566b29 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -100,7 +100,7 @@ void UserInputMapper::registerDevice(InputDevice::Pointer device) { } _registeredDevices[deviceID] = device; - auto mapping = loadMapping(device->getDefaultMappingConfig()); + auto mapping = loadMappings(device->getDefaultMappingConfigs()); if (mapping) { _mappingsByDevice[deviceID] = mapping; enableMapping(mapping); @@ -139,7 +139,7 @@ void UserInputMapper::loadDefaultMapping(uint16 deviceID) { } - auto mapping = loadMapping(proxyEntry->second->getDefaultMappingConfig()); + auto mapping = loadMappings(proxyEntry->second->getDefaultMappingConfigs()); if (mapping) { auto prevMapping = _mappingsByDevice[deviceID]; disableMapping(prevMapping); @@ -710,6 +710,21 @@ Mapping::Pointer UserInputMapper::loadMapping(const QString& jsonFile) { return parseMapping(json); } +MappingPointer UserInputMapper::loadMappings(const QStringList& jsonFiles) { + Mapping::Pointer result; + for (const QString& jsonFile : jsonFiles) { + auto subMapping = loadMapping(jsonFile); + if (subMapping) { + if (!result) { + result = subMapping; + } else { + auto& routes = result->routes; + routes.insert(routes.end(), subMapping->routes.begin(), subMapping->routes.end()); + } + } + } + return result; +} static const QString JSON_NAME = QStringLiteral("name"); @@ -888,7 +903,7 @@ Endpoint::Pointer UserInputMapper::parseDestination(const QJsonValue& value) { Endpoint::Pointer UserInputMapper::parseAxis(const QJsonValue& value) { if (value.isObject()) { - auto object = value.toObject(); + auto object = value.toObject(); if (object.contains("makeAxis")) { auto axisValue = object.value("makeAxis"); if (axisValue.isArray()) { @@ -985,6 +1000,20 @@ Route::Pointer UserInputMapper::parseRoute(const QJsonValue& value) { return result; } +void injectConditional(Route::Pointer& route, Conditional::Pointer& conditional) { + if (!conditional) { + return; + } + + if (!route->conditional) { + route->conditional = conditional; + return; + } + + route->conditional = std::make_shared(conditional, route->conditional); +} + + Mapping::Pointer UserInputMapper::parseMapping(const QJsonValue& json) { if (!json.isObject()) { return Mapping::Pointer(); @@ -994,12 +1023,24 @@ Mapping::Pointer UserInputMapper::parseMapping(const QJsonValue& json) { auto mapping = std::make_shared("default"); mapping->name = obj[JSON_NAME].toString(); const auto& jsonChannels = obj[JSON_CHANNELS].toArray(); + Conditional::Pointer globalConditional; + if (obj.contains(JSON_CHANNEL_WHEN)) { + auto conditionalsValue = obj[JSON_CHANNEL_WHEN]; + globalConditional = parseConditional(conditionalsValue); + } + for (const auto& channelIt : jsonChannels) { Route::Pointer route = parseRoute(channelIt); + if (!route) { qWarning() << "Couldn't parse route"; continue; } + + if (globalConditional) { + injectConditional(route, globalConditional); + } + mapping->routes.push_back(route); } _mappingsByName[mapping->name] = mapping; diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index d93a93016c..98a85a2a44 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -107,6 +107,7 @@ namespace controller { MappingPointer newMapping(const QString& mappingName); MappingPointer parseMapping(const QString& json); MappingPointer loadMapping(const QString& jsonFile); + MappingPointer loadMappings(const QStringList& jsonFiles); void loadDefaultMapping(uint16 deviceID); void enableMapping(const QString& mappingName, bool enable = true); diff --git a/libraries/controllers/src/controllers/impl/conditionals/AndConditional.h b/libraries/controllers/src/controllers/impl/conditionals/AndConditional.h index c60e4b15df..2299843a24 100644 --- a/libraries/controllers/src/controllers/impl/conditionals/AndConditional.h +++ b/libraries/controllers/src/controllers/impl/conditionals/AndConditional.h @@ -18,7 +18,11 @@ class AndConditional : public Conditional { public: using Pointer = std::shared_ptr; - AndConditional(Conditional::List children) : _children(children) { } + AndConditional(Conditional::List children) + : _children(children) {} + + AndConditional(Conditional::Pointer& first, Conditional::Pointer& second) + : _children({ first, second }) {} virtual bool satisfied() override; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index f517c49b00..07d9031f41 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -717,11 +717,11 @@ void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const } } -void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityTreePointer entityTree, - const EntityItemID& id, const Collision& collision) { +bool EntityTreeRenderer::isCollisionOwner(const QUuid& myNodeID, EntityTreePointer entityTree, + const EntityItemID& id, const Collision& collision) { EntityItemPointer entity = entityTree->findEntityByEntityItemID(id); if (!entity) { - return; + return false; } QUuid simulatorID = entity->getSimulatorID(); if (simulatorID.isNull()) { @@ -730,14 +730,30 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT const EntityItemID& otherID = (id == collision.idA) ? collision.idB : collision.idA; EntityItemPointer otherEntity = entityTree->findEntityByEntityItemID(otherID); if (!otherEntity) { - return; + return false; } simulatorID = otherEntity->getSimulatorID(); } if (simulatorID.isNull() || (simulatorID != myNodeID)) { - return; // Only one injector per simulation, please. + return false; } + + return true; +} + +void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityTreePointer entityTree, + const EntityItemID& id, const Collision& collision) { + + if (!isCollisionOwner(myNodeID, entityTree, id, collision)) { + return; + } + + EntityItemPointer entity = entityTree->findEntityByEntityItemID(id); + if (!entity) { + return; + } + const QString& collisionSoundURL = entity->getCollisionSoundURL(); if (collisionSoundURL.isEmpty()) { return; @@ -767,7 +783,12 @@ void EntityTreeRenderer::playEntityCollisionSound(const QUuid& myNodeID, EntityT // Shift the pitch down by ln(1 + (size / COLLISION_SIZE_FOR_STANDARD_PITCH)) / ln(2) const float COLLISION_SIZE_FOR_STANDARD_PITCH = 0.2f; - const float stretchFactor = log(1.0f + (entity->getMinimumAACube().getLargestDimension() / COLLISION_SIZE_FOR_STANDARD_PITCH)) / log(2); + bool success; + auto minAACube = entity->getMinimumAACube(success); + if (!success) { + return; + } + const float stretchFactor = log(1.0f + (minAACube.getLargestDimension() / COLLISION_SIZE_FOR_STANDARD_PITCH)) / log(2); AudioInjector::playSound(collisionSoundURL, volume, stretchFactor, position); } @@ -791,10 +812,15 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons playEntityCollisionSound(myNodeID, entityTree, idB, collision); // And now the entity scripts - emit collisionWithEntity(idA, idB, collision); - _entitiesScriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision); - emit collisionWithEntity(idB, idA, collision); - _entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, collision); + if (isCollisionOwner(myNodeID, entityTree, idA, collision)) { + emit collisionWithEntity(idA, idB, collision); + _entitiesScriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision); + } + + if (isCollisionOwner(myNodeID, entityTree, idA, collision)) { + emit collisionWithEntity(idB, idA, collision); + _entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, collision); + } } void EntityTreeRenderer::updateEntityRenderStatus(bool shouldRenderEntities) { diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 2c205336c0..e2cd4fc2dd 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -148,6 +148,9 @@ private: bool _wantScripts; ScriptEngine* _entitiesScriptEngine; + bool isCollisionOwner(const QUuid& myNodeID, EntityTreePointer entityTree, + const EntityItemID& id, const Collision& collision); + void playEntityCollisionSound(const QUuid& myNodeID, EntityTreePointer entityTree, const EntityItemID& id, const Collision& collision); diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp index a1cdbbaf51..4de363016b 100644 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp @@ -56,14 +56,20 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { gpu::Batch& batch = *args->_batch; glm::vec4 cubeColor(toGlm(getXColor()), getLocalRenderAlpha()); + bool success; + auto transToCenter = getTransformToCenter(success); + if (!success) { + return; + } + if (_procedural->ready()) { - batch.setModelTransform(getTransformToCenter()); // we want to include the scale as well + batch.setModelTransform(transToCenter); // we want to include the scale as well _procedural->prepare(batch, getPosition(), getDimensions()); auto color = _procedural->getColor(cubeColor); batch._glColor4f(color.r, color.g, color.b, color.a); DependencyManager::get()->renderCube(batch); } else { - DependencyManager::get()->renderSolidCubeInstance(batch, getTransformToCenter(), cubeColor); + DependencyManager::get()->renderSolidCubeInstance(batch, transToCenter, cubeColor); } static const auto triCount = DependencyManager::get()->getCubeTriangleCount(); args->_details._trianglesRendered += (int)triCount; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 5504268dce..25f4052c75 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -28,7 +28,12 @@ namespace render { template <> const Item::Bound payloadGetBound(const RenderableEntityItemProxy::Pointer& payload) { if (payload && payload->entity) { - return payload->entity->getAABox(); + bool success; + auto result = payload->entity->getAABox(success); + if (!success) { + return render::Item::Bound(); + } + return result; } return render::Item::Bound(); } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 9127e4ca22..f5090cc20c 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -196,7 +197,12 @@ namespace render { template <> const Item::Bound payloadGetBound(const RenderableModelEntityItemMeta::Pointer& payload) { if (payload && payload->entity) { - return payload->entity->getAABox(); + bool success; + auto result = payload->entity->getAABox(success); + if (!success) { + return render::Item::Bound(); + } + return result; } return render::Item::Bound(); } @@ -230,8 +236,8 @@ bool RenderableModelEntityItem::addToScene(EntityItemPointer self, std::shared_p return true; } - -void RenderableModelEntityItem::removeFromScene(EntityItemPointer self, std::shared_ptr scene, + +void RenderableModelEntityItem::removeFromScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges) { pendingChanges.removeItem(_myMetaItem); if (_model) { @@ -239,6 +245,84 @@ void RenderableModelEntityItem::removeFromScene(EntityItemPointer self, std::sha } } +void RenderableModelEntityItem::resizeJointArrays(int newSize) { + if (newSize < 0) { + if (_model && _model->isActive() && _model->isLoaded() && !_needsInitialSimulation) { + newSize = _model->getJointStateCount(); + } + } + ModelEntityItem::resizeJointArrays(newSize); +} + +bool RenderableModelEntityItem::getAnimationFrame() { + bool newFrame = false; + + if (!_model || !_model->isActive() || !_model->isLoaded() || _needsInitialSimulation) { + return false; + } + + if (!hasAnimation() || !_jointMappingCompleted) { + return false; + } + AnimationPointer myAnimation = getAnimation(_animationProperties.getURL()); // FIXME: this could be optimized + if (myAnimation && myAnimation->isLoaded()) { + + const QVector& frames = myAnimation->getFramesReference(); // NOTE: getFrames() is too heavy + auto& fbxJoints = myAnimation->getGeometry().joints; + + int frameCount = frames.size(); + if (frameCount > 0) { + int animationCurrentFrame = (int)(glm::floor(getAnimationCurrentFrame())) % frameCount; + if (animationCurrentFrame < 0 || animationCurrentFrame > frameCount) { + animationCurrentFrame = 0; + } + + if (animationCurrentFrame != _lastKnownCurrentFrame) { + _lastKnownCurrentFrame = animationCurrentFrame; + newFrame = true; + + resizeJointArrays(); + if (_jointMapping.size() != _model->getJointStateCount()) { + qDebug() << "RenderableModelEntityItem::getAnimationFrame -- joint count mismatch" + << _jointMapping.size() << _model->getJointStateCount(); + assert(false); + return false; + } + + const QVector& rotations = frames[animationCurrentFrame].rotations; + const QVector& translations = frames[animationCurrentFrame].translations; + + for (int j = 0; j < _jointMapping.size(); j++) { + int index = _jointMapping[j]; + if (index >= 0) { + glm::mat4 translationMat; + if (index < translations.size()) { + translationMat = glm::translate(translations[index]); + } + glm::mat4 rotationMat(glm::mat4::_null); + if (index < rotations.size()) { + rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * rotations[index] * fbxJoints[index].postRotation); + } else { + rotationMat = glm::mat4_cast(fbxJoints[index].preRotation * fbxJoints[index].postRotation); + } + glm::mat4 finalMat = (translationMat * fbxJoints[index].preTransform * + rotationMat * fbxJoints[index].postTransform); + _absoluteJointTranslationsInObjectFrame[j] = extractTranslation(finalMat); + _absoluteJointTranslationsInObjectFrameSet[j] = true; + _absoluteJointTranslationsInObjectFrameDirty[j] = true; + + _absoluteJointRotationsInObjectFrame[j] = glmExtractRotation(finalMat); + + _absoluteJointRotationsInObjectFrameSet[j] = true; + _absoluteJointRotationsInObjectFrameDirty[j] = true; + } + } + } + } + } + + return newFrame; +} // NOTE: this only renders the "meta" portion of the Model, namely it renders debugging items, and it handles // the per frame simulation/update that might be required if the models properties changed. @@ -292,26 +376,32 @@ void RenderableModelEntityItem::render(RenderArgs* args) { } if (_model) { - // handle animations.. if (hasAnimation()) { if (!jointsMapped()) { QStringList modelJointNames = _model->getJointNames(); mapJoints(modelJointNames); } + } - if (jointsMapped()) { - bool newFrame; - QVector frameDataRotations; - QVector frameDataTranslations; - getAnimationFrame(newFrame, frameDataRotations, frameDataTranslations); - assert(frameDataRotations.size() == frameDataTranslations.size()); - if (newFrame) { - for (int i = 0; i < frameDataRotations.size(); i++) { - _model->setJointState(i, true, frameDataRotations[i], frameDataTranslations[i], 1.0f); - } + _jointDataLock.withWriteLock([&] { + getAnimationFrame(); + + // relay any inbound joint changes from scripts/animation/network to the model/rig + for (int index = 0; index < _absoluteJointRotationsInObjectFrame.size(); index++) { + if (_absoluteJointRotationsInObjectFrameDirty[index]) { + glm::quat rotation = _absoluteJointRotationsInObjectFrame[index]; + _model->setJointRotation(index, true, rotation, 1.0f); + _absoluteJointRotationsInObjectFrameDirty[index] = false; } } - } + for (int index = 0; index < _absoluteJointTranslationsInObjectFrame.size(); index++) { + if (_absoluteJointTranslationsInObjectFrameDirty[index]) { + glm::vec3 translation = _absoluteJointTranslationsInObjectFrame[index]; + _model->setJointTranslation(index, true, translation, 1.0f); + _absoluteJointTranslationsInObjectFrameDirty[index] = false; + } + } + }); bool movingOrAnimating = isMoving() || isAnimatingSomething(); if ((movingOrAnimating || @@ -338,9 +428,12 @@ void RenderableModelEntityItem::render(RenderArgs* args) { } else { static glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f); gpu::Batch& batch = *args->_batch; - auto shapeTransform = getTransformToCenter(); - batch.setModelTransform(Transform()); // we want to include the scale as well - DependencyManager::get()->renderWireCubeInstance(batch, shapeTransform, greenColor); + bool success; + auto shapeTransform = getTransformToCenter(success); + if (success) { + batch.setModelTransform(Transform()); // we want to include the scale as well + DependencyManager::get()->renderWireCubeInstance(batch, shapeTransform, greenColor); + } } } @@ -600,7 +693,7 @@ bool RenderableModelEntityItem::contains(const glm::vec3& point) const { const FBXGeometry& collisionGeometry = collisionNetworkGeometry->getFBXGeometry(); return collisionGeometry.convexHullContains(worldToEntity(point)); } - + return false; } @@ -624,6 +717,36 @@ glm::vec3 RenderableModelEntityItem::getAbsoluteJointTranslationInObjectFrame(in return glm::vec3(0.0f); } +bool RenderableModelEntityItem::setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) { + bool result = false; + _jointDataLock.withWriteLock([&] { + resizeJointArrays(); + if (index >= 0 && index < _absoluteJointRotationsInObjectFrame.size() && + _absoluteJointRotationsInObjectFrame[index] != rotation) { + _absoluteJointRotationsInObjectFrame[index] = rotation; + _absoluteJointRotationsInObjectFrameSet[index] = true; + _absoluteJointRotationsInObjectFrameDirty[index] = true; + result = true; + } + }); + return result; +} + +bool RenderableModelEntityItem::setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) { + bool result = false; + _jointDataLock.withWriteLock([&] { + resizeJointArrays(); + if (index >= 0 && index < _absoluteJointTranslationsInObjectFrame.size() && + _absoluteJointTranslationsInObjectFrame[index] != translation) { + _absoluteJointTranslationsInObjectFrame[index] = translation; + _absoluteJointTranslationsInObjectFrameSet[index] = true; + _absoluteJointTranslationsInObjectFrameDirty[index] = true; + result = true; + } + }); + return result; +} + void RenderableModelEntityItem::locationChanged() { EntityItem::locationChanged(); if (_model && _model->isActive()) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index cf948bd7a0..eb7958ed47 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -33,15 +33,15 @@ public: virtual EntityItemProperties getProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()) const override; virtual bool setProperties(const EntityItemProperties& properties) override; - virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) override; - + virtual void somethingChangedNotification() override { // FIX ME: this is overly aggressive. We only really need to simulate() if something about // the world space transform has changed and/or if some animation is occurring. - _needsInitialSimulation = true; + _needsInitialSimulation = true; } virtual bool readyToAddToScene(RenderArgs* renderArgs = nullptr); @@ -52,12 +52,12 @@ public: virtual void render(RenderArgs* args) override; virtual bool supportsDetailedRayIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElementPointer& element, float& distance, + bool& keepSearching, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, void** intersectedObject, bool precisionPicking) const override; Model* getModel(EntityTreeRenderer* renderer); - + virtual bool needsToCallUpdate() const override; virtual void update(const quint64& now) override; @@ -65,19 +65,23 @@ public: virtual bool isReadyToComputeShape() override; virtual void computeShapeInfo(ShapeInfo& info) override; - + virtual bool contains(const glm::vec3& point) const override; // these are in the frame of this object (model space) virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; + virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override; + virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override; virtual void loader() override; virtual void locationChanged() override; + virtual void resizeJointArrays(int newSize = -1) override; + private: void remapTextures(); - + Model* _model = nullptr; bool _needsInitialSimulation = true; bool _needsModelReload = true; @@ -87,10 +91,12 @@ private: bool _originalTexturesRead = false; QVector> _points; bool _dimensionsInitialized = true; - + render::ItemID _myMetaItem; bool _showCollisionHull = false; + + bool getAnimationFrame(); }; #endif // hifi_RenderableModelEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 567d2bc24a..115eba0a05 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -235,10 +235,15 @@ void RenderableParticleEffectEntityItem::updateRenderItem() { for (auto& particle : _particles) { particlePrimitives->emplace_back(particle.position, glm::vec2(particle.lifetime, particle.seed)); } - - auto bounds = getAABox(); - auto position = getPosition(); - auto rotation = getRotation(); + + bool successb, successp, successr; + auto bounds = getAABox(successb); + auto position = getPosition(successp); + auto rotation = getOrientation(successr); + bool success = successb && successp && successr; + if (!success) { + return; + } Transform transform; if (!getEmitterShouldTrail()) { transform.setTranslation(position); diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index 7b3bbc4c02..fd28bc043d 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -122,13 +122,19 @@ void RenderablePolyLineEntityItem::updateVertices() { glm::vec3 v1, v2, tangent, binormal, point; int finalIndex = minVectorSize - 1; + + // Guard against an empty polyline + if (finalIndex < 0) { + return; + } + for (int i = 0; i < finalIndex; i++) { float width = _strokeWidths.at(i); point = _points.at(i); tangent = _points.at(i); - tangent = _points.at(i + 1) - point; + tangent = _points.at(i + 1) - point; glm::vec3 normal = _normals.at(i); binormal = glm::normalize(glm::cross(tangent, normal)) * width; @@ -141,11 +147,6 @@ void RenderablePolyLineEntityItem::updateVertices() { _vertices << v1 << v2; } - // Guard against an empty polyline - if (finalIndex < 0) { - return; - } - // For last point we can assume binormals are the same since it represents the last two vertices of quad point = _points.at(finalIndex); v1 = point + binormal; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp index 25530fe41a..1048f594f4 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -142,9 +142,11 @@ glm::vec3 RenderablePolyVoxEntityItem::getSurfacePositionAdjustment() const { glm::mat4 RenderablePolyVoxEntityItem::voxelToLocalMatrix() const { glm::vec3 scale = getDimensions() / _voxelVolumeSize; // meters / voxel-units - glm::vec3 center = getCenterPosition(); - glm::vec3 position = getPosition(); + bool success; // TODO -- Does this actually have to happen in world space? + glm::vec3 center = getCenterPosition(success); + glm::vec3 position = getPosition(success); glm::vec3 positionToCenter = center - position; + positionToCenter -= getDimensions() * Vectors::HALF - getSurfacePositionAdjustment(); glm::mat4 centerToCorner = glm::translate(glm::mat4(), positionToCenter); glm::mat4 scaled = glm::scale(centerToCorner, scale); @@ -581,7 +583,12 @@ namespace render { template <> const Item::Bound payloadGetBound(const PolyVoxPayload::Pointer& payload) { if (payload && payload->_owner) { auto polyVoxEntity = std::dynamic_pointer_cast(payload->_owner); - return polyVoxEntity->getAABox(); + bool success; + auto result = polyVoxEntity->getAABox(success); + if (!success) { + return render::Item::Bound(); + } + return result; } return render::Item::Bound(); } diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h index 0411945ede..efd9b4afda 100644 --- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -77,7 +77,7 @@ public: glm::mat4 localToVoxelMatrix() const; virtual ShapeType getShapeType() const; - virtual bool shouldBePhysical() const { return true; } + virtual bool shouldBePhysical() const { return !isDead(); } virtual bool isReadyToComputeShape(); virtual void computeShapeInfo(ShapeInfo& info); diff --git a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp index 6c72dc488c..9bed5b3bb0 100644 --- a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp @@ -59,7 +59,11 @@ void RenderableSphereEntityItem::render(RenderArgs* args) { gpu::Batch& batch = *args->_batch; glm::vec4 sphereColor(toGlm(getXColor()), getLocalRenderAlpha()); - Transform modelTransform = getTransformToCenter(); + bool success; + Transform modelTransform = getTransformToCenter(success); + if (!success) { + return; + } modelTransform.postScale(SPHERE_ENTITY_SCALE); if (_procedural->ready()) { batch.setModelTransform(modelTransform); // use a transform with scale, rotation, registration point and translation diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index bdf3b5b97c..d88b4d5f0a 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -44,8 +44,12 @@ void RenderableTextEntityItem::render(RenderArgs* args) { // Batch render calls Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; - - Transform transformToTopLeft = getTransformToCenter(); + + bool success; + Transform transformToTopLeft = getTransformToCenter(success); + if (!success) { + return; + } if (getFaceCamera()) { //rotate about vertical to face the camera glm::vec3 dPosition = args->_viewFrustum->getPosition() - getPosition(); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 44a0740a4d..979ec32878 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -54,6 +54,11 @@ RenderableWebEntityItem::~RenderableWebEntityItem() { webSurface->deleteLater(); }); } + + QObject::disconnect(_mousePressConnection); + QObject::disconnect(_mouseReleaseConnection); + QObject::disconnect(_mouseMoveConnection); + QObject::disconnect(_hoverLeaveConnection); qDebug() << "Destroyed web entity " << getID(); } @@ -156,10 +161,10 @@ void RenderableWebEntityItem::render(RenderArgs* args) { }; EntityTreeRenderer* renderer = static_cast(args->_renderer); - QObject::connect(renderer, &EntityTreeRenderer::mousePressOnEntity, forwardMouseEvent); - QObject::connect(renderer, &EntityTreeRenderer::mouseReleaseOnEntity, forwardMouseEvent); - QObject::connect(renderer, &EntityTreeRenderer::mouseMoveOnEntity, forwardMouseEvent); - QObject::connect(renderer, &EntityTreeRenderer::hoverLeaveEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) { + _mousePressConnection = QObject::connect(renderer, &EntityTreeRenderer::mousePressOnEntity, forwardMouseEvent); + _mouseReleaseConnection = QObject::connect(renderer, &EntityTreeRenderer::mouseReleaseOnEntity, forwardMouseEvent); + _mouseMoveConnection = QObject::connect(renderer, &EntityTreeRenderer::mouseMoveOnEntity, forwardMouseEvent); + _hoverLeaveConnection = QObject::connect(renderer, &EntityTreeRenderer::hoverLeaveEntity, [=](const EntityItemID& entityItemID, const MouseEvent& event) { if (this->_pressed && this->getID() == entityItemID) { // If the user mouses off the entity while the button is down, simulate a mouse release QMouseEvent mappedEvent(QEvent::MouseButtonRelease, @@ -185,7 +190,11 @@ void RenderableWebEntityItem::render(RenderArgs* args) { Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; - batch.setModelTransform(getTransformToCenter()); + bool success; + batch.setModelTransform(getTransformToCenter(success)); + if (!success) { + return; + } bool textured = false, culled = false, emissive = false; if (_texture) { batch._glActiveBindTexture(GL_TEXTURE0, GL_TEXTURE_2D, _texture); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 2266401b5d..799a414151 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -40,6 +40,11 @@ private: ivec2 _lastPress{ INT_MIN }; bool _pressed{ false }; ivec2 _lastMove{ INT_MIN }; + + QMetaObject::Connection _mousePressConnection; + QMetaObject::Connection _mouseReleaseConnection; + QMetaObject::Connection _mouseMoveConnection; + QMetaObject::Connection _hoverLeaveConnection; }; diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index ca16b3aca1..eb01bd551e 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -135,7 +135,11 @@ void RenderableZoneEntityItem::render(RenderArgs* args) { gpu::Batch& batch = *args->_batch; batch.setModelTransform(Transform()); - auto shapeTransform = getTransformToCenter(); + bool success; + auto shapeTransform = getTransformToCenter(success); + if (!success) { + break; + } auto deferredLightingEffect = DependencyManager::get(); if (getShapeType() == SHAPE_TYPE_SPHERE) { shapeTransform.postScale(SPHERE_ENTITY_SCALE); @@ -190,7 +194,12 @@ namespace render { template <> const Item::Bound payloadGetBound(const RenderableZoneEntityItemMeta::Pointer& payload) { if (payload && payload->entity) { - return payload->entity->getAABox(); + bool success; + auto result = payload->entity->getAABox(success); + if (!success) { + return render::Item::Bound(); + } + return result; } return render::Item::Bound(); } diff --git a/libraries/entities-renderer/src/polyvox.slf b/libraries/entities-renderer/src/polyvox.slf index c694630770..2bdecbf9fe 100644 --- a/libraries/entities-renderer/src/polyvox.slf +++ b/libraries/entities-renderer/src/polyvox.slf @@ -28,6 +28,9 @@ void main(void) { vec3 worldNormal = cross(dFdy(_worldPosition.xyz), dFdx(_worldPosition.xyz)); worldNormal = normalize(worldNormal); + vec3 specular = DEFAULT_SPECULAR; + float shininess = DEFAULT_SHININESS; + float inPositionX = (_worldPosition.x - 0.5) / voxelVolumeSize.x; float inPositionY = (_worldPosition.y - 0.5) / voxelVolumeSize.y; float inPositionZ = (_worldPosition.z - 0.5) / voxelVolumeSize.z; @@ -41,5 +44,5 @@ void main(void) { vec3 yzDiffuseScaled = yzDiffuse.rgb * abs(worldNormal.x); vec4 diffuse = vec4(xyDiffuseScaled + xzDiffuseScaled + yzDiffuseScaled, 1.0); - packDeferredFragment(_normal, 0.0, vec3(diffuse), vec3(0.01, 0.01, 0.01), 10.0); + packDeferredFragment(_normal, 1.0, vec3(diffuse), specular, shininess); } diff --git a/libraries/entities-renderer/src/textured_particle.slv b/libraries/entities-renderer/src/textured_particle.slv index e1f18c2b5f..4d819d389f 100644 --- a/libraries/entities-renderer/src/textured_particle.slv +++ b/libraries/entities-renderer/src/textured_particle.slv @@ -56,13 +56,50 @@ float bezierInterpolate(float y1, float y2, float y3, float u) { return (1.0 - u) * (1.0 - u) * y1 + 2.0 * (1.0 - u) * u * y2 + u * u * y3; } -vec4 interpolate3Vec4(vec4 y1, vec4 y2, vec4 y3, float u) { - return vec4(bezierInterpolate(y1.x, y2.x, y3.x, u), - bezierInterpolate(y1.y, y2.y, y3.y, u), - bezierInterpolate(y1.z, y2.z, y3.z, u), - bezierInterpolate(y1.w, y2.w, y3.w, u)); +float interpolate3Points(float y1, float y2, float y3, float u) { + // Makes the interpolated values intersect the middle value. + + if ((u <= 0.5f && y1 == y2) || (u >= 0.5f && y2 == y3)) { + // Flat line. + return y2; + } + + float halfSlope; + if ((y2 >= y1 && y2 >= y3) || (y2 <= y1 && y2 <= y3)) { + // U or inverted-U shape. + // Make the slope at y2 = 0, which means that the control points half way between the value points have the value y2. + halfSlope = 0.0f; + + } else { + // L or inverted and/or mirrored L shape. + // Make the slope at y2 be the slope between y1 and y3, up to a maximum of double the minimum of the slopes between y1 + // and y2, and y2 and y3. Use this slope to calculate the control points half way between the value points. + // Note: The maximum ensures that the control points and therefore the interpolated values stay between y1 and y3. + halfSlope = (y3 - y1) / 2.0f; + float slope12 = y2 - y1; + float slope23 = y3 - y2; + if (abs(halfSlope) > abs(slope12)) { + halfSlope = slope12; + } else if (abs(halfSlope) > abs(slope23)) { + halfSlope = slope23; + } + } + + float stepU = step(0.5f, u); // 0.0 if u < 0.5, 1.0 otherwise. + float slopeSign = 2.0f * stepU - 1.0f; // -1.0 if u < 0.5, 1.0 otherwise + float start = (1.0f - stepU) * y1 + stepU * y2; // y1 if u < 0.5, y2 otherwise + float middle = y2 + slopeSign * halfSlope; + float finish = (1.0f - stepU) * y2 + stepU * y3; // y2 if u < 0.5, y3 otherwise + float v = 2.0f * u - step(0.5f, u); // 0.0-0.5 -> 0.0-1.0 and 0.5-1.0 -> 0.0-1.0 + return bezierInterpolate(start, middle, finish, v); } +vec4 interpolate3Vec4(vec4 y1, vec4 y2, vec4 y3, float u) { + return vec4(interpolate3Points(y1.x, y2.x, y3.x, u), + interpolate3Points(y1.y, y2.y, y3.y, u), + interpolate3Points(y1.z, y2.z, y3.z, u), + interpolate3Points(y1.w, y2.w, y3.w, u)); +} void main(void) { TransformCamera cam = getTransformCamera(); @@ -73,21 +110,21 @@ void main(void) { // Which quad vertex pos? int twoTriID = gl_VertexID - particleID * NUM_VERTICES_PER_PARTICLE; - // Particle properties + // Particle properties float age = inColor.x / particle.lifespan; float seed = inColor.y; // Pass the texcoord and the z texcoord is representing the texture icon varTexcoord = vec2((UNIT_QUAD[twoTriID].xy + 1.0) * 0.5); varColor = interpolate3Vec4(particle.color.start, particle.color.middle, particle.color.finish, age); - + // anchor point in eye space - float radius = bezierInterpolate(particle.radius.start, particle.radius.middle, particle.radius.finish, age); + float radius = interpolate3Points(particle.radius.start, particle.radius.middle, particle.radius.finish, age); vec4 quadPos = radius * UNIT_QUAD[twoTriID]; vec4 anchorPoint; <$transformModelToEyePos(cam, obj, inPosition, anchorPoint)$> - + vec4 eyePos = anchorPoint + quadPos; <$transformEyeToClipPos(cam, eyePos, gl_Position)$> } diff --git a/libraries/entities/src/AddEntityOperator.cpp b/libraries/entities/src/AddEntityOperator.cpp index 2bfbbe2fd9..1c39b40495 100644 --- a/libraries/entities/src/AddEntityOperator.cpp +++ b/libraries/entities/src/AddEntityOperator.cpp @@ -25,7 +25,13 @@ AddEntityOperator::AddEntityOperator(EntityTreePointer tree, EntityItemPointer n // caller must have verified existence of newEntity assert(_newEntity); - _newEntityBox = _newEntity->getMaximumAACube().clamp((float)(-HALF_TREE_SCALE), (float)HALF_TREE_SCALE); + bool success; + auto queryCube = _newEntity->getQueryAACube(success); + if (!success) { + _newEntity->markAncestorMissing(true); + } + + _newEntityBox = queryCube.clamp((float)(-HALF_TREE_SCALE), (float)HALF_TREE_SCALE); } bool AddEntityOperator::preRecursion(OctreeElementPointer element) { diff --git a/libraries/entities/src/AnimationPropertyGroup.cpp b/libraries/entities/src/AnimationPropertyGroup.cpp index ef89128e30..97c8a1816a 100644 --- a/libraries/entities/src/AnimationPropertyGroup.cpp +++ b/libraries/entities/src/AnimationPropertyGroup.cpp @@ -169,7 +169,7 @@ bool AnimationPropertyGroup::appendToEditPacket(OctreePacketData* packetData, EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, + int& propertyCount, OctreeElement::AppendState& appendState) const { bool successPropertyFits = true; @@ -248,7 +248,7 @@ void AnimationPropertyGroup::markAllChanged() { EntityPropertyFlags AnimationPropertyGroup::getChangedProperties() const { EntityPropertyFlags changedProperties; - + CHECK_PROPERTY_CHANGE(PROP_ANIMATION_URL, url); CHECK_PROPERTY_CHANGE(PROP_ANIMATION_FPS, fps); CHECK_PROPERTY_CHANGE(PROP_ANIMATION_FRAME_INDEX, currentFrame); @@ -321,13 +321,13 @@ EntityPropertyFlags AnimationPropertyGroup::getEntityProperties(EncodeBitstreamP return requestedProperties; } - -void AnimationPropertyGroup::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + +void AnimationPropertyGroup::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, + int& propertyCount, OctreeElement::AppendState& appendState) const { bool successPropertyFits = true; @@ -352,7 +352,7 @@ void AnimationPropertyGroup::appendSubclassData(OctreePacketData* packetData, En } } -int AnimationPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, +int AnimationPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) { diff --git a/libraries/entities/src/AnimationPropertyGroup.h b/libraries/entities/src/AnimationPropertyGroup.h index 8e85d8bc99..3ee452cc5f 100644 --- a/libraries/entities/src/AnimationPropertyGroup.h +++ b/libraries/entities/src/AnimationPropertyGroup.h @@ -39,11 +39,11 @@ public: virtual void debugDump() const; virtual void listChangedProperties(QList& out); - virtual bool appendToEditPacket(OctreePacketData* packetData, + virtual bool appendToEditPacket(OctreePacketData* packetData, EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, + int& propertyCount, OctreeElement::AppendState& appendState) const; virtual bool decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt , int& processedBytes); @@ -53,25 +53,25 @@ public: // EntityItem related helpers // methods for getting/setting all properties of an entity virtual void getProperties(EntityItemProperties& propertiesOut) const; - + /// returns true if something changed virtual bool setProperties(const EntityItemProperties& properties); virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; - - virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, + int& propertyCount, OctreeElement::AppendState& appendState) const; - virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged); - + DEFINE_PROPERTY_REF(PROP_ANIMATION_URL, URL, url, QString, ""); DEFINE_PROPERTY(PROP_ANIMATION_FPS, FPS, fps, float, 30.0f); DEFINE_PROPERTY(PROP_ANIMATION_FRAME_INDEX, CurrentFrame, currentFrame, float, 0.0f); diff --git a/libraries/entities/src/BoundingBoxRelatedProperties.cpp b/libraries/entities/src/BoundingBoxRelatedProperties.cpp deleted file mode 100644 index e9ee302300..0000000000 --- a/libraries/entities/src/BoundingBoxRelatedProperties.cpp +++ /dev/null @@ -1,83 +0,0 @@ -// -// BoundingBoxRelatedProperties.cpp -// libraries/entities/src -// -// Created by Seth Alves on 2015-9-24 -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "EntityItemProperties.h" -#include "BoundingBoxRelatedProperties.h" -#include "EntityTree.h" - -BoundingBoxRelatedProperties::BoundingBoxRelatedProperties(EntityItemPointer entity) : - position(entity->getPosition()), - rotation(entity->getRotation()), - registrationPoint(entity->getRegistrationPoint()), - dimensions(entity->getDimensions()), - parentID(entity->getParentID()) { -} - -BoundingBoxRelatedProperties::BoundingBoxRelatedProperties(EntityItemPointer entity, - const EntityItemProperties& propertiesWithUpdates) : - BoundingBoxRelatedProperties(entity) { - - if (propertiesWithUpdates.parentIDChanged()) { - parentID = propertiesWithUpdates.getParentID(); - } - - bool parentFound = false; - if (parentID != UNKNOWN_ENTITY_ID) { - EntityTreePointer tree = entity->getTree(); - EntityItemPointer parentZone = tree->findEntityByID(parentID); - if (parentZone) { - parentFound = true; - glm::vec3 localPosition = propertiesWithUpdates.containsPositionChange() ? - propertiesWithUpdates.getPosition() : - entity->getLocalPosition(); - - glm::quat localRotation = propertiesWithUpdates.rotationChanged() ? - propertiesWithUpdates.getRotation() : - entity->getLocalOrientation(); - - const Transform parentTransform = parentZone->getTransformToCenter(); - Transform parentDescaled(parentTransform.getRotation(), glm::vec3(1.0f), parentTransform.getTranslation()); - - Transform localTransform(localRotation, glm::vec3(1.0f), localPosition); - Transform result; - Transform::mult(result, parentDescaled, localTransform); - position = result.getTranslation(); - rotation = result.getRotation(); - } - } - - if (!parentFound) { - if (propertiesWithUpdates.containsPositionChange()) { - position = propertiesWithUpdates.getPosition(); - } - if (propertiesWithUpdates.rotationChanged()) { - rotation = propertiesWithUpdates.getRotation(); - } - } - - if (propertiesWithUpdates.registrationPointChanged()) { - registrationPoint = propertiesWithUpdates.getRegistrationPoint(); - } - - if (propertiesWithUpdates.dimensionsChanged()) { - dimensions = propertiesWithUpdates.getDimensions(); - } -} - -AACube BoundingBoxRelatedProperties::getMaximumAACube() const { - // see EntityItem::getMaximumAACube for comments which explain the following. - glm::vec3 scaledRegistrationPoint = (dimensions * registrationPoint); - glm::vec3 registrationRemainder = (dimensions * (glm::vec3(1.0f, 1.0f, 1.0f) - registrationPoint)); - glm::vec3 furthestExtentFromRegistration = glm::max(scaledRegistrationPoint, registrationRemainder); - float radius = glm::length(furthestExtentFromRegistration); - glm::vec3 minimumCorner = position - glm::vec3(radius, radius, radius); - return AACube(minimumCorner, radius * 2.0f); -} diff --git a/libraries/entities/src/BoundingBoxRelatedProperties.h b/libraries/entities/src/BoundingBoxRelatedProperties.h deleted file mode 100644 index 811c885fd2..0000000000 --- a/libraries/entities/src/BoundingBoxRelatedProperties.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// BoundingBoxRelatedProperties.h -// libraries/entities/src -// -// Created by Seth Alves on 2015-9-24 -// Copyright 2013 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "EntityItem.h" - -#ifndef hifi_BoundingBoxRelatedProperties_h -#define hifi_BoundingBoxRelatedProperties_h - -class BoundingBoxRelatedProperties { - public: - BoundingBoxRelatedProperties(EntityItemPointer entity); - BoundingBoxRelatedProperties(EntityItemPointer entity, const EntityItemProperties& propertiesWithUpdates); - AACube getMaximumAACube() const; - - glm::vec3 position; - glm::quat rotation; - glm::vec3 registrationPoint; - glm::vec3 dimensions; - EntityItemID parentID; -}; - -#endif // hifi_BoundingBoxRelatedProperties_h diff --git a/libraries/entities/src/BoxEntityItem.h b/libraries/entities/src/BoxEntityItem.h index 351feb7e54..6196346b9a 100644 --- a/libraries/entities/src/BoxEntityItem.h +++ b/libraries/entities/src/BoxEntityItem.h @@ -53,7 +53,7 @@ public: } virtual ShapeType getShapeType() const { return SHAPE_TYPE_BOX; } - virtual bool shouldBePhysical() const { return true; } + virtual bool shouldBePhysical() const { return !isDead(); } virtual void debugDump() const; diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 13d5c2836f..97043a635d 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -133,6 +133,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_ACTION_DATA; requestedProperties += PROP_PARENT_ID; requestedProperties += PROP_PARENT_JOINT_INDEX; + requestedProperties += PROP_QUERY_AA_CUBE; return requestedProperties; } @@ -269,6 +270,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, getActionData()); APPEND_ENTITY_PROPERTY(PROP_PARENT_ID, getParentID()); APPEND_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, getParentJointIndex()); + APPEND_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, getQueryAACube()); appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, @@ -693,6 +695,8 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_PARENT_ID, QUuid, setParentID); READ_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, quint16, setParentJointIndex); + READ_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, AACube, setQueryAACube); + bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData, somethingChanged); @@ -1047,6 +1051,7 @@ EntityItemProperties EntityItem::getProperties(EntityPropertyFlags desiredProper COPY_ENTITY_PROPERTY_TO_PROPERTIES(actionData, getActionData); COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentID, getParentID); COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentJointIndex, getParentJointIndex); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(queryAACube, getQueryAACube); properties._defaultSettings = false; @@ -1111,6 +1116,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(actionData, setActionData); SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentID, setParentID); SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentJointIndex, setParentJointIndex); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(queryAACube, setQueryAACube); if (somethingChanged) { uint64_t now = usecTimestampNow(); @@ -1152,8 +1158,8 @@ void EntityItem::recordCreationTime() { _lastSimulated = now; } -const Transform EntityItem::getTransformToCenter() const { - Transform result = getTransform(); +const Transform EntityItem::getTransformToCenter(bool& success) const { + Transform result = getTransform(success); if (getRegistrationPoint() != ENTITY_ITEM_HALF_VEC3) { // If it is not already centered, translate to center result.postTranslate(ENTITY_ITEM_HALF_VEC3 - getRegistrationPoint()); // Position to center } @@ -1171,28 +1177,31 @@ void EntityItem::setDimensions(const glm::vec3& value) { /// The maximum bounding cube for the entity, independent of it's rotation. /// This accounts for the registration point (upon which rotation occurs around). /// -const AACube& EntityItem::getMaximumAACube() const { +AACube EntityItem::getMaximumAACube(bool& success) const { if (_recalcMaxAACube) { // * we know that the position is the center of rotation - glm::vec3 centerOfRotation = getPosition(); // also where _registration point is + glm::vec3 centerOfRotation = getPosition(success); // also where _registration point is + if (success) { + // * we know that the registration point is the center of rotation + // * we can calculate the length of the furthest extent from the registration point + // as the dimensions * max (registrationPoint, (1.0,1.0,1.0) - registrationPoint) + glm::vec3 registrationPoint = (getDimensions() * getRegistrationPoint()); + glm::vec3 registrationRemainder = (getDimensions() * (glm::vec3(1.0f, 1.0f, 1.0f) - getRegistrationPoint())); + glm::vec3 furthestExtentFromRegistration = glm::max(registrationPoint, registrationRemainder); - // * we know that the registration point is the center of rotation - // * we can calculate the length of the furthest extent from the registration point - // as the dimensions * max (registrationPoint, (1.0,1.0,1.0) - registrationPoint) - glm::vec3 registrationPoint = (getDimensions() * getRegistrationPoint()); - glm::vec3 registrationRemainder = (getDimensions() * (glm::vec3(1.0f, 1.0f, 1.0f) - getRegistrationPoint())); - glm::vec3 furthestExtentFromRegistration = glm::max(registrationPoint, registrationRemainder); + // * we know that if you rotate in any direction you would create a sphere + // that has a radius of the length of furthest extent from registration point + float radius = glm::length(furthestExtentFromRegistration); - // * we know that if you rotate in any direction you would create a sphere - // that has a radius of the length of furthest extent from registration point - float radius = glm::length(furthestExtentFromRegistration); + // * we know that the minimum bounding cube of this maximum possible sphere is + // (center - radius) to (center + radius) + glm::vec3 minimumCorner = centerOfRotation - glm::vec3(radius, radius, radius); - // * we know that the minimum bounding cube of this maximum possible sphere is - // (center - radius) to (center + radius) - glm::vec3 minimumCorner = centerOfRotation - glm::vec3(radius, radius, radius); - - _maxAACube = AACube(minimumCorner, radius * 2.0f); - _recalcMaxAACube = false; + _maxAACube = AACube(minimumCorner, radius * 2.0f); + _recalcMaxAACube = false; + } + } else { + success = true; } return _maxAACube; } @@ -1200,7 +1209,7 @@ const AACube& EntityItem::getMaximumAACube() const { /// The minimum bounding cube for the entity accounting for it's rotation. /// This accounts for the registration point (upon which rotation occurs around). /// -const AACube& EntityItem::getMinimumAACube() const { +AACube EntityItem::getMinimumAACube(bool& success) const { if (_recalcMinAACube) { // _position represents the position of the registration point. glm::vec3 registrationRemainder = glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint; @@ -1208,25 +1217,30 @@ const AACube& EntityItem::getMinimumAACube() const { glm::vec3 unrotatedMinRelativeToEntity = - (getDimensions() * getRegistrationPoint()); glm::vec3 unrotatedMaxRelativeToEntity = getDimensions() * registrationRemainder; Extents unrotatedExtentsRelativeToRegistrationPoint = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity }; - Extents rotatedExtentsRelativeToRegistrationPoint = unrotatedExtentsRelativeToRegistrationPoint.getRotated(getRotation()); + Extents rotatedExtentsRelativeToRegistrationPoint = + unrotatedExtentsRelativeToRegistrationPoint.getRotated(getRotation()); // shift the extents to be relative to the position/registration point - rotatedExtentsRelativeToRegistrationPoint.shiftBy(getPosition()); + rotatedExtentsRelativeToRegistrationPoint.shiftBy(getPosition(success)); - // the cube that best encompasses extents is... - AABox box(rotatedExtentsRelativeToRegistrationPoint); - glm::vec3 centerOfBox = box.calcCenter(); - float longestSide = box.getLargestDimension(); - float halfLongestSide = longestSide / 2.0f; - glm::vec3 cornerOfCube = centerOfBox - glm::vec3(halfLongestSide, halfLongestSide, halfLongestSide); + if (success) { + // the cube that best encompasses extents is... + AABox box(rotatedExtentsRelativeToRegistrationPoint); + glm::vec3 centerOfBox = box.calcCenter(); + float longestSide = box.getLargestDimension(); + float halfLongestSide = longestSide / 2.0f; + glm::vec3 cornerOfCube = centerOfBox - glm::vec3(halfLongestSide, halfLongestSide, halfLongestSide); - _minAACube = AACube(cornerOfCube, longestSide); - _recalcMinAACube = false; + _minAACube = AACube(cornerOfCube, longestSide); + _recalcMinAACube = false; + } + } else { + success = true; } return _minAACube; } -const AABox& EntityItem::getAABox() const { +AABox EntityItem::getAABox(bool& success) const { if (_recalcAABox) { // _position represents the position of the registration point. glm::vec3 registrationRemainder = glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint; @@ -1234,17 +1248,37 @@ const AABox& EntityItem::getAABox() const { glm::vec3 unrotatedMinRelativeToEntity = - (getDimensions() * _registrationPoint); glm::vec3 unrotatedMaxRelativeToEntity = getDimensions() * registrationRemainder; Extents unrotatedExtentsRelativeToRegistrationPoint = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity }; - Extents rotatedExtentsRelativeToRegistrationPoint = unrotatedExtentsRelativeToRegistrationPoint.getRotated(getRotation()); + Extents rotatedExtentsRelativeToRegistrationPoint = + unrotatedExtentsRelativeToRegistrationPoint.getRotated(getRotation()); // shift the extents to be relative to the position/registration point - rotatedExtentsRelativeToRegistrationPoint.shiftBy(getPosition()); + rotatedExtentsRelativeToRegistrationPoint.shiftBy(getPosition(success)); - _cachedAABox = AABox(rotatedExtentsRelativeToRegistrationPoint); - _recalcAABox = false; + if (success) { + _cachedAABox = AABox(rotatedExtentsRelativeToRegistrationPoint); + _recalcAABox = false; + } + } else { + success = true; } return _cachedAABox; } +AACube EntityItem::getQueryAACube(bool& success) const { + AACube result = SpatiallyNestable::getQueryAACube(success); + if (success) { + return result; + } + // this is for when we've loaded an older json file that didn't have queryAACube properties. + result = getMaximumAACube(success); + if (success) { + _queryAACube = result; + _queryAACubeSet = true; + } + return result; +} + + // NOTE: This should only be used in cases of old bitstreams which only contain radius data // 0,0,0 --> maxDimension,maxDimension,maxDimension // ... has a corner to corner distance of glm::length(maxDimension,maxDimension,maxDimension) @@ -1274,7 +1308,9 @@ float EntityItem::getRadius() const { bool EntityItem::contains(const glm::vec3& point) const { if (getShapeType() == SHAPE_TYPE_COMPOUND) { - return getAABox().contains(point); + bool success; + bool result = getAABox(success).contains(point); + return result && success; } else { ShapeInfo info; info.setParams(getShapeType(), glm::vec3(0.5f)); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 244198f7b8..83f2ad164e 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -56,6 +56,7 @@ namespace render { #define debugTimeOnly(T) qPrintable(QString("%1").arg(T, 16, 10)) #define debugTreeVector(V) V << "[" << V << " in meters ]" + /// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available /// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate /// one directly, instead you must only construct one of it's derived classes with additional features. @@ -161,11 +162,10 @@ public: // attributes applicable to all entity types EntityTypes::EntityType getType() const { return _type; } - inline glm::vec3 getCenterPosition() const { return getTransformToCenter().getTranslation(); } + inline glm::vec3 getCenterPosition(bool& success) const { return getTransformToCenter(success).getTranslation(); } void setCenterPosition(const glm::vec3& position); - const Transform getTransformToCenter() const; - void setTranformToCenter(const Transform& transform); + const Transform getTransformToCenter(bool& success) const; inline void requiresRecalcBoxes() { _recalcAABox = true; _recalcMinAACube = true; _recalcMaxAACube = true; } @@ -232,9 +232,12 @@ public: quint64 getExpiry() const; // position, size, and bounds related helpers - const AACube& getMaximumAACube() const; - const AACube& getMinimumAACube() const; - const AABox& getAABox() const; /// axis aligned bounding box in world-frame (meters) + virtual AACube getMaximumAACube(bool& success) const override; + AACube getMinimumAACube(bool& success) const; + AABox getAABox(bool& success) const; /// axis aligned bounding box in world-frame (meters) + + using SpatiallyNestable::getQueryAACube; + virtual AACube getQueryAACube(bool& success) const override; const QString& getScript() const { return _script; } void setScript(const QString& value) { _script = value; } @@ -299,7 +302,7 @@ public: virtual bool contains(const glm::vec3& point) const; - virtual bool isReadyToComputeShape() { return true; } + virtual bool isReadyToComputeShape() { return !isDead(); } virtual void computeShapeInfo(ShapeInfo& info); virtual float getVolumeEstimate() const { return getDimensions().x * getDimensions().y * getDimensions().z; } @@ -334,6 +337,8 @@ public: bool isMoving() const; + bool isSimulated() const { return _simulated; } + void* getPhysicsInfo() const { return _physicsInfo; } void setPhysicsInfo(void* data) { _physicsInfo = data; } @@ -381,11 +386,15 @@ public: // these are in the frame of this object virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override { return glm::quat(); } virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override { return glm::vec3(0.0f); } + virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) override { return false; } + virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) override { return false; } virtual void loader() {} // called indirectly when urls for geometry are updated protected: + void setSimulated(bool simulated) { _simulated = simulated; } + const QByteArray getActionDataInternal() const; void setActionDataInternal(QByteArray actionData); diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 3a2fdf55d4..214f1e5d78 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -82,7 +82,7 @@ void EntityItemProperties::debugDump() const { getAtmosphere().debugDump(); getSkybox().debugDump(); getKeyLight().debugDump(); - + qCDebug(entities) << " changed properties..."; EntityPropertyFlags props = getChangedProperties(); props.debugDumpBits(); @@ -262,6 +262,11 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_Z_P_NEIGHBOR_ID, zPNeighborID); CHECK_PROPERTY_CHANGE(PROP_PARENT_ID, parentID); CHECK_PROPERTY_CHANGE(PROP_PARENT_JOINT_INDEX, parentJointIndex); + CHECK_PROPERTY_CHANGE(PROP_JOINT_ROTATIONS_SET, jointRotationsSet); + CHECK_PROPERTY_CHANGE(PROP_JOINT_ROTATIONS, jointRotations); + CHECK_PROPERTY_CHANGE(PROP_JOINT_TRANSLATIONS_SET, jointTranslationsSet); + CHECK_PROPERTY_CHANGE(PROP_JOINT_TRANSLATIONS, jointTranslations); + CHECK_PROPERTY_CHANGE(PROP_QUERY_AA_CUBE, queryAACube); changedProperties += _animation.getChangedProperties(); changedProperties += _keyLight.getChangedProperties(); @@ -363,6 +368,10 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool if (_type == EntityTypes::Model) { COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MODEL_URL, modelURL); _animation.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_ROTATIONS_SET, jointRotationsSet); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_ROTATIONS, jointRotations); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_TRANSLATIONS_SET, jointTranslationsSet); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_TRANSLATIONS, jointTranslations); } if (_type == EntityTypes::Model || _type == EntityTypes::Zone || _type == EntityTypes::ParticleEffect) { @@ -398,7 +407,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool // Zones only if (_type == EntityTypes::Zone) { _keyLight.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); - + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BACKGROUND_MODE, backgroundMode, getBackgroundModeAsString()); _stage.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); @@ -473,10 +482,18 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_ID, parentID); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_JOINT_INDEX, parentJointIndex); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_QUERY_AA_CUBE, queryAACube); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_POSITION, localPosition); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ROTATION, localRotation); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_ROTATIONS_SET, jointRotationsSet); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_ROTATIONS, jointRotations); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_TRANSLATIONS_SET, jointTranslationsSet); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_TRANSLATIONS, jointTranslations); + + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_QUERY_AA_CUBE, queryAACube); + // FIXME - I don't think these properties are supported any more //COPY_PROPERTY_TO_QSCRIPTVALUE(glowLevel); //COPY_PROPERTY_TO_QSCRIPTVALUE(localRenderAlpha); @@ -601,10 +618,16 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(parentID, QUuid, setParentID); COPY_PROPERTY_FROM_QSCRIPTVALUE(parentJointIndex, quint16, setParentJointIndex); + COPY_PROPERTY_FROM_QSCRIPTVALUE(queryAACube, AACube, setQueryAACube); COPY_PROPERTY_FROM_QSCRIPTVALUE(localPosition, glmVec3, setLocalPosition); COPY_PROPERTY_FROM_QSCRIPTVALUE(localRotation, glmQuat, setLocalRotation); + COPY_PROPERTY_FROM_QSCRIPTVALUE(jointRotationsSet, qVectorBool, setJointRotationsSet); + COPY_PROPERTY_FROM_QSCRIPTVALUE(jointRotations, qVectorQuat, setJointRotations); + COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslationsSet, qVectorBool, setJointTranslationsSet); + COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslations, qVectorVec3, setJointTranslations); + _lastEdited = usecTimestampNow(); } @@ -646,7 +669,7 @@ static QHash _propertyStringsToEnums; void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags) { static std::once_flag initMap; - std::call_once(initMap, [](){ + std::call_once(initMap, [](){ ADD_PROPERTY_TO_MAP(PROP_VISIBLE, Visible, visible, bool); ADD_PROPERTY_TO_MAP(PROP_POSITION, Position, position, glm::vec3); ADD_PROPERTY_TO_MAP(PROP_DIMENSIONS, Dimensions, dimensions, glm::vec3); @@ -739,6 +762,14 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_Y_P_NEIGHBOR_ID, YPNeighborID, yPNeighborID, EntityItemID); ADD_PROPERTY_TO_MAP(PROP_Z_P_NEIGHBOR_ID, ZPNeighborID, zPNeighborID, EntityItemID); + ADD_PROPERTY_TO_MAP(PROP_PARENT_ID, ParentID, parentID, QUuid); + ADD_PROPERTY_TO_MAP(PROP_PARENT_JOINT_INDEX, ParentJointIndex, parentJointIndex, uint16_t); + + ADD_PROPERTY_TO_MAP(PROP_JOINT_ROTATIONS_SET, JointRotationsSet, jointRotationsSet, QVector); + ADD_PROPERTY_TO_MAP(PROP_JOINT_ROTATIONS, JointRotations, jointRotations, QVector); + ADD_PROPERTY_TO_MAP(PROP_JOINT_TRANSLATIONS_SET, JointTranslationsSet, jointTranslationsSet, QVector); + ADD_PROPERTY_TO_MAP(PROP_JOINT_TRANSLATIONS, JointTranslations, jointTranslations, QVector); + ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_URL, Animation, animation, URL, url); ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_FPS, Animation, animation, FPS, fps); ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_FRAME_INDEX, Animation, animation, CurrentFrame, currentFrame); @@ -922,6 +953,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, properties.getDescription()); APPEND_ENTITY_PROPERTY(PROP_PARENT_ID, properties.getParentID()); APPEND_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, properties.getParentJointIndex()); + APPEND_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, properties.getQueryAACube()); if (properties.getType() == EntityTypes::Web) { APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, properties.getSourceUrl()); @@ -943,6 +975,11 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem _staticAnimation.setProperties(properties); _staticAnimation.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + + APPEND_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS_SET, properties.getJointRotationsSet()); + APPEND_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS, properties.getJointRotations()); + APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, properties.getJointTranslationsSet()); + APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS, properties.getJointTranslations()); } if (properties.getType() == EntityTypes::Light) { @@ -986,7 +1023,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem if (properties.getType() == EntityTypes::Zone) { _staticKeyLight.setProperties(properties); _staticKeyLight.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); - + _staticStage.setProperties(properties); _staticStage.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); @@ -1021,7 +1058,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_LINE_WIDTH, properties.getLineWidth()); APPEND_ENTITY_PROPERTY(PROP_LINE_POINTS, properties.getLinePoints()); } - + if (properties.getType() == EntityTypes::PolyLine) { APPEND_ENTITY_PROPERTY(PROP_LINE_WIDTH, properties.getLineWidth()); APPEND_ENTITY_PROPERTY(PROP_LINE_POINTS, properties.getLinePoints()); @@ -1029,7 +1066,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_STROKE_WIDTHS, properties.getStrokeWidths()); APPEND_ENTITY_PROPERTY(PROP_TEXTURES, properties.getTextures()); } - + APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, properties.getMarketplaceID()); APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL()); @@ -1117,7 +1154,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int bytesToRead, int& processedBytes, EntityItemID& entityID, EntityItemProperties& properties) { bool valid = false; - + const unsigned char* dataAt = data; processedBytes = 0; @@ -1208,6 +1245,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DESCRIPTION, QString, setDescription); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARENT_ID, QUuid, setParentID); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARENT_JOINT_INDEX, quint16, setParentJointIndex); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_QUERY_AA_CUBE, AACube, setQueryAACube); if (properties.getType() == EntityTypes::Web) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOURCE_URL, QString, setSourceUrl); @@ -1228,6 +1266,11 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE_TYPE, ShapeType, setShapeType); properties.getAnimation().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_ROTATIONS_SET, QVector, setJointRotationsSet); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_ROTATIONS, QVector, setJointRotations); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_TRANSLATIONS_SET, QVector, setJointTranslationsSet); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_TRANSLATIONS, QVector, setJointTranslations); } if (properties.getType() == EntityTypes::Light) { @@ -1298,8 +1341,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_WIDTH, float, setLineWidth); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_POINTS, QVector, setLinePoints); } - - + + if (properties.getType() == EntityTypes::PolyLine) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_WIDTH, float, setLineWidth); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_POINTS, QVector, setLinePoints); @@ -1460,32 +1503,13 @@ void EntityItemProperties::markAllChanged() { _parentIDChanged = true; _parentJointIndexChanged = true; -} -/// The maximum bounding cube for the entity, independent of it's rotation. -/// This accounts for the registration point (upon which rotation occurs around). -/// -AACube EntityItemProperties::getMaximumAACube() const { - // * we know that the position is the center of rotation - glm::vec3 centerOfRotation = _position; // also where _registration point is + _jointRotationsSetChanged = true; + _jointRotationsChanged = true; + _jointTranslationsSetChanged = true; + _jointTranslationsChanged = true; - // * we know that the registration point is the center of rotation - // * we can calculate the length of the furthest extent from the registration point - // as the dimensions * max (registrationPoint, (1.0,1.0,1.0) - registrationPoint) - glm::vec3 registrationPoint = (_dimensions * _registrationPoint); - glm::vec3 registrationRemainder = (_dimensions * (glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint)); - glm::vec3 furthestExtentFromRegistration = glm::max(registrationPoint, registrationRemainder); - - // * we know that if you rotate in any direction you would create a sphere - // that has a radius of the length of furthest extent from registration point - float radius = glm::length(furthestExtentFromRegistration); - - // * we know that the minimum bounding cube of this maximum possible sphere is - // (center - radius) to (center + radius) - glm::vec3 minimumCorner = centerOfRotation - glm::vec3(radius, radius, radius); - float diameter = radius * 2.0f; - - return AACube(minimumCorner, diameter); + _queryAACubeChanged = true; } // The minimum bounding box for the entity. @@ -1785,6 +1809,21 @@ QList EntityItemProperties::listChangedProperties() { if (parentJointIndexChanged()) { out += "parentJointIndex"; } + if (jointRotationsSetChanged()) { + out += "jointRotationsSet"; + } + if (jointRotationsChanged()) { + out += "jointRotations"; + } + if (jointTranslationsSetChanged()) { + out += "jointTranslationsSet"; + } + if (jointTranslationsChanged()) { + out += "jointTranslations"; + } + if (queryAACubeChanged()) { + out += "queryAACube"; + } getAnimation().listChangedProperties(out); getKeyLight().listChangedProperties(out); @@ -1798,3 +1837,7 @@ QList EntityItemProperties::listChangedProperties() { bool EntityItemProperties::parentDependentPropertyChanged() const { return localPositionChanged() || positionChanged() || localRotationChanged() || rotationChanged(); } + +bool EntityItemProperties::parentRelatedPropertyChanged() const { + return parentDependentPropertyChanged() || parentIDChanged() || parentJointIndexChanged(); +} diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 5420e75aed..35e7bdfb78 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -84,8 +84,8 @@ public: EntityPropertyFlags getChangedProperties() const; bool parentDependentPropertyChanged() const; // was there a changed in a property that requires parent info to interpret? + bool parentRelatedPropertyChanged() const; // parentDependentPropertyChanged or parentID or parentJointIndex - AACube getMaximumAACube() const; AABox getAABox() const; void debugDump() const; @@ -192,12 +192,18 @@ public: DEFINE_PROPERTY_REF(PROP_Y_P_NEIGHBOR_ID, YPNeighborID, yPNeighborID, EntityItemID, UNKNOWN_ENTITY_ID); DEFINE_PROPERTY_REF(PROP_Z_P_NEIGHBOR_ID, ZPNeighborID, zPNeighborID, EntityItemID, UNKNOWN_ENTITY_ID); DEFINE_PROPERTY_REF(PROP_PARENT_ID, ParentID, parentID, QUuid, UNKNOWN_ENTITY_ID); - DEFINE_PROPERTY_REF(PROP_PARENT_JOINT_INDEX, ParentJointIndex, parentJointIndex, quint16, 0); + DEFINE_PROPERTY_REF(PROP_PARENT_JOINT_INDEX, ParentJointIndex, parentJointIndex, quint16, -1); + DEFINE_PROPERTY_REF(PROP_QUERY_AA_CUBE, QueryAACube, queryAACube, AACube, AACube()); // these are used when bouncing location data into and out of scripts DEFINE_PROPERTY_REF(PROP_LOCAL_POSITION, LocalPosition, localPosition, glmVec3, ENTITY_ITEM_ZERO_VEC3); DEFINE_PROPERTY_REF(PROP_LOCAL_ROTATION, LocalRotation, localRotation, glmQuat, ENTITY_ITEM_DEFAULT_ROTATION); + DEFINE_PROPERTY_REF(PROP_JOINT_ROTATIONS_SET, JointRotationsSet, jointRotationsSet, QVector, QVector()); + DEFINE_PROPERTY_REF(PROP_JOINT_ROTATIONS, JointRotations, jointRotations, QVector, QVector()); + DEFINE_PROPERTY_REF(PROP_JOINT_TRANSLATIONS_SET, JointTranslationsSet, jointTranslationsSet, QVector, QVector()); + DEFINE_PROPERTY_REF(PROP_JOINT_TRANSLATIONS, JointTranslations, jointTranslations, QVector, QVector()); + static QString getBackgroundModeString(BackgroundMode mode); @@ -260,10 +266,13 @@ public: void setActionDataDirty() { _actionDataChanged = true; } QList listChangedProperties(); - + bool getDimensionsInitialized() const { return _dimensionsInitialized; } void setDimensionsInitialized(bool dimensionsInitialized) { _dimensionsInitialized = dimensionsInitialized; } - + + void setJointRotationsDirty() { _jointRotationsSetChanged = true; _jointRotationsChanged = true; } + void setJointTranslationsDirty() { _jointTranslationsSetChanged = true; _jointTranslationsChanged = true; } + private: QUuid _id; bool _idSet; @@ -397,6 +406,12 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, ParentID, parentID, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, ParentJointIndex, parentJointIndex, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, QueryAACube, queryAACube, ""); + + DEBUG_PROPERTY_IF_CHANGED(debug, properties, JointRotationsSet, jointRotationsSet, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, JointRotations, jointRotations, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, JointTranslationsSet, jointTranslationsSet, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, JointTranslations, jointTranslations, ""); properties.getAnimation().debugDump(); properties.getAtmosphere().debugDump(); diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h index 304e6f672c..ad98ca7ba1 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -113,6 +113,8 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const xColor& v) { retu inline QScriptValue convertScriptValue(QScriptEngine* e, const glm::quat& v) { return quatToScriptValue(e, v); } inline QScriptValue convertScriptValue(QScriptEngine* e, const QScriptValue& v) { return v; } inline QScriptValue convertScriptValue(QScriptEngine* e, const QVector& v) {return qVectorVec3ToScriptValue(e, v); } +inline QScriptValue convertScriptValue(QScriptEngine* e, const QVector& v) {return qVectorQuatToScriptValue(e, v); } +inline QScriptValue convertScriptValue(QScriptEngine* e, const QVector& v) {return qVectorBoolToScriptValue(e, v); } inline QScriptValue convertScriptValue(QScriptEngine* e, const QVector& v) { return qVectorFloatToScriptValue(e, v); } inline QScriptValue convertScriptValue(QScriptEngine* e, const QByteArray& v) { @@ -122,6 +124,9 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const QByteArray& v) { inline QScriptValue convertScriptValue(QScriptEngine* e, const EntityItemID& v) { return QScriptValue(QUuid(v).toString()); } +inline QScriptValue convertScriptValue(QScriptEngine* e, const AACube& v) { return aaCubeToScriptValue(e, v); } + + #define COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(X,G,g,P,p) \ if ((desiredProperties.isEmpty() || desiredProperties.getHasProperty(X)) && \ @@ -173,6 +178,8 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const EntityItemID& v) typedef glm::vec3 glmVec3; typedef glm::quat glmQuat; typedef QVector qVectorVec3; +typedef QVector qVectorQuat; +typedef QVector qVectorBool; typedef QVector qVectorFloat; inline float float_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toFloat(&isValid); } inline quint64 quint64_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toULongLong(&isValid); } @@ -225,6 +232,13 @@ inline glmVec3 glmVec3_convertFromScriptValue(const QScriptValue& v, bool& isVal return glm::vec3(0); } +inline AACube AACube_convertFromScriptValue(const QScriptValue& v, bool& isValid) { + isValid = true; + AACube result; + aaCubeFromScriptValue(v, result); + return result; +} + inline qVectorFloat qVectorFloat_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = true; return qVectorFloatFromScriptValue(v); @@ -235,6 +249,16 @@ inline qVectorVec3 qVectorVec3_convertFromScriptValue(const QScriptValue& v, boo return qVectorVec3FromScriptValue(v); } +inline qVectorQuat qVectorQuat_convertFromScriptValue(const QScriptValue& v, bool& isValid) { + isValid = true; + return qVectorQuatFromScriptValue(v); +} + +inline qVectorBool qVectorBool_convertFromScriptValue(const QScriptValue& v, bool& isValid) { + isValid = true; + return qVectorBoolFromScriptValue(v); +} + inline glmQuat glmQuat_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = false; /// assume it can't be converted QScriptValue x = v.property("x"); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 2ba86a491e..c59ed7141b 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -157,6 +157,14 @@ enum EntityPropertyList { PROP_LOCAL_POSITION, // only used to convert values to and from scripts PROP_LOCAL_ROTATION, // only used to convert values to and from scripts + PROP_QUERY_AA_CUBE, // how the EntityTree considers the size and position on an entity + + // ModelEntity joint state + PROP_JOINT_ROTATIONS_SET, + PROP_JOINT_ROTATIONS, + PROP_JOINT_TRANSLATIONS_SET, + PROP_JOINT_TRANSLATIONS, + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 0746c2a824..4e2052c283 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -72,12 +72,15 @@ EntityItemProperties convertLocationToScriptSemantics(const EntityItemProperties scriptSideProperties.setLocalPosition(entitySideProperties.getPosition()); scriptSideProperties.setLocalRotation(entitySideProperties.getRotation()); + bool success; glm::vec3 worldPosition = SpatiallyNestable::localToWorld(entitySideProperties.getPosition(), entitySideProperties.getParentID(), - entitySideProperties.getParentJointIndex()); + entitySideProperties.getParentJointIndex(), + success); glm::quat worldRotation = SpatiallyNestable::localToWorld(entitySideProperties.getRotation(), entitySideProperties.getParentID(), - entitySideProperties.getParentJointIndex()); + entitySideProperties.getParentJointIndex(), + success); scriptSideProperties.setPosition(worldPosition); scriptSideProperties.setRotation(worldRotation); @@ -89,13 +92,15 @@ EntityItemProperties convertLocationFromScriptSemantics(const EntityItemProperti // convert position and rotation properties from world-space to local, unless localPosition and localRotation // are set. If they are set, they overwrite position and rotation. EntityItemProperties entitySideProperties = scriptSideProperties; + bool success; if (scriptSideProperties.localPositionChanged()) { entitySideProperties.setPosition(scriptSideProperties.getLocalPosition()); } else if (scriptSideProperties.positionChanged()) { glm::vec3 localPosition = SpatiallyNestable::worldToLocal(entitySideProperties.getPosition(), entitySideProperties.getParentID(), - entitySideProperties.getParentJointIndex()); + entitySideProperties.getParentJointIndex(), + success); entitySideProperties.setPosition(localPosition); } @@ -104,7 +109,8 @@ EntityItemProperties convertLocationFromScriptSemantics(const EntityItemProperti } else if (scriptSideProperties.rotationChanged()) { glm::quat localRotation = SpatiallyNestable::worldToLocal(entitySideProperties.getRotation(), entitySideProperties.getParentID(), - entitySideProperties.getParentJointIndex()); + entitySideProperties.getParentJointIndex(), + success); entitySideProperties.setRotation(localRotation); } @@ -128,6 +134,10 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties auto nodeList = DependencyManager::get(); const QUuid myNodeID = nodeList->getSessionUUID(); propertiesWithSimID.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); + if (propertiesWithSimID.parentRelatedPropertyChanged()) { + // due to parenting, the server may not know where something is in world-space, so include the bounding cube. + propertiesWithSimID.setQueryAACube(entity->getQueryAACube()); + } // and make note of it now, so we can act on it right away. entity->setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); @@ -193,16 +203,15 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& scriptSideProperties) { EntityItemProperties properties = scriptSideProperties; EntityItemID entityID(id); - // If we have a local entity tree set, then also update it. if (!_entityTree) { queueEntityMessage(PacketType::EntityEdit, entityID, properties); return id; } + // If we have a local entity tree set, then also update it. bool updatedEntity = false; _entityTree->withWriteLock([&] { - if (scriptSideProperties.parentDependentPropertyChanged() || - scriptSideProperties.parentIDChanged() || scriptSideProperties.parentJointIndexChanged()) { + if (scriptSideProperties.parentRelatedPropertyChanged()) { // All of parentID, parentJointIndex, position, rotation are needed to make sense of any of them. // If any of these changed, pull any missing properties from the entity. EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); @@ -265,7 +274,24 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& entity->flagForOwnership(); } } + if (properties.parentRelatedPropertyChanged() && entity->computePuffedQueryAACube()) { + properties.setQueryAACube(entity->getQueryAACube()); + } entity->setLastBroadcast(usecTimestampNow()); + + // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server + // if they've changed. + entity->forEachDescendant([&](SpatiallyNestablePointer descendant) { + if (descendant->getNestableType() == NestableType::Entity) { + if (descendant->computePuffedQueryAACube()) { + EntityItemPointer entityDescendant = std::static_pointer_cast(descendant); + EntityItemProperties newQueryCubeProperties; + newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); + queueEntityMessage(PacketType::EntityEdit, descendant->getID(), newQueryCubeProperties); + entityDescendant->setLastBroadcast(usecTimestampNow()); + } + } + }); } }); queueEntityMessage(PacketType::EntityEdit, entityID, properties); @@ -828,3 +854,113 @@ glm::quat EntityScriptingInterface::getAbsoluteJointRotationInObjectFrame(const return glm::quat(); } } + +bool EntityScriptingInterface::setAbsoluteJointTranslationInObjectFrame(const QUuid& entityID, + int jointIndex, glm::vec3 translation) { + if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Model)) { + auto now = usecTimestampNow(); + auto modelEntity = std::dynamic_pointer_cast(entity); + bool result = modelEntity->setAbsoluteJointTranslationInObjectFrame(jointIndex, translation); + if (result) { + EntityItemProperties properties; + _entityTree->withWriteLock([&] { + properties = entity->getProperties(); + entity->setLastBroadcast(now); + }); + + properties.setJointTranslationsDirty(); + properties.setLastEdited(now); + queueEntityMessage(PacketType::EntityEdit, entityID, properties); + return true; + } + } + return false; +} + +bool EntityScriptingInterface::setAbsoluteJointRotationInObjectFrame(const QUuid& entityID, + int jointIndex, glm::quat rotation) { + if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Model)) { + auto now = usecTimestampNow(); + auto modelEntity = std::dynamic_pointer_cast(entity); + bool result = modelEntity->setAbsoluteJointRotationInObjectFrame(jointIndex, rotation); + if (result) { + EntityItemProperties properties; + _entityTree->withWriteLock([&] { + properties = entity->getProperties(); + entity->setLastBroadcast(now); + }); + + properties.setJointRotationsDirty(); + properties.setLastEdited(now); + queueEntityMessage(PacketType::EntityEdit, entityID, properties); + return true; + } + } + return false; +} + + + +bool EntityScriptingInterface::setAbsoluteJointRotationsInObjectFrame(const QUuid& entityID, + const QVector& rotations) { + if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Model)) { + auto now = usecTimestampNow(); + auto modelEntity = std::dynamic_pointer_cast(entity); + + bool result = false; + for (int index = 0; index < rotations.size(); index++) { + result |= modelEntity->setAbsoluteJointRotationInObjectFrame(index, rotations[index]); + } + if (result) { + EntityItemProperties properties; + _entityTree->withWriteLock([&] { + entity->setLastEdited(now); + entity->setLastBroadcast(now); + properties = entity->getProperties(); + }); + + properties.setJointRotationsDirty(); + properties.setLastEdited(now); + queueEntityMessage(PacketType::EntityEdit, entityID, properties); + return true; + } + } + return false; +} + + +bool EntityScriptingInterface::setAbsoluteJointTranslationsInObjectFrame(const QUuid& entityID, + const QVector& translations) { + if (auto entity = checkForTreeEntityAndTypeMatch(entityID, EntityTypes::Model)) { + auto now = usecTimestampNow(); + auto modelEntity = std::dynamic_pointer_cast(entity); + + bool result = false; + for (int index = 0; index < translations.size(); index++) { + result |= modelEntity->setAbsoluteJointTranslationInObjectFrame(index, translations[index]); + } + if (result) { + EntityItemProperties properties; + _entityTree->withWriteLock([&] { + entity->setLastEdited(now); + entity->setLastBroadcast(now); + properties = entity->getProperties(); + }); + + properties.setJointTranslationsDirty(); + properties.setLastEdited(now); + queueEntityMessage(PacketType::EntityEdit, entityID, properties); + return true; + } + } + return false; +} + + +bool EntityScriptingInterface::setAbsoluteJointsDataInObjectFrame(const QUuid& entityID, + const QVector& rotations, + const QVector& translations) { + // for a model with 80 joints, sending both these in one edit packet causes the packet to be too large. + return setAbsoluteJointRotationsInObjectFrame(entityID, rotations) || + setAbsoluteJointTranslationsInObjectFrame(entityID, translations); +} diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index d08a1b7e36..5c22d7f6b8 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -151,6 +151,15 @@ public slots: Q_INVOKABLE glm::vec3 getAbsoluteJointTranslationInObjectFrame(const QUuid& entityID, int jointIndex); Q_INVOKABLE glm::quat getAbsoluteJointRotationInObjectFrame(const QUuid& entityID, int jointIndex); + Q_INVOKABLE bool setAbsoluteJointTranslationInObjectFrame(const QUuid& entityID, int jointIndex, glm::vec3 translation); + Q_INVOKABLE bool setAbsoluteJointRotationInObjectFrame(const QUuid& entityID, int jointIndex, glm::quat rotation); + Q_INVOKABLE bool setAbsoluteJointRotationsInObjectFrame(const QUuid& entityID, + const QVector& rotations); + Q_INVOKABLE bool setAbsoluteJointTranslationsInObjectFrame(const QUuid& entityID, + const QVector& translations); + Q_INVOKABLE bool setAbsoluteJointsDataInObjectFrame(const QUuid& entityID, + const QVector& rotations, + const QVector& translations); signals: void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index 70b973e672..ab29cfb2a4 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -38,15 +38,35 @@ void EntitySimulation::updateEntities() { sortEntitiesThatMoved(); } -void EntitySimulation::getEntitiesToDelete(VectorOfEntities& entitiesToDelete) { +void EntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) { QMutexLocker lock(&_mutex); for (auto entity : _entitiesToDelete) { - // this entity is still in its tree, so we insert into the external list + // push this entity onto the external list entitiesToDelete.push_back(entity); } _entitiesToDelete.clear(); } +void EntitySimulation::removeEntityInternal(EntityItemPointer entity) { + // remove from all internal lists except _entitiesToDelete + _mortalEntities.remove(entity); + _entitiesToUpdate.remove(entity); + _entitiesToSort.remove(entity); + _simpleKinematicEntities.remove(entity); + _allEntities.remove(entity); + entity->setSimulated(false); +} + +void EntitySimulation::prepareEntityForDelete(EntityItemPointer entity) { + assert(entity); + assert(entity->isDead()); + if (entity->isSimulated()) { + entity->clearActions(this); + removeEntityInternal(entity); + _entitiesToDelete.insert(entity); + } +} + void EntitySimulation::addEntityInternal(EntityItemPointer entity) { if (entity->isMoving() && !entity->getPhysicsInfo()) { _simpleKinematicEntities.insert(entity); @@ -71,15 +91,9 @@ void EntitySimulation::expireMortalEntities(const quint64& now) { EntityItemPointer entity = *itemItr; quint64 expiry = entity->getExpiry(); if (expiry < now) { - _entitiesToDelete.insert(entity); itemItr = _mortalEntities.erase(itemItr); - _entitiesToUpdate.remove(entity); - _entitiesToSort.remove(entity); - _simpleKinematicEntities.remove(entity); - removeEntityInternal(entity); - - _allEntities.remove(entity); - entity->_simulated = false; + entity->die(); + prepareEntityForDelete(entity); } else { if (expiry < _nextExpiry) { // remeber the smallest _nextExpiry so we know when to start the next search @@ -97,7 +111,7 @@ void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) { SetOfEntities::iterator itemItr = _entitiesToUpdate.begin(); while (itemItr != _entitiesToUpdate.end()) { EntityItemPointer entity = *itemItr; - // TODO: catch transition from needing update to not as a "change" + // TODO: catch transition from needing update to not as a "change" // so we don't have to scan for it here. if (!entity->needsToCallUpdate()) { itemItr = _entitiesToUpdate.erase(itemItr); @@ -119,19 +133,13 @@ void EntitySimulation::sortEntitiesThatMoved() { while (itemItr != _entitiesToSort.end()) { EntityItemPointer entity = *itemItr; // check to see if this movement has sent the entity outside of the domain. - AACube newCube = entity->getMaximumAACube(); - if (!domainBounds.touches(newCube)) { + bool success; + AACube newCube = entity->getQueryAACube(success); + if (success && !domainBounds.touches(newCube)) { qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; - _entitiesToDelete.insert(entity); - _mortalEntities.remove(entity); - _entitiesToUpdate.remove(entity); - _simpleKinematicEntities.remove(entity); - removeEntityInternal(entity); - - _allEntities.remove(entity); - entity->_simulated = false; - itemItr = _entitiesToSort.erase(itemItr); + entity->die(); + prepareEntityForDelete(entity); } else { moveOperator.addEntityToMoveList(entity, newCube); ++itemItr; @@ -162,54 +170,36 @@ void EntitySimulation::addEntity(EntityItemPointer entity) { addEntityInternal(entity); _allEntities.insert(entity); - entity->_simulated = true; + entity->setSimulated(true); - // DirtyFlags are used to signal changes to entities that have already been added, + // DirtyFlags are used to signal changes to entities that have already been added, // so we can clear them for this entity which has just been added. entity->clearDirtyFlags(); } -void EntitySimulation::removeEntity(EntityItemPointer entity) { - QMutexLocker lock(&_mutex); - assert(entity); - _entitiesToUpdate.remove(entity); - _mortalEntities.remove(entity); - _entitiesToSort.remove(entity); - _simpleKinematicEntities.remove(entity); - _entitiesToDelete.remove(entity); - removeEntityInternal(entity); - - _allEntities.remove(entity); - entity->_simulated = false; -} - void EntitySimulation::changeEntity(EntityItemPointer entity) { QMutexLocker lock(&_mutex); assert(entity); - if (!entity->_simulated) { + if (!entity->isSimulated()) { // This entity was either never added to the simulation or has been removed - // (probably for pending delete), so we don't want to keep a pointer to it + // (probably for pending delete), so we don't want to keep a pointer to it // on any internal lists. return; } // Although it is not the responsibility of the EntitySimulation to sort the tree for EXTERNAL changes - // it IS responsibile for triggering deletes for entities that leave the bounds of the domain, hence + // it IS responsibile for triggering deletes for entities that leave the bounds of the domain, hence // we must check for that case here, however we rely on the change event to have set DIRTY_POSITION flag. bool wasRemoved = false; uint32_t dirtyFlags = entity->getDirtyFlags(); if (dirtyFlags & Simulation::DIRTY_POSITION) { AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE), (float)TREE_SCALE); - AACube newCube = entity->getMaximumAACube(); - if (!domainBounds.touches(newCube)) { + bool success; + AACube newCube = entity->getQueryAACube(success); + if (success && !domainBounds.touches(newCube)) { qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; - _entitiesToDelete.insert(entity); - _mortalEntities.remove(entity); - _entitiesToUpdate.remove(entity); - _entitiesToSort.remove(entity); - _simpleKinematicEntities.remove(entity); - removeEntityInternal(entity); - entity->_simulated = false; + entity->die(); + prepareEntityForDelete(entity); wasRemoved = true; } } @@ -242,14 +232,15 @@ void EntitySimulation::clearEntities() { _entitiesToUpdate.clear(); _entitiesToSort.clear(); _simpleKinematicEntities.clear(); - _entitiesToDelete.clear(); clearEntitiesInternal(); - for (auto entityItr : _allEntities) { - entityItr->_simulated = false; + for (auto entity : _allEntities) { + entity->setSimulated(false); + entity->die(); } _allEntities.clear(); + _entitiesToDelete.clear(); } void EntitySimulation::moveSimpleKinematics(const quint64& now) { diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h index 4519effbd3..442ff4a74b 100644 --- a/libraries/entities/src/EntitySimulation.h +++ b/libraries/entities/src/EntitySimulation.h @@ -63,25 +63,20 @@ public: /// \sideeffect sets relevant backpointers in entity, but maybe later when appropriate data structures are locked void addEntity(EntityItemPointer entity); - /// \param entity pointer to EntityItem to be removed - /// \brief the actual removal may happen later when appropriate data structures are locked - /// \sideeffect nulls relevant backpointers in entity - void removeEntity(EntityItemPointer entity); - - /// \param entity pointer to EntityItem to that may have changed in a way that would affect its simulation + /// \param entity pointer to EntityItem that may have changed in a way that would affect its simulation /// call this whenever an entity was changed from some EXTERNAL event (NOT by the EntitySimulation itself) void changeEntity(EntityItemPointer entity); void clearEntities(); void moveSimpleKinematics(const quint64& now); -protected: // these only called by the EntityTree? - -public: EntityTreePointer getEntityTree() { return _entityTree; } - void getEntitiesToDelete(VectorOfEntities& entitiesToDelete); + virtual void takeEntitiesToDelete(VectorOfEntities& entitiesToDelete); + + /// \param entity pointer to EntityItem that needs to be put on the entitiesToDelete list and removed from others. + virtual void prepareEntityForDelete(EntityItemPointer entity); signals: void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); @@ -106,6 +101,9 @@ protected: QList _actionsToAdd; QSet _actionsToRemove; +protected: + SetOfEntities _entitiesToDelete; // entities simulation decided needed to be deleted (EntityTree will actually delete) + private: void moveSimpleKinematics(); @@ -120,7 +118,6 @@ private: SetOfEntities _entitiesToUpdate; // entities that need to call EntityItem::update() - SetOfEntities _entitiesToDelete; // entities simulation decided needed to be deleted (EntityTree will actually delete) }; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index f0a03623c2..9861252d63 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -89,6 +89,9 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { _isDirty = true; maybeNotifyNewCollisionSoundURL("", entity->getCollisionSoundURL()); emit addingEntity(entity->getEntityItemID()); + + // find and hook up any entities with this entity as a (previously) missing parent + fixupMissingParents(); } bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode) { @@ -142,8 +145,12 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI EntityItemProperties tempProperties; tempProperties.setLocked(wantsLocked); - BoundingBoxRelatedProperties newBBRelProperties(entity, tempProperties); - UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, newBBRelProperties); + bool success; + AACube queryCube = entity->getQueryAACube(success); + if (!success) { + qCDebug(entities) << "Warning -- failed to get query-cube for" << entity->getID(); + } + UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, queryCube); recurseTreeWithOperator(&theOperator); entity->setProperties(tempProperties); _isDirty = true; @@ -211,8 +218,7 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI QString collisionSoundURLBefore = entity->getCollisionSoundURL(); uint32_t preFlags = entity->getDirtyFlags(); - BoundingBoxRelatedProperties newBBRelProperties(entity, properties); - UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, newBBRelProperties); + UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, properties.getQueryAACube()); recurseTreeWithOperator(&theOperator); entity->setProperties(properties); @@ -229,14 +235,19 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI if (!childEntity) { continue; } - BoundingBoxRelatedProperties newChildBBRelProperties(childEntity); EntityTreeElementPointer containingElement = childEntity->getElement(); if (!containingElement) { continue; } - UpdateEntityOperator theChildOperator(getThisPointer(), - containingElement, - childEntity, newChildBBRelProperties); + + bool success; + AACube queryCube = childEntity->getQueryAACube(success); + if (!success) { + _missingParent.append(childEntity); + continue; + } + + UpdateEntityOperator theChildOperator(getThisPointer(), containingElement, childEntity, queryCube); recurseTreeWithOperator(&theChildOperator); foreach (SpatiallyNestablePointer childChild, childEntity->getChildren()) { if (childChild && childChild->getNestableType() == NestableType::Entity) { @@ -316,6 +327,11 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti // Recurse the tree and store the entity in the correct tree element AddEntityOperator theOperator(getThisPointer(), result); recurseTreeWithOperator(&theOperator); + if (result->getAncestorMissing()) { + // we added the entity, but didn't know about all its ancestors, so it went into the wrong place. + // add it to a list of entities needing to be fixed once their parents are known. + _missingParent.append(result); + } postAddEntity(result); } @@ -426,6 +442,7 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) const RemovedEntities& entities = theOperator.getEntities(); foreach(const EntityToDeleteDetails& details, entities) { EntityItemPointer theEntity = details.entity; + theEntity->die(); if (getIsServer()) { // set up the deleted entities ID @@ -437,8 +454,7 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) } if (_simulation) { - theEntity->clearActions(_simulation); - _simulation->removeEntity(theEntity); + _simulation->prepareEntityForDelete(theEntity); } } } @@ -757,6 +773,42 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList= 0) { + QUuid value = properties.getParentID(); + changedProperties[index] = QString("parentID:") + value.toString(); + } + } + + if (properties.jointRotationsSetChanged()) { + int index = changedProperties.indexOf("jointRotationsSet"); + if (index >= 0) { + auto value = properties.getJointRotationsSet().size(); + changedProperties[index] = QString("jointRotationsSet:") + QString::number((int)value); + } + } + if (properties.jointRotationsChanged()) { + int index = changedProperties.indexOf("jointRotations"); + if (index >= 0) { + auto value = properties.getJointRotations().size(); + changedProperties[index] = QString("jointRotations:") + QString::number((int)value); + } + } + if (properties.jointTranslationsSetChanged()) { + int index = changedProperties.indexOf("jointTranslationsSet"); + if (index >= 0) { + auto value = properties.getJointTranslationsSet().size(); + changedProperties[index] = QString("jointTranslationsSet:") + QString::number((int)value); + } + } + if (properties.jointTranslationsChanged()) { + int index = changedProperties.indexOf("jointTranslations"); + if (index >= 0) { + auto value = properties.getJointTranslations().size(); + changedProperties[index] = QString("jointTranslations:") + QString::number((int)value); + } + } } int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, @@ -918,12 +970,43 @@ void EntityTree::entityChanged(EntityItemPointer entity) { } } +void EntityTree::fixupMissingParents() { + MovingEntitiesOperator moveOperator(getThisPointer()); + + QMutableVectorIterator iter(_missingParent); + while (iter.hasNext()) { + EntityItemWeakPointer entityWP = iter.next(); + EntityItemPointer entity = entityWP.lock(); + if (entity) { + bool success; + AACube newCube = entity->getQueryAACube(success); + if (success) { + // this entity's parent (or ancestry) was previously not fully known, and now is. Update its + // location in the EntityTree. + moveOperator.addEntityToMoveList(entity, newCube); + iter.remove(); + entity->markAncestorMissing(false); + } + } else { + // entity was deleted before we found its parent. + iter.remove(); + } + } + + if (moveOperator.hasMovingEntities()) { + PerformanceTimer perfTimer("recurseTreeWithOperator"); + recurseTreeWithOperator(&moveOperator); + } + +} + void EntityTree::update() { + fixupMissingParents(); if (_simulation) { withWriteLock([&] { _simulation->updateEntities(); VectorOfEntities pendingDeletes; - _simulation->getEntitiesToDelete(pendingDeletes); + _simulation->takeEntitiesToDelete(pendingDeletes); if (pendingDeletes.size() > 0) { // translate into list of ID's diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index f68e2d59e9..0f77c4af9a 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -307,6 +307,9 @@ protected: quint64 _totalEditDeltas = 0; quint64 _maxEditDelta = 0; quint64 _treeResetTime = 0; + + void fixupMissingParents(); + QVector _missingParent; }; #endif // hifi_EntityTree_h diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 8944c95cbc..2320004be8 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -302,8 +302,9 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData // simulation changing what's visible. consider the case where the entity contains an angular velocity // the entity may not be in view and then in view a frame later, let the client side handle it's view // frustum culling on rendering. - AACube entityCube = entity->getMaximumAACube(); - if (params.viewFrustum->cubeInFrustum(entityCube) == ViewFrustum::OUTSIDE) { + bool success; + AACube entityCube = entity->getQueryAACube(success); + if (!success || params.viewFrustum->cubeInFrustum(entityCube) == ViewFrustum::OUTSIDE) { includeThisEntity = false; // out of view, don't include it } } @@ -413,19 +414,29 @@ OctreeElement::AppendState EntityTreeElement::appendElementData(OctreePacketData } bool EntityTreeElement::containsEntityBounds(EntityItemPointer entity) const { - return containsBounds(entity->getMaximumAACube()); + bool success; + auto queryCube = entity->getQueryAACube(success); + if (!success) { + return false; + } + return containsBounds(queryCube); } bool EntityTreeElement::bestFitEntityBounds(EntityItemPointer entity) const { - return bestFitBounds(entity->getMaximumAACube()); + bool success; + auto queryCube = entity->getQueryAACube(success); + if (!success) { + return false; + } + return bestFitBounds(queryCube); } bool EntityTreeElement::containsBounds(const EntityItemProperties& properties) const { - return containsBounds(properties.getMaximumAACube()); + return containsBounds(properties.getQueryAACube()); } bool EntityTreeElement::bestFitBounds(const EntityItemProperties& properties) const { - return bestFitBounds(properties.getMaximumAACube()); + return bestFitBounds(properties.getQueryAACube()); } bool EntityTreeElement::containsBounds(const AACube& bounds) const { @@ -526,7 +537,12 @@ bool EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, con return; } - AABox entityBox = entity->getAABox(); + bool success; + AABox entityBox = entity->getAABox(success); + if (!success) { + return; + } + float localDistance; BoxFace localFace; glm::vec3 localSurfaceNormal; @@ -631,11 +647,12 @@ EntityItemPointer EntityTreeElement::getClosestEntity(glm::vec3 position) const void EntityTreeElement::getEntities(const glm::vec3& searchPosition, float searchRadius, QVector& foundEntities) const { forEachEntity([&](EntityItemPointer entity) { - AABox entityBox = entity->getAABox(); + bool success; + AABox entityBox = entity->getAABox(success); // if the sphere doesn't intersect with our world frame AABox, we don't need to consider the more complex case glm::vec3 penetration; - if (entityBox.findSpherePenetration(searchPosition, searchRadius, penetration)) { + if (success && entityBox.findSpherePenetration(searchPosition, searchRadius, penetration)) { glm::vec3 dimensions = entity->getDimensions(); @@ -651,9 +668,12 @@ void EntityTreeElement::getEntities(const glm::vec3& searchPosition, float searc // maximum bounding sphere, which is actually larger than our actual radius float entityTrueRadius = dimensions.x / 2.0f; - if (findSphereSpherePenetration(searchPosition, searchRadius, - entity->getCenterPosition(), entityTrueRadius, penetration)) { - foundEntities.push_back(entity); + bool success; + if (findSphereSpherePenetration(searchPosition, searchRadius, + entity->getCenterPosition(success), entityTrueRadius, penetration)) { + if (success) { + foundEntities.push_back(entity); + } } } else { // determine the worldToEntityMatrix that doesn't include scale because @@ -679,7 +699,8 @@ void EntityTreeElement::getEntities(const glm::vec3& searchPosition, float searc void EntityTreeElement::getEntities(const AACube& cube, QVector& foundEntities) { forEachEntity([&](EntityItemPointer entity) { - AABox entityBox = entity->getAABox(); + bool success; + AABox entityBox = entity->getAABox(success); // FIXME - handle entity->getShapeType() == SHAPE_TYPE_SPHERE case better // FIXME - consider allowing the entity to determine penetration so that // entities could presumably dull actuall hull testing if they wanted to @@ -693,10 +714,10 @@ void EntityTreeElement::getEntities(const AACube& cube, QVector& foundEntities) { forEachEntity([&](EntityItemPointer entity) { - AABox entityBox = entity->getAABox(); + bool success; + AABox entityBox = entity->getAABox(success); // FIXME - handle entity->getShapeType() == SHAPE_TYPE_SPHERE case better // FIXME - consider allowing the entity to determine penetration so that // entities could presumably dull actuall hull testing if they wanted to @@ -718,10 +740,10 @@ void EntityTreeElement::getEntities(const AABox& box, QVector // test the triangles of the face against the box? // if translated search face triangle intersect target box // add to result - // + // // If the entities AABox touches the search cube then consider it to be found - if (entityBox.touches(box)) { + if (success && entityBox.touches(box)) { foundEntities.push_back(entity); } }); @@ -940,7 +962,11 @@ bool EntityTreeElement::pruneChildren() { void EntityTreeElement::expandExtentsToContents(Extents& extents) { withReadLock([&] { foreach(EntityItemPointer entity, _entityItems) { - extents.add(entity->getAABox()); + bool success; + AABox aaBox = entity->getAABox(success); + if (success) { + extents.add(aaBox); + } } }); } diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index a1f16ee251..bef4406f71 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -50,6 +50,11 @@ EntityItemProperties ModelEntityItem::getProperties(EntityPropertyFlags desiredP COPY_ENTITY_PROPERTY_TO_PROPERTIES(glowLevel, getGlowLevel); COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointRotationsSet, getJointRotationsSet); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointRotations, getJointRotations); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointTranslationsSet, getJointTranslationsSet); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointTranslations, getJointTranslations); + _animationProperties.getProperties(properties); return properties; } @@ -63,6 +68,10 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, updateShapeType); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotationsSet, setJointRotationsSet); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotations, setJointRotations); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslationsSet, setJointTranslationsSet); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslations, setJointTranslations); bool somethingChangedInAnimations = _animationProperties.setProperties(properties); @@ -133,6 +142,11 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, somethingChanged = true; } + READ_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS_SET, QVector, setJointRotationsSet); + READ_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS, QVector, setJointRotations); + READ_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, QVector, setJointTranslationsSet); + READ_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS, QVector, setJointTranslations); + return bytesRead; } @@ -145,6 +159,10 @@ EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& requestedProperties += PROP_TEXTURES; requestedProperties += PROP_SHAPE_TYPE; requestedProperties += _animationProperties.getEntityProperties(params); + requestedProperties += PROP_JOINT_ROTATIONS_SET; + requestedProperties += PROP_JOINT_ROTATIONS; + requestedProperties += PROP_JOINT_TRANSLATIONS_SET; + requestedProperties += PROP_JOINT_TRANSLATIONS; return requestedProperties; } @@ -168,6 +186,11 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit propertyFlags, propertiesDidntFit, propertyCount, appendState); APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType()); + + APPEND_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS_SET, getJointRotationsSet()); + APPEND_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS, getJointRotations()); + APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, getJointTranslationsSet()); + APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS, getJointTranslations()); } @@ -214,62 +237,6 @@ void ModelEntityItem::mapJoints(const QStringList& modelJointNames) { } } -void ModelEntityItem::getAnimationFrame(bool& newFrame, - QVector& rotationsResult, QVector& translationsResult) { - newFrame = false; - - if (!hasAnimation() || !_jointMappingCompleted) { - rotationsResult = _lastKnownFrameDataRotations; - translationsResult = _lastKnownFrameDataTranslations; - } - AnimationPointer myAnimation = getAnimation(_animationProperties.getURL()); // FIXME: this could be optimized - if (myAnimation && myAnimation->isLoaded()) { - - const QVector& frames = myAnimation->getFramesReference(); // NOTE: getFrames() is too heavy - auto& fbxJoints = myAnimation->getGeometry().joints; - - int frameCount = frames.size(); - if (frameCount > 0) { - int animationCurrentFrame = (int)(glm::floor(getAnimationCurrentFrame())) % frameCount; - if (animationCurrentFrame < 0 || animationCurrentFrame > frameCount) { - animationCurrentFrame = 0; - } - - if (animationCurrentFrame != _lastKnownCurrentFrame) { - _lastKnownCurrentFrame = animationCurrentFrame; - newFrame = true; - - const QVector& rotations = frames[animationCurrentFrame].rotations; - const QVector& translations = frames[animationCurrentFrame].translations; - - _lastKnownFrameDataRotations.resize(_jointMapping.size()); - _lastKnownFrameDataTranslations.resize(_jointMapping.size()); - - for (int j = 0; j < _jointMapping.size(); j++) { - int index = _jointMapping[j]; - if (index >= 0) { - glm::mat4 translationMat; - if (index < translations.size()) { - translationMat = glm::translate(translations[index]); - } - glm::mat4 rotationMat; - if (index < rotations.size()) { - rotationMat = glm::mat4_cast(rotations[index]); - } - glm::mat4 finalMat = (translationMat * fbxJoints[index].preTransform * - rotationMat * fbxJoints[index].postTransform); - _lastKnownFrameDataTranslations[j] = extractTranslation(finalMat); - _lastKnownFrameDataRotations[j] = glmExtractRotation(finalMat); - } - } - } - } - } - - rotationsResult = _lastKnownFrameDataRotations; - translationsResult = _lastKnownFrameDataTranslations; -} - bool ModelEntityItem::isAnimatingSomething() const { return getAnimationIsPlaying() && getAnimationFPS() != 0.0f && @@ -410,5 +377,90 @@ void ModelEntityItem::setAnimationFPS(float value) { // virtual bool ModelEntityItem::shouldBePhysical() const { - return getShapeType() != SHAPE_TYPE_NONE; + return !isDead() && getShapeType() != SHAPE_TYPE_NONE; +} + +void ModelEntityItem::resizeJointArrays(int newSize) { + if (newSize >= 0 && newSize > _absoluteJointRotationsInObjectFrame.size()) { + _absoluteJointRotationsInObjectFrame.resize(newSize); + _absoluteJointRotationsInObjectFrameSet.resize(newSize); + _absoluteJointRotationsInObjectFrameDirty.resize(newSize); + _absoluteJointTranslationsInObjectFrame.resize(newSize); + _absoluteJointTranslationsInObjectFrameSet.resize(newSize); + _absoluteJointTranslationsInObjectFrameDirty.resize(newSize); + } +} + +void ModelEntityItem::setJointRotations(const QVector& rotations) { + _jointDataLock.withWriteLock([&] { + resizeJointArrays(rotations.size()); + for (int index = 0; index < rotations.size(); index++) { + if (_absoluteJointRotationsInObjectFrameSet[index]) { + _absoluteJointRotationsInObjectFrame[index] = rotations[index]; + _absoluteJointRotationsInObjectFrameDirty[index] = true; + } + } + }); +} + +void ModelEntityItem::setJointRotationsSet(const QVector& rotationsSet) { + _jointDataLock.withWriteLock([&] { + resizeJointArrays(rotationsSet.size()); + for (int index = 0; index < rotationsSet.size(); index++) { + _absoluteJointRotationsInObjectFrameSet[index] = rotationsSet[index]; + } + }); +} + +void ModelEntityItem::setJointTranslations(const QVector& translations) { + _jointDataLock.withWriteLock([&] { + resizeJointArrays(translations.size()); + for (int index = 0; index < translations.size(); index++) { + if (_absoluteJointTranslationsInObjectFrameSet[index]) { + _absoluteJointTranslationsInObjectFrame[index] = translations[index]; + _absoluteJointTranslationsInObjectFrameSet[index] = true; + } + } + }); +} + +void ModelEntityItem::setJointTranslationsSet(const QVector& translationsSet) { + _jointDataLock.withWriteLock([&] { + resizeJointArrays(translationsSet.size()); + for (int index = 0; index < translationsSet.size(); index++) { + _absoluteJointTranslationsInObjectFrameSet[index] = translationsSet[index]; + } + }); +} + +QVector ModelEntityItem::getJointRotations() const { + QVector result; + _jointDataLock.withReadLock([&] { + result = _absoluteJointRotationsInObjectFrame; + }); + return result; +} + +QVector ModelEntityItem::getJointRotationsSet() const { + QVector result; + _jointDataLock.withReadLock([&] { + result = _absoluteJointRotationsInObjectFrameSet; + }); + return result; +} + +QVector ModelEntityItem::getJointTranslations() const { + QVector result; + _jointDataLock.withReadLock([&] { + result = _absoluteJointTranslationsInObjectFrame; + }); + return result; +} + +QVector ModelEntityItem::getJointTranslationsSet() const { + QVector result; + _jointDataLock.withReadLock([&] { + result = _absoluteJointTranslationsInObjectFrameSet; + }); + return result; } diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index b08fed5970..686fb1f72d 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -32,16 +32,16 @@ public: // TODO: eventually only include properties changed since the params.lastViewFrustumSent time virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; - virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData, EntityPropertyFlags& requestedProperties, EntityPropertyFlags& propertyFlags, EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, + int& propertyCount, OctreeElement::AppendState& appendState) const; - virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged); @@ -92,20 +92,19 @@ public: void setAnimationLoop(bool loop) { _animationLoop.setLoop(loop); } bool getAnimationLoop() const { return _animationLoop.getLoop(); } - + void setAnimationHold(bool hold) { _animationLoop.setHold(hold); } bool getAnimationHold() const { return _animationLoop.getHold(); } - + void setAnimationFirstFrame(float firstFrame) { _animationLoop.setFirstFrame(firstFrame); } float getAnimationFirstFrame() const { return _animationLoop.getFirstFrame(); } - + void setAnimationLastFrame(float lastFrame) { _animationLoop.setLastFrame(lastFrame); } float getAnimationLastFrame() const { return _animationLoop.getLastFrame(); } - + void mapJoints(const QStringList& modelJointNames); - void getAnimationFrame(bool& newFrame, QVector& rotationsResult, QVector& translationsResult); bool jointsMapped() const { return _jointMappingURL == getAnimationURL() && _jointMappingCompleted; } - + bool getAnimationIsPlaying() const { return _animationLoop.getRunning(); } float getAnimationCurrentFrame() const { return _animationLoop.getCurrentFrame(); } float getAnimationFPS() const { return _animationLoop.getFPS(); } @@ -115,20 +114,40 @@ public: void setTextures(const QString& textures) { _textures = textures; } virtual bool shouldBePhysical() const; - + static void cleanupLoadedAnimations(); - + virtual glm::vec3 getJointPosition(int jointIndex) const { return glm::vec3(); } virtual glm::quat getJointRotation(int jointIndex) const { return glm::quat(); } + void setJointRotations(const QVector& rotations); + void setJointRotationsSet(const QVector& rotationsSet); + void setJointTranslations(const QVector& translations); + void setJointTranslationsSet(const QVector& translationsSet); + QVector getJointRotations() const; + QVector getJointRotationsSet() const; + QVector getJointTranslations() const; + QVector getJointTranslationsSet() const; + private: void setAnimationSettings(const QString& value); // only called for old bitstream format protected: - QVector _lastKnownFrameDataRotations; - QVector _lastKnownFrameDataTranslations; + // these are used: + // - to bounce joint data from an animation into the model/rig. + // - to relay changes from scripts to model/rig. + // - to relay between network and model/rig + // they aren't currently updated from data in the model/rig, and they don't have a direct effect + // on what's rendered. + ReadWriteLockable _jointDataLock; + QVector _absoluteJointRotationsInObjectFrame; + QVector _absoluteJointRotationsInObjectFrameSet; // ever set? + QVector _absoluteJointRotationsInObjectFrameDirty; // needs a relay to model/rig? + QVector _absoluteJointTranslationsInObjectFrame; + QVector _absoluteJointTranslationsInObjectFrameSet; // ever set? + QVector _absoluteJointTranslationsInObjectFrameDirty; // needs a relay to model/rig? int _lastKnownCurrentFrame; - + virtual void resizeJointArrays(int newSize = -1); bool isAnimatingSomething() const; @@ -145,7 +164,7 @@ protected: // used on client side bool _jointMappingCompleted; - QVector _jointMapping; + QVector _jointMapping; // domain is index into model-joints, range is index into animation-joints QString _jointMappingURL; static AnimationPointer getAnimation(const QString& url); diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index 81d9445f92..3b2523bc92 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -53,6 +53,7 @@ void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) { } void SimpleEntitySimulation::removeEntityInternal(EntityItemPointer entity) { + EntitySimulation::removeEntityInternal(entity); _entitiesWithSimulator.remove(entity); } diff --git a/libraries/entities/src/SimpleEntitySimulation.h b/libraries/entities/src/SimpleEntitySimulation.h index fff0659067..83c51525a8 100644 --- a/libraries/entities/src/SimpleEntitySimulation.h +++ b/libraries/entities/src/SimpleEntitySimulation.h @@ -22,11 +22,11 @@ public: virtual ~SimpleEntitySimulation() { clearEntitiesInternal(); } protected: - virtual void updateEntitiesInternal(const quint64& now); - virtual void addEntityInternal(EntityItemPointer entity); - virtual void removeEntityInternal(EntityItemPointer entity); - virtual void changeEntityInternal(EntityItemPointer entity); - virtual void clearEntitiesInternal(); + virtual void updateEntitiesInternal(const quint64& now) override; + virtual void addEntityInternal(EntityItemPointer entity) override; + virtual void removeEntityInternal(EntityItemPointer entity) override; + virtual void changeEntityInternal(EntityItemPointer entity) override; + virtual void clearEntitiesInternal() override; SetOfEntities _entitiesWithSimulator; }; diff --git a/libraries/entities/src/SphereEntityItem.cpp b/libraries/entities/src/SphereEntityItem.cpp index 841b70aa56..7ad7b39f20 100644 --- a/libraries/entities/src/SphereEntityItem.cpp +++ b/libraries/entities/src/SphereEntityItem.cpp @@ -110,7 +110,11 @@ bool SphereEntityItem::findDetailedRayIntersection(const glm::vec3& origin, cons // then translate back to work coordinates glm::vec3 hitAt = glm::vec3(entityToWorldMatrix * glm::vec4(entityFrameHitAt, 1.0f)); distance = glm::distance(origin, hitAt); - surfaceNormal = glm::normalize(hitAt - getCenterPosition()); + bool success; + surfaceNormal = glm::normalize(hitAt - getCenterPosition(success)); + if (!success) { + return false; + } return true; } return false; diff --git a/libraries/entities/src/SphereEntityItem.h b/libraries/entities/src/SphereEntityItem.h index 941d5a167c..fda5eab009 100644 --- a/libraries/entities/src/SphereEntityItem.h +++ b/libraries/entities/src/SphereEntityItem.h @@ -52,7 +52,7 @@ public: } virtual ShapeType getShapeType() const { return SHAPE_TYPE_SPHERE; } - virtual bool shouldBePhysical() const { return true; } + virtual bool shouldBePhysical() const { return !isDead(); } virtual bool supportsDetailedRayIntersection() const { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, diff --git a/libraries/entities/src/UpdateEntityOperator.cpp b/libraries/entities/src/UpdateEntityOperator.cpp index 4acc386333..94599496b0 100644 --- a/libraries/entities/src/UpdateEntityOperator.cpp +++ b/libraries/entities/src/UpdateEntityOperator.cpp @@ -14,12 +14,11 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTreePointer tree, EntityTreeElementPointer containingElement, EntityItemPointer existingEntity, - const BoundingBoxRelatedProperties& newProperties) : + const AACube newQueryAACube) : _tree(tree), _existingEntity(existingEntity), _containingElement(containingElement), _containingElementCube(containingElement->getAACube()), - _newProperties(newProperties), _entityItemID(existingEntity->getEntityItemID()), _foundOld(false), _foundNew(false), @@ -41,13 +40,13 @@ UpdateEntityOperator::UpdateEntityOperator(EntityTreePointer tree, // entity into the the element, or do we want to use the entities "relaxed" bounds // which can handle all potential rotations? // the getMaximumAACube is the relaxed form. - _oldEntityCube = _existingEntity->getMaximumAACube(); + _oldEntityCube = _existingEntity->getQueryAACube(); _oldEntityBox = _oldEntityCube.clamp((float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); // clamp to domain bounds // If our new properties don't have bounds details (no change to position, etc) or if this containing element would // be the best fit for our new properties, then just do the new portion of the store pass, since the change path will // be the same for both parts of the update - bool oldElementBestFit = _containingElement->bestFitBounds(newProperties.getMaximumAACube()); + bool oldElementBestFit = _containingElement->bestFitBounds(newQueryAACube); // For some reason we've seen a case where the original containing element isn't a best fit for the old properties // in this case we want to move it, even if the properties haven't changed. diff --git a/libraries/entities/src/UpdateEntityOperator.h b/libraries/entities/src/UpdateEntityOperator.h index aac442d415..ea6faabe3e 100644 --- a/libraries/entities/src/UpdateEntityOperator.h +++ b/libraries/entities/src/UpdateEntityOperator.h @@ -12,7 +12,6 @@ #ifndef hifi_UpdateEntityOperator_h #define hifi_UpdateEntityOperator_h -#include "BoundingBoxRelatedProperties.h" #include "EntitiesLogging.h" #include "EntityItem.h" #include "EntityItemProperties.h" @@ -22,7 +21,7 @@ class UpdateEntityOperator : public RecurseOctreeOperator { public: UpdateEntityOperator(EntityTreePointer tree, EntityTreeElementPointer containingElement, - EntityItemPointer existingEntity, const BoundingBoxRelatedProperties& newProperties); + EntityItemPointer existingEntity, const AACube newQueryAACube); ~UpdateEntityOperator(); @@ -34,7 +33,6 @@ private: EntityItemPointer _existingEntity; EntityTreeElementPointer _containingElement; AACube _containingElementCube; // we temporarily store our cube here in case we need to delete the containing element - BoundingBoxRelatedProperties _newProperties; EntityItemID _entityItemID; bool _foundOld; bool _foundNew; diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 19206f8acc..bf323248c0 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -57,7 +57,7 @@ public: static bool getDrawZoneBoundaries() { return _drawZoneBoundaries; } static void setDrawZoneBoundaries(bool value) { _drawZoneBoundaries = value; } - virtual bool isReadyToComputeShape() { return true; } + virtual bool isReadyToComputeShape() { return false; } void updateShapeType(ShapeType type) { _shapeType = type; } virtual ShapeType getShapeType() const; diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index 26a564e20b..d9e8579d58 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -254,10 +254,33 @@ private: _quit = true; } + static const uint64_t MAX_SHUTDOWN_WAIT_SECS = 2; void stop() { - QMutexLocker lock(&_mutex); - post(STOP); - _cond.wait(&_mutex); + if (_thread.isRunning()) { + qDebug() << "Stopping QML render thread " << _thread.currentThreadId(); + { + QMutexLocker lock(&_mutex); + post(STOP); + } + auto start = usecTimestampNow(); + auto now = usecTimestampNow(); + bool shutdownClean = false; + while (now - start < (MAX_SHUTDOWN_WAIT_SECS * USECS_PER_SECOND)) { + QMutexLocker lock(&_mutex); + if (_cond.wait(&_mutex, MSECS_PER_SECOND)) { + shutdownClean = true; + break; + } + now = usecTimestampNow(); + } + + if (!shutdownClean) { + qWarning() << "Failed to shut down the QML render thread"; + } + + } else { + qDebug() << "QML render thread already completed"; + } } bool allowNewFrame(uint8_t fps) { diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.h b/libraries/gl/src/gl/OffscreenQmlSurface.h index d66cbeb285..608e811b4b 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.h +++ b/libraries/gl/src/gl/OffscreenQmlSurface.h @@ -37,7 +37,7 @@ public: using MouseTranslator = std::function; - void create(QOpenGLContext* context); + virtual void create(QOpenGLContext* context); void resize(const QSize& size); QSize size() const; Q_INVOKABLE QObject* load(const QUrl& qmlSource, std::function f = [](QQmlContext*, QObject*) {}); diff --git a/libraries/model/src/model/Geometry.h b/libraries/model/src/model/Geometry.h index fa29faff5f..f9d9b0eeb4 100755 --- a/libraries/model/src/model/Geometry.h +++ b/libraries/model/src/model/Geometry.h @@ -58,7 +58,7 @@ public: const gpu::Stream::FormatPointer getVertexFormat() const { return _vertexFormat; } // BufferStream on the mesh vertices and attributes matching the vertex format - const gpu::BufferStream getVertexStream() const { return _vertexStream; } + const gpu::BufferStream& getVertexStream() const { return _vertexStream; } // Index Buffer void setIndexBuffer(const BufferView& buffer); diff --git a/libraries/networking/CMakeLists.txt b/libraries/networking/CMakeLists.txt index 470c9145df..53ebb86b83 100644 --- a/libraries/networking/CMakeLists.txt +++ b/libraries/networking/CMakeLists.txt @@ -2,6 +2,8 @@ set(TARGET_NAME networking) setup_hifi_library(Network) link_hifi_libraries(shared) +target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/includes") + if (WIN32) # we need ws2_32.lib on windows, but it's static so we don't bubble it up target_link_libraries(${TARGET_NAME} ws2_32.lib) @@ -31,4 +33,3 @@ endif (UNIX) # append tbb includes to our list of includes to bubble target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${TBB_INCLUDE_DIRS}) -include_application_version() diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 5af2bbe7c7..7870e61723 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -19,7 +19,6 @@ #include #include -#include #include #include diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index fd465a0aed..f195011290 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -23,16 +23,27 @@ QMutex ResourceManager::_prefixMapLock; void ResourceManager::setUrlPrefixOverride(const QString& prefix, const QString& replacement) { QMutexLocker locker(&_prefixMapLock); - _prefixMap[prefix] = replacement; + if (replacement.isEmpty()) { + _prefixMap.erase(prefix); + } else { + _prefixMap[prefix] = replacement; + } } QString ResourceManager::normalizeURL(const QString& urlString) { QString result = urlString; - QMutexLocker locker(&_prefixMapLock); - foreach(const auto& entry, _prefixMap) { + PrefixMap copy; + + { + QMutexLocker locker(&_prefixMapLock); + copy = _prefixMap; + } + + foreach(const auto& entry, copy) { const auto& prefix = entry.first; const auto& replacement = entry.second; if (result.startsWith(prefix)) { + qDebug() << "Replacing " << prefix << " with " << replacement; result.replace(0, prefix.size(), replacement); } } diff --git a/libraries/networking/src/ResourceScriptingInterface.cpp b/libraries/networking/src/ResourceScriptingInterface.cpp new file mode 100644 index 0000000000..38be49049c --- /dev/null +++ b/libraries/networking/src/ResourceScriptingInterface.cpp @@ -0,0 +1,15 @@ +// +// Created by Bradley Austin Davis on 2015/12/29 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "ResourceScriptingInterface.h" + +#include "ResourceManager.h" + +void ResourceScriptingInterface::overrideUrlPrefix(const QString& prefix, const QString& replacement) { + ResourceManager::setUrlPrefixOverride(prefix, replacement); +} diff --git a/libraries/networking/src/ResourceScriptingInterface.h b/libraries/networking/src/ResourceScriptingInterface.h new file mode 100644 index 0000000000..d9777e7514 --- /dev/null +++ b/libraries/networking/src/ResourceScriptingInterface.h @@ -0,0 +1,31 @@ +// +// AssetClient.h +// libraries/networking/src +// +// Created by Ryan Huffman on 2015/07/21 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#ifndef hifi_networking_ResourceScriptingInterface_h +#define hifi_networking_ResourceScriptingInterface_h + +#include + +#include + +class ResourceScriptingInterface : public QObject, public Dependency { + Q_OBJECT +public: + Q_INVOKABLE void overrideUrlPrefix(const QString& prefix, const QString& replacement); + + Q_INVOKABLE void restoreUrlPrefix(const QString& prefix) { + overrideUrlPrefix(prefix, ""); + } +}; + + +#endif diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index 2f5ab7a015..303755c8f3 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -20,10 +20,16 @@ ThreadedAssignment::ThreadedAssignment(ReceivedMessage& message) : Assignment(message), - _isFinished(false) - + _isFinished(false), + _domainServerTimer(this), + _statsTimer(this) { + static const int STATS_TIMEOUT_MS = 1000; + _statsTimer.setInterval(STATS_TIMEOUT_MS); + connect(&_statsTimer, &QTimer::timeout, this, &ThreadedAssignment::sendStatsPacket); + connect(&_domainServerTimer, &QTimer::timeout, this, &ThreadedAssignment::checkInWithDomainServerOrExit); + _domainServerTimer.setInterval(DOMAIN_SERVER_CHECK_IN_MSECS); } void ThreadedAssignment::setFinished(bool isFinished) { @@ -47,16 +53,9 @@ void ThreadedAssignment::setFinished(bool isFinished) { // send a disconnect packet to the domain nodeList->getDomainHandler().disconnect(); - if (_domainServerTimer) { - // stop the domain-server check in timer by calling deleteLater so it gets cleaned up on NL thread - _domainServerTimer->deleteLater(); - _domainServerTimer = nullptr; - } - - if (_statsTimer) { - _statsTimer->deleteLater(); - _statsTimer = nullptr; - } + // stop our owned timers + _domainServerTimer.stop(); + _statsTimer.stop(); // call our virtual aboutToFinish method - this gives the ThreadedAssignment subclass a chance to cleanup aboutToFinish(); @@ -66,30 +65,22 @@ void ThreadedAssignment::setFinished(bool isFinished) { } } -void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeType, bool shouldSendStats) { +void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeType) { // change the logging target name while the assignment is running LogHandler::getInstance().setTargetName(targetName); auto nodeList = DependencyManager::get(); nodeList->setOwnerType(nodeType); - _domainServerTimer = new QTimer; - connect(_domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit())); - _domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); - - // send a domain-server check in immediately + // send a domain-server check in immediately and start the timer to fire them every DOMAIN_SERVER_CHECK_IN_MSECS checkInWithDomainServerOrExit(); - - // move the domain server time to the NL so check-ins fire from there - _domainServerTimer->moveToThread(nodeList->thread()); + _domainServerTimer.start(); - if (shouldSendStats) { - // start sending stats packet once we connect to the domain - connect(&nodeList->getDomainHandler(), &DomainHandler::connectedToDomain, this, &ThreadedAssignment::startSendingStats); - - // stop sending stats if we disconnect - connect(&nodeList->getDomainHandler(), &DomainHandler::disconnectedFromDomain, this, &ThreadedAssignment::stopSendingStats); - } + // start sending stats packet once we connect to the domain + connect(&nodeList->getDomainHandler(), SIGNAL(connectedToDomain(const QString&)), &_statsTimer, SLOT(start())); + + // stop sending stats if we disconnect + connect(&nodeList->getDomainHandler(), &DomainHandler::disconnectedFromDomain, &_statsTimer, &QTimer::stop); } void ThreadedAssignment::addPacketStatsAndSendStatsPacket(QJsonObject &statsObject) { @@ -111,28 +102,12 @@ void ThreadedAssignment::sendStatsPacket() { addPacketStatsAndSendStatsPacket(statsObject); } -void ThreadedAssignment::startSendingStats() { - // send the stats packet every 1s - if (!_statsTimer) { - _statsTimer = new QTimer; - connect(_statsTimer, &QTimer::timeout, this, &ThreadedAssignment::sendStatsPacket); - } - - _statsTimer->start(1000); -} - -void ThreadedAssignment::stopSendingStats() { - if (_statsTimer) { - // stop sending stats, we just disconnected from domain - _statsTimer->stop(); - } -} - void ThreadedAssignment::checkInWithDomainServerOrExit() { if (DependencyManager::get()->getNumNoReplyDomainCheckIns() == MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { setFinished(true); } else { - DependencyManager::get()->sendDomainServerCheckIn(); + auto nodeList = DependencyManager::get(); + QMetaObject::invokeMethod(nodeList.data(), "sendDomainServerCheckIn"); } } diff --git a/libraries/networking/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h index 87d503d2bf..42d4903c2f 100644 --- a/libraries/networking/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -38,17 +38,15 @@ signals: void finished(); protected: - void commonInit(const QString& targetName, NodeType_t nodeType, bool shouldSendStats = true); + void commonInit(const QString& targetName, NodeType_t nodeType); bool _isFinished; - QTimer* _domainServerTimer = nullptr; - QTimer* _statsTimer = nullptr; + QTimer _domainServerTimer; + QTimer _statsTimer; protected slots: void domainSettingsRequestFailed(); private slots: - void startSendingStats(); - void stopSendingStats(); void checkInWithDomainServerOrExit(); }; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 4daf7f83b6..43f4b5dcc9 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -41,10 +41,10 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityAdd: case PacketType::EntityEdit: case PacketType::EntityData: - return VERSION_ENTITIES_REMOVED_START_AUTOMATICALLY_FROM_ANIMATION_PROPERTY_GROUP; + return VERSION_ENTITITES_HAVE_QUERY_BOX; case PacketType::AvatarData: case PacketType::BulkAvatarData: - return 17; + return static_cast(AvatarMixerPacketVersion::SoftAttachmentSupport); default: return 17; } @@ -60,7 +60,7 @@ QDebug operator<<(QDebug debug, const PacketType& type) { QMetaObject metaObject = PacketTypeEnum::staticMetaObject; QMetaEnum metaEnum = metaObject.enumerator(metaObject.enumeratorOffset()); QString typeName = metaEnum.valueToKey((int) type); - + debug.nospace().noquote() << (uint8_t) type << " (" << typeName << ")"; return debug.space(); } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 5daa185af4..c1404178ff 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -162,5 +162,12 @@ const PacketVersion VERSION_ENTITIES_PARTICLES_ADDITIVE_BLENDING = 49; const PacketVersion VERSION_ENTITIES_POLYLINE_TEXTURE = 50; const PacketVersion VERSION_ENTITIES_HAVE_PARENTS = 51; const PacketVersion VERSION_ENTITIES_REMOVED_START_AUTOMATICALLY_FROM_ANIMATION_PROPERTY_GROUP = 52; +const PacketVersion VERSION_MODEL_ENTITIES_JOINTS_ON_WIRE = 53; +const PacketVersion VERSION_ENTITITES_HAVE_QUERY_BOX = 54; + +enum class AvatarMixerPacketVersion : PacketVersion { + TranslationSupport = 17, + SoftAttachmentSupport +}; #endif // hifi_PacketHeaders_h diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index f063c60fd7..5380aaa6ce 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -14,6 +14,7 @@ #include "OctreeLogging.h" #include "OctreePacketData.h" +#include "NumericalConstants.h" bool OctreePacketData::_debug = false; AtomicUIntStat OctreePacketData::_totalBytesOfOctalCodes { 0 }; @@ -23,6 +24,11 @@ AtomicUIntStat OctreePacketData::_totalBytesOfValues { 0 }; AtomicUIntStat OctreePacketData::_totalBytesOfPositions { 0 }; AtomicUIntStat OctreePacketData::_totalBytesOfRawData { 0 }; +struct aaCubeData { + glm::vec3 corner; + float scale; +}; + OctreePacketData::OctreePacketData(bool enableCompression, int targetSize) { changeSettings(enableCompression, targetSize); // does reset... } @@ -392,6 +398,28 @@ bool OctreePacketData::appendValue(const QVector& value) { return success; } +bool OctreePacketData::appendValue(const QVector& value) { + uint16_t qVecSize = value.size(); + bool success = appendValue(qVecSize); + + if (success) { + QByteArray dataByteArray(udt::MAX_PACKET_SIZE, 0); + unsigned char* start = reinterpret_cast(dataByteArray.data()); + unsigned char* destinationBuffer = start; + for (int index = 0; index < value.size(); index++) { + destinationBuffer += packOrientationQuatToBytes(destinationBuffer, value[index]); + } + int quatsSize = destinationBuffer - start; + success = append(start, quatsSize); + if (success) { + _bytesOfValues += quatsSize; + _totalBytesOfValues += quatsSize; + } + } + + return success; +} + bool OctreePacketData::appendValue(const QVector& value) { uint16_t qVecSize = value.size(); bool success = appendValue(qVecSize); @@ -405,6 +433,34 @@ bool OctreePacketData::appendValue(const QVector& value) { return success; } +bool OctreePacketData::appendValue(const QVector& value) { + uint16_t qVecSize = value.size(); + bool success = appendValue(qVecSize); + + if (success) { + QByteArray dataByteArray(udt::MAX_PACKET_SIZE, 0); + unsigned char* start = reinterpret_cast(dataByteArray.data()); + unsigned char* destinationBuffer = start; + int bit = 0; + for (int index = 0; index < value.size(); index++) { + if (value[index]) { + (*destinationBuffer) |= (1 << bit); + } + if (++bit == BITS_IN_BYTE) { + destinationBuffer++; + bit = 0; + } + } + int boolsSize = destinationBuffer - start; + success = append(start, boolsSize); + if (success) { + _bytesOfValues += boolsSize; + _totalBytesOfValues += boolsSize; + } + } + return success; +} + bool OctreePacketData::appendValue(const glm::quat& value) { const size_t VALUES_PER_QUAT = 4; const size_t PACKED_QUAT_SIZE = sizeof(uint16_t) * VALUES_PER_QUAT; @@ -461,6 +517,17 @@ bool OctreePacketData::appendValue(const QByteArray& bytes) { return success; } +bool OctreePacketData::appendValue(const AACube& aaCube) { + aaCubeData cube { aaCube.getCorner(), aaCube.getScale() }; + const unsigned char* data = (const unsigned char*)&cube; + int length = sizeof(aaCubeData); + bool success = append(data, length); + if (success) { + _bytesOfValues += length; + _totalBytesOfValues += length; + } + return success; +} bool OctreePacketData::appendPosition(const glm::vec3& value) { const unsigned char* data = (const unsigned char*)&value; @@ -621,6 +688,20 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto return sizeof(uint16_t) + length * sizeof(glm::vec3); } +int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVector& result) { + uint16_t length; + memcpy(&length, dataBytes, sizeof(uint16_t)); + dataBytes += sizeof(length); + result.resize(length); + + const unsigned char *start = dataBytes; + for (int i = 0; i < length; i++) { + dataBytes += unpackOrientationQuatFromBytes(dataBytes, result[i]); + } + + return (dataBytes - start) + (int)sizeof(uint16_t); +} + int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVector& result) { uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); @@ -630,7 +711,27 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto return sizeof(uint16_t) + length * sizeof(float); } -int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QByteArray& result) { +int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVector& result) { + uint16_t length; + memcpy(&length, dataBytes, sizeof(uint16_t)); + dataBytes += sizeof(length); + result.resize(length); + + int bit = 0; + unsigned char current = 0; + const unsigned char *start = dataBytes; + for (int i = 0; i < length; i ++) { + if (bit == 0) { + current = *dataBytes++; + } + result[i] = (bool)(current & (1 << bit)); + bit = (bit + 1) % BITS_IN_BYTE; + } + + return (dataBytes - start) + (int)sizeof(uint16_t); +} + +int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QByteArray& result) { uint16_t length; memcpy(&length, dataBytes, sizeof(length)); dataBytes += sizeof(length); @@ -638,3 +739,10 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QByteA result = value; return sizeof(length) + length; } + +int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, AACube& result) { + aaCubeData cube; + memcpy(&cube, dataBytes, sizeof(aaCubeData)); + result = AACube(cube.corner, cube.scale); + return sizeof(aaCubeData); +} diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index 2c86d518ad..0aa66b06d0 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -165,19 +165,25 @@ public: /// appends a non-position vector to the end of the stream, may fail if new data stream is too long to fit in packet bool appendValue(const glm::vec3& value); - - //appends a QVector of vec3's to the end of the stream, may fail if new data stream is too long to fit in packet + + /// appends a QVector of vec3s to the end of the stream, may fail if new data stream is too long to fit in packet bool appendValue(const QVector& value); - - //appends a QVector of floats to the end of the stream, may fail if new data stream is too long to fit in packet + + /// appends a QVector of quats to the end of the stream, may fail if new data stream is too long to fit in packet + bool appendValue(const QVector& value); + + /// appends a QVector of floats to the end of the stream, may fail if new data stream is too long to fit in packet bool appendValue(const QVector& value); + /// appends a QVector of bools to the end of the stream, may fail if new data stream is too long to fit in packet + bool appendValue(const QVector& value); + /// appends a packed quat to the end of the stream, may fail if new data stream is too long to fit in packet bool appendValue(const glm::quat& value); /// appends a bool value to the end of the stream, may fail if new data stream is too long to fit in packet bool appendValue(bool value); - + /// appends a string value to the end of the stream, may fail if new data stream is too long to fit in packet bool appendValue(const QString& string); @@ -187,6 +193,9 @@ public: /// appends a QByteArray value to the end of the stream, may fail if new data stream is too long to fit in packet bool appendValue(const QByteArray& bytes); + /// appends an AACube value to the end of the stream, may fail if new data stream is too long to fit in packet + bool appendValue(const AACube& aaCube); + /// appends a position to the end of the stream, may fail if new data stream is too long to fit in packet bool appendPosition(const glm::vec3& value); @@ -251,8 +260,11 @@ public: static int unpackDataFromBytes(const unsigned char* dataBytes, QUuid& result); static int unpackDataFromBytes(const unsigned char* dataBytes, xColor& result); static int unpackDataFromBytes(const unsigned char* dataBytes, QVector& result); + static int unpackDataFromBytes(const unsigned char* dataBytes, QVector& result); static int unpackDataFromBytes(const unsigned char* dataBytes, QVector& result); + static int unpackDataFromBytes(const unsigned char* dataBytes, QVector& result); static int unpackDataFromBytes(const unsigned char* dataBytes, QByteArray& result); + static int unpackDataFromBytes(const unsigned char* dataBytes, AACube& result); private: /// appends raw bytes, might fail if byte would cause packet to be too large diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index f86369a514..c1338b772c 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -34,7 +34,7 @@ const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5; #ifdef WANT_DEBUG_ENTITY_TREE_LOCKS bool EntityMotionState::entityTreeIsLocked() const { - EntityTreeElementPointer element = _entity ? _entity->getElement() : nullptr; + EntityTreeElementPointer element = _entity->getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; if (!tree) { return true; @@ -50,7 +50,8 @@ bool entityTreeIsLocked() { EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer entity) : ObjectMotionState(shape), - _entity(entity), + _entityPtr(entity), + _entity(entity.get()), _sentInactive(true), _lastStep(0), _serverPosition(0.0f), @@ -69,14 +70,14 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer _loopsWithoutOwner(0) { _type = MOTIONSTATE_TYPE_ENTITY; - assert(_entity != nullptr); + assert(_entity); assert(entityTreeIsLocked()); setMass(_entity->computeMass()); } EntityMotionState::~EntityMotionState() { - // be sure to clear _entity before calling the destructor - assert(!_entity); + assert(_entity); + _entity = nullptr; } void EntityMotionState::updateServerPhysicsVariables(const QUuid& sessionID) { @@ -138,11 +139,6 @@ bool EntityMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* return ObjectMotionState::handleHardAndEasyChanges(flags, engine); } -void EntityMotionState::clearObjectBackPointer() { - ObjectMotionState::clearObjectBackPointer(); - _entity = nullptr; -} - MotionType EntityMotionState::computeObjectMotionType() const { if (!_entity) { return MOTION_TYPE_STATIC; @@ -221,22 +217,16 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { // virtual and protected -bool EntityMotionState::isReadyToComputeShape() { - if (_entity) { - return _entity->isReadyToComputeShape(); - } - return false; +bool EntityMotionState::isReadyToComputeShape() const { + return _entity->isReadyToComputeShape(); } // virtual and protected btCollisionShape* EntityMotionState::computeNewShape() { - if (_entity) { - ShapeInfo shapeInfo; - assert(entityTreeIsLocked()); - _entity->computeShapeInfo(shapeInfo); - return getShapeManager()->getShape(shapeInfo); - } - return nullptr; + ShapeInfo shapeInfo; + assert(entityTreeIsLocked()); + _entity->computeShapeInfo(shapeInfo); + return getShapeManager()->getShape(shapeInfo); } bool EntityMotionState::isCandidateForOwnership(const QUuid& sessionID) const { @@ -374,6 +364,10 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep, const QUuid& s return true; } + if (_entity->queryAABoxNeedsUpdate()) { + return true; + } + if (_entity->getSimulatorID() != sessionID) { // we don't own the simulation, but maybe we should... if (_outgoingPriority != NO_PRORITY) { @@ -466,6 +460,11 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q properties.setActionData(_serverActionData); } + if (properties.parentRelatedPropertyChanged() && _entity->computePuffedQueryAACube()) { + // due to parenting, the server may not know where something is in world-space, so include the bounding cube. + properties.setQueryAACube(_entity->getQueryAACube()); + } + // set the LastEdited of the properties but NOT the entity itself quint64 now = usecTimestampNow(); properties.setLastEdited(now); @@ -502,6 +501,20 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, id, properties); _entity->setLastBroadcast(usecTimestampNow()); + // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server + // if they've changed. + _entity->forEachDescendant([&](SpatiallyNestablePointer descendant) { + if (descendant->getNestableType() == NestableType::Entity) { + EntityItemPointer entityDescendant = std::static_pointer_cast(descendant); + if (descendant->computePuffedQueryAACube()) { + EntityItemProperties newQueryCubeProperties; + newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); + entityPacketSender->queueEditEntityMessage(PacketType::EntityEdit, descendant->getID(), newQueryCubeProperties); + entityDescendant->setLastBroadcast(usecTimestampNow()); + } + } + }); + _lastStep = step; } @@ -530,26 +543,17 @@ void EntityMotionState::clearIncomingDirtyFlags() { // virtual quint8 EntityMotionState::getSimulationPriority() const { - if (_entity) { - return _entity->getSimulationPriority(); - } - return NO_PRORITY; + return _entity->getSimulationPriority(); } // virtual QUuid EntityMotionState::getSimulatorID() const { - if (_entity) { - assert(entityTreeIsLocked()); - return _entity->getSimulatorID(); - } - return QUuid(); + assert(entityTreeIsLocked()); + return _entity->getSimulatorID(); } -// virtual void EntityMotionState::bump(quint8 priority) { - if (_entity) { - setOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority)); - } + setOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority)); } void EntityMotionState::resetMeasuredBodyAcceleration() { @@ -600,19 +604,13 @@ void EntityMotionState::setMotionType(MotionType motionType) { // virtual -QString EntityMotionState::getName() { - if (_entity) { - assert(entityTreeIsLocked()); - return _entity->getName(); - } - return ""; +QString EntityMotionState::getName() const { + assert(entityTreeIsLocked()); + return _entity->getName(); } // virtual -int16_t EntityMotionState::computeCollisionGroup() { - if (!_entity) { - return COLLISION_GROUP_STATIC; - } +int16_t EntityMotionState::computeCollisionGroup() const { if (_entity->getIgnoreForCollisions()) { return COLLISION_GROUP_COLLISIONLESS; } diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index c666f87221..53e7982ae1 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -12,11 +12,11 @@ #ifndef hifi_EntityMotionState_h #define hifi_EntityMotionState_h +#include #include #include "ObjectMotionState.h" -class EntityItem; // From the MotionState's perspective: // Inside = physics simulation @@ -38,10 +38,10 @@ public: virtual bool isMoving() const; // this relays incoming position/rotation to the RigidBody - virtual void getWorldTransform(btTransform& worldTrans) const; + virtual void getWorldTransform(btTransform& worldTrans) const override; // this relays outgoing position/rotation to the EntityItem - virtual void setWorldTransform(const btTransform& worldTrans); + virtual void setWorldTransform(const btTransform& worldTrans) override; bool isCandidateForOwnership(const QUuid& sessionID) const; bool remoteSimulationOutOfSync(uint32_t simulationStep); @@ -55,32 +55,32 @@ public: void resetAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount = 0; } quint8 getAccelerationNearlyGravityCount() { return _accelerationNearlyGravityCount; } - virtual float getObjectRestitution() const { return _entity->getRestitution(); } - virtual float getObjectFriction() const { return _entity->getFriction(); } - virtual float getObjectLinearDamping() const { return _entity->getDamping(); } - virtual float getObjectAngularDamping() const { return _entity->getAngularDamping(); } + virtual float getObjectRestitution() const override { return _entity->getRestitution(); } + virtual float getObjectFriction() const override { return _entity->getFriction(); } + virtual float getObjectLinearDamping() const override { return _entity->getDamping(); } + virtual float getObjectAngularDamping() const override { return _entity->getAngularDamping(); } - virtual glm::vec3 getObjectPosition() const { return _entity->getPosition() - ObjectMotionState::getWorldOffset(); } - virtual glm::quat getObjectRotation() const { return _entity->getRotation(); } - virtual glm::vec3 getObjectLinearVelocity() const { return _entity->getVelocity(); } - virtual glm::vec3 getObjectAngularVelocity() const { return _entity->getAngularVelocity(); } - virtual glm::vec3 getObjectGravity() const { return _entity->getGravity(); } - virtual glm::vec3 getObjectLinearVelocityChange() const; + virtual glm::vec3 getObjectPosition() const override { return _entity->getPosition() - ObjectMotionState::getWorldOffset(); } + virtual glm::quat getObjectRotation() const override { return _entity->getRotation(); } + virtual glm::vec3 getObjectLinearVelocity() const override { return _entity->getVelocity(); } + virtual glm::vec3 getObjectAngularVelocity() const override { return _entity->getAngularVelocity(); } + virtual glm::vec3 getObjectGravity() const override { return _entity->getGravity(); } + virtual glm::vec3 getObjectLinearVelocityChange() const override; - virtual const QUuid& getObjectID() const { return _entity->getID(); } + virtual const QUuid& getObjectID() const override { return _entity->getID(); } - virtual quint8 getSimulationPriority() const; - virtual QUuid getSimulatorID() const; - virtual void bump(quint8 priority); + virtual quint8 getSimulationPriority() const override; + virtual QUuid getSimulatorID() const override; + virtual void bump(quint8 priority) override; - EntityItemPointer getEntity() const { return _entity; } + EntityItemPointer getEntity() const { return _entityPtr.lock(); } void resetMeasuredBodyAcceleration(); void measureBodyAcceleration(); - virtual QString getName(); + virtual QString getName() const override; - virtual int16_t computeCollisionGroup(); + virtual int16_t computeCollisionGroup() const override; // eternal logic can suggest a simuator priority bid for the next outgoing update void setOutgoingPriority(quint8 priority); @@ -92,12 +92,19 @@ protected: bool entityTreeIsLocked() const; #endif - virtual bool isReadyToComputeShape(); + virtual bool isReadyToComputeShape() const override; virtual btCollisionShape* computeNewShape(); - virtual void clearObjectBackPointer(); virtual void setMotionType(MotionType motionType); - EntityItemPointer _entity; + // In the glorious future (when entities lib depends on physics lib) the EntityMotionState will be + // properly "owned" by the EntityItem and will be deleted by it in the dtor. In pursuit of that + // state of affairs we can't keep a real EntityItemPointer as data member (it would produce a + // recursive dependency). Instead we keep a EntityItemWeakPointer to break that dependency while + // still granting us the capability to generate EntityItemPointers as necessary (for external data + // structures that use the MotionState to get to the EntityItem). + EntityItemWeakPointer _entityPtr; + // Meanwhile we also keep a raw EntityItem* for internal stuff where the pointer is guaranteed valid. + EntityItem* _entity; bool _sentInactive; // true if body was inactive when we sent last update diff --git a/libraries/physics/src/ObjectAction.cpp b/libraries/physics/src/ObjectAction.cpp index 17b565ba21..f90286845a 100644 --- a/libraries/physics/src/ObjectAction.cpp +++ b/libraries/physics/src/ObjectAction.cpp @@ -23,15 +23,15 @@ ObjectAction::~ObjectAction() { } void ObjectAction::updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep) { - bool ownerEntityExpired = false; quint64 expiresWhen = 0; + EntityItemPointer ownerEntity = nullptr; withReadLock([&]{ - ownerEntityExpired = _ownerEntity.expired(); + ownerEntity = _ownerEntity.lock(); expiresWhen = _expires; }); - if (ownerEntityExpired) { + if (!ownerEntity) { qDebug() << "warning -- action with no entity removing self from btCollisionWorld."; btDynamicsWorld* dynamicsWorld = static_cast(collisionWorld); if (dynamicsWorld) { @@ -43,10 +43,8 @@ void ObjectAction::updateAction(btCollisionWorld* collisionWorld, btScalar delta if (expiresWhen > 0) { quint64 now = usecTimestampNow(); if (now > expiresWhen) { - EntityItemPointer ownerEntity = nullptr; QUuid myID; withWriteLock([&]{ - ownerEntity = _ownerEntity.lock(); _active = false; myID = getID(); }); @@ -239,10 +237,13 @@ void ObjectAction::setAngularVelocity(glm::vec3 angularVelocity) { rigidBody->activate(); } -void ObjectAction::activateBody() { +void ObjectAction::activateBody(bool forceActivation) { auto rigidBody = getRigidBody(); if (rigidBody) { - rigidBody->activate(); + rigidBody->activate(forceActivation); + assert(rigidBody->isActive()); + } else { + qDebug() << "ObjectAction::activateBody -- no rigid body" << (void*)rigidBody; } } diff --git a/libraries/physics/src/ObjectAction.h b/libraries/physics/src/ObjectAction.h index 4ca13f2fbf..efab75b802 100644 --- a/libraries/physics/src/ObjectAction.h +++ b/libraries/physics/src/ObjectAction.h @@ -62,7 +62,7 @@ protected: virtual void setLinearVelocity(glm::vec3 linearVelocity) override; virtual glm::vec3 getAngularVelocity() override; virtual void setAngularVelocity(glm::vec3 angularVelocity) override; - virtual void activateBody(); + virtual void activateBody(bool forceActivation = false); virtual void forceBodyNonStatic(); EntityItemWeakPointer _ownerEntity; diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index 7389d18143..c434f67ad2 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -72,7 +72,8 @@ ObjectMotionState::ObjectMotionState(btCollisionShape* shape) : ObjectMotionState::~ObjectMotionState() { assert(!_body); - assert(!_shape); + releaseShape(); + _type = MOTIONSTATE_TYPE_INVALID; } void ObjectMotionState::setBodyLinearVelocity(const glm::vec3& velocity) const { diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 8f97b25dcc..e10d58e3ed 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -134,9 +134,9 @@ public: virtual QUuid getSimulatorID() const = 0; virtual void bump(quint8 priority) {} - virtual QString getName() { return ""; } + virtual QString getName() const { return ""; } - virtual int16_t computeCollisionGroup() = 0; + virtual int16_t computeCollisionGroup() const = 0; bool isActive() const { return _body ? _body->isActive() : false; } @@ -148,14 +148,11 @@ public: friend class PhysicsEngine; protected: - virtual bool isReadyToComputeShape() = 0; + virtual bool isReadyToComputeShape() const = 0; virtual btCollisionShape* computeNewShape() = 0; void setMotionType(MotionType motionType); void updateCCDConfiguration(); - // clearObjectBackPointer() overrrides should call the base method, then actually clear the object back pointer. - virtual void clearObjectBackPointer() { _type = MOTIONSTATE_TYPE_INVALID; } - void setRigidBody(btRigidBody* body); MotionStateType _type = MOTIONSTATE_TYPE_INVALID; // type of MotionState diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 5a12627abd..9ef27aaf53 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -44,10 +44,11 @@ void PhysicalEntitySimulation::updateEntitiesInternal(const quint64& now) { void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) { assert(entity); + assert(!entity->isDead()); if (entity->shouldBePhysical()) { EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); if (!motionState) { - _pendingAdds.insert(entity); + _entitiesToAddToPhysics.insert(entity); } } else if (entity->isMoving()) { _simpleKinematicEntities.insert(entity); @@ -55,14 +56,33 @@ void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) { } void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) { + EntitySimulation::removeEntityInternal(entity); + _entitiesToAddToPhysics.remove(entity); + EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); if (motionState) { - motionState->clearObjectBackPointer(); - entity->setPhysicsInfo(nullptr); - _pendingRemoves.insert(motionState); _outgoingChanges.remove(motionState); + _entitiesToRemoveFromPhysics.insert(entity); + } else { + _entitiesToDelete.insert(entity); } - _pendingAdds.remove(entity); +} + +void PhysicalEntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) { + QMutexLocker lock(&_mutex); + for (auto entity : _entitiesToDelete) { + // this entity is still in its tree, so we insert into the external list + entitiesToDelete.push_back(entity); + + // Someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo + // rather than do it here + EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); + if (motionState) { + delete motionState; + entity->setPhysicsInfo(nullptr); + } + } + _entitiesToDelete.clear(); } void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) { @@ -74,8 +94,8 @@ void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) { // the entity should be removed from the physical simulation _pendingChanges.remove(motionState); _physicalObjects.remove(motionState); - _pendingRemoves.insert(motionState); _outgoingChanges.remove(motionState); + _entitiesToRemoveFromPhysics.insert(entity); if (entity->isMoving()) { _simpleKinematicEntities.insert(entity); } @@ -85,7 +105,7 @@ void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) { } else if (entity->shouldBePhysical()) { // The intent is for this object to be in the PhysicsEngine, but it has no MotionState yet. // Perhaps it's shape has changed and it can now be added? - _pendingAdds.insert(entity); + _entitiesToAddToPhysics.insert(entity); _simpleKinematicEntities.remove(entity); // just in case it's non-physical-kinematic } else if (entity->isMoving()) { _simpleKinematicEntities.insert(entity); @@ -102,55 +122,70 @@ void PhysicalEntitySimulation::clearEntitiesInternal() { // first disconnect each MotionStates from its Entity for (auto stateItr : _physicalObjects) { EntityMotionState* motionState = static_cast(&(*stateItr)); - EntityItemPointer entity = motionState->getEntity(); - if (entity) { - entity->setPhysicsInfo(nullptr); - } - motionState->clearObjectBackPointer(); + _entitiesToDelete.insert(motionState->getEntity()); } - // then delete the objects (aka MotionStates) - _physicsEngine->deleteObjects(_physicalObjects); + // then remove the objects (aka MotionStates) from physics + _physicsEngine->removeObjects(_physicalObjects); - // finally clear all lists (which now have only dangling pointers) + // delete the MotionStates + // TODO: after we invert the entities/physics lib dependencies we will let EntityItem delete + // its own PhysicsInfo rather than do it here + for (auto entity : _entitiesToDelete) { + EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); + if (motionState) { + delete motionState; + entity->setPhysicsInfo(nullptr); + } + } + + // finally clear all lists maintained by this class _physicalObjects.clear(); - _pendingRemoves.clear(); - _pendingAdds.clear(); + _entitiesToRemoveFromPhysics.clear(); + _entitiesToAddToPhysics.clear(); _pendingChanges.clear(); _outgoingChanges.clear(); } + +// virtual +void PhysicalEntitySimulation::prepareEntityForDelete(EntityItemPointer entity) { + assert(entity); + assert(entity->isDead()); + entity->clearActions(this); + removeEntityInternal(entity); +} // end EntitySimulation overrides - -void PhysicalEntitySimulation::getObjectsToDelete(VectorOfMotionStates& result) { +void PhysicalEntitySimulation::getObjectsToRemoveFromPhysics(VectorOfMotionStates& result) { result.clear(); QMutexLocker lock(&_mutex); - for (auto stateItr : _pendingRemoves) { - EntityMotionState* motionState = &(*stateItr); - _pendingChanges.remove(motionState); - _physicalObjects.remove(motionState); - - EntityItemPointer entity = motionState->getEntity(); - if (entity) { - _pendingAdds.remove(entity); - entity->setPhysicsInfo(nullptr); - motionState->clearObjectBackPointer(); + for (auto entity: _entitiesToRemoveFromPhysics) { + EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); + if (motionState) { + _pendingChanges.remove(motionState); + _physicalObjects.remove(motionState); + result.push_back(motionState); + } + _entitiesToAddToPhysics.remove(entity); + if (entity->isDead()) { + _entitiesToDelete.insert(entity); } - result.push_back(motionState); } - _pendingRemoves.clear(); + _entitiesToRemoveFromPhysics.clear(); } -void PhysicalEntitySimulation::getObjectsToAdd(VectorOfMotionStates& result) { +void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& result) { result.clear(); QMutexLocker lock(&_mutex); - SetOfEntities::iterator entityItr = _pendingAdds.begin(); - while (entityItr != _pendingAdds.end()) { - EntityItemPointer entity = *entityItr; + SetOfEntities::iterator entityItr = _entitiesToAddToPhysics.begin(); + while (entityItr != _entitiesToAddToPhysics.end()) { + EntityItemPointer entity = (*entityItr); assert(!entity->getPhysicsInfo()); - if (!entity->shouldBePhysical()) { - // this entity should no longer be on the internal _pendingAdds - entityItr = _pendingAdds.erase(entityItr); + if (entity->isDead()) { + prepareEntityForDelete(entity); + } else if (!entity->shouldBePhysical()) { + // this entity should no longer be on the internal _entitiesToAddToPhysics + entityItr = _entitiesToAddToPhysics.erase(entityItr); if (entity->isMoving()) { _simpleKinematicEntities.insert(entity); } @@ -163,7 +198,7 @@ void PhysicalEntitySimulation::getObjectsToAdd(VectorOfMotionStates& result) { entity->setPhysicsInfo(static_cast(motionState)); _physicalObjects.insert(motionState); result.push_back(motionState); - entityItr = _pendingAdds.erase(entityItr); + entityItr = _entitiesToAddToPhysics.erase(entityItr); } else { //qDebug() << "Warning! Failed to generate new shape for entity." << entity->getName(); ++entityItr; @@ -199,12 +234,11 @@ void PhysicalEntitySimulation::handleOutgoingChanges(const VectorOfMotionStates& if (state && state->getType() == MOTIONSTATE_TYPE_ENTITY) { EntityMotionState* entityState = static_cast(state); EntityItemPointer entity = entityState->getEntity(); - if (entity) { - if (entityState->isCandidateForOwnership(sessionID)) { - _outgoingChanges.insert(entityState); - } - _entitiesToSort.insert(entityState->getEntity()); + assert(entity.get()); + if (entityState->isCandidateForOwnership(sessionID)) { + _outgoingChanges.insert(entityState); } + _entitiesToSort.insert(entity); } } diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index c4f96e023a..bc80d50d0a 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -35,6 +35,8 @@ public: virtual void addAction(EntityActionPointer action) override; virtual void applyActionChanges() override; + virtual void takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) override; + protected: // only called by EntitySimulation // overrides for EntitySimulation virtual void updateEntitiesInternal(const quint64& now) override; @@ -44,8 +46,10 @@ protected: // only called by EntitySimulation virtual void clearEntitiesInternal() override; public: - void getObjectsToDelete(VectorOfMotionStates& result); - void getObjectsToAdd(VectorOfMotionStates& result); + virtual void prepareEntityForDelete(EntityItemPointer entity) override; + + void getObjectsToRemoveFromPhysics(VectorOfMotionStates& result); + void getObjectsToAddToPhysics(VectorOfMotionStates& result); void setObjectsToChange(const VectorOfMotionStates& objectsToChange); void getObjectsToChange(VectorOfMotionStates& result); @@ -55,12 +59,10 @@ public: EntityEditPacketSender* getPacketSender() { return _entityPacketSender; } private: - // incoming changes - SetOfEntityMotionStates _pendingRemoves; // EntityMotionStates to be removed from PhysicsEngine (and deleted) - SetOfEntities _pendingAdds; // entities to be be added to PhysicsEngine (and a their EntityMotionState created) - SetOfEntityMotionStates _pendingChanges; // EntityMotionStates already in PhysicsEngine that need their physics changed + SetOfEntities _entitiesToRemoveFromPhysics; + SetOfEntities _entitiesToAddToPhysics; - // outgoing changes + SetOfEntityMotionStates _pendingChanges; // EntityMotionStates already in PhysicsEngine that need their physics changed SetOfEntityMotionStates _outgoingChanges; // EntityMotionStates for which we need to send updates to entity-server SetOfMotionStates _physicalObjects; // MotionStates of entities in PhysicsEngine diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 22695a1b66..9e295d5cf5 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -67,7 +67,8 @@ void PhysicsEngine::init() { } } -void PhysicsEngine::addObject(ObjectMotionState* motionState) { +// private +void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) { assert(motionState); btVector3 inertia(0.0f, 0.0f, 0.0f); @@ -144,7 +145,8 @@ void PhysicsEngine::addObject(ObjectMotionState* motionState) { motionState->clearIncomingDirtyFlags(); } -void PhysicsEngine::removeObject(ObjectMotionState* object) { +// private +void PhysicsEngine::removeObjectFromDynamicsWorld(ObjectMotionState* object) { // wake up anything touching this object bump(object); removeContacts(object); @@ -154,38 +156,34 @@ void PhysicsEngine::removeObject(ObjectMotionState* object) { _dynamicsWorld->removeRigidBody(body); } -void PhysicsEngine::deleteObjects(const VectorOfMotionStates& objects) { +void PhysicsEngine::removeObjects(const VectorOfMotionStates& objects) { for (auto object : objects) { - removeObject(object); + removeObjectFromDynamicsWorld(object); // NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it. btRigidBody* body = object->getRigidBody(); object->setRigidBody(nullptr); body->setMotionState(nullptr); delete body; - object->releaseShape(); - delete object; } } // Same as above, but takes a Set instead of a Vector. Should only be called during teardown. -void PhysicsEngine::deleteObjects(const SetOfMotionStates& objects) { +void PhysicsEngine::removeObjects(const SetOfMotionStates& objects) { for (auto object : objects) { btRigidBody* body = object->getRigidBody(); - removeObject(object); + removeObjectFromDynamicsWorld(object); // NOTE: setRigidBody() modifies body->m_userPointer so we should clear the MotionState's body BEFORE deleting it. object->setRigidBody(nullptr); body->setMotionState(nullptr); delete body; - object->releaseShape(); - delete object; } } void PhysicsEngine::addObjects(const VectorOfMotionStates& objects) { for (auto object : objects) { - addObject(object); + addObjectToDynamicsWorld(object); } } @@ -211,8 +209,8 @@ VectorOfMotionStates PhysicsEngine::changeObjects(const VectorOfMotionStates& ob } void PhysicsEngine::reinsertObject(ObjectMotionState* object) { - removeObject(object); - addObject(object); + removeObjectFromDynamicsWorld(object); + addObjectToDynamicsWorld(object); } void PhysicsEngine::removeContacts(ObjectMotionState* motionState) { diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 05032ccae2..0ca9b2aca8 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -54,11 +54,9 @@ public: void setSessionUUID(const QUuid& sessionID) { _sessionID = sessionID; } const QUuid& getSessionID() const { return _sessionID; } - void addObject(ObjectMotionState* motionState); - void removeObject(ObjectMotionState* motionState); + void removeObjects(const VectorOfMotionStates& objects); + void removeObjects(const SetOfMotionStates& objects); // only called during teardown - void deleteObjects(const VectorOfMotionStates& objects); - void deleteObjects(const SetOfMotionStates& objects); // only called during teardown void addObjects(const VectorOfMotionStates& objects); VectorOfMotionStates changeObjects(const VectorOfMotionStates& objects); void reinsertObject(ObjectMotionState* object); @@ -86,8 +84,6 @@ public: /// \brief call bump on any objects that touch the object corresponding to motionState void bump(ObjectMotionState* motionState); - void removeRigidBody(btRigidBody* body); - void setCharacterController(CharacterController* character); void dumpNextStats() { _dumpNextStats = true; } @@ -100,6 +96,9 @@ public: void forEachAction(std::function actor); private: + void addObjectToDynamicsWorld(ObjectMotionState* motionState); + void removeObjectFromDynamicsWorld(ObjectMotionState* motionState); + void removeContacts(ObjectMotionState* motionState); void doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB); @@ -116,7 +115,6 @@ private: ContactMap _contactMap; uint32_t _numContactFrames = 0; - uint32_t _lastNumSubstepsAtUpdateInternal = 0; /// character collisions CharacterController* _myAvatarController; diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index 27e326fcba..4429f49346 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -79,6 +79,7 @@ const DisplayPluginList& PluginManager::getDisplayPlugins() { auto& container = PluginContainer::getInstance(); for (auto plugin : displayPlugins) { plugin->setContainer(&container); + plugin->init(); } }); @@ -104,6 +105,7 @@ const InputPluginList& PluginManager::getInputPlugins() { auto& container = PluginContainer::getInstance(); for (auto plugin : inputPlugins) { plugin->setContainer(&container); + plugin->init(); } }); return inputPlugins; diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index db0e47de5e..e6892b95f4 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -819,8 +819,6 @@ model::MeshPointer DeferredLightingEffect::getSpotLightMesh() { //DEBUG: model::Mesh::Part part(0, indices, 0, model::Mesh::LINE_STRIP); _spotLightMesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(sizeof(part), (gpu::Byte*) &part), gpu::Element::PART_DRAWCALL)); - - _spotLightMesh->getVertexStream(); } return _spotLightMesh; } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index dbd3a6289f..ef9a837b27 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -95,6 +95,9 @@ const float METERS_PER_MILLIMETER = 0.01f; void Model::setScaleInternal(const glm::vec3& scale) { if (glm::distance(_scale, scale) > METERS_PER_MILLIMETER) { _scale = scale; + if (_scale.x == 0.0f || _scale.y == 0.0f || _scale.z == 0.0f) { + assert(false); + } initJointTransforms(); } } @@ -971,11 +974,14 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) { _needsUpdateClusterMatrices = true; _rig->updateAnimations(deltaTime, parentTransform); } + void Model::simulateInternal(float deltaTime) { // update the world space transforms for all joints glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset); updateRig(deltaTime, parentTransform); } + +// virtual void Model::updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation) { PerformanceTimer perfTimer("Model::updateClusterMatrices"); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index a93338e41a..3416a9b71e 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -112,7 +112,7 @@ public: bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; } virtual void simulate(float deltaTime, bool fullUpdate = true); - void updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation); + virtual void updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation); /// Returns a reference to the shared geometry. const QSharedPointer& getGeometry() const { return _geometry; } @@ -312,7 +312,7 @@ protected: // hook for derived classes to be notified when setUrl invalidates the current model. virtual void onInvalidate() {}; -private: +protected: void deleteGeometry(); void initJointTransforms(); @@ -370,7 +370,6 @@ private: bool _showCollisionHull = false; friend class ModelMeshPartPayload; -protected: RigPointer _rig; }; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index ef448e93f0..8026102478 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +51,8 @@ #include "MIDIEvent.h" +static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3"; + Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature) static int functionSignatureMetaID = qRegisterMetaType(); @@ -111,7 +114,7 @@ static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName const auto line = QString::number(engine.uncaughtExceptionLineNumber()); engine.clearExceptions(); - auto message = QString("[UncaughtException] %1 in %2:%3").arg(exception, fileName, line); + auto message = QString(SCRIPT_EXCEPTION_FORMAT).arg(exception, fileName, line); if (!backtrace.empty()) { static const auto lineSeparator = "\n "; message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator)); @@ -132,6 +135,10 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam _allScriptsMutex.lock(); _allKnownScriptEngines.insert(this); _allScriptsMutex.unlock(); + + connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) { + hadUncaughtExceptions(*this, _fileNameString); + }); } ScriptEngine::~ScriptEngine() { @@ -391,7 +398,7 @@ void ScriptEngine::init() { registerGlobalObject("Recording", recordingInterface.data()); registerGlobalObject("Assets", &_assetScriptingInterface); - + registerGlobalObject("Resources", DependencyManager::get().data()); } void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) { @@ -1296,4 +1303,4 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS entityScript.property(methodName).call(entityScript, args); } } -} \ No newline at end of file +} diff --git a/libraries/shared/src/AACube.cpp b/libraries/shared/src/AACube.cpp index 32a304872c..4218826141 100644 --- a/libraries/shared/src/AACube.cpp +++ b/libraries/shared/src/AACube.cpp @@ -465,3 +465,16 @@ AABox AACube::clamp(float min, float max) const { return temp.clamp(min, max); } +AACube& AACube::operator += (const glm::vec3& point) { + glm::vec3 oldMaximumPoint = getMaximumPoint(); + _corner = glm::vec3(glm::min(_corner.x, point.x), + glm::min(_corner.y, point.y), + glm::min(_corner.z, point.z)); + + glm::vec3 scaleOld = oldMaximumPoint - _corner; + glm::vec3 scalePoint = point - _corner; + _scale = glm::max(_scale, scalePoint.x, scalePoint.y, scalePoint.z); + _scale = glm::max(_scale, scaleOld.x, scaleOld.y, scaleOld.z); + + return (*this); +} diff --git a/libraries/shared/src/AACube.h b/libraries/shared/src/AACube.h index d301207429..fbbbe9992a 100644 --- a/libraries/shared/src/AACube.h +++ b/libraries/shared/src/AACube.h @@ -64,6 +64,8 @@ public: AABox clamp(const glm::vec3& min, const glm::vec3& max) const; AABox clamp(float min, float max) const; + AACube& operator += (const glm::vec3& point); + private: glm::vec3 getClosestPointOnFace(const glm::vec3& point, BoxFace face) const; glm::vec3 getClosestPointOnFace(const glm::vec4& origin, const glm::vec4& direction, BoxFace face) const; diff --git a/libraries/shared/src/PIDController.h b/libraries/shared/src/PIDController.h index 0b2411530a..0a376872cc 100644 --- a/libraries/shared/src/PIDController.h +++ b/libraries/shared/src/PIDController.h @@ -31,7 +31,7 @@ public: float update(float measuredValue, float dt, bool resetAccumulator = false); // returns the new computedValue void setHistorySize(QString label = QString(""), int size = 0) { _history.reserve(size); _history.resize(0); _label = label; } // non-empty does logging - bool getIsLogging() { return _history.capacity(); } + bool getIsLogging() { return !_label.isEmpty(); } float getMeasuredValueSetpoint() const { return _measuredValueSetpoint; } // In normal operation (where we can easily reach setpoint), controlledValue is typcially pinned at max. // Defaults to [0, max float], but for 1/LODdistance, it might be, say, [0, 0.2 or 0.1] diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 008ac238d5..c2c35df957 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -20,6 +20,8 @@ static int vec4MetaTypeId = qRegisterMetaType(); static int vec3MetaTypeId = qRegisterMetaType(); static int qVectorVec3MetaTypeId = qRegisterMetaType>(); +static int qVectorQuatMetaTypeId = qRegisterMetaType>(); +static int qVectorBoolMetaTypeId = qRegisterMetaType>(); static int vec2MetaTypeId = qRegisterMetaType(); static int quatMetaTypeId = qRegisterMetaType(); static int xColorMetaTypeId = qRegisterMetaType(); @@ -31,6 +33,8 @@ void registerMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, vec4toScriptValue, vec4FromScriptValue); qScriptRegisterMetaType(engine, vec3toScriptValue, vec3FromScriptValue); qScriptRegisterMetaType(engine, qVectorVec3ToScriptValue, qVectorVec3FromScriptValue); + qScriptRegisterMetaType(engine, qVectorQuatToScriptValue, qVectorQuatFromScriptValue); + qScriptRegisterMetaType(engine, qVectorBoolToScriptValue, qVectorBoolFromScriptValue); qScriptRegisterMetaType(engine, qVectorFloatToScriptValue, qVectorFloatFromScriptValue); qScriptRegisterMetaType(engine, vec2toScriptValue, vec2FromScriptValue); qScriptRegisterMetaType(engine, quatToScriptValue, quatFromScriptValue); @@ -42,7 +46,7 @@ void registerMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, collisionToScriptValue, collisionFromScriptValue); qScriptRegisterMetaType(engine, quuidToScriptValue, quuidFromScriptValue); qScriptRegisterMetaType(engine, qSizeFToScriptValue, qSizeFFromScriptValue); - + qScriptRegisterMetaType(engine, aaCubeToScriptValue, aaCubeFromScriptValue); } QScriptValue vec4toScriptValue(QScriptEngine* engine, const glm::vec4& vec4) { @@ -87,6 +91,42 @@ QScriptValue qVectorVec3ToScriptValue(QScriptEngine* engine, const QVectornewObject(); + if (quat.x != quat.x || quat.y != quat.y || quat.z != quat.z || quat.w != quat.w) { + // if quat contains a NaN don't try to convert it + return obj; + } + obj.setProperty("x", quat.x); + obj.setProperty("y", quat.y); + obj.setProperty("z", quat.z); + obj.setProperty("w", quat.w); + return obj; +} + +void quatFromScriptValue(const QScriptValue &object, glm::quat &quat) { + quat.x = object.property("x").toVariant().toFloat(); + quat.y = object.property("y").toVariant().toFloat(); + quat.z = object.property("z").toVariant().toFloat(); + quat.w = object.property("w").toVariant().toFloat(); +} + +QScriptValue qVectorQuatToScriptValue(QScriptEngine* engine, const QVector& vector) { + QScriptValue array = engine->newArray(); + for (int i = 0; i < vector.size(); i++) { + array.setProperty(i, quatToScriptValue(engine, vector.at(i))); + } + return array; +} + +QScriptValue qVectorBoolToScriptValue(QScriptEngine* engine, const QVector& vector) { + QScriptValue array = engine->newArray(); + for (int i = 0; i < vector.size(); i++) { + array.setProperty(i, vector.at(i)); + } + return array; +} + QVector qVectorFloatFromScriptValue(const QScriptValue& array) { if(!array.isArray()) { return QVector(); @@ -149,7 +189,7 @@ QVector qVectorVec3FromScriptValue(const QScriptValue& array){ void qVectorVec3FromScriptValue(const QScriptValue& array, QVector& vector ) { int length = array.property("length").toInteger(); - + for (int i = 0; i < length; i++) { glm::vec3 newVec3 = glm::vec3(); vec3FromScriptValue(array.property(i), newVec3); @@ -157,6 +197,46 @@ void qVectorVec3FromScriptValue(const QScriptValue& array, QVector& v } } +QVector qVectorQuatFromScriptValue(const QScriptValue& array){ + QVector newVector; + int length = array.property("length").toInteger(); + + for (int i = 0; i < length; i++) { + glm::quat newQuat = glm::quat(); + quatFromScriptValue(array.property(i), newQuat); + newVector << newQuat; + } + return newVector; +} + +void qVectorQuatFromScriptValue(const QScriptValue& array, QVector& vector ) { + int length = array.property("length").toInteger(); + + for (int i = 0; i < length; i++) { + glm::quat newQuat = glm::quat(); + quatFromScriptValue(array.property(i), newQuat); + vector << newQuat; + } +} + +QVector qVectorBoolFromScriptValue(const QScriptValue& array){ + QVector newVector; + int length = array.property("length").toInteger(); + + for (int i = 0; i < length; i++) { + newVector << array.property(i).toBool(); + } + return newVector; +} + +void qVectorBoolFromScriptValue(const QScriptValue& array, QVector& vector ) { + int length = array.property("length").toInteger(); + + for (int i = 0; i < length; i++) { + vector << array.property(i).toBool(); + } +} + QScriptValue vec2toScriptValue(QScriptEngine* engine, const glm::vec2 &vec2) { QScriptValue obj = engine->newObject(); obj.setProperty("x", vec2.x); @@ -169,22 +249,6 @@ void vec2FromScriptValue(const QScriptValue &object, glm::vec2 &vec2) { vec2.y = object.property("y").toVariant().toFloat(); } -QScriptValue quatToScriptValue(QScriptEngine* engine, const glm::quat& quat) { - QScriptValue obj = engine->newObject(); - obj.setProperty("x", quat.x); - obj.setProperty("y", quat.y); - obj.setProperty("z", quat.z); - obj.setProperty("w", quat.w); - return obj; -} - -void quatFromScriptValue(const QScriptValue &object, glm::quat& quat) { - quat.x = object.property("x").toVariant().toFloat(); - quat.y = object.property("y").toVariant().toFloat(); - quat.z = object.property("z").toVariant().toFloat(); - quat.w = object.property("w").toVariant().toFloat(); -} - QScriptValue qRectToScriptValue(QScriptEngine* engine, const QRect& rect) { QScriptValue obj = engine->newObject(); obj.setProperty("x", rect.x()); @@ -238,6 +302,26 @@ QScriptValue qColorToScriptValue(QScriptEngine* engine, const QColor& color) { return object; } +QScriptValue aaCubeToScriptValue(QScriptEngine* engine, const AACube& aaCube) { + QScriptValue obj = engine->newObject(); + const glm::vec3& corner = aaCube.getCorner(); + obj.setProperty("x", corner.x); + obj.setProperty("y", corner.y); + obj.setProperty("z", corner.z); + obj.setProperty("scale", aaCube.getScale()); + return obj; +} + +void aaCubeFromScriptValue(const QScriptValue &object, AACube& aaCube) { + glm::vec3 corner; + corner.x = object.property("x").toVariant().toFloat(); + corner.y = object.property("y").toVariant().toFloat(); + corner.z = object.property("z").toVariant().toFloat(); + float scale = object.property("scale").toVariant().toFloat(); + + aaCube.setBox(corner, scale); +} + void qColorFromScriptValue(const QScriptValue& object, QColor& color) { if (object.isNumber()) { color.setRgb(object.toUInt32()); diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index cd1e3b0d3e..81314cef69 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -18,6 +18,7 @@ #include #include +#include "AACube.h" #include "SharedUtil.h" class QColor; @@ -30,6 +31,7 @@ Q_DECLARE_METATYPE(glm::quat) Q_DECLARE_METATYPE(xColor) Q_DECLARE_METATYPE(QVector) Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(AACube) void registerMetaTypes(QScriptEngine* engine); @@ -61,12 +63,23 @@ QScriptValue qVectorVec3ToScriptValue(QScriptEngine* engine, const QVector& vector); QVector qVectorVec3FromScriptValue(const QScriptValue& array); +QScriptValue qVectorQuatToScriptValue(QScriptEngine* engine, const QVector& vector); +void qVectorQuatFromScriptValue(const QScriptValue& array, QVector& vector); +QVector qVectorQuatFromScriptValue(const QScriptValue& array); + +QScriptValue qVectorBoolToScriptValue(QScriptEngine* engine, const QVector& vector); +void qVectorBoolFromScriptValue(const QScriptValue& array, QVector& vector); +QVector qVectorBoolFromScriptValue(const QScriptValue& array); + QScriptValue qVectorFloatToScriptValue(QScriptEngine* engine, const QVector& vector); void qVectorFloatFromScriptValue(const QScriptValue& array, QVector& vector); QVector qVectorFloatFromScriptValue(const QScriptValue& array); QVector qVectorQUuidFromScriptValue(const QScriptValue& array); +QScriptValue aaCubeToScriptValue(QScriptEngine* engine, const AACube& aaCube); +void aaCubeFromScriptValue(const QScriptValue &object, AACube& aaCube); + class PickRay { public: PickRay() : origin(0.0f), direction(0.0f) { } diff --git a/libraries/shared/src/SpatialParentFinder.h b/libraries/shared/src/SpatialParentFinder.h index 936d497eae..9b49490fa5 100644 --- a/libraries/shared/src/SpatialParentFinder.h +++ b/libraries/shared/src/SpatialParentFinder.h @@ -31,7 +31,7 @@ public: SpatialParentFinder() { } virtual ~SpatialParentFinder() { } - virtual SpatiallyNestableWeakPointer find(QUuid parentID) const = 0; + virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success) const = 0; }; #endif // hifi_SpatialParentFinder_h diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index 98d127cc1e..07d5ddeeb0 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -14,6 +14,7 @@ #include "DependencyManager.h" #include "SpatiallyNestable.h" +const float defaultAACubeSize = 1.0f; SpatiallyNestable::SpatiallyNestable(NestableType nestableType, QUuid id) : _nestableType(nestableType), @@ -24,21 +25,25 @@ SpatiallyNestable::SpatiallyNestable(NestableType nestableType, QUuid id) : _transform.setRotation(glm::quat()); } -Transform SpatiallyNestable::getParentTransform() const { +Transform SpatiallyNestable::getParentTransform(bool& success) const { Transform result; - SpatiallyNestablePointer parent = getParentPointer(); + SpatiallyNestablePointer parent = getParentPointer(success); + if (!success) { + return result; + } if (parent) { - Transform parentTransform = parent->getTransform(_parentJointIndex); - result = parentTransform.setScale(1.0f); + Transform parentTransform = parent->getTransform(_parentJointIndex, success); + result = parentTransform.setScale(1.0f); // TODO: scaling } return result; } -SpatiallyNestablePointer SpatiallyNestable::getParentPointer() const { +SpatiallyNestablePointer SpatiallyNestable::getParentPointer(bool& success) const { SpatiallyNestablePointer parent = _parent.lock(); if (!parent && _parentID.isNull()) { // no parent + success = true; return nullptr; } @@ -48,6 +53,7 @@ SpatiallyNestablePointer SpatiallyNestable::getParentPointer() const { parent->beParentOfChild(getThisPointer()); _parentKnowsMe = true; } + success = true; return parent; } @@ -63,9 +69,14 @@ SpatiallyNestablePointer SpatiallyNestable::getParentPointer() const { // we have a _parentID but no parent pointer, or our parent pointer was to the wrong thing QSharedPointer parentFinder = DependencyManager::get(); if (!parentFinder) { + success = false; return nullptr; } - _parent = parentFinder->find(_parentID); + _parent = parentFinder->find(_parentID, success); + if (!success) { + return nullptr; + } + parent = _parent.lock(); if (parent) { parent->beParentOfChild(thisPointer); @@ -73,7 +84,9 @@ SpatiallyNestablePointer SpatiallyNestable::getParentPointer() const { } if (parent || _parentID.isNull()) { - thisPointer->parentChanged(); + success = true; + } else { + success = false; } return parent; @@ -96,131 +109,255 @@ void SpatiallyNestable::setParentID(const QUuid& parentID) { _parentID = parentID; _parentKnowsMe = false; } - parentChanged(); } -glm::vec3 SpatiallyNestable::worldToLocal(const glm::vec3& position, const QUuid& parentID, int parentJointIndex) { +void SpatiallyNestable::setParentJointIndex(quint16 parentJointIndex) { + _parentJointIndex = parentJointIndex; +} + +glm::vec3 SpatiallyNestable::worldToLocal(const glm::vec3& position, + const QUuid& parentID, int parentJointIndex, + bool& success) { + Transform result; QSharedPointer parentFinder = DependencyManager::get(); - Transform parentTransform; - if (parentFinder) { - auto parentWP = parentFinder->find(parentID); - auto parent = parentWP.lock(); - if (parent) { - parentTransform = parent->getTransform(parentJointIndex); - parentTransform.setScale(1.0f); - } + if (!parentFinder) { + success = false; + return glm::vec3(0.0f); } + Transform parentTransform; + auto parentWP = parentFinder->find(parentID, success); + if (!success) { + return glm::vec3(0.0f); + } + + auto parent = parentWP.lock(); + if (!parentID.isNull() && !parent) { + success = false; + return glm::vec3(0.0f); + } + + if (parent) { + parentTransform = parent->getTransform(parentJointIndex, success); + if (!success) { + return glm::vec3(0.0f); + } + parentTransform.setScale(1.0f); // TODO: scale + } + success = true; + Transform positionTransform; positionTransform.setTranslation(position); Transform myWorldTransform; Transform::mult(myWorldTransform, parentTransform, positionTransform); - myWorldTransform.setTranslation(position); - Transform result; Transform::inverseMult(result, parentTransform, myWorldTransform); return result.getTranslation(); } -glm::quat SpatiallyNestable::worldToLocal(const glm::quat& orientation, const QUuid& parentID, int parentJointIndex) { +glm::quat SpatiallyNestable::worldToLocal(const glm::quat& orientation, + const QUuid& parentID, int parentJointIndex, + bool& success) { + Transform result; QSharedPointer parentFinder = DependencyManager::get(); - Transform parentTransform; - if (parentFinder) { - auto parentWP = parentFinder->find(parentID); - auto parent = parentWP.lock(); - if (parent) { - parentTransform = parent->getTransform(parentJointIndex); - parentTransform.setScale(1.0f); - } + if (!parentFinder) { + success = false; + return glm::quat(); } + Transform parentTransform; + auto parentWP = parentFinder->find(parentID, success); + if (!success) { + return glm::quat(); + } + + auto parent = parentWP.lock(); + if (!parentID.isNull() && !parent) { + success = false; + return glm::quat(); + } + + if (parent) { + parentTransform = parent->getTransform(parentJointIndex, success); + if (!success) { + return glm::quat(); + } + parentTransform.setScale(1.0f); // TODO: scale + } + success = true; + Transform orientationTransform; orientationTransform.setRotation(orientation); Transform myWorldTransform; Transform::mult(myWorldTransform, parentTransform, orientationTransform); myWorldTransform.setRotation(orientation); - Transform result; Transform::inverseMult(result, parentTransform, myWorldTransform); return result.getRotation(); } -glm::vec3 SpatiallyNestable::localToWorld(const glm::vec3& position, const QUuid& parentID, int parentJointIndex) { +glm::vec3 SpatiallyNestable::localToWorld(const glm::vec3& position, + const QUuid& parentID, int parentJointIndex, + bool& success) { + Transform result; QSharedPointer parentFinder = DependencyManager::get(); - Transform parentTransform; - if (parentFinder) { - auto parentWP = parentFinder->find(parentID); - auto parent = parentWP.lock(); - if (parent) { - parentTransform = parent->getTransform(parentJointIndex); - parentTransform.setScale(1.0f); - } + if (!parentFinder) { + success = false; + return glm::vec3(0.0f); } + + Transform parentTransform; + auto parentWP = parentFinder->find(parentID, success); + if (!success) { + return glm::vec3(0.0f); + } + + auto parent = parentWP.lock(); + if (!parentID.isNull() && !parent) { + success = false; + return glm::vec3(0.0f); + } + + if (parent) { + parentTransform = parent->getTransform(parentJointIndex, success); + if (!success) { + return glm::vec3(0.0f); + } + parentTransform.setScale(1.0f); // TODO: scale + } + success = true; + Transform positionTransform; positionTransform.setTranslation(position); - Transform result; Transform::mult(result, parentTransform, positionTransform); return result.getTranslation(); } -glm::quat SpatiallyNestable::localToWorld(const glm::quat& orientation, const QUuid& parentID, int parentJointIndex) { +glm::quat SpatiallyNestable::localToWorld(const glm::quat& orientation, + const QUuid& parentID, int parentJointIndex, + bool& success) { + Transform result; QSharedPointer parentFinder = DependencyManager::get(); - Transform parentTransform; - if (parentFinder) { - auto parentWP = parentFinder->find(parentID); - auto parent = parentWP.lock(); - if (parent) { - parentTransform = parent->getTransform(parentJointIndex); - parentTransform.setScale(1.0f); - } + if (!parentFinder) { + success = false; + return glm::quat(); } + + Transform parentTransform; + auto parentWP = parentFinder->find(parentID, success); + if (!success) { + return glm::quat(); + } + + auto parent = parentWP.lock(); + if (!parentID.isNull() && !parent) { + success = false; + return glm::quat(); + } + + if (parent) { + parentTransform = parent->getTransform(parentJointIndex, success); + if (!success) { + return glm::quat(); + } + parentTransform.setScale(1.0f); + } + success = true; + Transform orientationTransform; orientationTransform.setRotation(orientation); - Transform result; Transform::mult(result, parentTransform, orientationTransform); return result.getRotation(); } +glm::vec3 SpatiallyNestable::getPosition(bool& success) const { + return getTransform(success).getTranslation(); +} + glm::vec3 SpatiallyNestable::getPosition() const { - return getTransform().getTranslation(); + bool success; + auto result = getPosition(success); + #ifdef WANT_DEBUG + if (!success) { + qDebug() << "Warning -- getPosition failed" << getID(); + } + #endif + return result; } -glm::vec3 SpatiallyNestable::getPosition(int jointIndex) const { - return getTransform(jointIndex).getTranslation(); +glm::vec3 SpatiallyNestable::getPosition(int jointIndex, bool& success) const { + return getTransform(jointIndex, success).getTranslation(); } -void SpatiallyNestable::setPosition(const glm::vec3& position) { - Transform parentTransform = getParentTransform(); +void SpatiallyNestable::setPosition(const glm::vec3& position, bool& success) { + Transform parentTransform = getParentTransform(success); Transform myWorldTransform; _transformLock.withWriteLock([&] { Transform::mult(myWorldTransform, parentTransform, _transform); myWorldTransform.setTranslation(position); Transform::inverseMult(_transform, parentTransform, myWorldTransform); }); - locationChanged(); + if (success) { + locationChanged(); + } else { + qDebug() << "setPosition failed for" << getID(); + } +} + +void SpatiallyNestable::setPosition(const glm::vec3& position) { + bool success; + setPosition(position, success); + #ifdef WANT_DEBUG + if (!success) { + qDebug() << "Warning -- setPosition failed" << getID(); + } + #endif +} + +glm::quat SpatiallyNestable::getOrientation(bool& success) const { + return getTransform(success).getRotation(); } glm::quat SpatiallyNestable::getOrientation() const { - return getTransform().getRotation(); + bool success; + auto result = getOrientation(success); + #ifdef WANT_DEBUG + if (!success) { + qDebug() << "Warning -- getOrientation failed" << getID(); + } + #endif + return result; } -glm::quat SpatiallyNestable::getOrientation(int jointIndex) const { - return getTransform(jointIndex).getRotation(); +glm::quat SpatiallyNestable::getOrientation(int jointIndex, bool& success) const { + return getTransform(jointIndex, success).getRotation(); } -void SpatiallyNestable::setOrientation(const glm::quat& orientation) { - Transform parentTransform = getParentTransform(); +void SpatiallyNestable::setOrientation(const glm::quat& orientation, bool& success) { + Transform parentTransform = getParentTransform(success); Transform myWorldTransform; _transformLock.withWriteLock([&] { Transform::mult(myWorldTransform, parentTransform, _transform); myWorldTransform.setRotation(orientation); Transform::inverseMult(_transform, parentTransform, myWorldTransform); }); - locationChanged(); + if (success) { + locationChanged(); + } } -const Transform SpatiallyNestable::getTransform() const { +void SpatiallyNestable::setOrientation(const glm::quat& orientation) { + bool success; + setOrientation(orientation, success); + #ifdef WANT_DEBUG + if (!success) { + qDebug() << "Warning -- setOrientation failed" << getID(); + } + #endif +} + +const Transform SpatiallyNestable::getTransform(bool& success) const { // return a world-space transform for this object's location - Transform parentTransform = getParentTransform(); + Transform parentTransform = getParentTransform(success); Transform result; _transformLock.withReadLock([&] { Transform::mult(result, parentTransform, _transform); @@ -228,25 +365,34 @@ const Transform SpatiallyNestable::getTransform() const { return result; } -const Transform SpatiallyNestable::getTransform(int jointIndex) const { +const Transform SpatiallyNestable::getTransform(int jointIndex, bool& success) const { // this returns the world-space transform for this object. It finds its parent's transform (which may // cause this object's parent to query its parent, etc) and multiplies this object's local transform onto it. - Transform worldTransform = getTransform(); - Transform jointInObjectFrame = getAbsoluteJointTransformInObjectFrame(jointIndex); Transform jointInWorldFrame; + + Transform worldTransform = getTransform(success); + if (!success) { + return jointInWorldFrame; + } + + Transform jointInObjectFrame = getAbsoluteJointTransformInObjectFrame(jointIndex); Transform::mult(jointInWorldFrame, worldTransform, jointInObjectFrame); + success = true; return jointInWorldFrame; } -void SpatiallyNestable::setTransform(const Transform& transform) { - Transform parentTransform = getParentTransform(); +void SpatiallyNestable::setTransform(const Transform& transform, bool& success) { + Transform parentTransform = getParentTransform(success); _transformLock.withWriteLock([&] { Transform::inverseMult(_transform, parentTransform, transform); }); - locationChanged(); + if (success) { + locationChanged(); + } } glm::vec3 SpatiallyNestable::getScale() const { + // TODO: scale glm::vec3 result; _transformLock.withReadLock([&] { result = _transform.getScale(); @@ -255,11 +401,12 @@ glm::vec3 SpatiallyNestable::getScale() const { } glm::vec3 SpatiallyNestable::getScale(int jointIndex) const { - // XXX ... something with joints + // TODO: scale return getScale(); } void SpatiallyNestable::setScale(const glm::vec3& scale) { + // TODO: scale _transformLock.withWriteLock([&] { _transform.setScale(scale); }); @@ -312,6 +459,7 @@ void SpatiallyNestable::setLocalOrientation(const glm::quat& orientation) { } glm::vec3 SpatiallyNestable::getLocalScale() const { + // TODO: scale glm::vec3 result; _transformLock.withReadLock([&] { result = _transform.getScale(); @@ -320,6 +468,7 @@ glm::vec3 SpatiallyNestable::getLocalScale() const { } void SpatiallyNestable::setLocalScale(const glm::vec3& scale) { + // TODO: scale _transformLock.withWriteLock([&] { _transform.setScale(scale); }); @@ -380,3 +529,83 @@ void SpatiallyNestable::locationChanged() { object->locationChanged(); }); } + +AACube SpatiallyNestable::getMaximumAACube(bool& success) const { + return AACube(getPosition(success) - glm::vec3(defaultAACubeSize / 2.0f), defaultAACubeSize); +} + +void SpatiallyNestable::setQueryAACube(const AACube& queryAACube) { + _queryAACube = queryAACube; + if (queryAACube.getScale() > 0.0f) { + _queryAACubeSet = true; + } +} + +bool SpatiallyNestable::queryAABoxNeedsUpdate() const { + bool success; + AACube currentAACube = getMaximumAACube(success); + if (!success) { + qDebug() << "can't getMaximumAACube for" << getID(); + return false; + } + + // make sure children are still in their boxes, also. + bool childNeedsUpdate = false; + getThisPointer()->forEachDescendant([&](SpatiallyNestablePointer descendant) { + if (!childNeedsUpdate && descendant->queryAABoxNeedsUpdate()) { + childNeedsUpdate = true; + } + }); + if (childNeedsUpdate) { + return true; + } + + if (_queryAACubeSet && _queryAACube.contains(currentAACube)) { + return false; + } + + return true; +} + +bool SpatiallyNestable::computePuffedQueryAACube() { + if (!queryAABoxNeedsUpdate()) { + return false; + } + bool success; + AACube currentAACube = getMaximumAACube(success); + // make an AACube with edges thrice as long and centered on the object + _queryAACube = AACube(currentAACube.getCorner() - glm::vec3(currentAACube.getScale()), currentAACube.getScale() * 3.0f); + _queryAACubeSet = true; + + getThisPointer()->forEachDescendant([&](SpatiallyNestablePointer descendant) { + bool success; + AACube descendantAACube = descendant->getQueryAACube(success); + if (success) { + if (_queryAACube.contains(descendantAACube)) { + return; + } + _queryAACube += descendantAACube.getMinimumPoint(); + _queryAACube += descendantAACube.getMaximumPoint(); + } + }); + + return true; +} + +AACube SpatiallyNestable::getQueryAACube(bool& success) const { + if (_queryAACubeSet) { + success = true; + return _queryAACube; + } + success = false; + return AACube(getPosition(success) - glm::vec3(defaultAACubeSize / 2.0f), defaultAACubeSize); +} + +AACube SpatiallyNestable::getQueryAACube() const { + bool success; + auto result = getQueryAACube(success); + if (!success) { + qDebug() << "getQueryAACube failed for" << getID(); + } + return result; +} diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index 7a43e2a563..dc38671091 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -15,6 +15,7 @@ #include #include "Transform.h" +#include "AACube.h" #include "SpatialParentFinder.h" #include "shared/ReadWriteLockable.h" @@ -38,37 +39,49 @@ public: virtual const QUuid& getID() const { return _id; } virtual void setID(const QUuid& id) { _id = id; } - virtual const QUuid getParentID() const { return _parentID; } + virtual QUuid getParentID() const { return _parentID; } virtual void setParentID(const QUuid& parentID); virtual quint16 getParentJointIndex() const { return _parentJointIndex; } - virtual void setParentJointIndex(quint16 parentJointIndex) { _parentJointIndex = parentJointIndex; } + virtual void setParentJointIndex(quint16 parentJointIndex); - static glm::vec3 worldToLocal(const glm::vec3& position, const QUuid& parentID, int parentJointIndex); - static glm::quat worldToLocal(const glm::quat& orientation, const QUuid& parentID, int parentJointIndex); + static glm::vec3 worldToLocal(const glm::vec3& position, const QUuid& parentID, int parentJointIndex, bool& success); + static glm::quat worldToLocal(const glm::quat& orientation, const QUuid& parentID, int parentJointIndex, bool& success); - static glm::vec3 localToWorld(const glm::vec3& position, const QUuid& parentID, int parentJointIndex); - static glm::quat localToWorld(const glm::quat& orientation, const QUuid& parentID, int parentJointIndex); + static glm::vec3 localToWorld(const glm::vec3& position, const QUuid& parentID, int parentJointIndex, bool& success); + static glm::quat localToWorld(const glm::quat& orientation, const QUuid& parentID, int parentJointIndex, bool& success); // world frame - virtual const Transform getTransform() const; - virtual void setTransform(const Transform& transform); + virtual const Transform getTransform(bool& success) const; + virtual void setTransform(const Transform& transform, bool& success); - virtual Transform getParentTransform() const; + virtual Transform getParentTransform(bool& success) const; + virtual glm::vec3 getPosition(bool& success) const; virtual glm::vec3 getPosition() const; + virtual void setPosition(const glm::vec3& position, bool& success); virtual void setPosition(const glm::vec3& position); + virtual glm::quat getOrientation(bool& success) const; virtual glm::quat getOrientation() const; - virtual glm::quat getOrientation(int jointIndex) const; + virtual glm::quat getOrientation(int jointIndex, bool& success) const; + virtual void setOrientation(const glm::quat& orientation, bool& success); virtual void setOrientation(const glm::quat& orientation); + virtual AACube getMaximumAACube(bool& success) const; + virtual bool computePuffedQueryAACube(); + + virtual void setQueryAACube(const AACube& queryAACube); + virtual bool queryAABoxNeedsUpdate() const; + virtual AACube getQueryAACube(bool& success) const; + virtual AACube getQueryAACube() const; + virtual glm::vec3 getScale() const; virtual void setScale(const glm::vec3& scale); // get world-frame values for a specific joint - virtual const Transform getTransform(int jointIndex) const; - virtual glm::vec3 getPosition(int jointIndex) const; + virtual const Transform getTransform(int jointIndex, bool& success) const; + virtual glm::vec3 getPosition(int jointIndex, bool& success) const; virtual glm::vec3 getScale(int jointIndex) const; // object's parent's frame @@ -89,17 +102,28 @@ public: // this object's frame virtual const Transform getAbsoluteJointTransformInObjectFrame(int jointIndex) const; - virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const { assert(false); return glm::quat(); } - virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const { assert(false); return glm::vec3(); } - + virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const = 0; + virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const = 0; + virtual bool setAbsoluteJointRotationInObjectFrame(int index, const glm::quat& rotation) = 0; + virtual bool setAbsoluteJointTranslationInObjectFrame(int index, const glm::vec3& translation) = 0; + SpatiallyNestablePointer getThisPointer() const; + void markAncestorMissing(bool value) { _missingAncestor = value; } + bool getAncestorMissing() { return _missingAncestor; } + + void forEachChild(std::function actor); + void forEachDescendant(std::function actor); + + void die() { _isDead = true; } + bool isDead() const { return _isDead; } + protected: const NestableType _nestableType; // EntityItem or an AvatarData QUuid _id; QUuid _parentID; // what is this thing's transform relative to? quint16 _parentJointIndex { 0 }; // which joint of the parent is this relative to? - SpatiallyNestablePointer getParentPointer() const; + SpatiallyNestablePointer getParentPointer(bool& success) const; mutable SpatiallyNestableWeakPointer _parent; virtual void beParentOfChild(SpatiallyNestablePointer newChild) const; @@ -108,17 +132,20 @@ protected: mutable ReadWriteLockable _childrenLock; mutable QHash _children; - virtual void parentChanged() {} // called when parent pointer is updated virtual void locationChanged(); // called when a this object's location has changed virtual void dimensionsChanged() {} // called when a this object's dimensions have changed - void forEachChild(std::function actor); - void forEachDescendant(std::function actor); + // _queryAACube is used to decide where something lives in the octree + mutable AACube _queryAACube; + mutable bool _queryAACubeSet { false }; + + bool _missingAncestor { false }; private: mutable ReadWriteLockable _transformLock; Transform _transform; // this is to be combined with parent's world-transform to produce this' world-transform. - mutable bool _parentKnowsMe = false; + mutable bool _parentKnowsMe { false }; + bool _isDead { false }; }; diff --git a/libraries/shared/src/ThreadSafeValueCache.h b/libraries/shared/src/ThreadSafeValueCache.h new file mode 100644 index 0000000000..e4e78ca3d7 --- /dev/null +++ b/libraries/shared/src/ThreadSafeValueCache.h @@ -0,0 +1,49 @@ +// +// ThreadSafeValueCache.h +// interface/src/avatar +// +// Copyright 2012 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ThreadSafeValueCache_h +#define hifi_ThreadSafeValueCache_h + +#include + +// Helper class for for sharing a value type between threads. +// It allows many threads to get or set a value atomically. +// This provides cache semantics, any get will return the last set value. +// +// For example: This can be used to copy values between C++ code running on the application thread +// and JavaScript which is running on a different thread. + +template +class ThreadSafeValueCache { +public: + ThreadSafeValueCache(const T& v) : _value { v } {} + + // returns atomic copy of the cached value. + T get() const { + std::lock_guard guard(_mutex); + return _value; + } + + // will reflect copy of value into the cache. + void set(const T& v) { + std::lock_guard guard(_mutex); + _value = v; + } + +private: + mutable std::mutex _mutex; + T _value; + + // no copies + ThreadSafeValueCache(const ThreadSafeValueCache&) = delete; + ThreadSafeValueCache& operator=(const ThreadSafeValueCache&) = delete; +}; + +#endif // #define hifi_ThreadSafeValueCache_h diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index a1f00ab5ad..67e6b089d6 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -9,10 +9,13 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "OffscreenUi.h" -#include -#include -#include -#include + +#include +#include + +#include +#include + #include "ErrorDialog.h" #include "MessageDialog.h" @@ -27,7 +30,62 @@ public: } }; +class OffscreenFlags : public QObject { + Q_OBJECT + Q_PROPERTY(bool navigationFocused READ isNavigationFocused WRITE setNavigationFocused NOTIFY navigationFocusedChanged) +public: + + OffscreenFlags(QObject* parent = nullptr) : QObject(parent) {} + bool isNavigationFocused() const { return _navigationFocused; } + void setNavigationFocused(bool focused) { + if (_navigationFocused != focused) { + _navigationFocused = focused; + emit navigationFocusedChanged(); + } + } + +signals: + void navigationFocusedChanged(); + +private: + bool _navigationFocused { false }; +}; + +class UrlHandler : public QObject { + Q_OBJECT +public: + Q_INVOKABLE bool canHandleUrl(const QString& url) { + static auto handler = dynamic_cast(qApp); + return handler->canAcceptURL(url); + } + + Q_INVOKABLE bool handleUrl(const QString& url) { + static auto handler = dynamic_cast(qApp); + return handler->acceptURL(url); + } + + // FIXME hack for authentication, remove when we migrate to Qt 5.6 + Q_INVOKABLE QString fixupUrl(const QString& originalUrl) { + static const QString ACCESS_TOKEN_PARAMETER = "access_token"; + static const QString ALLOWED_HOST = "metaverse.highfidelity.com"; + QString result = originalUrl; + QUrl url(originalUrl); + QUrlQuery query(url); + if (url.host() == ALLOWED_HOST && query.allQueryItemValues(ACCESS_TOKEN_PARAMETER).empty()) { + qDebug() << "Updating URL with auth token"; + AccountManager& accountManager = AccountManager::getInstance(); + query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager.getAccountInfo().getAccessToken().token); + url.setQuery(query.query()); + result = url.toString(); + } + + return result; + } +}; + +static UrlHandler * urlHandler { nullptr }; +static OffscreenFlags* offscreenFlags { nullptr }; // This hack allows the QML UI to work with keys that are also bound as // shortcuts at the application level. However, it seems as though the @@ -58,9 +116,15 @@ OffscreenUi::OffscreenUi() { ::qmlRegisterType("Hifi", 1, 0, "Root"); } -OffscreenUi::~OffscreenUi() { -} +void OffscreenUi::create(QOpenGLContext* context) { + OffscreenQmlSurface::create(context); + auto rootContext = getRootContext(); + offscreenFlags = new OffscreenFlags(); + rootContext->setContextProperty("offscreenFlags", offscreenFlags); + urlHandler = new UrlHandler(); + rootContext->setContextProperty("urlHandler", urlHandler); +} void OffscreenUi::show(const QUrl& url, const QString& name, std::function f) { QQuickItem* item = getRootItem()->findChild(name); @@ -110,12 +174,37 @@ void OffscreenUi::information(const QString& title, const QString& text, } void OffscreenUi::question(const QString& title, const QString& text, - ButtonCallback callback, - QMessageBox::StandardButtons buttons) { - messageBox(title, text, callback, - static_cast(MessageDialog::Question), buttons); + ButtonCallback callback, + QMessageBox::StandardButtons buttons) { + + bool waiting = true; + ButtonCallback blockingCallback = [&](QMessageBox::StandardButton response){ + callback(response); // call the actual callback + waiting = false; + }; + + messageBox(title, text, blockingCallback, + static_cast(MessageDialog::Question), buttons); + + // block until the call back has been called + while (waiting) { + QCoreApplication::processEvents(); + } } +QMessageBox::StandardButton OffscreenUi::question(void* ignored, const QString& title, const QString& text, + QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) { + + QMessageBox::StandardButton result = defaultButton; + + OffscreenUi::question(title, text, [&](QMessageBox::StandardButton response){ + result = response; + }, buttons); + + return result; +} + + void OffscreenUi::warning(const QString& title, const QString& text, ButtonCallback callback, QMessageBox::StandardButtons buttons) { @@ -123,6 +212,26 @@ void OffscreenUi::warning(const QString& title, const QString& text, static_cast(MessageDialog::Warning), buttons); } +QMessageBox::StandardButton OffscreenUi::warning(void* ignored, const QString& title, const QString& text, + QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) { + + bool waiting = true; + QMessageBox::StandardButton result = defaultButton; + + OffscreenUi::warning(title, text, [&](QMessageBox::StandardButton response){ + result = response; + waiting = false; + }, buttons); + + // block until the call back has been called + while (waiting) { + QCoreApplication::processEvents(); + } + + return result; +} + + void OffscreenUi::critical(const QString& title, const QString& text, ButtonCallback callback, QMessageBox::StandardButtons buttons) { @@ -139,7 +248,14 @@ void OffscreenUi::error(const QString& text) { pDialog->setEnabled(true); } - OffscreenUi::ButtonCallback OffscreenUi::NO_OP_CALLBACK = [](QMessageBox::StandardButton) {}; +bool OffscreenUi::navigationFocused() { + return offscreenFlags->isNavigationFocused(); +} + +void OffscreenUi::setNavigationFocused(bool focused) { + offscreenFlags->setNavigationFocused(focused); +} + #include "OffscreenUi.moc" diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index be7f6b5e2e..7063d25279 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -25,10 +25,12 @@ class OffscreenUi : public OffscreenQmlSurface, public Dependency { public: OffscreenUi(); - virtual ~OffscreenUi(); + virtual void create(QOpenGLContext* context) override; void show(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); void toggle(const QUrl& url, const QString& name, std::function f = [](QQmlContext*, QObject*) {}); bool shouldSwallowShortcut(QEvent* event); + bool navigationFocused(); + void setNavigationFocused(bool focused); // Messagebox replacement functions using ButtonCallback = std::function; @@ -43,14 +45,25 @@ public: ButtonCallback callback = NO_OP_CALLBACK, QMessageBox::StandardButtons buttons = QMessageBox::Ok); + /// Note: will block until user clicks a response to the question static void question(const QString& title, const QString& text, ButtonCallback callback = NO_OP_CALLBACK, QMessageBox::StandardButtons buttons = QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No)); + /// Same design as QMessageBox::question(), will block, returns result + static QMessageBox::StandardButton question(void* ignored, const QString& title, const QString& text, + QMessageBox::StandardButtons buttons = QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No), + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); + static void warning(const QString& title, const QString& text, ButtonCallback callback = NO_OP_CALLBACK, QMessageBox::StandardButtons buttons = QMessageBox::Ok); + /// Same design as QMessageBox::warning(), will block, returns result + static QMessageBox::StandardButton warning(void* ignored, const QString& title, const QString& text, + QMessageBox::StandardButtons buttons = QMessageBox::StandardButtons(QMessageBox::Yes | QMessageBox::No), + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton); + static void critical(const QString& title, const QString& text, ButtonCallback callback = NO_OP_CALLBACK, QMessageBox::StandardButtons buttons = QMessageBox::Ok); diff --git a/libraries/ui/src/QmlWebWindowClass.cpp b/libraries/ui/src/QmlWebWindowClass.cpp index 781b8c1b76..940ba121f3 100644 --- a/libraries/ui/src/QmlWebWindowClass.cpp +++ b/libraries/ui/src/QmlWebWindowClass.cpp @@ -8,155 +8,42 @@ #include "QmlWebWindowClass.h" -#include - -#include -#include -#include #include #include #include + #include + #include #include -#include -#include -#include + +#include #include +#include #include #include #include "OffscreenUi.h" -QWebSocketServer* QmlWebWindowClass::_webChannelServer { nullptr }; -static QWebChannel webChannel; -static const uint16_t WEB_CHANNEL_PORT = 51016; -static std::atomic nextWindowId; static const char* const URL_PROPERTY = "source"; -static const char* const TITLE_PROPERTY = "title"; -static const QRegExp HIFI_URL_PATTERN { "^hifi://" }; - -void QmlScriptEventBridge::emitWebEvent(const QString& data) { - QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data)); -} - -void QmlScriptEventBridge::emitScriptEvent(const QString& data) { - QMetaObject::invokeMethod(this, "scriptEventReceived", Qt::QueuedConnection, - Q_ARG(int, _webWindow->getWindowId()), Q_ARG(QString, data)); -} - -class QmlWebTransport : public QWebChannelAbstractTransport { - Q_OBJECT -public: - QmlWebTransport(QWebSocket* webSocket) : _webSocket(webSocket) { - // Translate from the websocket layer to the webchannel layer - connect(webSocket, &QWebSocket::textMessageReceived, [this](const QString& message) { - QJsonParseError error; - QJsonDocument document = QJsonDocument::fromJson(message.toUtf8(), &error); - if (error.error || !document.isObject()) { - qWarning() << "Unable to parse incoming JSON message" << message; - return; - } - emit messageReceived(document.object(), this); - }); - } - - virtual void sendMessage(const QJsonObject &message) override { - // Translate from the webchannel layer to the websocket layer - _webSocket->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact)); - } - -private: - QWebSocket* const _webSocket; -}; - - -void QmlWebWindowClass::setupServer() { - if (!_webChannelServer) { - _webChannelServer = new QWebSocketServer("EventBridge Server", QWebSocketServer::NonSecureMode); - if (!_webChannelServer->listen(QHostAddress::LocalHost, WEB_CHANNEL_PORT)) { - qFatal("Failed to open web socket server."); - } - - QObject::connect(_webChannelServer, &QWebSocketServer::newConnection, [] { - webChannel.connectTo(new QmlWebTransport(_webChannelServer->nextPendingConnection())); - }); - } -} - -class UrlFixer : public QObject { - Q_OBJECT -public: - Q_INVOKABLE QString fixupUrl(const QString& originalUrl) { - static const QString ACCESS_TOKEN_PARAMETER = "access_token"; - static const QString ALLOWED_HOST = "metaverse.highfidelity.com"; - QString result = originalUrl; - QUrl url(originalUrl); - QUrlQuery query(url); - if (url.host() == ALLOWED_HOST && query.allQueryItemValues(ACCESS_TOKEN_PARAMETER).empty()) { - qDebug() << "Updating URL with auth token"; - AccountManager& accountManager = AccountManager::getInstance(); - query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager.getAccountInfo().getAccessToken().token); - url.setQuery(query.query()); - result = url.toString(); - } - - return result; - } -}; - -static UrlFixer URL_FIXER; // Method called by Qt scripts to create a new web window in the overlay QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { - QmlWebWindowClass* retVal { nullptr }; - const QString title = context->argument(0).toString(); - QString url = context->argument(1).toString(); - if (!url.startsWith("http") && !url.startsWith("file://") && !url.startsWith("about:")) { - url = QUrl::fromLocalFile(url).toString(); - } - const int width = std::max(100, std::min(1280, context->argument(2).toInt32()));; - const int height = std::max(100, std::min(720, context->argument(3).toInt32()));; - - - // Build the event bridge and wrapper on the main thread - QMetaObject::invokeMethod(DependencyManager::get().data(), "load", Qt::BlockingQueuedConnection, - Q_ARG(const QString&, "QmlWebWindow.qml"), - Q_ARG(std::function, [&](QQmlContext* context, QObject* object) { - setupServer(); - retVal = new QmlWebWindowClass(object); - webChannel.registerObject(url.toLower(), retVal); - context->setContextProperty("urlFixer", &URL_FIXER); - retVal->setTitle(title); - retVal->setURL(url); - retVal->setSize(width, height); - })); - connect(engine, &QScriptEngine::destroyed, retVal, &QmlWebWindowClass::deleteLater); - return engine->newQObject(retVal); + return QmlWindowClass::internalConstructor("QmlWebWindow.qml", context, engine, + [&](QQmlContext* context, QObject* object) { return new QmlWebWindowClass(object); }); } -QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) - : _windowId(++nextWindowId), _qmlWindow(qmlWindow) -{ - qDebug() << "Created window with ID " << _windowId; - Q_ASSERT(_qmlWindow); - Q_ASSERT(dynamic_cast(_qmlWindow)); +QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow) : QmlWindowClass(qmlWindow) { QObject::connect(_qmlWindow, SIGNAL(navigating(QString)), this, SLOT(handleNavigation(QString))); } void QmlWebWindowClass::handleNavigation(const QString& url) { bool handled = false; - - if (url.contains(HIFI_URL_PATTERN)) { - DependencyManager::get()->handleLookupString(url); - handled = true; - } else { - static auto handler = dynamic_cast(qApp); - if (handler) { - if (handler->canAcceptURL(url)) { - handled = handler->acceptURL(url); - } + static auto handler = dynamic_cast(qApp); + if (handler) { + if (handler->canAcceptURL(url)) { + handled = handler->acceptURL(url); } } @@ -165,80 +52,6 @@ void QmlWebWindowClass::handleNavigation(const QString& url) { } } -void QmlWebWindowClass::setVisible(bool visible) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setVisible", Qt::AutoConnection, Q_ARG(bool, visible)); - return; - } - - auto qmlWindow = asQuickItem(); - if (qmlWindow->isEnabled() != visible) { - qmlWindow->setEnabled(visible); - emit visibilityChanged(visible); - } -} - -QQuickItem* QmlWebWindowClass::asQuickItem() const { - return dynamic_cast(_qmlWindow); -} - -bool QmlWebWindowClass::isVisible() const { - if (QThread::currentThread() != thread()) { - bool result; - QMetaObject::invokeMethod(const_cast(this), "isVisible", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result)); - return result; - } - - return asQuickItem()->isEnabled(); -} - - -glm::vec2 QmlWebWindowClass::getPosition() const { - if (QThread::currentThread() != thread()) { - glm::vec2 result; - QMetaObject::invokeMethod(const_cast(this), "getPosition", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result)); - return result; - } - - return glm::vec2(asQuickItem()->x(), asQuickItem()->y()); -} - - -void QmlWebWindowClass::setPosition(const glm::vec2& position) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setPosition", Qt::QueuedConnection, Q_ARG(glm::vec2, position)); - return; - } - - asQuickItem()->setPosition(QPointF(position.x, position.y)); -} - -void QmlWebWindowClass::setPosition(int x, int y) { - setPosition(glm::vec2(x, y)); -} - -glm::vec2 QmlWebWindowClass::getSize() const { - if (QThread::currentThread() != thread()) { - glm::vec2 result; - QMetaObject::invokeMethod(const_cast(this), "getSize", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result)); - return result; - } - - return glm::vec2(asQuickItem()->width(), asQuickItem()->height()); -} - -void QmlWebWindowClass::setSize(const glm::vec2& size) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setSize", Qt::QueuedConnection, Q_ARG(glm::vec2, size)); - } - - asQuickItem()->setSize(QSizeF(size.x, size.y)); -} - -void QmlWebWindowClass::setSize(int width, int height) { - setSize(glm::vec2(width, height)); -} - QString QmlWebWindowClass::getURL() const { if (QThread::currentThread() != thread()) { QString result; @@ -254,31 +67,4 @@ void QmlWebWindowClass::setURL(const QString& urlString) { QMetaObject::invokeMethod(this, "setURL", Qt::QueuedConnection, Q_ARG(QString, urlString)); } _qmlWindow->setProperty(URL_PROPERTY, urlString); -} - - -void QmlWebWindowClass::setTitle(const QString& title) { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "setTitle", Qt::QueuedConnection, Q_ARG(QString, title)); - } - - _qmlWindow->setProperty(TITLE_PROPERTY, title); -} - -void QmlWebWindowClass::close() { - if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection); - } - _qmlWindow->setProperty("destroyOnInvisible", true); - _qmlWindow->setProperty("visible", false); - _qmlWindow->deleteLater(); -} - -void QmlWebWindowClass::hasClosed() { -} - -void QmlWebWindowClass::raise() { - // FIXME -} - -#include "QmlWebWindowClass.moc" +} \ No newline at end of file diff --git a/libraries/ui/src/QmlWebWindowClass.h b/libraries/ui/src/QmlWebWindowClass.h index 0f2531deb8..14e533c7b4 100644 --- a/libraries/ui/src/QmlWebWindowClass.h +++ b/libraries/ui/src/QmlWebWindowClass.h @@ -9,97 +9,26 @@ #ifndef hifi_ui_QmlWebWindowClass_h #define hifi_ui_QmlWebWindowClass_h -#include -#include -#include -#include - -#include - -class QScriptEngine; -class QScriptContext; -class QmlWebWindowClass; -class QWebSocketServer; -class QWebSocket; - -class QmlScriptEventBridge : public QObject { - Q_OBJECT -public: - QmlScriptEventBridge(const QmlWebWindowClass* webWindow) : _webWindow(webWindow) {} - -public slots : - void emitWebEvent(const QString& data); - void emitScriptEvent(const QString& data); - -signals: - void webEventReceived(const QString& data); - void scriptEventReceived(int windowId, const QString& data); - -private: - const QmlWebWindowClass* _webWindow { nullptr }; - QWebSocket *_socket { nullptr }; -}; +#include "QmlWindowClass.h" // FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping -class QmlWebWindowClass : public QObject { +class QmlWebWindowClass : public QmlWindowClass { Q_OBJECT - Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT) - Q_PROPERTY(int windowId READ getWindowId CONSTANT) Q_PROPERTY(QString url READ getURL CONSTANT) - Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition) - Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize) - Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged) public: static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); QmlWebWindowClass(QObject* qmlWindow); public slots: - bool isVisible() const; - void setVisible(bool visible); - - glm::vec2 getPosition() const; - void setPosition(const glm::vec2& position); - void setPosition(int x, int y); - - glm::vec2 getSize() const; - void setSize(const glm::vec2& size); - void setSize(int width, int height); - QString getURL() const; void setURL(const QString& url); - void setTitle(const QString& title); - - // Ugh.... do not want to do - Q_INVOKABLE void raise(); - Q_INVOKABLE void close(); - Q_INVOKABLE int getWindowId() const { return _windowId; }; - Q_INVOKABLE QmlScriptEventBridge* getEventBridge() const { return _eventBridge; }; - signals: - void visibilityChanged(bool visible); // Tool window void urlChanged(); - void moved(glm::vec2 position); - void resized(QSizeF size); - void closed(); private slots: - void hasClosed(); void handleNavigation(const QString& url); - -private: - static void setupServer(); - static QWebSocketServer* _webChannelServer; - - QQuickItem* asQuickItem() const; - QmlScriptEventBridge* const _eventBridge { new QmlScriptEventBridge(this) }; - - // FIXME needs to be initialized in the ctor once we have support - // for tool window panes in QML - const bool _isToolWindow { false }; - const int _windowId; - QObject* const _qmlWindow; }; #endif diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp new file mode 100644 index 0000000000..269ef6d6c9 --- /dev/null +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -0,0 +1,282 @@ +// +// Created by Bradley Austin Davis on 2015-12-15 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "QmlWindowClass.h" + +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "OffscreenUi.h" + +QWebSocketServer* QmlWindowClass::_webChannelServer { nullptr }; +static QWebChannel webChannel; +static const uint16_t WEB_CHANNEL_PORT = 51016; +static std::atomic nextWindowId; +static const char* const SOURCE_PROPERTY = "source"; +static const char* const TITLE_PROPERTY = "title"; +static const char* const WIDTH_PROPERTY = "width"; +static const char* const HEIGHT_PROPERTY = "height"; +static const char* const VISIBILE_PROPERTY = "visible"; + +void QmlScriptEventBridge::emitWebEvent(const QString& data) { + QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data)); +} + +void QmlScriptEventBridge::emitScriptEvent(const QString& data) { + QMetaObject::invokeMethod(this, "scriptEventReceived", Qt::QueuedConnection, + Q_ARG(int, _webWindow->getWindowId()), Q_ARG(QString, data)); +} + +class QmlWebTransport : public QWebChannelAbstractTransport { + Q_OBJECT +public: + QmlWebTransport(QWebSocket* webSocket) : _webSocket(webSocket) { + // Translate from the websocket layer to the webchannel layer + connect(webSocket, &QWebSocket::textMessageReceived, [this](const QString& message) { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(message.toUtf8(), &error); + if (error.error || !document.isObject()) { + qWarning() << "Unable to parse incoming JSON message" << message; + return; + } + emit messageReceived(document.object(), this); + }); + } + + virtual void sendMessage(const QJsonObject &message) override { + // Translate from the webchannel layer to the websocket layer + _webSocket->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact)); + } + +private: + QWebSocket* const _webSocket; +}; + + +void QmlWindowClass::setupServer() { + if (!_webChannelServer) { + _webChannelServer = new QWebSocketServer("EventBridge Server", QWebSocketServer::NonSecureMode); + if (!_webChannelServer->listen(QHostAddress::LocalHost, WEB_CHANNEL_PORT)) { + qFatal("Failed to open web socket server."); + } + + QObject::connect(_webChannelServer, &QWebSocketServer::newConnection, [] { + webChannel.connectTo(new QmlWebTransport(_webChannelServer->nextPendingConnection())); + }); + } +} + +QScriptValue QmlWindowClass::internalConstructor(const QString& qmlSource, + QScriptContext* context, QScriptEngine* engine, + std::function function) +{ + const auto argumentCount = context->argumentCount(); + QString url; + QString title; + int width = 100, height = 100; + bool visible = true; + if (argumentCount > 1) { + + if (!context->argument(0).isUndefined()) { + title = context->argument(0).toString(); + } + if (!context->argument(1).isUndefined()) { + url = context->argument(1).toString(); + } + if (context->argument(2).isNumber()) { + width = context->argument(2).toInt32(); + } + if (context->argument(3).isNumber()) { + height = context->argument(3).toInt32(); + } + } else { + auto argumentObject = context->argument(0); + qDebug() << argumentObject.toString(); + if (!argumentObject.property(TITLE_PROPERTY).isUndefined()) { + title = argumentObject.property(TITLE_PROPERTY).toString(); + } + if (!argumentObject.property(SOURCE_PROPERTY).isUndefined()) { + url = argumentObject.property(SOURCE_PROPERTY).toString(); + } + if (argumentObject.property(WIDTH_PROPERTY).isNumber()) { + width = argumentObject.property(WIDTH_PROPERTY).toInt32(); + } + if (argumentObject.property(HEIGHT_PROPERTY).isNumber()) { + height = argumentObject.property(HEIGHT_PROPERTY).toInt32(); + } + if (argumentObject.property(VISIBILE_PROPERTY).isBool()) { + visible = argumentObject.property(VISIBILE_PROPERTY).toBool(); + } + } + + if (!url.startsWith("http") && !url.startsWith("file://") && !url.startsWith("about:")) { + url = QUrl::fromLocalFile(url).toString(); + } + + width = std::max(100, std::min(1280, width)); + height = std::max(100, std::min(720, height)); + + QmlWindowClass* retVal{ nullptr }; + + // Build the event bridge and wrapper on the main thread + QMetaObject::invokeMethod(DependencyManager::get().data(), "load", Qt::BlockingQueuedConnection, + Q_ARG(const QString&, qmlSource), + Q_ARG(std::function, [&](QQmlContext* context, QObject* object) { + setupServer(); + retVal = function(context, object); + context->engine()->setObjectOwnership(retVal->_qmlWindow, QQmlEngine::CppOwnership); + registerObject(url.toLower(), retVal); + if (!title.isEmpty()) { + retVal->setTitle(title); + } + retVal->setSize(width, height); + object->setProperty(SOURCE_PROPERTY, url); + if (visible) { + object->setProperty("enabled", true); + } + })); + connect(engine, &QScriptEngine::destroyed, retVal, &QmlWindowClass::deleteLater); + return engine->newQObject(retVal); +} + + +// Method called by Qt scripts to create a new web window in the overlay +QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { + return internalConstructor("QmlWindow.qml", context, engine, [&](QQmlContext* context, QObject* object){ + return new QmlWindowClass(object); + }); +} + +QmlWindowClass::QmlWindowClass(QObject* qmlWindow) + : _windowId(++nextWindowId), _qmlWindow(qmlWindow) +{ + qDebug() << "Created window with ID " << _windowId; + Q_ASSERT(_qmlWindow); + Q_ASSERT(dynamic_cast(_qmlWindow)); +} + +void QmlWindowClass::registerObject(const QString& name, QObject* object) { + webChannel.registerObject(name, object); +} + +void QmlWindowClass::deregisterObject(QObject* object) { + webChannel.deregisterObject(object); +} + +void QmlWindowClass::setVisible(bool visible) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setVisible", Qt::AutoConnection, Q_ARG(bool, visible)); + return; + } + + auto qmlWindow = asQuickItem(); + if (qmlWindow->isEnabled() != visible) { + qmlWindow->setEnabled(visible); + emit visibilityChanged(visible); + } +} + +QQuickItem* QmlWindowClass::asQuickItem() const { + return dynamic_cast(_qmlWindow); +} + +bool QmlWindowClass::isVisible() const { + if (QThread::currentThread() != thread()) { + bool result; + QMetaObject::invokeMethod(const_cast(this), "isVisible", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result)); + return result; + } + + return asQuickItem()->isEnabled(); +} + + +glm::vec2 QmlWindowClass::getPosition() const { + if (QThread::currentThread() != thread()) { + glm::vec2 result; + QMetaObject::invokeMethod(const_cast(this), "getPosition", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result)); + return result; + } + + return glm::vec2(asQuickItem()->x(), asQuickItem()->y()); +} + + +void QmlWindowClass::setPosition(const glm::vec2& position) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setPosition", Qt::QueuedConnection, Q_ARG(glm::vec2, position)); + return; + } + + asQuickItem()->setPosition(QPointF(position.x, position.y)); +} + +void QmlWindowClass::setPosition(int x, int y) { + setPosition(glm::vec2(x, y)); +} + +glm::vec2 QmlWindowClass::getSize() const { + if (QThread::currentThread() != thread()) { + glm::vec2 result; + QMetaObject::invokeMethod(const_cast(this), "getSize", Qt::BlockingQueuedConnection, Q_RETURN_ARG(glm::vec2, result)); + return result; + } + + return glm::vec2(asQuickItem()->width(), asQuickItem()->height()); +} + +void QmlWindowClass::setSize(const glm::vec2& size) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setSize", Qt::QueuedConnection, Q_ARG(glm::vec2, size)); + } + + asQuickItem()->setSize(QSizeF(size.x, size.y)); +} + +void QmlWindowClass::setSize(int width, int height) { + setSize(glm::vec2(width, height)); +} + +void QmlWindowClass::setTitle(const QString& title) { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "setTitle", Qt::QueuedConnection, Q_ARG(QString, title)); + } + + _qmlWindow->setProperty(TITLE_PROPERTY, title); +} + +void QmlWindowClass::close() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection); + } + _qmlWindow->setProperty("destroyOnInvisible", true); + _qmlWindow->setProperty("visible", false); + _qmlWindow->deleteLater(); +} + +void QmlWindowClass::hasClosed() { +} + +void QmlWindowClass::raise() { + QMetaObject::invokeMethod(_qmlWindow, "raiseWindow", Qt::QueuedConnection); +} + +#include "QmlWindowClass.moc" diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h new file mode 100644 index 0000000000..41572b448d --- /dev/null +++ b/libraries/ui/src/QmlWindowClass.h @@ -0,0 +1,103 @@ +// +// Created by Bradley Austin Davis on 2015-12-15 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ui_QmlWindowClass_h +#define hifi_ui_QmlWindowClass_h + +#include +#include +#include +#include +#include + +class QScriptEngine; +class QScriptContext; +class QmlWindowClass; +class QWebSocketServer; +class QWebSocket; + +class QmlScriptEventBridge : public QObject { + Q_OBJECT +public: + QmlScriptEventBridge(const QmlWindowClass* webWindow) : _webWindow(webWindow) {} + +public slots : + void emitWebEvent(const QString& data); + void emitScriptEvent(const QString& data); + +signals: + void webEventReceived(const QString& data); + void scriptEventReceived(int windowId, const QString& data); + +private: + const QmlWindowClass* _webWindow { nullptr }; + QWebSocket *_socket { nullptr }; +}; + +// FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping +class QmlWindowClass : public QObject { + Q_OBJECT + Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT) + Q_PROPERTY(int windowId READ getWindowId CONSTANT) + Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition) + Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize) + Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibilityChanged) + +public: + static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); + QmlWindowClass(QObject* qmlWindow); + +public slots: + bool isVisible() const; + void setVisible(bool visible); + + glm::vec2 getPosition() const; + void setPosition(const glm::vec2& position); + void setPosition(int x, int y); + + glm::vec2 getSize() const; + void setSize(const glm::vec2& size); + void setSize(int width, int height); + + void setTitle(const QString& title); + + // Ugh.... do not want to do + Q_INVOKABLE void raise(); + Q_INVOKABLE void close(); + Q_INVOKABLE int getWindowId() const { return _windowId; }; + Q_INVOKABLE QmlScriptEventBridge* getEventBridge() const { return _eventBridge; }; + +signals: + void visibilityChanged(bool visible); // Tool window + void moved(glm::vec2 position); + void resized(QSizeF size); + void closed(); + +protected slots: + void hasClosed(); + +protected: + static QScriptValue internalConstructor(const QString& qmlSource, + QScriptContext* context, QScriptEngine* engine, + std::function function); + static void setupServer(); + static void registerObject(const QString& name, QObject* object); + static void deregisterObject(QObject* object); + static QWebSocketServer* _webChannelServer; + + QQuickItem* asQuickItem() const; + QmlScriptEventBridge* const _eventBridge { new QmlScriptEventBridge(this) }; + + // FIXME needs to be initialized in the ctor once we have support + // for tool window panes in QML + const bool _isToolWindow { false }; + const int _windowId; + QObject* const _qmlWindow; +}; + +#endif diff --git a/plugins/hifiSixense/src/SixenseManager.cpp b/plugins/hifiSixense/src/SixenseManager.cpp index 455fdd2b5e..3377aac14c 100644 --- a/plugins/hifiSixense/src/SixenseManager.cpp +++ b/plugins/hifiSixense/src/SixenseManager.cpp @@ -58,7 +58,7 @@ bool SixenseManager::_sixenseLoaded = false; const QString SixenseManager::NAME = "Sixense"; const QString SixenseManager::HYDRA_ID_STRING = "Razer Hydra"; -const QString MENU_PARENT = "Avatar"; +const QString MENU_PARENT = "Developer"; const QString MENU_NAME = "Sixense"; const QString MENU_PATH = MENU_PARENT + ">" + MENU_NAME; const QString TOGGLE_SMOOTH = "Smooth Sixense Movement"; @@ -73,7 +73,7 @@ bool SixenseManager::isSupported() const { #else return true; #endif - + #else return false; #endif @@ -81,7 +81,7 @@ bool SixenseManager::isSupported() const { void SixenseManager::activate() { InputPlugin::activate(); - + #ifdef HAVE_SIXENSE _container->addMenu(MENU_PATH); _container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, TOGGLE_SMOOTH, @@ -307,7 +307,7 @@ void SixenseManager::InputDevice::updateCalibration(SixenseControllerData* contr switch (_calibrationState) { case CALIBRATION_STATE_IDLE: return; - + case CALIBRATION_STATE_COMPLETE: { // compute calibration results _avatarPosition = -0.5f * (_reachLeft + _reachRight); // neck is midway between right and left hands diff --git a/plugins/hifiSixense/src/SixenseManager.h b/plugins/hifiSixense/src/SixenseManager.h index 37da7358e0..753974e996 100644 --- a/plugins/hifiSixense/src/SixenseManager.h +++ b/plugins/hifiSixense/src/SixenseManager.h @@ -98,7 +98,7 @@ private: static const QString NAME; static const QString HYDRA_ID_STRING; - + static bool _sixenseLoaded; }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1593b649a0..ffdf4b7602 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -19,8 +19,6 @@ add_custom_target("test-extensions" list(APPEND ALL_TEST_TARGETS "test-extensions") set_target_properties("test-extensions" PROPERTIES FOLDER "Tests") -message(STATUS "ALL_TEST_TARGETS = ${ALL_TEST_TARGETS}") - # Create the all-tests build target. # The dependency list (ALL_TEST_TARGETS) is generated from setup_hifi_testcase invocations in the CMakeLists.txt # files in the test subdirs. Note: since variables normally do *not* persist into parent scope, we use a hack: @@ -28,7 +26,7 @@ message(STATUS "ALL_TEST_TARGETS = ${ALL_TEST_TARGETS}") # list(APPEND ALL_TEST_TARGETS ${targets_to_add...}) # appends to a local list var (copied from parent scope) # set (ALL_TEST_TARGETS "${ALL_TEST_TARGETS}" PARENT_SCOPE) # copies this back to parent scope # -add_custom_target("all-tests" +add_custom_target("all-tests" COMMAND ctest . DEPENDS "${ALL_TEST_TARGETS}") set_target_properties("all-tests" PROPERTIES FOLDER "hidden/test-targets") diff --git a/tools/nsis/release.nsi b/tools/nsis/release.nsi index 5e711a3454..4e79749da8 100644 --- a/tools/nsis/release.nsi +++ b/tools/nsis/release.nsi @@ -24,11 +24,11 @@ !define company "$%INSTALLER_COMPANY%" !define install_directory "$%INSTALLER_DIRECTORY%" -; Executables and icons for GUI applications that will be added as shortcuts. +; Executables and icons for GUI applications that will be added as shortcuts. !define interface_exec "interface.exe" -!define stack_manager_exec "stack-manager.exe" +!define console_exec "server-console.exe" !define interface_icon "interface.ico" -!define stack_manager_icon "stack-manager.ico" +!define console_icon "server-console.ico" ; Registry entries !define regkey "Software\${install_directory}" @@ -76,7 +76,7 @@ Section /o "Default Content Set" "CONTENT" Delete "$ChosenInstallDir\content.exe" SectionEnd -Section "Registry Entries and Procotol Handler" "REGISTRY" +Section "Registry Entries and Protocol Handler" "REGISTRY" SetRegView 64 SectionIn RO WriteRegStr HKLM "${regkey}" "Install_Dir" "$ChosenInstallDir" @@ -100,36 +100,35 @@ SectionEnd Section "Base Files" "BASE" SectionIn RO SetOutPath $ChosenInstallDir - File /r /x assignment-client.* /x domain-server.* /x interface.* /x stack-manager.* "${installer_srcdir}\" + File /r /x assignment-client.* /x domain-server.* /x interface.* /x server-console.* "${installer_srcdir}\" SectionEnd -Section "HighFidelity Interface" "CLIENT" +Section "High Fidelity Interface" "CLIENT" SetOutPath $ChosenInstallDir File /a "${installer_srcdir}\interface.*" File /a "${installer_srcdir}\${interface_icon}" SectionEnd -Section "HighFidelity Server" "SERVER" +Section "High Fidelity Server" "SERVER" SetOutPath $ChosenInstallDir - File /a "${installer_srcdir}\stack-manager.*" + File /a "${installer_srcdir}\server-console.*" File /a "${installer_srcdir}\domain-server.*" File /a "${installer_srcdir}\assignment-client.*" - File /a "${installer_srcdir}\${stack_manager_icon}" SectionEnd Section "Start Menu Shortcuts" "SHORTCUTS" SetShellVarContext all CreateDirectory "${startmenu_company}" SetOutPath $ChosenInstallDir - CreateShortCut "${startmenu_company}\Client.lnk" "$ChosenInstallDir\${interface_exec}" "" "$ChosenInstallDir\${interface_icon}" - CreateShortCut "${startmenu_company}\Home Server.lnk" "$ChosenInstallDir\${stack_manager_exec}" "" "$ChosenInstallDir\${stack_manager_icon}" + CreateShortCut "${startmenu_company}\High Fidelity.lnk" "$ChosenInstallDir\${interface_exec}" "" "$ChosenInstallDir\${interface_icon}" + CreateShortCut "${startmenu_company}\Server Console.lnk" "$ChosenInstallDir\${console_exec}" "" "$ChosenInstallDir\${console_icon}" CreateShortCut "${startmenu_company}\Uninstall ${company}.lnk" "$ChosenInstallDir\${uninstaller}" SectionEnd Section "Uninstall" SetRegView 64 Delete "$INSTDIR\${uninstaller}" - Delete "$SMSTARTUP\High Fidelity Home Server.lnk" + Delete "$SMSTARTUP\High Fidelity Server Console.lnk" RMDir /r "$INSTDIR" RMDir /r "${startmenu_company}" RMDir /r "$0" @@ -170,11 +169,11 @@ Function HandleCheckBoxes ${NSD_GetState} $ServerCheckBox $ServerCheckBox_state ${If} $ServerCheckBox_state == ${BST_CHECKED} SetOutPath $ChosenInstallDir - ExecShell "" '"$ChosenInstallDir\stack-manager.exe"' + ExecShell "" '"$ChosenInstallDir\${console_exec}"' ${EndIf} ${NSD_GetState} $RunOnStartupCheckBox $RunOnStartupCheckBox_state ${If} $ServerCheckBox_state == ${BST_CHECKED} - CreateShortCut "$SMSTARTUP\High Fidelity Home Server.lnk" "$ChosenInstallDir\${stack_manager_exec}" "" "$ChosenInstallDir\${stack_manager_icon}" + CreateShortCut "$SMSTARTUP\High Fidelity Server Console.lnk" "$ChosenInstallDir\${console_exec}" "" "$ChosenInstallDir\${console_icon}" ${EndIf} ${EndIf} FunctionEnd diff --git a/unpublishedScripts/hiddenEntityReset.js b/unpublishedScripts/hiddenEntityReset.js index a53d6e721f..4352a5d562 100644 --- a/unpublishedScripts/hiddenEntityReset.js +++ b/unpublishedScripts/hiddenEntityReset.js @@ -170,10 +170,12 @@ function createRaveStick(position) { var modelURL = "http://hifi-content.s3.amazonaws.com/eric/models/raveStick.fbx"; + var rotation = Quat.fromPitchYawRollDegrees(0, 0, 0); var stick = Entities.addEntity({ type: "Model", name: "raveStick", modelURL: modelURL, + rotation: rotation, position: position, shapeType: 'box', collisionsWillMove: true, @@ -206,6 +208,66 @@ }) }); + var forwardVec = Quat.getFront(rotation); + var forwardQuat = Quat.rotationBetween(Vec3.UNIT_Z, forwardVec); + var color = { + red: 150, + green: 20, + blue: 100 + } + var raveGlowEmitter = Entities.addEntity({ + type: "ParticleEffect", + name: "Rave Stick Glow Emitter", + position: position, + parentID: stick, + isEmitting: true, + colorStart: color, + color: { + red: 200, + green: 200, + blue: 255 + }, + colorFinish: color, + maxParticles: 100000, + lifespan: 0.8, + emitRate: 1000, + emitOrientation: forwardQuat, + emitSpeed: 0.2, + speedSpread: 0.0, + emitDimensions: { + x: 0, + y: 0, + z: 0 + }, + polarStart: 0, + polarFinish: 0, + azimuthStart: 0.1, + azimuthFinish: 0.01, + emitAcceleration: { + x: 0, + y: 0, + z: 0 + }, + accelerationSpread: { + x: 0.00, + y: 0.00, + z: 0.00 + }, + radiusStart: 0.01, + radiusFinish: 0.005, + alpha: 0.7, + alphaSpread: 0.1, + alphaStart: 0.1, + alphaFinish: 0.1, + textures: "https://s3.amazonaws.com/hifi-public/eric/textures/particleSprites/beamParticle.png", + emitterShouldTrail: false, + userData: JSON.stringify({ + resetMe: { + resetMe: true + } + }) + }); + } function createGun(position) { diff --git a/unpublishedScripts/masterReset.js b/unpublishedScripts/masterReset.js index 2d6d9a0d01..8aedd1c650 100644 --- a/unpublishedScripts/masterReset.js +++ b/unpublishedScripts/masterReset.js @@ -149,10 +149,12 @@ MasterReset = function() { function createRaveStick(position) { var modelURL = "http://hifi-content.s3.amazonaws.com/eric/models/raveStick.fbx"; + var rotation = Quat.fromPitchYawRollDegrees(0, 0, 0); var stick = Entities.addEntity({ type: "Model", name: "raveStick", modelURL: modelURL, + rotation: rotation, position: position, shapeType: 'box', collisionsWillMove: true, @@ -189,6 +191,66 @@ MasterReset = function() { } }) }); + + var forwardVec = Quat.getFront(rotation); + var forwardQuat = Quat.rotationBetween(Vec3.UNIT_Z, forwardVec); + var color = { + red: 150, + green: 20, + blue: 100 + } + var raveGlowEmitter = Entities.addEntity({ + type: "ParticleEffect", + name: "Rave Stick Glow Emitter", + position: position, + parentID: stick, + isEmitting: true, + colorStart: color, + color: { + red: 200, + green: 200, + blue: 255 + }, + colorFinish: color, + maxParticles: 100000, + lifespan: 0.8, + emitRate: 1000, + emitOrientation: forwardQuat, + emitSpeed: 0.2, + speedSpread: 0.0, + emitDimensions: { + x: 0, + y: 0, + z: 0 + }, + polarStart: 0, + polarFinish: 0, + azimuthStart: 0.1, + azimuthFinish: 0.01, + emitAcceleration: { + x: 0, + y: 0, + z: 0 + }, + accelerationSpread: { + x: 0.00, + y: 0.00, + z: 0.00 + }, + radiusStart: 0.01, + radiusFinish: 0.005, + alpha: 0.7, + alphaSpread: 0.1, + alphaStart: 0.1, + alphaFinish: 0.1, + textures: "https://s3.amazonaws.com/hifi-public/eric/textures/particleSprites/beamParticle.png", + emitterShouldTrail: false, + userData: JSON.stringify({ + resetMe: { + resetMe: true + } + }) + }); } function createGun(position) { @@ -1084,7 +1146,7 @@ MasterReset = function() { y: 0, z: 0.06 }, - relativeRotation: Quat.fromPitchYawRollDegrees(0,-90, -90) + relativeRotation: Quat.fromPitchYawRollDegrees(0, -90, -90) }, invertSolidWhileHeld: true }